too much for one commit
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
using EntKube.Clusters.Domain;
|
||||
using EntKube.Clusters.Features.AdoptCluster;
|
||||
using EntKube.Clusters.Features.AdoptCluster.DeployComponent;
|
||||
using EntKube.Clusters.Infrastructure;
|
||||
using EntKube.SharedKernel.Domain;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
|
||||
namespace EntKube.Clusters.Tests.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Tests the deploy handler with all new component installers registered.
|
||||
/// Verifies that the handler correctly routes to the right installer based on
|
||||
/// component name and passes parameters through.
|
||||
/// </summary>
|
||||
public class DeployNewComponentsTests
|
||||
{
|
||||
private readonly InMemoryClusterRepository repository;
|
||||
private readonly Mock<IComponentInstaller> cnpgInstaller;
|
||||
private readonly Mock<IComponentInstaller> redisInstaller;
|
||||
private readonly Mock<IComponentInstaller> certManagerInstaller;
|
||||
private readonly Mock<IComponentInstaller> externalSecretsInstaller;
|
||||
private readonly Mock<IComponentInstaller> rabbitmqInstaller;
|
||||
private readonly Mock<IComponentInstaller> customDnsInstaller;
|
||||
private readonly DeployComponentHandler handler;
|
||||
|
||||
public DeployNewComponentsTests()
|
||||
{
|
||||
repository = new InMemoryClusterRepository();
|
||||
|
||||
cnpgInstaller = new Mock<IComponentInstaller>();
|
||||
cnpgInstaller.Setup(i => i.ComponentName).Returns("cnpg");
|
||||
|
||||
redisInstaller = new Mock<IComponentInstaller>();
|
||||
redisInstaller.Setup(i => i.ComponentName).Returns("redis");
|
||||
|
||||
certManagerInstaller = new Mock<IComponentInstaller>();
|
||||
certManagerInstaller.Setup(i => i.ComponentName).Returns("cert-manager");
|
||||
|
||||
externalSecretsInstaller = new Mock<IComponentInstaller>();
|
||||
externalSecretsInstaller.Setup(i => i.ComponentName).Returns("external-secrets");
|
||||
|
||||
rabbitmqInstaller = new Mock<IComponentInstaller>();
|
||||
rabbitmqInstaller.Setup(i => i.ComponentName).Returns("rabbitmq");
|
||||
|
||||
customDnsInstaller = new Mock<IComponentInstaller>();
|
||||
customDnsInstaller.Setup(i => i.ComponentName).Returns("custom-dns");
|
||||
|
||||
List<IComponentInstaller> installers = new()
|
||||
{
|
||||
cnpgInstaller.Object,
|
||||
redisInstaller.Object,
|
||||
certManagerInstaller.Object,
|
||||
externalSecretsInstaller.Object,
|
||||
rabbitmqInstaller.Object,
|
||||
customDnsInstaller.Object
|
||||
};
|
||||
|
||||
handler = new DeployComponentHandler(repository, installers, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_DeployCnpg_CallsInstaller()
|
||||
{
|
||||
// Arrange — A connected cluster wants CloudNativePG installed.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
cnpgInstaller
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "cnpg", "CloudNativePG 0.22.0 installed", new List<string> { "Helm install" }));
|
||||
|
||||
DeployComponentRequest request = new("cnpg", Version: "0.22.0");
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value!.ComponentName.Should().Be("cnpg");
|
||||
cnpgInstaller.Verify(
|
||||
i => i.InstallAsync(cluster, It.Is<ComponentInstallOptions>(o => o.Version == "0.22.0"), It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_DeployRedis_PassesSentinelParameter()
|
||||
{
|
||||
// Arrange — Deploy Redis with Sentinel HA enabled.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
redisInstaller
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "redis", "Redis installed", new List<string> { "Helm install" }));
|
||||
|
||||
DeployComponentRequest request = new("redis", Parameters: new Dictionary<string, string>
|
||||
{
|
||||
["sentinelEnabled"] = "true",
|
||||
["replicaCount"] = "3"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
redisInstaller.Verify(
|
||||
i => i.InstallAsync(cluster,
|
||||
It.Is<ComponentInstallOptions>(o =>
|
||||
o.Parameters!["sentinelEnabled"] == "true" &&
|
||||
o.Parameters["replicaCount"] == "3"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_DeployCertManager_PassesLetsEncryptConfig()
|
||||
{
|
||||
// Arrange — Deploy cert-manager with Let's Encrypt + Cloudflare DNS01.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
certManagerInstaller
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "cert-manager", "cert-manager installed with LE issuers",
|
||||
new List<string> { "Helm install", "ClusterIssuer created" }));
|
||||
|
||||
DeployComponentRequest request = new("cert-manager", Parameters: new Dictionary<string, string>
|
||||
{
|
||||
["letsEncryptEmail"] = "admin@example.com",
|
||||
["httpSolverEnabled"] = "true",
|
||||
["dnsSolverEnabled"] = "true",
|
||||
["dnsSolverProvider"] = "cloudflare",
|
||||
["dnsSolverSecretName"] = "cloudflare-api-token"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
certManagerInstaller.Verify(
|
||||
i => i.InstallAsync(cluster,
|
||||
It.Is<ComponentInstallOptions>(o =>
|
||||
o.Parameters!["letsEncryptEmail"] == "admin@example.com" &&
|
||||
o.Parameters["dnsSolverProvider"] == "cloudflare"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_DeployExternalSecrets_PassesVaultConfig()
|
||||
{
|
||||
// Arrange — Deploy ESO with Vault as the secret store provider.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
externalSecretsInstaller
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "external-secrets", "ESO installed",
|
||||
new List<string> { "Helm install", "ClusterSecretStore created" }));
|
||||
|
||||
DeployComponentRequest request = new("external-secrets", Parameters: new Dictionary<string, string>
|
||||
{
|
||||
["storeProvider"] = "vault",
|
||||
["storeEndpoint"] = "http://vault.vault:8200"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
externalSecretsInstaller.Verify(
|
||||
i => i.InstallAsync(cluster,
|
||||
It.Is<ComponentInstallOptions>(o =>
|
||||
o.Parameters!["storeProvider"] == "vault" &&
|
||||
o.Parameters["storeEndpoint"] == "http://vault.vault:8200"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_DeployRabbitMQ_PassesTopologyOperatorParam()
|
||||
{
|
||||
// Arrange — Deploy RabbitMQ with messaging topology operator.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
rabbitmqInstaller
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "rabbitmq", "RabbitMQ operator installed",
|
||||
new List<string> { "Helm install", "Topology operator enabled" }));
|
||||
|
||||
DeployComponentRequest request = new("rabbitmq", Parameters: new Dictionary<string, string>
|
||||
{
|
||||
["topologyOperator"] = "true",
|
||||
["monitoringEnabled"] = "true"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
rabbitmqInstaller.Verify(
|
||||
i => i.InstallAsync(cluster,
|
||||
It.Is<ComponentInstallOptions>(o =>
|
||||
o.Parameters!["topologyOperator"] == "true"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_DisconnectedCluster_ReturnsFailure()
|
||||
{
|
||||
// Arrange — A cluster that isn't connected can't have components deployed.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"offline-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
DeployComponentRequest request = new("cnpg");
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Should().Contain("connected");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_InstallerFails_ReturnsFailure()
|
||||
{
|
||||
// Arrange — The installer reports a failure.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
redisInstaller
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(false, "redis", "Helm timeout", new List<string> { "Error: timeout" }));
|
||||
|
||||
DeployComponentRequest request = new("redis");
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Should().Contain("Helm timeout");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_DeployCustomDns_PassesStubZonesAndRecords()
|
||||
{
|
||||
// Arrange — Deploy custom DNS with stub zones and static records.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
customDnsInstaller
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "custom-dns", "CoreDNS customized",
|
||||
new List<string> { "Stub zones configured", "Custom records applied" }));
|
||||
|
||||
DeployComponentRequest request = new("custom-dns", Parameters: new Dictionary<string, string>
|
||||
{
|
||||
["stubZones"] = "corp.internal=10.0.0.53,10.0.0.54;db.internal=10.1.0.53",
|
||||
["customRecords"] = "api.local=192.168.1.100;cache.local=192.168.1.101"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value!.ComponentName.Should().Be("custom-dns");
|
||||
customDnsInstaller.Verify(
|
||||
i => i.InstallAsync(cluster,
|
||||
It.Is<ComponentInstallOptions>(o =>
|
||||
o.Parameters!["stubZones"] == "corp.internal=10.0.0.53,10.0.0.54;db.internal=10.1.0.53" &&
|
||||
o.Parameters["customRecords"] == "api.local=192.168.1.100;cache.local=192.168.1.101"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user