using EntKube.Clusters.Domain; using EntKube.Clusters.Features.ClusterSettings; using EntKube.Clusters.Infrastructure; using EntKube.SharedKernel.Domain; using FluentAssertions; using Moq; namespace EntKube.Clusters.Tests.Features; /// /// Cluster settings let operators view and edit configuration that lives on the /// live cluster — like the list of allowed container registries in the Kyverno /// restrict-image-registries policy. These tests verify the handler logic without /// touching a real cluster. The actual Kubernetes interaction is behind an /// IClusterSettingsReader abstraction so we can mock it in tests. /// public class ClusterSettingsTests { private readonly InMemoryClusterRepository repository; private readonly Mock settingsReader; private readonly Mock settingsWriter; private readonly GetClusterSettingsHandler getHandler; private readonly UpdateClusterSettingsHandler updateHandler; public ClusterSettingsTests() { repository = new InMemoryClusterRepository(); settingsReader = new Mock(); settingsWriter = new Mock(); getHandler = new GetClusterSettingsHandler(repository, settingsReader.Object); updateHandler = new UpdateClusterSettingsHandler(repository, settingsWriter.Object); } // ─── GET ─────────────────────────────────────────────────────────────── [Fact] public async Task GetSettings_WithConnectedCluster_ReturnsSettings() { // Arrange — a connected cluster with some registries configured. KubernetesCluster cluster = KubernetesCluster.Register( "prod", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid()); cluster.MarkConnected(); await repository.AddAsync(cluster); List registries = new() { "docker.io", "ghcr.io", "registry.k8s.io" }; settingsReader .Setup(r => r.ReadAllowedRegistriesAsync(cluster, It.IsAny())) .ReturnsAsync(registries); // Act Result result = await getHandler.HandleAsync(cluster.Id); // Assert — the returned settings should include the registries from the live cluster. result.IsSuccess.Should().BeTrue(); result.Value!.AllowedRegistries.Should().BeEquivalentTo(registries); } [Fact] public async Task GetSettings_WithNonExistentCluster_ReturnsFailure() { // Act Result result = await getHandler.HandleAsync(Guid.NewGuid()); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("not found"); } [Fact] public async Task GetSettings_WithDisconnectedCluster_ReturnsFailure() { // Arrange — a cluster that hasn't been connected yet. KubernetesCluster cluster = KubernetesCluster.Register( "pending", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid()); await repository.AddAsync(cluster); // Act Result result = await getHandler.HandleAsync(cluster.Id); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("connected"); } // ─── UPDATE ──────────────────────────────────────────────────────────── [Fact] public async Task UpdateSettings_WithValidRegistries_CallsWriter() { // Arrange — a connected cluster. KubernetesCluster cluster = KubernetesCluster.Register( "prod", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid()); cluster.MarkConnected(); await repository.AddAsync(cluster); List newRegistries = new() { "docker.io", "ghcr.io", "myregistry.com" }; settingsWriter .Setup(w => w.UpdateAllowedRegistriesAsync(cluster, newRegistries, It.IsAny())) .ReturnsAsync(Result.Success()); UpdateClusterSettingsRequest request = new(AllowedRegistries: newRegistries); // Act Result result = await updateHandler.HandleAsync(cluster.Id, request); // Assert result.IsSuccess.Should().BeTrue(); settingsWriter.Verify( w => w.UpdateAllowedRegistriesAsync(cluster, newRegistries, It.IsAny()), Times.Once); } [Fact] public async Task UpdateSettings_WithEmptyRegistries_ReturnsFailure() { // Arrange KubernetesCluster cluster = KubernetesCluster.Register( "prod", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid()); cluster.MarkConnected(); await repository.AddAsync(cluster); UpdateClusterSettingsRequest request = new(AllowedRegistries: new List()); // Act Result result = await updateHandler.HandleAsync(cluster.Id, request); // Assert — can't have zero allowed registries, that would block all pods. result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("at least one"); } [Fact] public async Task UpdateSettings_WithNonExistentCluster_ReturnsFailure() { // Arrange UpdateClusterSettingsRequest request = new(AllowedRegistries: new List { "docker.io" }); // Act Result result = await updateHandler.HandleAsync(Guid.NewGuid(), request); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("not found"); } [Fact] public async Task UpdateSettings_WithDisconnectedCluster_ReturnsFailure() { // Arrange KubernetesCluster cluster = KubernetesCluster.Register( "pending", "https://k8s.example.com:6443", "kubeconfig", Guid.NewGuid(), "ctx", Guid.NewGuid()); await repository.AddAsync(cluster); UpdateClusterSettingsRequest request = new(AllowedRegistries: new List { "docker.io" }); // Act Result result = await updateHandler.HandleAsync(cluster.Id, request); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("connected"); } }