using EntKube.Clusters.Domain; using EntKube.Clusters.Features.AdoptCluster; using EntKube.Clusters.Features.AdoptCluster.UninstallComponent; using EntKube.Clusters.Infrastructure; using EntKube.SharedKernel.Domain; using FluentAssertions; using Moq; namespace EntKube.Clusters.Tests.Features; public class UninstallComponentHandlerTests { private readonly InMemoryClusterRepository repository; private readonly Mock kyvernoInstaller; private readonly Mock monitoringInstaller; private readonly UninstallComponentHandler handler; public UninstallComponentHandlerTests() { repository = new InMemoryClusterRepository(); kyvernoInstaller = new Mock(); monitoringInstaller = new Mock(); kyvernoInstaller.Setup(i => i.ComponentName).Returns("kyverno"); monitoringInstaller.Setup(i => i.ComponentName).Returns("monitoring"); List installers = new() { kyvernoInstaller.Object, monitoringInstaller.Object }; handler = new UninstallComponentHandler(repository, installers, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); } [Fact] public async Task HandleAsync_ClusterNotFound_ReturnsFailure() { // Arrange — request an uninstall on a cluster that doesn't exist. UninstallComponentRequest request = new("kyverno"); // Act Result result = await handler.HandleAsync(Guid.NewGuid(), request); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("not found"); } [Fact] public async Task HandleAsync_ClusterNotConnected_ReturnsFailure() { // Arrange — Cluster is Pending, can't uninstall from it. KubernetesCluster cluster = KubernetesCluster.Register( "pending-cluster", "https://k8s.dev:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid()); await repository.AddAsync(cluster); UninstallComponentRequest request = new("kyverno"); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("connected"); } [Fact] public async Task HandleAsync_UnknownComponent_ReturnsFailureWithAvailable() { // Arrange — Requesting an uninstall for a component that has no installer. KubernetesCluster cluster = KubernetesCluster.Register( "test-cluster", "https://k8s.test:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid()); cluster.MarkConnected(); await repository.AddAsync(cluster); UninstallComponentRequest request = new("nonexistent-thing"); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — Should list available installers. result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("kyverno"); result.Error.Should().Contain("monitoring"); } [Fact] public async Task HandleAsync_ValidComponent_DelegatesToInstallerUninstall() { // Arrange — Uninstall Kyverno from a connected cluster. KubernetesCluster cluster = KubernetesCluster.Register( "prod-cluster", "https://k8s.prod:6443", "kubeconfig-data", Guid.NewGuid(), "prod-ctx", Guid.NewGuid()); cluster.MarkConnected(); await repository.AddAsync(cluster); kyvernoInstaller.Setup(i => i.UninstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "kyverno", Message: "Kyverno uninstalled successfully", Actions: new List { "Helm uninstall kyverno" })); UninstallComponentRequest request = new("kyverno"); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — Should succeed and return the uninstall result. result.IsSuccess.Should().BeTrue(); result.Value!.Success.Should().BeTrue(); result.Value.ComponentName.Should().Be("kyverno"); result.Value.Actions.Should().Contain("Helm uninstall kyverno"); } [Fact] public async Task HandleAsync_InstallerFails_ReturnsFailure() { // Arrange — The uninstall encounters an error. KubernetesCluster cluster = KubernetesCluster.Register( "flaky-cluster", "https://k8s.flaky:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid()); cluster.MarkConnected(); await repository.AddAsync(cluster); kyvernoInstaller.Setup(i => i.UninstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: false, ComponentName: "kyverno", Message: "Helm uninstall failed: resources still in use", Actions: new List { "Error: resources still in use" })); UninstallComponentRequest request = new("kyverno"); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("resources still in use"); } [Fact] public async Task HandleAsync_PassesOptionsToInstaller() { // Arrange — Uninstall monitoring with a custom namespace. KubernetesCluster cluster = KubernetesCluster.Register( "config-cluster", "https://k8s.cfg:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid()); cluster.MarkConnected(); await repository.AddAsync(cluster); ComponentInstallOptions? capturedOptions = null; monitoringInstaller.Setup(i => i.UninstallAsync(cluster, It.IsAny(), It.IsAny())) .Callback((_, opts, _) => capturedOptions = opts) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "monitoring", Message: "Monitoring uninstalled", Actions: new List())); UninstallComponentRequest request = new("monitoring", Namespace: "custom-monitoring"); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — The handler should forward the namespace to the installer. result.IsSuccess.Should().BeTrue(); capturedOptions.Should().NotBeNull(); capturedOptions!.Namespace.Should().Be("custom-monitoring"); } [Fact] public async Task HandleAsync_ComponentNameIsCaseInsensitive() { // Arrange — Use mixed case for the component name. KubernetesCluster cluster = KubernetesCluster.Register( "case-cluster", "https://k8s.case:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid()); cluster.MarkConnected(); await repository.AddAsync(cluster); kyvernoInstaller.Setup(i => i.UninstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "kyverno", Message: "Uninstalled", Actions: new List())); UninstallComponentRequest request = new("Kyverno"); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert result.IsSuccess.Should().BeTrue(); } }