198 lines
6.8 KiB
C#
198 lines
6.8 KiB
C#
using EntKube.Clusters.Features.Certificates;
|
|
using FluentAssertions;
|
|
|
|
namespace EntKube.Clusters.Tests.Features;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<string> { "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<string>(),
|
|
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<string> { "*.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<string> { "*.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<string>(),
|
|
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<string> { "*.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<string> { "*.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<CaType>().Should().HaveCount(3);
|
|
Enum.GetValues<CaType>().Should().Contain(CaType.Internal);
|
|
Enum.GetValues<CaType>().Should().Contain(CaType.Domain);
|
|
Enum.GetValues<CaType>().Should().Contain(CaType.LetsEncrypt);
|
|
}
|
|
}
|