using EntKube.Clusters.Domain; using EntKube.Clusters.Features.DiscoverPrometheus; using EntKube.Clusters.Infrastructure; using EntKube.SharedKernel.Domain; using FluentAssertions; namespace EntKube.Clusters.Tests.Features; public class DiscoverPrometheusHandlerTests { private readonly InMemoryClusterRepository repository; public DiscoverPrometheusHandlerTests() { repository = new InMemoryClusterRepository(); } [Fact] public async Task HandleAsync_WithDiscoveredEndpoints_StoresThemOnCluster() { // Arrange — A registered cluster and a scanner that finds two Prometheus instances. KubernetesCluster cluster = KubernetesCluster.Register( "production", "https://k8s.example.com:6443", "kubeconfig-data", Guid.NewGuid(), "prod-ctx", Guid.NewGuid()); await repository.AddAsync(cluster); List fakeEndpoints = new() { new PrometheusEndpoint("http://prometheus.monitoring:9090", "monitoring", "prometheus-server"), new PrometheusEndpoint("http://prometheus.observability:9090", "observability", "prom-stack") }; FakePrometheusScanner scanner = new(fakeEndpoints); DiscoverPrometheusHandler handler = new(repository, scanner); // Act — Run discovery on the cluster. Result> result = await handler.HandleAsync(cluster.Id); // Assert — Both endpoints should be stored and returned. result.IsSuccess.Should().BeTrue(); result.Value.Should().HaveCount(2); result.Value![0].Namespace.Should().Be("monitoring"); result.Value[1].Namespace.Should().Be("observability"); KubernetesCluster? updated = await repository.GetByIdAsync(cluster.Id); updated!.PrometheusEndpoints.Should().HaveCount(2); } [Fact] public async Task HandleAsync_WithNoPrometheus_ReturnsEmptyList() { // Arrange — A cluster with no Prometheus running. KubernetesCluster cluster = KubernetesCluster.Register( "staging", "https://staging.example.com:6443", "kubeconfig-data", Guid.NewGuid(), "staging-ctx", Guid.NewGuid()); await repository.AddAsync(cluster); FakePrometheusScanner scanner = new(new List()); DiscoverPrometheusHandler handler = new(repository, scanner); // Act Result> result = await handler.HandleAsync(cluster.Id); // Assert — Success but empty list. No Prometheus found is not an error. result.IsSuccess.Should().BeTrue(); result.Value.Should().BeEmpty(); } [Fact] public async Task HandleAsync_WithNonExistentCluster_ReturnsFailure() { // Arrange FakePrometheusScanner scanner = new(new List()); DiscoverPrometheusHandler handler = new(repository, scanner); // Act Result> result = await handler.HandleAsync(Guid.NewGuid()); // Assert result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("not found"); } [Fact] public async Task HandleAsync_ReplacesExistingEndpoints_OnRediscovery() { // Arrange — A cluster that already had endpoints from a previous scan. KubernetesCluster cluster = KubernetesCluster.Register( "production", "https://k8s.example.com:6443", "kubeconfig-data", Guid.NewGuid(), "prod-ctx", Guid.NewGuid()); cluster.SetPrometheusEndpoints(new List { new("http://old-prometheus:9090", "legacy", "old-prom") }); await repository.AddAsync(cluster); // A new scan finds a different Prometheus. List newEndpoints = new() { new PrometheusEndpoint("http://prometheus.monitoring:9090", "monitoring", "prometheus-server") }; FakePrometheusScanner scanner = new(newEndpoints); DiscoverPrometheusHandler handler = new(repository, scanner); // Act Result> result = await handler.HandleAsync(cluster.Id); // Assert — The old endpoint is replaced by the new one. result.IsSuccess.Should().BeTrue(); result.Value.Should().HaveCount(1); result.Value![0].ServiceName.Should().Be("prometheus-server"); KubernetesCluster? updated = await repository.GetByIdAsync(cluster.Id); updated!.PrometheusEndpoints.Should().HaveCount(1); } } /// /// Test double that returns pre-configured Prometheus endpoints /// without actually connecting to a Kubernetes cluster. /// public class FakePrometheusScanner : IPrometheusScanner { private readonly List endpoints; public FakePrometheusScanner(List endpoints) { this.endpoints = endpoints; } public Task> ScanAsync(KubernetesCluster cluster, CancellationToken ct = default) { return Task.FromResult(endpoints); } }