using EntKube.Provisioning.Domain; using FluentAssertions; namespace EntKube.Provisioning.Tests.Domain; public class GrafanaInstanceTests { private readonly Guid environmentId = Guid.NewGuid(); private readonly Guid clusterId = Guid.NewGuid(); // ─── Creation ─────────────────────────────────────────────────────── [Fact] public void Create_WithValidInputs_CreatesInstanceInPendingState() { // Arrange & Act — An operator provisions a Grafana instance for an environment. // Grafana is per-environment: one centralized visualization for all clusters // in that environment, deployed to a specific cluster. GrafanaInstance instance = GrafanaInstance.Create( environmentId, clusterId, "prod-grafana", "monitoring"); // Assert — The instance should be in Pending state with sensible defaults. instance.Id.Should().NotBe(Guid.Empty); instance.EnvironmentId.Should().Be(environmentId); instance.ClusterId.Should().Be(clusterId); instance.Name.Should().Be("prod-grafana"); instance.Namespace.Should().Be("monitoring"); instance.Status.Should().Be(GrafanaInstanceStatus.Pending); instance.CreatedAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5)); // Grafana defaults instance.Persistence.Should().BeFalse(); instance.PersistenceSize.Should().Be("10Gi"); instance.Oidc.Should().BeNull(); instance.Ingress.Should().BeNull(); } [Fact] public void Create_WithEmptyEnvironmentId_ThrowsArgumentException() { // Arrange & Act — Grafana must belong to an environment. Action act = () => GrafanaInstance.Create(Guid.Empty, clusterId, "prod-grafana", "monitoring"); // Assert act.Should().Throw() .WithParameterName("environmentId"); } [Fact] public void Create_WithEmptyClusterId_ThrowsArgumentException() { // Arrange & Act — Grafana must be deployed to a specific cluster. Action act = () => GrafanaInstance.Create(environmentId, Guid.Empty, "prod-grafana", "monitoring"); // Assert act.Should().Throw() .WithParameterName("clusterId"); } [Fact] public void Create_WithEmptyName_ThrowsArgumentException() { // Arrange & Act Action act = () => GrafanaInstance.Create(environmentId, clusterId, "", "monitoring"); // Assert act.Should().Throw() .WithParameterName("name"); } [Fact] public void Create_WithEmptyNamespace_ThrowsArgumentException() { // Arrange & Act Action act = () => GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", ""); // Assert act.Should().Throw() .WithParameterName("ns"); } // ─── Persistence Configuration ────────────────────────────────── [Fact] public void ConfigurePersistence_UpdatesSettings() { // Arrange — An operator wants Grafana to keep dashboards across restarts. GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); // Act instance.ConfigurePersistence(enabled: true, size: "20Gi"); // Assert instance.Persistence.Should().BeTrue(); instance.PersistenceSize.Should().Be("20Gi"); } // ─── OIDC Configuration ───────────────────────────────────────── [Fact] public void ConfigureOidc_SetsOidcProvider() { // Arrange — Integrate Grafana with an OIDC provider (e.g., Entra ID) // so users authenticate with corporate credentials. GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); // Act OidcConfiguration oidc = new( Provider: "generic_oauth", ClientId: "grafana-client-id", ClientSecretRef: "vault://monitoring/grafana-oidc-client-secret", AuthUrl: "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/authorize", TokenUrl: "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token", ApiUrl: "https://graph.microsoft.com/oidc/userinfo", Scopes: "openid profile email", RoleAttributePath: "contains(groups[*], 'admin-group-id') && 'Admin' || 'Viewer'", AutoLogin: true); instance.ConfigureOidc(oidc); // Assert instance.Oidc.Should().NotBeNull(); instance.Oidc!.Provider.Should().Be("generic_oauth"); instance.Oidc.ClientId.Should().Be("grafana-client-id"); instance.Oidc.ClientSecretRef.Should().Be("vault://monitoring/grafana-oidc-client-secret"); instance.Oidc.AutoLogin.Should().BeTrue(); } [Fact] public void ConfigureOidc_WithEmptyClientId_ThrowsArgumentException() { // Arrange GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); // Act & Assert OidcConfiguration oidc = new( Provider: "generic_oauth", ClientId: "", ClientSecretRef: "vault://secret", AuthUrl: "https://example.com/auth", TokenUrl: "https://example.com/token", ApiUrl: "https://example.com/userinfo", Scopes: "openid", RoleAttributePath: null, AutoLogin: false); Action act = () => instance.ConfigureOidc(oidc); act.Should().Throw(); } [Fact] public void RemoveOidc_ClearsOidcConfiguration() { // Arrange — A Grafana instance with OIDC configured. GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); OidcConfiguration oidc = new( Provider: "generic_oauth", ClientId: "client-id", ClientSecretRef: "vault://secret", AuthUrl: "https://example.com/auth", TokenUrl: "https://example.com/token", ApiUrl: "https://example.com/userinfo", Scopes: "openid", RoleAttributePath: null, AutoLogin: false); instance.ConfigureOidc(oidc); // Act — Remove OIDC, fall back to local auth. instance.RemoveOidc(); // Assert instance.Oidc.Should().BeNull(); } // ─── Ingress ──────────────────────────────────────────────────── [Fact] public void ConfigureIngress_SetsGrafanaRouting() { // Arrange — Publish Grafana externally with its own hostname. GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); // Act GrafanaIngressConfiguration ingress = new( Enabled: true, Provider: IngressProvider.GatewayApi, Hostname: "grafana.prod01.example.com", TlsEnabled: true, TlsCertificateMode: TlsCertificateMode.CertManager, TlsSecretName: null, ClusterIssuer: "letsencrypt-prod"); instance.ConfigureIngress(ingress); // Assert instance.Ingress.Should().NotBeNull(); instance.Ingress!.Enabled.Should().BeTrue(); instance.Ingress.Provider.Should().Be(IngressProvider.GatewayApi); instance.Ingress.Hostname.Should().Be("grafana.prod01.example.com"); instance.Ingress.TlsCertificateMode.Should().Be(TlsCertificateMode.CertManager); instance.Ingress.ClusterIssuer.Should().Be("letsencrypt-prod"); } [Fact] public void DisableIngress_ClearsIngressConfig() { // Arrange GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); GrafanaIngressConfiguration ingress = new( Enabled: true, Provider: IngressProvider.GatewayApi, Hostname: "grafana.example.com", TlsEnabled: true, TlsCertificateMode: TlsCertificateMode.Manual, TlsSecretName: "grafana-tls"); instance.ConfigureIngress(ingress); // Act instance.DisableIngress(); // Assert instance.Ingress.Should().BeNull(); } // ─── Dashboards ───────────────────────────────────────────────── [Fact] public void AddDashboard_StoresDashboardDefinition() { // Arrange — Dashboards are JSON definitions deployed as ConfigMaps // with the grafana_dashboard=1 label so the sidecar picks them up. GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); // Act — Add a Kubernetes cluster dashboard. GrafanaDashboard dashboard = new( Name: "k8s-cluster", DisplayName: "Kubernetes Cluster", Category: DashboardCategory.Kubernetes, JsonContent: """{"dashboard": {"title": "K8s Cluster"}}"""); instance.AddDashboard(dashboard); // Assert instance.Dashboards.Should().HaveCount(1); instance.Dashboards[0].Name.Should().Be("k8s-cluster"); instance.Dashboards[0].Category.Should().Be(DashboardCategory.Kubernetes); } [Fact] public void AddDashboard_WithDuplicateName_ThrowsInvalidOperationException() { // Arrange GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); GrafanaDashboard dashboard = new( Name: "k8s-cluster", DisplayName: "Kubernetes Cluster", Category: DashboardCategory.Kubernetes, JsonContent: """{"dashboard": {}}"""); instance.AddDashboard(dashboard); // Act & Assert — Can't add two dashboards with the same name. Action act = () => instance.AddDashboard(dashboard); act.Should().Throw(); } [Fact] public void RemoveDashboard_DeletesByName() { // Arrange GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); instance.AddDashboard(new GrafanaDashboard("k8s-cluster", "Kubernetes Cluster", DashboardCategory.Kubernetes, "{}")); instance.AddDashboard(new GrafanaDashboard("istio-mesh", "Istio Mesh", DashboardCategory.Istio, "{}")); // Act instance.RemoveDashboard("k8s-cluster"); // Assert instance.Dashboards.Should().HaveCount(1); instance.Dashboards[0].Name.Should().Be("istio-mesh"); } // ─── Lifecycle ────────────────────────────────────────────────── [Fact] public void MarkRunning_SetsStatusToRunning() { // Arrange GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); // Act instance.MarkRunning(); // Assert instance.Status.Should().Be(GrafanaInstanceStatus.Running); } [Fact] public void MarkDegraded_SetsStatusToDegraded() { // Arrange GrafanaInstance instance = GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "monitoring"); // Act instance.MarkDegraded(); // Assert instance.Status.Should().Be(GrafanaInstanceStatus.Degraded); } }