Files
Entkube/tests/EntKube.Clusters.Tests/Features/CnpgDatabaseProvisionerTests.cs
2026-05-13 14:01:32 +02:00

138 lines
5.2 KiB
C#

using EntKube.Clusters.Features.AdoptCluster.Components;
using FluentAssertions;
using k8s.Models;
namespace EntKube.Clusters.Tests.Features;
/// <summary>
/// Tests for the shared CNPG database provisioner that builds Job and Secret
/// manifests for creating databases on a CNPG cluster. These are pure manifest
/// builders — no Kubernetes API calls — so they're fully unit-testable.
/// </summary>
public class CnpgDatabaseProvisionerTests
{
[Fact]
public void GetPrimaryHost_ReturnsCorrectFqdn()
{
// The CNPG primary service follows the naming convention
// {clusterName}-rw.{namespace}.svc.cluster.local.
string host = CnpgDatabaseProvisioner.GetPrimaryHost("shared-pg", "cnpg-system");
host.Should().Be("shared-pg-rw.cnpg-system.svc.cluster.local");
}
[Fact]
public void BuildCreateDatabaseJob_TargetsPrimaryService()
{
// The Job should connect to the CNPG cluster's read-write primary
// service to execute the CREATE ROLE and CREATE DATABASE commands.
V1Job job = CnpgDatabaseProvisioner.BuildCreateDatabaseJob(
"shared-pg", "cnpg-system", "gitea", "gitea", "secret123");
// The psql command should target {clusterName}-rw.
string args = job.Spec.Template.Spec.Containers[0].Args[0];
args.Should().Contain("psql -h shared-pg-rw");
}
[Fact]
public void BuildCreateDatabaseJob_UsesSuperuserSecret()
{
// The Job needs the superuser password from the CNPG cluster's
// automatically created {clusterName}-superuser secret.
V1Job job = CnpgDatabaseProvisioner.BuildCreateDatabaseJob(
"shared-pg", "cnpg-system", "gitea", "gitea", "secret123");
V1EnvVar passwordEnv = job.Spec.Template.Spec.Containers[0].Env[0];
passwordEnv.Name.Should().Be("POSTGRES_PASSWORD");
passwordEnv.ValueFrom!.SecretKeyRef!.Name.Should().Be("shared-pg-superuser");
passwordEnv.ValueFrom.SecretKeyRef.Key.Should().Be("password");
}
[Fact]
public void BuildCreateDatabaseJob_SetsCorrectNamespace()
{
// The Job must run in the same namespace as the CNPG cluster
// so it can resolve the primary service by short name.
V1Job job = CnpgDatabaseProvisioner.BuildCreateDatabaseJob(
"shared-pg", "cnpg-system", "harbor", "harbor", "pass");
job.Metadata.NamespaceProperty.Should().Be("cnpg-system");
}
[Fact]
public void BuildCreateDatabaseJob_HasManagementLabels()
{
// The Job should be labeled for EntKube management and discoverability.
V1Job job = CnpgDatabaseProvisioner.BuildCreateDatabaseJob(
"shared-pg", "cnpg-system", "keycloak", "keycloak", "pass");
job.Metadata.Labels["app.kubernetes.io/managed-by"].Should().Be("entkube");
job.Metadata.Labels["entkube.io/operation"].Should().Be("create-database");
}
[Fact]
public void BuildCreateDatabaseJob_UsesIdempotentSql()
{
// The SQL script should use IF NOT EXISTS patterns so re-running
// the Job on an already-provisioned database doesn't fail.
V1Job job = CnpgDatabaseProvisioner.BuildCreateDatabaseJob(
"shared-pg", "cnpg-system", "gitea", "gitea", "secret123");
string args = job.Spec.Template.Spec.Containers[0].Args[0];
args.Should().Contain("WHERE NOT EXISTS (SELECT FROM pg_roles");
args.Should().Contain("WHERE NOT EXISTS (SELECT FROM pg_database");
}
[Fact]
public void BuildCredentialsSecret_ContainsAllConnectionDetails()
{
// The credentials secret should contain host, database, username,
// and password so the consuming service can connect.
V1Secret secret = CnpgDatabaseProvisioner.BuildCredentialsSecret(
"gitea-db-credentials", "gitea",
"shared-pg-rw.cnpg-system.svc.cluster.local",
"gitea", "gitea", "secret123");
secret.StringData["host"].Should().Be("shared-pg-rw.cnpg-system.svc.cluster.local");
secret.StringData["database"].Should().Be("gitea");
secret.StringData["username"].Should().Be("gitea");
secret.StringData["password"].Should().Be("secret123");
}
[Fact]
public void BuildCredentialsSecret_HasManagementLabels()
{
// The secret should be labeled for EntKube management.
V1Secret secret = CnpgDatabaseProvisioner.BuildCredentialsSecret(
"harbor-db-credentials", "harbor",
"shared-pg-rw.cnpg-system.svc.cluster.local",
"harbor", "harbor", "pass");
secret.Metadata.Labels["app.kubernetes.io/managed-by"].Should().Be("entkube");
secret.Metadata.Labels["entkube.io/purpose"].Should().Be("database-credentials");
}
[Fact]
public void BuildCredentialsSecret_SetsCorrectNamespace()
{
// The secret must be created in the target service's namespace,
// not the CNPG cluster's namespace.
V1Secret secret = CnpgDatabaseProvisioner.BuildCredentialsSecret(
"keycloak-db-credentials", "keycloak",
"shared-pg-rw.cnpg-system.svc.cluster.local",
"keycloak", "keycloak", "pass");
secret.Metadata.NamespaceProperty.Should().Be("keycloak");
}
}