too much for one commit
This commit is contained in:
@@ -0,0 +1,448 @@
|
||||
using EntKube.Clusters.Domain;
|
||||
using EntKube.Clusters.Features.AdoptCluster;
|
||||
using EntKube.Clusters.Features.AdoptCluster.ConfigureComponent;
|
||||
using EntKube.Clusters.Infrastructure;
|
||||
using EntKube.SharedKernel.Domain;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
|
||||
namespace EntKube.Clusters.Tests.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Tests the configure handler with the new component installers.
|
||||
/// Verifies that reconfiguration requests route correctly and pass
|
||||
/// the right configuration values to each installer.
|
||||
/// </summary>
|
||||
public class ConfigureNewComponentsTests
|
||||
{
|
||||
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 ConfigureComponentHandler handler;
|
||||
|
||||
public ConfigureNewComponentsTests()
|
||||
{
|
||||
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 ConfigureComponentHandler(repository, installers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConfigureCnpg_PassesMonitoringFlag()
|
||||
{
|
||||
// Arrange — Reconfigure CNPG to enable Prometheus monitoring.
|
||||
|
||||
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.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "cnpg", "CNPG reconfigured", new List<string> { "monitoring=true" }));
|
||||
|
||||
ConfigureComponentRequest request = new("cnpg", Values: new Dictionary<string, string>
|
||||
{
|
||||
["monitoringEnabled"] = "true",
|
||||
["operatorReplicas"] = "2"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
cnpgInstaller.Verify(
|
||||
i => i.ConfigureAsync(cluster,
|
||||
It.Is<ComponentConfiguration>(c =>
|
||||
c.Values["monitoringEnabled"] == "true" &&
|
||||
c.Values["operatorReplicas"] == "2"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConfigureRedis_PassesSentinelAndPersistence()
|
||||
{
|
||||
// Arrange — Reconfigure Redis: scale replicas and change persistence size.
|
||||
|
||||
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.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "redis", "Redis reconfigured", new List<string> { "replicas=5" }));
|
||||
|
||||
ConfigureComponentRequest request = new("redis", Values: new Dictionary<string, string>
|
||||
{
|
||||
["replicaCount"] = "5",
|
||||
["persistenceSize"] = "16Gi",
|
||||
["maxMemory"] = "512mb"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
redisInstaller.Verify(
|
||||
i => i.ConfigureAsync(cluster,
|
||||
It.Is<ComponentConfiguration>(c =>
|
||||
c.Values["replicaCount"] == "5" &&
|
||||
c.Values["persistenceSize"] == "16Gi" &&
|
||||
c.Values["maxMemory"] == "512mb"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConfigureCertManager_PassesDnsSolverConfig()
|
||||
{
|
||||
// Arrange — Switch cert-manager to use Route53 DNS01 solver.
|
||||
|
||||
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.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "cert-manager", "cert-manager reconfigured",
|
||||
new List<string> { "Updated issuers with Route53 DNS01" }));
|
||||
|
||||
ConfigureComponentRequest request = new("cert-manager", Values: new Dictionary<string, string>
|
||||
{
|
||||
["letsEncryptEmail"] = "ops@company.com",
|
||||
["dnsSolverEnabled"] = "true",
|
||||
["dnsSolverProvider"] = "route53",
|
||||
["dnsSolverHostedZone"] = "Z1234567890"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
certManagerInstaller.Verify(
|
||||
i => i.ConfigureAsync(cluster,
|
||||
It.Is<ComponentConfiguration>(c =>
|
||||
c.Values["dnsSolverProvider"] == "route53" &&
|
||||
c.Values["dnsSolverHostedZone"] == "Z1234567890"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConfigureExternalSecrets_PassesStoreProvider()
|
||||
{
|
||||
// Arrange — Add an AWS Secrets Manager ClusterSecretStore.
|
||||
|
||||
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.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "external-secrets", "ESO reconfigured",
|
||||
new List<string> { "ClusterSecretStore for AWS created" }));
|
||||
|
||||
ConfigureComponentRequest request = new("external-secrets", Values: new Dictionary<string, string>
|
||||
{
|
||||
["storeProvider"] = "aws",
|
||||
["storeRegion"] = "eu-north-1",
|
||||
["storeSecretName"] = "aws-credentials"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
externalSecretsInstaller.Verify(
|
||||
i => i.ConfigureAsync(cluster,
|
||||
It.Is<ComponentConfiguration>(c =>
|
||||
c.Values["storeProvider"] == "aws" &&
|
||||
c.Values["storeRegion"] == "eu-north-1"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConfigureRabbitMQ_PassesTopologyConfig()
|
||||
{
|
||||
// Arrange — Enable topology operator on RabbitMQ.
|
||||
|
||||
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.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "rabbitmq", "RabbitMQ reconfigured",
|
||||
new List<string> { "Topology operator enabled" }));
|
||||
|
||||
ConfigureComponentRequest request = new("rabbitmq", Values: new Dictionary<string, string>
|
||||
{
|
||||
["topologyOperator"] = "true",
|
||||
["operatorReplicas"] = "2"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
rabbitmqInstaller.Verify(
|
||||
i => i.ConfigureAsync(cluster,
|
||||
It.Is<ComponentConfiguration>(c =>
|
||||
c.Values["topologyOperator"] == "true" &&
|
||||
c.Values["operatorReplicas"] == "2"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConfigureCertManager_CaseInsensitiveName()
|
||||
{
|
||||
// Arrange — Component name matching should be case-insensitive.
|
||||
|
||||
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.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "cert-manager", "Reconfigured", new List<string>()));
|
||||
|
||||
ConfigureComponentRequest request = new("Cert-Manager", Values: new Dictionary<string, string>
|
||||
{
|
||||
["replicas"] = "2"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConfigureWithNamespace_PassesNamespaceThrough()
|
||||
{
|
||||
// Arrange — Specify a custom namespace for Redis reconfiguration.
|
||||
|
||||
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.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "redis", "Reconfigured", new List<string>()));
|
||||
|
||||
ConfigureComponentRequest request = new("redis", Namespace: "custom-redis-ns", Values: new Dictionary<string, string>
|
||||
{
|
||||
["replicaCount"] = "5"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
redisInstaller.Verify(
|
||||
i => i.ConfigureAsync(cluster,
|
||||
It.Is<ComponentConfiguration>(c => c.Namespace == "custom-redis-ns"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConfigureCustomDns_PassesStubZonesAndCorefile()
|
||||
{
|
||||
// Arrange — Reconfigure CoreDNS with new stub zones and custom Corefile entries.
|
||||
|
||||
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.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "custom-dns", "CoreDNS reconfigured",
|
||||
new List<string> { "Stub zones updated", "Custom Corefile entries applied" }));
|
||||
|
||||
ConfigureComponentRequest request = new("custom-dns", Values: new Dictionary<string, string>
|
||||
{
|
||||
["stubZones"] = "corp.internal=10.0.0.53;svc.internal=10.0.1.53",
|
||||
["customCorefileEntries"] = "log\nrewrite name api.old.local api.new.local",
|
||||
["customRecords"] = "myservice.local=10.10.10.10"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
customDnsInstaller.Verify(
|
||||
i => i.ConfigureAsync(cluster,
|
||||
It.Is<ComponentConfiguration>(c =>
|
||||
c.Values["stubZones"] == "corp.internal=10.0.0.53;svc.internal=10.0.1.53" &&
|
||||
c.Values["customCorefileEntries"] == "log\nrewrite name api.old.local api.new.local" &&
|
||||
c.Values["customRecords"] == "myservice.local=10.10.10.10"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WithSchema_RejectsInvalidIntegerValue()
|
||||
{
|
||||
// Arrange — CNPG has a schema requiring operatorReplicas as Integer with min/max.
|
||||
// Providing a non-numeric value should fail validation before reaching the installer.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
ConfigurationSchema cnpgSchema = ComponentSchemas.GetSchema("cnpg");
|
||||
cnpgInstaller.Setup(i => i.GetSchema()).Returns(cnpgSchema);
|
||||
|
||||
ConfigureComponentRequest request = new("cnpg", Values: new Dictionary<string, string>
|
||||
{
|
||||
["operatorReplicas"] = "not-a-number"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert — should fail validation without ever calling ConfigureAsync.
|
||||
|
||||
result.IsSuccess.Should().BeFalse();
|
||||
result.Error.Should().Contain("must be an integer");
|
||||
cnpgInstaller.Verify(
|
||||
i => i.ConfigureAsync(It.IsAny<KubernetesCluster>(), It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WithSchema_RejectsValueOutOfRange()
|
||||
{
|
||||
// Arrange — Redis schema has maxReplicas=10 for replicas field.
|
||||
// Providing a value above max should fail.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
ConfigurationSchema redisSchema = ComponentSchemas.GetSchema("redis");
|
||||
redisInstaller.Setup(i => i.GetSchema()).Returns(redisSchema);
|
||||
|
||||
ConfigureComponentRequest request = new("redis", Values: new Dictionary<string, string>
|
||||
{
|
||||
["replicaCount"] = "99"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeFalse();
|
||||
result.Error.Should().Contain("must be at most 10");
|
||||
redisInstaller.Verify(
|
||||
i => i.ConfigureAsync(It.IsAny<KubernetesCluster>(), It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WithSchema_PassesValidConfiguration()
|
||||
{
|
||||
// Arrange — Provide valid values matching the CNPG schema.
|
||||
// Validation should pass and ConfigureAsync should be called.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
ConfigurationSchema cnpgSchema = ComponentSchemas.GetSchema("cnpg");
|
||||
cnpgInstaller.Setup(i => i.GetSchema()).Returns(cnpgSchema);
|
||||
cnpgInstaller
|
||||
.Setup(i => i.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "cnpg", "Configured", new List<string>()));
|
||||
|
||||
ConfigureComponentRequest request = new("cnpg", Values: new Dictionary<string, string>
|
||||
{
|
||||
["operatorReplicas"] = "2",
|
||||
["monitoringEnabled"] = "true"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert — validation passes, installer is called.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
cnpgInstaller.Verify(
|
||||
i => i.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user