too much for one commit

This commit is contained in:
Nils Blomgren
2026-05-13 14:01:32 +02:00
parent a96dd33039
commit 328d494530
394 changed files with 98104 additions and 78 deletions

View File

@@ -0,0 +1,405 @@
using EntKube.SharedKernel.Domain;
using FluentAssertions;
namespace EntKube.Provisioning.Tests.Domain;
/// <summary>
/// Tests for the MongoCluster aggregate root. This rich domain model tracks
/// everything about a MongoDB Community Operator cluster: replica set members,
/// storage, backup configuration, databases, users, and version lifecycle.
///
/// MongoDB Community Operator manages MongoDBCommunity CRDs on Kubernetes.
/// Each cluster is a replica set with configurable members, storage, and
/// authentication. Backup is handled via scheduled mongodump jobs to MinIO.
/// </summary>
public class MongoClusterTests
{
// ─── Creation ──────────────────────────────────────────────────────────────
[Fact]
public void Create_WithValidInputs_ReturnsClusterInProvisioningState()
{
// Arrange & Act — A platform admin provisions a new MongoDB cluster
// for shared use on a specific Kubernetes cluster.
Guid environmentId = Guid.NewGuid();
Guid clusterId = Guid.NewGuid();
Provisioning.Domain.MongoCluster mongoCluster = Provisioning.Domain.MongoCluster.Create(
environmentId: environmentId,
clusterId: clusterId,
name: "platform-mongo",
ns: "mongodb-system",
mongoVersion: "7.0.12",
members: 3,
storageSize: "50Gi",
minioEndpoint: "minio.minio-system.svc:9000",
minioBucket: "mongo-backups",
minioCredentialsSecret: "minio-mongo-credentials");
// Assert — The cluster starts in Provisioning state, waiting for the
// reconciler to create the MongoDBCommunity resource.
mongoCluster.Id.Should().NotBe(Guid.Empty);
mongoCluster.EnvironmentId.Should().Be(environmentId);
mongoCluster.ClusterId.Should().Be(clusterId);
mongoCluster.Name.Should().Be("platform-mongo");
mongoCluster.Namespace.Should().Be("mongodb-system");
mongoCluster.MongoVersion.Should().Be("7.0.12");
mongoCluster.Members.Should().Be(3);
mongoCluster.StorageSize.Should().Be("50Gi");
mongoCluster.Status.Should().Be(Provisioning.Domain.MongoClusterStatus.Provisioning);
mongoCluster.Databases.Should().BeEmpty();
}
[Fact]
public void Create_WithMinIOBackupConfig_SetsBackupConfiguration()
{
// Arrange & Act — Backup to MinIO is configured at creation time
// for mongodump-based backups. Unlike CNPG WAL archiving, MongoDB
// uses periodic full dumps.
Provisioning.Domain.MongoCluster mongoCluster = Provisioning.Domain.MongoCluster.Create(
environmentId: Guid.NewGuid(),
clusterId: Guid.NewGuid(),
name: "prod-mongo",
ns: "mongodb-prod",
mongoVersion: "7.0.12",
members: 3,
storageSize: "100Gi",
minioEndpoint: "minio.minio-system.svc:9000",
minioBucket: "mongo-dump-archive",
minioCredentialsSecret: "minio-mongo-creds");
// Assert — Backup configuration is stored as part of the aggregate.
mongoCluster.BackupConfig.Should().NotBeNull();
mongoCluster.BackupConfig!.MinioEndpoint.Should().Be("minio.minio-system.svc:9000");
mongoCluster.BackupConfig.Bucket.Should().Be("mongo-dump-archive");
mongoCluster.BackupConfig.CredentialsSecret.Should().Be("minio-mongo-creds");
}
[Fact]
public void Create_WithEmptyName_ThrowsArgumentException()
{
// Arrange & Act — The domain rejects invalid cluster names.
Action act = () => Provisioning.Domain.MongoCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "", "ns", "7.0.12", 3, "50Gi",
"minio:9000", "bucket", "secret");
// Assert
act.Should().Throw<ArgumentException>().WithParameterName("name");
}
[Fact]
public void Create_WithZeroMembers_ThrowsArgumentException()
{
// Arrange & Act — Must have at least 1 replica set member.
Action act = () => Provisioning.Domain.MongoCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "db", "ns", "7.0.12", 0, "50Gi",
"minio:9000", "bucket", "secret");
// Assert
act.Should().Throw<ArgumentException>().WithParameterName("members");
}
[Fact]
public void Create_WithEmptyMinioEndpoint_CreatesClusterWithoutBackup()
{
// Arrange & Act — Backup is optional. When MinIO endpoint is empty,
// the cluster should be created successfully without backup config.
Provisioning.Domain.MongoCluster mongoCluster = Provisioning.Domain.MongoCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "db", "ns", "7.0.12", 3, "50Gi",
"", "bucket", "secret");
// Assert — cluster exists but has no backup configuration.
mongoCluster.Should().NotBeNull();
mongoCluster.BackupConfig.Should().BeNull();
}
// ─── Adoption ──────────────────────────────────────────────────────────────
[Fact]
public void Adopt_FromExistingCluster_CreatesRunningClusterWithDiscoveredConfig()
{
// Arrange & Act — When we adopt an existing MongoDBCommunity cluster
// that's already running on K8s, we create it in Running state.
Provisioning.Domain.MongoCluster mongoCluster = Provisioning.Domain.MongoCluster.Adopt(
environmentId: Guid.NewGuid(),
clusterId: Guid.NewGuid(),
name: "existing-mongo",
ns: "mongodb-system",
mongoVersion: "6.0.16",
members: 2,
storageSize: "20Gi",
minioEndpoint: "minio.minio-system.svc:9000",
minioBucket: "existing-backups",
minioCredentialsSecret: "existing-minio-creds",
databases: new List<string> { "app_db", "analytics_db" });
// Assert — Adopted clusters start in Running state and include
// any databases that were already present.
mongoCluster.Status.Should().Be(Provisioning.Domain.MongoClusterStatus.Running);
mongoCluster.Databases.Should().HaveCount(2);
mongoCluster.Databases.Should().Contain("app_db");
mongoCluster.Databases.Should().Contain("analytics_db");
}
// ─── Status transitions ────────────────────────────────────────────────────
[Fact]
public void MarkRunning_TransitionsFromProvisioning()
{
// Arrange — A freshly created cluster in Provisioning state.
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
// Act — The reconciler confirmed the cluster is ready.
mongoCluster.MarkRunning();
// Assert
mongoCluster.Status.Should().Be(Provisioning.Domain.MongoClusterStatus.Running);
mongoCluster.LastReconcileAt.Should().NotBeNull();
}
[Fact]
public void MarkDegraded_SetsStatusToDegraded()
{
// Arrange
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
mongoCluster.MarkRunning();
// Act — Reconciler detected an issue.
mongoCluster.MarkDegraded("Replica set member not ready");
// Assert
mongoCluster.Status.Should().Be(Provisioning.Domain.MongoClusterStatus.Degraded);
mongoCluster.StatusMessage.Should().Be("Replica set member not ready");
}
// ─── Database management ───────────────────────────────────────────────────
[Fact]
public void AddDatabase_WhenRunning_AddsDatabaseToList()
{
// Arrange — A running cluster can have databases added.
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
mongoCluster.MarkRunning();
// Act — An application requests a new database.
Result<string> result = mongoCluster.AddDatabase("my_app_db", "app_user");
// Assert
result.IsSuccess.Should().BeTrue();
mongoCluster.Databases.Should().Contain("my_app_db");
}
[Fact]
public void AddDatabase_WhenNotRunning_ReturnsFailure()
{
// Arrange — A cluster still provisioning can't accept new databases.
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
// Act
Result<string> result = mongoCluster.AddDatabase("my_db", "user");
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("running");
}
[Fact]
public void AddDatabase_DuplicateName_ReturnsFailure()
{
// Arrange — A running cluster with one database already.
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
mongoCluster.MarkRunning();
mongoCluster.AddDatabase("existing_db", "user1");
// Act — Try to add a database with the same name.
Result<string> result = mongoCluster.AddDatabase("existing_db", "user2");
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("already exists");
}
// ─── Version upgrades ──────────────────────────────────────────────────────
[Fact]
public void RequestUpgrade_WhenRunning_SetsUpgradingStatusAndTargetVersion()
{
// Arrange — A running cluster at version 6.0.16.
Provisioning.Domain.MongoCluster mongoCluster = Provisioning.Domain.MongoCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "db", "ns", "6.0.16", 3, "50Gi",
"minio:9000", "bucket", "secret");
mongoCluster.MarkRunning();
// Act — Platform admin requests upgrade to 7.0.12.
Result<string> result = mongoCluster.RequestUpgrade("7.0.12");
// Assert — The cluster transitions to Upgrading state.
result.IsSuccess.Should().BeTrue();
mongoCluster.Status.Should().Be(Provisioning.Domain.MongoClusterStatus.Upgrading);
mongoCluster.TargetVersion.Should().Be("7.0.12");
}
[Fact]
public void RequestUpgrade_WhenNotRunning_ReturnsFailure()
{
// Arrange — A cluster still provisioning can't be upgraded.
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
// Act
Result<string> result = mongoCluster.RequestUpgrade("7.0.12");
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("running");
}
[Fact]
public void RequestUpgrade_ToSameVersion_ReturnsFailure()
{
// Arrange — A cluster already at version 7.0.12.
Provisioning.Domain.MongoCluster mongoCluster = Provisioning.Domain.MongoCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "db", "ns", "7.0.12", 3, "50Gi",
"minio:9000", "bucket", "secret");
mongoCluster.MarkRunning();
// Act — Try to upgrade to the same version.
Result<string> result = mongoCluster.RequestUpgrade("7.0.12");
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("same version");
}
[Fact]
public void CompleteUpgrade_TransitionsToRunningWithNewVersion()
{
// Arrange — A cluster mid-upgrade.
Provisioning.Domain.MongoCluster mongoCluster = Provisioning.Domain.MongoCluster.Create(
Guid.NewGuid(), Guid.NewGuid(), "db", "ns", "6.0.16", 3, "50Gi",
"minio:9000", "bucket", "secret");
mongoCluster.MarkRunning();
mongoCluster.RequestUpgrade("7.0.12");
// Act — Reconciler confirmed upgrade completed.
mongoCluster.CompleteUpgrade();
// Assert — Version updated, status back to Running.
mongoCluster.MongoVersion.Should().Be("7.0.12");
mongoCluster.Status.Should().Be(Provisioning.Domain.MongoClusterStatus.Running);
mongoCluster.TargetVersion.Should().BeNull();
}
// ─── Scaling ───────────────────────────────────────────────────────────────
[Fact]
public void ScaleMembers_UpdatesMemberCount()
{
// Arrange — A running 3-member cluster.
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
mongoCluster.MarkRunning();
// Act — Scale up to 5 members for more redundancy.
Result<string> result = mongoCluster.ScaleMembers(5);
// Assert
result.IsSuccess.Should().BeTrue();
mongoCluster.Members.Should().Be(5);
}
[Fact]
public void ScaleMembers_ToZero_ReturnsFailure()
{
// Arrange
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
mongoCluster.MarkRunning();
// Act
Result<string> result = mongoCluster.ScaleMembers(0);
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("at least 1");
}
// ─── Backup configuration ──────────────────────────────────────────────────
[Fact]
public void ConfigureBackupSchedule_UpdatesRetentionPolicy()
{
// Arrange — A running cluster needs its backup schedule updated.
Provisioning.Domain.MongoCluster mongoCluster = CreateTestCluster();
mongoCluster.MarkRunning();
// Act — Set backup to run every 6 hours with 30-day retention.
mongoCluster.ConfigureBackupSchedule("0 */6 * * *", retentionDays: 30);
// Assert
mongoCluster.BackupConfig!.Schedule.Should().Be("0 */6 * * *");
mongoCluster.BackupConfig.RetentionDays.Should().Be(30);
}
// ─── Helper ────────────────────────────────────────────────────────────────
private static Provisioning.Domain.MongoCluster CreateTestCluster()
{
return Provisioning.Domain.MongoCluster.Create(
Guid.NewGuid(),
Guid.NewGuid(),
"test-mongo",
"mongodb-system",
"7.0.12",
3,
"50Gi",
"minio.minio-system.svc:9000",
"mongo-backups",
"minio-mongo-credentials");
}
}