406 lines
15 KiB
C#
406 lines
15 KiB
C#
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");
|
|
}
|
|
}
|