138 lines
5.2 KiB
C#
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");
|
|
}
|
|
}
|