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; public class DeployComponentHandlerTests { private readonly InMemoryClusterRepository repository; private readonly Mock kyvernoInstaller; private readonly Mock policiesInstaller; private readonly DeployComponentHandler handler; public DeployComponentHandlerTests() { repository = new InMemoryClusterRepository(); kyvernoInstaller = new Mock(); policiesInstaller = new Mock(); kyvernoInstaller.Setup(i => i.ComponentName).Returns("kyverno"); policiesInstaller.Setup(i => i.ComponentName).Returns("security-policies"); List installers = new() { kyvernoInstaller.Object, policiesInstaller.Object }; handler = new DeployComponentHandler(repository, installers, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); } [Fact] public async Task HandleAsync_ClusterNotFound_ReturnsFailure() { // Arrange DeployComponentRequest 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 deploy to it. KubernetesCluster cluster = KubernetesCluster.Register( "pending-cluster", "https://k8s.dev:6443", "kubeconfig-data", Guid.NewGuid(), "ctx", Guid.NewGuid()); await repository.AddAsync(cluster); DeployComponentRequest 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 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); DeployComponentRequest 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("security-policies"); } [Fact] public async Task HandleAsync_ValidComponent_DelegatesToInstaller() { // Arrange — Deploy Kyverno on 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.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "kyverno", Message: "Kyverno 3.3.4 installed successfully", Actions: new List { "Helm install/upgrade kyverno v3.3.4" })); DeployComponentRequest request = new("kyverno", Version: "3.3.4"); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — Should succeed and return installer output. result.IsSuccess.Should().BeTrue(); result.Value!.Success.Should().BeTrue(); result.Value.ComponentName.Should().Be("kyverno"); result.Value.Actions.Should().Contain("Helm install/upgrade kyverno v3.3.4"); } [Fact] public async Task HandleAsync_InstallerFails_ReturnsFailure() { // Arrange — The installer encounters an error (e.g., helm timeout). 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.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: false, ComponentName: "kyverno", Message: "Helm failed (exit 1): timed out waiting for condition", Actions: new List { "Error: timed out" })); DeployComponentRequest request = new("kyverno"); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("timed out"); } [Fact] public async Task HandleAsync_PassesOptionsToInstaller() { // Arrange — Deploy security policies with custom parameters. 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; policiesInstaller.Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .Callback((_, opts, _) => capturedOptions = opts) .ReturnsAsync(new InstallResult(true, "security-policies", "Done", new List())); DeployComponentRequest request = new( "security-policies", Parameters: new Dictionary { ["validationAction"] = "Enforce" }); // Act await handler.HandleAsync(cluster.Id, request); // Assert — The installer received the custom parameters. capturedOptions.Should().NotBeNull(); capturedOptions!.Parameters.Should().ContainKey("validationAction"); capturedOptions.Parameters!["validationAction"].Should().Be("Enforce"); } }