too much for one commit

This commit is contained in:
Nils Blomgren
2026-05-13 14:01:32 +02:00
parent a96dd33039
commit 328d494530
394 changed files with 98104 additions and 78 deletions

View File

@@ -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);
}
}