using EntKube.Clusters.Features.Certificates;
using FluentAssertions;
namespace EntKube.Clusters.Tests.Features;
///
/// Tests for the Certificate Authority feature. Since the handler connects to
/// a real K8s cluster, these tests focus on the domain models, request validation,
/// and operation result patterns. Integration tests with a real cluster would go
/// in a separate test suite.
///
public class CertificateAuthorityTests
{
// ─── CaOperationResult ────────────────────────────────────────────────
[Fact]
public void CaOperationResult_Ok_HasSuccessTrue()
{
// When we create a successful result, it should carry the message
// and have Success = true.
CaOperationResult result = CaOperationResult.Ok("CA created.", new List { "Step 1" });
result.Success.Should().BeTrue();
result.Message.Should().Be("CA created.");
result.Actions.Should().ContainSingle().Which.Should().Be("Step 1");
}
[Fact]
public void CaOperationResult_Failure_HasSuccessFalse()
{
// A failure result should carry the error message with Success = false.
CaOperationResult result = CaOperationResult.Failure("Something went wrong.");
result.Success.Should().BeFalse();
result.Message.Should().Be("Something went wrong.");
result.Actions.Should().BeEmpty();
}
[Fact]
public void CaOperationResult_Ok_WithNoActions_DefaultsToEmptyList()
{
// When no actions are provided, the list should default to empty.
CaOperationResult result = CaOperationResult.Ok("Done.");
result.Actions.Should().NotBeNull();
result.Actions.Should().BeEmpty();
}
// ─── CertificateAuthorityInfo ─────────────────────────────────────────
[Fact]
public void CertificateAuthorityInfo_InternalCA_HasCorrectType()
{
// An internal CA should be classified as CaType.Internal and have
// no domain restrictions.
CertificateAuthorityInfo ca = new(
Name: "platform-internal-ca",
Type: CaType.Internal,
SecretName: "platform-internal-ca-secret",
Domains: new List(),
IsExternal: false,
InTrustBundle: true,
Status: "Ready",
NotBefore: DateTimeOffset.UtcNow.AddYears(-1),
NotAfter: DateTimeOffset.UtcNow.AddYears(9),
Organization: "EntKube Platform");
ca.Type.Should().Be(CaType.Internal);
ca.Domains.Should().BeEmpty();
ca.IsExternal.Should().BeFalse();
ca.InTrustBundle.Should().BeTrue();
}
[Fact]
public void CertificateAuthorityInfo_DomainCA_HasDomains()
{
// A domain CA should carry the list of domains it's scoped to.
CertificateAuthorityInfo ca = new(
Name: "corp-ca",
Type: CaType.Domain,
SecretName: "corp-ca-secret",
Domains: new List { "*.internal.corp.com", "*.svc.local" },
IsExternal: false,
InTrustBundle: true,
Status: "Ready",
NotBefore: DateTimeOffset.UtcNow,
NotAfter: DateTimeOffset.UtcNow.AddYears(5),
Organization: "Corp Inc");
ca.Type.Should().Be(CaType.Domain);
ca.Domains.Should().HaveCount(2);
ca.Domains.Should().Contain("*.internal.corp.com");
}
[Fact]
public void CertificateAuthorityInfo_ExternalCA_FlagIsSet()
{
// An imported external CA should have IsExternal = true.
CertificateAuthorityInfo ca = new(
Name: "digicert-ca",
Type: CaType.Domain,
SecretName: "digicert-ca-secret",
Domains: new List { "*.example.com" },
IsExternal: true,
InTrustBundle: true,
Status: "Ready",
NotBefore: null,
NotAfter: null,
Organization: "DigiCert");
ca.IsExternal.Should().BeTrue();
}
[Fact]
public void CertificateAuthorityInfo_LetsEncrypt_HasAcmeType()
{
// A Let's Encrypt issuer is always external and publicly trusted.
CertificateAuthorityInfo ca = new(
Name: "letsencrypt-prod",
Type: CaType.LetsEncrypt,
SecretName: string.Empty,
Domains: new List(),
IsExternal: true,
InTrustBundle: true,
Status: "Ready",
NotBefore: null,
NotAfter: null,
Organization: "Let's Encrypt");
ca.Type.Should().Be(CaType.LetsEncrypt);
ca.IsExternal.Should().BeTrue();
}
// ─── Request models ──────────────────────────────────────────────────
[Fact]
public void CreateInternalCARequest_DefaultValues_AreReasonable()
{
// The default values for an internal CA should be sensible defaults.
CreateInternalCARequest request = new("my-ca");
request.Name.Should().Be("my-ca");
request.Organization.Should().Be("EntKube Platform");
request.DurationDays.Should().Be(3650);
request.BundleName.Should().BeNull();
}
[Fact]
public void CreateDomainCARequest_WithDomains_CarriesThem()
{
// A domain CA request should carry the domains it covers.
CreateDomainCARequest request = new(
Name: "corp-ca",
Domains: new List { "*.corp.com", "*.internal.corp.com" });
request.Domains.Should().HaveCount(2);
request.TlsCert.Should().BeNull();
request.TlsKey.Should().BeNull();
}
[Fact]
public void CreateDomainCARequest_WithImportedCert_HasTlsFields()
{
// An imported domain CA should carry the base64-encoded cert and key.
CreateDomainCARequest request = new(
Name: "imported-ca",
Domains: new List { "*.example.com" },
TlsCert: "base64cert",
TlsKey: "base64key");
request.TlsCert.Should().Be("base64cert");
request.TlsKey.Should().Be("base64key");
}
// ─── CaType enum ─────────────────────────────────────────────────────
[Fact]
public void CaType_HasExpectedValues()
{
// The enum should have exactly three values.
Enum.GetValues().Should().HaveCount(3);
Enum.GetValues().Should().Contain(CaType.Internal);
Enum.GetValues().Should().Contain(CaType.Domain);
Enum.GetValues().Should().Contain(CaType.LetsEncrypt);
}
}