too much for one commit

This commit is contained in:
Nils Blomgren
2026-05-13 14:01:32 +02:00
parent a96dd33039
commit 328d494530
394 changed files with 98104 additions and 78 deletions

View File

@@ -0,0 +1,360 @@
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<ArgumentException>()
.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<ArgumentException>()
.WithParameterName("clusterId");
}
[Fact]
public void Create_WithEmptyName_ThrowsArgumentException()
{
// Arrange & Act
Action act = () => GrafanaInstance.Create(environmentId, clusterId, "", "monitoring");
// Assert
act.Should().Throw<ArgumentException>()
.WithParameterName("name");
}
[Fact]
public void Create_WithEmptyNamespace_ThrowsArgumentException()
{
// Arrange & Act
Action act = () => GrafanaInstance.Create(environmentId, clusterId, "prod-grafana", "");
// Assert
act.Should().Throw<ArgumentException>()
.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<ArgumentException>();
}
[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<InvalidOperationException>();
}
[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);
}
}