too much for one commit
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
using EntKube.Clusters.Domain;
|
||||
using EntKube.Clusters.Features.AdoptCluster;
|
||||
using EntKube.Clusters.Features.AdoptCluster.ConfigureComponent;
|
||||
using EntKube.Clusters.Features.AdoptCluster.DeployComponent;
|
||||
using EntKube.Clusters.Infrastructure;
|
||||
using EntKube.SharedKernel.Domain;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
|
||||
namespace EntKube.Clusters.Tests.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that adoption scans, deployments, and configuration changes
|
||||
/// persist component state on the cluster aggregate. This ensures we don't
|
||||
/// need to re-scan the cluster every time the UI loads — detected components
|
||||
/// are stored and updated incrementally.
|
||||
/// </summary>
|
||||
public class ComponentPersistenceTests
|
||||
{
|
||||
private readonly InMemoryClusterRepository repository;
|
||||
|
||||
public ComponentPersistenceTests()
|
||||
{
|
||||
repository = new InMemoryClusterRepository();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdoptCluster_PersistsDetectedComponentsOnCluster()
|
||||
{
|
||||
// Arrange — A connected cluster with two components: one installed, one missing.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.prod:6443", "kubeconfig-data", Guid.NewGuid(), "prod-ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
Mock<IAdoptionCheck> kyvernoCheck = new();
|
||||
kyvernoCheck.Setup(c => c.ComponentName).Returns("kyverno");
|
||||
kyvernoCheck.Setup(c => c.CheckAsync(cluster, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ComponentCheckResult("kyverno", ComponentStatus.Installed,
|
||||
new List<string> { "Pod running" }, new List<string>(),
|
||||
new DiscoveredConfiguration("3.3.4", "kyverno", "kyverno",
|
||||
new Dictionary<string, string> { ["replicas"] = "3" })));
|
||||
|
||||
Mock<IAdoptionCheck> certManagerCheck = new();
|
||||
certManagerCheck.Setup(c => c.ComponentName).Returns("cert-manager");
|
||||
certManagerCheck.Setup(c => c.CheckAsync(cluster, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ComponentCheckResult("cert-manager", ComponentStatus.NotInstalled,
|
||||
new List<string>(), new List<string> { "No pods found" }));
|
||||
|
||||
List<IAdoptionCheck> checks = new() { kyvernoCheck.Object, certManagerCheck.Object };
|
||||
AdoptClusterHandler handler = new(repository, checks, NullLogger<AdoptClusterHandler>.Instance);
|
||||
|
||||
// Act — Run the adoption scan.
|
||||
|
||||
Result<AdoptionReport> result = await handler.HandleAsync(cluster.Id);
|
||||
|
||||
// Assert — Components should now be persisted on the cluster.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
|
||||
KubernetesCluster? persisted = await repository.GetByIdAsync(cluster.Id);
|
||||
persisted!.Components.Should().HaveCount(2);
|
||||
|
||||
ClusterComponent kyverno = persisted.Components.First(c => c.ComponentName == "kyverno");
|
||||
kyverno.Status.Should().Be(ComponentStatus.Installed);
|
||||
kyverno.Version.Should().Be("3.3.4");
|
||||
kyverno.Configuration["replicas"].Should().Be("3");
|
||||
|
||||
ClusterComponent certManager = persisted.Components.First(c => c.ComponentName == "cert-manager");
|
||||
certManager.Status.Should().Be(ComponentStatus.NotInstalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConfigureComponent_UpdatesPersistedConfiguration()
|
||||
{
|
||||
// Arrange — A cluster with monitoring already detected and persisted.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.prod:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
cluster.UpdateComponents(new List<ComponentCheckResult>
|
||||
{
|
||||
new("monitoring", ComponentStatus.Installed, new List<string>(), new List<string>(),
|
||||
new DiscoveredConfiguration("45.0.0", "monitoring", "kube-prometheus-stack",
|
||||
new Dictionary<string, string> { ["retention"] = "30d" }))
|
||||
});
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
Mock<IComponentInstaller> monitoringInstaller = new();
|
||||
monitoringInstaller.Setup(i => i.ComponentName).Returns("monitoring");
|
||||
monitoringInstaller
|
||||
.Setup(i => i.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "monitoring", "Reconfigured", new List<string> { "retention=90d" }));
|
||||
|
||||
List<IComponentInstaller> installers = new() { monitoringInstaller.Object };
|
||||
ConfigureComponentHandler handler = new(repository, installers);
|
||||
|
||||
ConfigureComponentRequest request = new("monitoring", Values: new Dictionary<string, string>
|
||||
{
|
||||
["retention"] = "90d"
|
||||
});
|
||||
|
||||
// Act — Apply the configuration change.
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert — The persisted component configuration should reflect the new value.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
|
||||
KubernetesCluster? persisted = await repository.GetByIdAsync(cluster.Id);
|
||||
ClusterComponent monitoring = persisted!.Components.First(c => c.ComponentName == "monitoring");
|
||||
monitoring.Configuration["retention"].Should().Be("90d");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeployComponent_UpdatesPersistedComponentStatus()
|
||||
{
|
||||
// Arrange — A cluster with cert-manager detected as NotInstalled.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.prod:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
cluster.UpdateComponents(new List<ComponentCheckResult>
|
||||
{
|
||||
new("cert-manager", ComponentStatus.NotInstalled, new List<string>(), new List<string> { "Missing" })
|
||||
});
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
Mock<IComponentInstaller> certManagerInstaller = new();
|
||||
certManagerInstaller.Setup(i => i.ComponentName).Returns("cert-manager");
|
||||
certManagerInstaller
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(true, "cert-manager", "Installed v1.14.0",
|
||||
new List<string> { "Helm install cert-manager v1.14.0" }));
|
||||
|
||||
List<IComponentInstaller> installers = new() { certManagerInstaller.Object };
|
||||
DeployComponentHandler handler = new(repository, installers, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
|
||||
|
||||
DeployComponentRequest request = new("cert-manager", Version: "1.14.0", Namespace: "cert-manager");
|
||||
|
||||
// Act — Deploy the component.
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert — The persisted component should now show Installed.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
|
||||
KubernetesCluster? persisted = await repository.GetByIdAsync(cluster.Id);
|
||||
ClusterComponent certManager = persisted!.Components.First(c => c.ComponentName == "cert-manager");
|
||||
certManager.Status.Should().Be(ComponentStatus.Installed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConfigureComponent_WhenInstallerFails_DoesNotUpdatePersisted()
|
||||
{
|
||||
// Arrange — Configuration that fails should NOT update the persisted state.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.prod:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
cluster.UpdateComponents(new List<ComponentCheckResult>
|
||||
{
|
||||
new("monitoring", ComponentStatus.Installed, new List<string>(), new List<string>(),
|
||||
new DiscoveredConfiguration("45.0.0", "monitoring", "kube-prometheus-stack",
|
||||
new Dictionary<string, string> { ["retention"] = "30d" }))
|
||||
});
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
Mock<IComponentInstaller> monitoringInstaller = new();
|
||||
monitoringInstaller.Setup(i => i.ComponentName).Returns("monitoring");
|
||||
monitoringInstaller
|
||||
.Setup(i => i.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(false, "monitoring", "Helm failed", new List<string>()));
|
||||
|
||||
List<IComponentInstaller> installers = new() { monitoringInstaller.Object };
|
||||
ConfigureComponentHandler handler = new(repository, installers);
|
||||
|
||||
ConfigureComponentRequest request = new("monitoring", Values: new Dictionary<string, string>
|
||||
{
|
||||
["retention"] = "90d"
|
||||
});
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert — Original value preserved.
|
||||
|
||||
result.IsFailure.Should().BeTrue();
|
||||
|
||||
KubernetesCluster? persisted = await repository.GetByIdAsync(cluster.Id);
|
||||
ClusterComponent monitoring = persisted!.Components.First(c => c.ComponentName == "monitoring");
|
||||
monitoring.Configuration["retention"].Should().Be("30d");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeployComponent_WhenInstallerFails_DoesNotUpdatePersisted()
|
||||
{
|
||||
// Arrange — Failed deployment should not change persisted status.
|
||||
|
||||
KubernetesCluster cluster = KubernetesCluster.Register(
|
||||
"prod-cluster", "https://k8s.prod:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid());
|
||||
cluster.MarkConnected();
|
||||
cluster.UpdateComponents(new List<ComponentCheckResult>
|
||||
{
|
||||
new("cert-manager", ComponentStatus.NotInstalled, new List<string>(), new List<string>())
|
||||
});
|
||||
await repository.AddAsync(cluster);
|
||||
|
||||
Mock<IComponentInstaller> installer = new();
|
||||
installer.Setup(i => i.ComponentName).Returns("cert-manager");
|
||||
installer
|
||||
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new InstallResult(false, "cert-manager", "Timeout", new List<string>()));
|
||||
|
||||
List<IComponentInstaller> installers = new() { installer.Object };
|
||||
DeployComponentHandler handler = new(repository, installers, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
|
||||
|
||||
DeployComponentRequest request = new("cert-manager");
|
||||
|
||||
// Act
|
||||
|
||||
Result<InstallResult> result = await handler.HandleAsync(cluster.Id, request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsFailure.Should().BeTrue();
|
||||
|
||||
KubernetesCluster? persisted = await repository.GetByIdAsync(cluster.Id);
|
||||
ClusterComponent certManager = persisted!.Components.First(c => c.ComponentName == "cert-manager");
|
||||
certManager.Status.Should().Be(ComponentStatus.NotInstalled);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user