using EntKube.Provisioning.Domain;
using EntKube.Provisioning.Features.MinioInstances;
using EntKube.Provisioning.Features.MinioInstances.AdoptMinioInstance;
using EntKube.Provisioning.Features.PostgresClusters;
using EntKube.Provisioning.Features.PostgresClusters.CreatePostgresCluster;
using EntKube.Provisioning.Infrastructure;
using EntKube.SharedKernel.Domain;
using FluentAssertions;
using Moq;
namespace EntKube.Provisioning.Tests.Features;
///
/// Tests for MinIO adoption and its integration with CNPG cluster provisioning.
/// MinIO must be adopted before CNPG clusters can be created — the create flow
/// resolves the MinIO details by reference (cluster ID) rather than raw values.
///
public class MinioAdoptionTests
{
private readonly InMemoryMinioInstanceRepository minioRepository;
private readonly InMemoryPostgresClusterRepository pgRepository;
private readonly Mock minioClient;
private readonly Mock cnpgClient;
public MinioAdoptionTests()
{
minioRepository = new InMemoryMinioInstanceRepository();
pgRepository = new InMemoryPostgresClusterRepository();
minioClient = new Mock();
cnpgClient = new Mock();
}
// ─── Adopt MinIO ──────────────────────────────────────────────────────────
[Fact]
public async Task AdoptMinio_WithDiscoveredTenant_CreatesRunningInstance()
{
// Arrange — The K8s cluster has an existing MinIO tenant running.
minioClient.Setup(c => c.DiscoverMinioAsync(
It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync(new DiscoveredMinioInstance(
Name: "platform-minio",
Namespace: "minio-system",
Endpoint: "minio.minio-system.svc:9000",
ConsoleEndpoint: "minio-console.minio-system.svc:9001",
CredentialsSecret: "minio-root-credentials",
TotalCapacity: "1Ti",
UsedCapacity: "250Gi",
Buckets: new List { "loki-logs", "velero-backups" }));
AdoptMinioInstanceHandler handler = new(minioRepository, minioClient.Object);
AdoptMinioInstanceRequest request = new(
ClusterId: Guid.NewGuid(),
KubeConfig: "config",
ContextName: "ctx");
// Act
Result result = await handler.HandleAsync(request);
// Assert — MinIO adopted and stored with all discovered details.
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBe(Guid.Empty);
IReadOnlyList all = await minioRepository.GetAllAsync();
all.Should().HaveCount(1);
all[0].Name.Should().Be("platform-minio");
all[0].Endpoint.Should().Be("minio.minio-system.svc:9000");
all[0].Status.Should().Be(MinioInstanceStatus.Running);
all[0].Buckets.Should().Contain("loki-logs");
all[0].Buckets.Should().Contain("velero-backups");
}
[Fact]
public async Task AdoptMinio_NoTenantFound_ReturnsFailure()
{
// Arrange — No MinIO tenant on this cluster.
minioClient.Setup(c => c.DiscoverMinioAsync(
It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync((DiscoveredMinioInstance?)null);
AdoptMinioInstanceHandler handler = new(minioRepository, minioClient.Object);
AdoptMinioInstanceRequest request = new(Guid.NewGuid(), "config", "ctx");
// Act
Result result = await handler.HandleAsync(request);
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Should().Contain("No MinIO");
}
[Fact]
public async Task AdoptMinio_AlreadyAdopted_ReturnsExistingId()
{
// Arrange — MinIO was already adopted for this cluster.
Guid clusterId = Guid.NewGuid();
MinioInstance existing = MinioInstance.Adopt(
clusterId, "platform-minio", "minio-system",
"minio:9000", null, "secret", "500Gi", null, null);
await minioRepository.AddAsync(existing);
minioClient.Setup(c => c.DiscoverMinioAsync(
It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync(new DiscoveredMinioInstance(
"platform-minio", "minio-system", "minio:9000",
null, "secret", "500Gi", null, null));
AdoptMinioInstanceHandler handler = new(minioRepository, minioClient.Object);
AdoptMinioInstanceRequest request = new(clusterId, "config", "ctx");
// Act
Result result = await handler.HandleAsync(request);
// Assert — Returns the existing ID without creating a duplicate.
result.IsSuccess.Should().BeTrue();
result.Value.Should().Be(existing.Id);
IReadOnlyList all = await minioRepository.GetAllAsync();
all.Should().HaveCount(1);
}
// ─── CNPG Create with MinIO Reference ─────────────────────────────────────
[Fact]
public async Task CreatePostgresCluster_WithMinioInstanceId_ResolvesMinioConfig()
{
// Arrange — An adopted MinIO instance exists. When creating a CNPG cluster,
// we reference it by the cluster ID instead of providing raw details.
Guid clusterId = Guid.NewGuid();
MinioInstance minio = MinioInstance.Adopt(
clusterId, "platform-minio", "minio-system",
"minio.minio-system.svc:9000", null,
"minio-root-credentials", "1Ti", null,
new List { "cnpg-backups" });
await minioRepository.AddAsync(minio);
CreatePostgresClusterHandler handler = new(pgRepository, cnpgClient.Object, minioRepository);
CreatePostgresClusterRequest request = new(
EnvironmentId: Guid.NewGuid(),
ClusterId: clusterId,
Name: "prod-db",
Namespace: "cnpg-system",
PostgresVersion: "16.4",
Instances: 3,
StorageSize: "50Gi",
KubeConfig: "config",
ContextName: "ctx",
MinioEndpoint: null,
MinioBucket: "cnpg-backups",
MinioCredentialsSecret: null);
// Act
Result result = await handler.HandleAsync(request);
// Assert — MinIO details resolved from the adopted instance.
result.IsSuccess.Should().BeTrue();
IReadOnlyList clusters = await pgRepository.GetAllAsync();
clusters.Should().HaveCount(1);
clusters[0].BackupConfig!.MinioEndpoint.Should().Be("minio.minio-system.svc:9000");
clusters[0].BackupConfig.CredentialsSecret.Should().Be("minio-root-credentials");
clusters[0].BackupConfig.Bucket.Should().Be("cnpg-backups");
cnpgClient.Verify(c => c.CreateClusterAsync(
"config", "ctx",
It.Is(s =>
s.MinioEndpoint == "minio.minio-system.svc:9000" &&
s.MinioCredentialsSecret == "minio-root-credentials"),
It.IsAny()), Times.Once);
}
[Fact]
public async Task CreatePostgresCluster_NoMinioAdopted_CreatesWithoutBackup()
{
// Arrange — No MinIO adopted for this cluster, and no explicit endpoint provided.
// The cluster should still be created, just without backup configuration.
CreatePostgresClusterHandler handler = new(pgRepository, cnpgClient.Object, minioRepository);
CreatePostgresClusterRequest request = new(
EnvironmentId: Guid.NewGuid(),
ClusterId: Guid.NewGuid(),
Name: "prod-db",
Namespace: "cnpg-system",
PostgresVersion: "16.4",
Instances: 3,
StorageSize: "50Gi",
KubeConfig: "config",
ContextName: "ctx",
MinioEndpoint: null,
MinioBucket: "backups",
MinioCredentialsSecret: null);
// Act
Result result = await handler.HandleAsync(request);
// Assert — Cluster is created successfully without backup.
result.IsSuccess.Should().BeTrue();
}
[Fact]
public async Task CreatePostgresCluster_WithExplicitMinioDetails_StillWorks()
{
// Arrange — Explicit MinIO details provided (bypass adoption lookup).
CreatePostgresClusterHandler handler = new(pgRepository, cnpgClient.Object, minioRepository);
CreatePostgresClusterRequest request = new(
EnvironmentId: Guid.NewGuid(),
ClusterId: Guid.NewGuid(),
Name: "prod-db",
Namespace: "cnpg-system",
PostgresVersion: "16.4",
Instances: 3,
StorageSize: "50Gi",
KubeConfig: "config",
ContextName: "ctx",
MinioEndpoint: "custom-minio:9000",
MinioBucket: "custom-bucket",
MinioCredentialsSecret: "custom-secret");
// Act
Result result = await handler.HandleAsync(request);
// Assert — Explicit values used directly.
result.IsSuccess.Should().BeTrue();
IReadOnlyList clusters = await pgRepository.GetAllAsync();
clusters[0].BackupConfig!.MinioEndpoint.Should().Be("custom-minio:9000");
clusters[0].BackupConfig.Bucket.Should().Be("custom-bucket");
clusters[0].BackupConfig.CredentialsSecret.Should().Be("custom-secret");
}
}