182 lines
6.7 KiB
C#
182 lines
6.7 KiB
C#
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<IComponentInstaller> kyvernoInstaller;
|
|
private readonly Mock<IComponentInstaller> policiesInstaller;
|
|
private readonly DeployComponentHandler handler;
|
|
|
|
public DeployComponentHandlerTests()
|
|
{
|
|
repository = new InMemoryClusterRepository();
|
|
kyvernoInstaller = new Mock<IComponentInstaller>();
|
|
policiesInstaller = new Mock<IComponentInstaller>();
|
|
|
|
kyvernoInstaller.Setup(i => i.ComponentName).Returns("kyverno");
|
|
policiesInstaller.Setup(i => i.ComponentName).Returns("security-policies");
|
|
|
|
List<IComponentInstaller> installers = new() { kyvernoInstaller.Object, policiesInstaller.Object };
|
|
handler = new DeployComponentHandler(repository, installers, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_ClusterNotFound_ReturnsFailure()
|
|
{
|
|
// Arrange
|
|
|
|
DeployComponentRequest request = new("kyverno");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> 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<InstallResult> 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<InstallResult> 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<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new InstallResult(
|
|
Success: true,
|
|
ComponentName: "kyverno",
|
|
Message: "Kyverno 3.3.4 installed successfully",
|
|
Actions: new List<string> { "Helm install/upgrade kyverno v3.3.4" }));
|
|
|
|
DeployComponentRequest request = new("kyverno", Version: "3.3.4");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> 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<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(new InstallResult(
|
|
Success: false,
|
|
ComponentName: "kyverno",
|
|
Message: "Helm failed (exit 1): timed out waiting for condition",
|
|
Actions: new List<string> { "Error: timed out" }));
|
|
|
|
DeployComponentRequest request = new("kyverno");
|
|
|
|
// Act
|
|
|
|
Result<InstallResult> 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<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
|
|
.Callback<KubernetesCluster, ComponentInstallOptions, CancellationToken>((_, opts, _) => capturedOptions = opts)
|
|
.ReturnsAsync(new InstallResult(true, "security-policies", "Done", new List<string>()));
|
|
|
|
DeployComponentRequest request = new(
|
|
"security-policies",
|
|
Parameters: new Dictionary<string, string> { ["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");
|
|
}
|
|
}
|