pkg/certs/certs_test.go
Rama main · réplica de lectura del repositorio original.
package certs
import (
"testing"
"time"
)
func at(now time.Time, days int) *time.Time {
t := now.Add(time.Duration(days) * 24 * time.Hour)
return &t
}
func find(certs []Cert, ns, name string) *Cert {
for i := range certs {
if certs[i].Namespace == ns && certs[i].Name == name {
return &certs[i]
}
}
return nil
}
func TestAggregate_HealthThresholds(t *testing.T) {
now := time.Now().UTC()
got := Aggregate(Sources{
Now: now,
TLSSecrets: []Input{
{Name: "healthy", Namespace: "a", NotAfter: at(now, 45), Source: SourceTLSSecret},
{Name: "degraded", Namespace: "a", NotAfter: at(now, 10), Source: SourceTLSSecret},
{Name: "soon", Namespace: "a", NotAfter: at(now, 3), Source: SourceTLSSecret},
{Name: "expired", Namespace: "a", NotAfter: at(now, -2), Source: SourceTLSSecret},
},
})
want := map[string]Health{
"healthy": HealthHealthy,
"degraded": HealthDegraded,
"soon": HealthUnhealthy,
"expired": HealthUnhealthy,
}
for name, wantHealth := range want {
c := find(got, "a", name)
if c == nil {
t.Fatalf("cert %q missing", name)
}
if c.Health != wantHealth {
t.Errorf("cert %q health = %q, want %q", name, c.Health, wantHealth)
}
}
}
func TestAggregate_DedupsCertManagerOwnedSecret(t *testing.T) {
now := time.Now().UTC()
got := Aggregate(Sources{
Now: now,
CertManager: []Input{{
Name: "api", Namespace: "prod", Issuer: "letsencrypt",
Domains: []string{"api.example.com"}, NotAfter: at(now, 40),
Source: SourceCertManager, SecretName: "api-tls",
}},
TLSSecrets: []Input{
// Same physical cert as the Certificate above — must be dropped.
{Name: "api-tls", Namespace: "prod", NotAfter: at(now, 40), Source: SourceTLSSecret},
// An unrelated raw secret — must survive.
{Name: "webhook", Namespace: "prod", NotAfter: at(now, 12), Source: SourceTLSSecret},
},
})
if len(got) != 2 {
t.Fatalf("got %d certs, want 2 (cert-manager api + raw webhook; api-tls deduped)", len(got))
}
if c := find(got, "prod", "api-tls"); c != nil {
t.Error("api-tls TLS-secret row should have been deduped against the cert-manager Certificate")
}
api := find(got, "prod", "api")
if api == nil || api.Source != SourceCertManager {
t.Errorf("cert-manager row missing or wrong source: %+v", api)
}
if w := find(got, "prod", "webhook"); w == nil || w.Source != SourceTLSSecret {
t.Errorf("unrelated raw secret row missing or wrong source: %+v", w)
}
}
func TestAggregate_SortsMostUrgentFirstUnknownLast(t *testing.T) {
now := time.Now().UTC()
got := Aggregate(Sources{
Now: now,
CertManager: []Input{
// No NotAfter → unknown, must sink to the bottom.
{Name: "pending", Namespace: "a", Source: SourceCertManager},
},
TLSSecrets: []Input{
{Name: "later", Namespace: "a", NotAfter: at(now, 30), Source: SourceTLSSecret},
{Name: "sooner", Namespace: "a", NotAfter: at(now, 5), Source: SourceTLSSecret},
},
})
if len(got) != 3 {
t.Fatalf("got %d, want 3", len(got))
}
if got[0].Name != "sooner" || got[1].Name != "later" {
t.Errorf("order = [%s,%s,...], want sooner,later first", got[0].Name, got[1].Name)
}
last := got[len(got)-1]
if last.Name != "pending" || last.DaysLeft != nil || last.Health != HealthUnknown {
t.Errorf("unknown-expiry cert should sort last as unknown: %+v", last)
}
}
func TestAggregate_FormatsDomains(t *testing.T) {
now := time.Now().UTC()
got := Aggregate(Sources{
Now: now,
TLSSecrets: []Input{
{Name: "multi", Namespace: "a", NotAfter: at(now, 40), Source: SourceTLSSecret,
Domains: []string{"a.example", "b.example", "c.example", "d.example"}},
{Name: "single", Namespace: "a", NotAfter: at(now, 41), Source: SourceTLSSecret,
Domains: []string{"only.example"}},
},
})
if c := find(got, "a", "multi"); c == nil || c.Domains != "a.example, b.example +2 more" {
t.Errorf("multi-domain abbreviation = %q, want 'a.example, b.example +2 more'", c.Domains)
}
if c := find(got, "a", "single"); c == nil || c.Domains != "only.example" {
t.Errorf("single domain = %q, want 'only.example'", c.Domains)
}
}
// A cluster without cert-manager contributes zero CertManager inputs, so its
// TLS-secret certs must still come through on their own.
func TestAggregate_NoCertManagerStillReturnsSecretCerts(t *testing.T) {
now := time.Now().UTC()
got := Aggregate(Sources{
Now: now,
TLSSecrets: []Input{{Name: "raw", Namespace: "a", NotAfter: at(now, 20), Source: SourceTLSSecret}},
})
if len(got) != 1 || got[0].Name != "raw" || got[0].Source != SourceTLSSecret {
t.Fatalf("want the lone TLS-secret cert, got %+v", got)
}
}
// A cert that expired an hour ago must floor to -1 day ("expired"), not
// truncate to 0 ("expires today"). Guards the math.Floor vs int() distinction.
func TestAggregate_JustExpiredFloorsToNegative(t *testing.T) {
now := time.Now().UTC()
oneHourAgo := now.Add(-time.Hour)
got := Aggregate(Sources{
Now: now,
TLSSecrets: []Input{{Name: "expired", Namespace: "a", NotAfter: &oneHourAgo, Source: SourceTLSSecret}},
})
if len(got) != 1 || got[0].DaysLeft == nil {
t.Fatalf("want 1 cert with a daysLeft, got %+v", got)
}
if *got[0].DaysLeft != -1 {
t.Errorf("daysLeft = %d, want -1 (floor — a just-expired cert is expired, not 'today')", *got[0].DaysLeft)
}
if got[0].Health != HealthUnhealthy {
t.Errorf("health = %q, want unhealthy", got[0].Health)
}
}
// Dedup keys on namespace+secretName, so the same secretName in two namespaces
// must NOT collapse — a prod/tls Certificate owning prod/tls must not suppress
// an unrelated staging/tls raw secret.
func TestAggregate_DedupIsNamespaceScoped(t *testing.T) {
now := time.Now().UTC()
got := Aggregate(Sources{
Now: now,
CertManager: []Input{{
Name: "cert", Namespace: "prod", NotAfter: at(now, 40),
Source: SourceCertManager, SecretName: "tls",
}},
TLSSecrets: []Input{
{Name: "tls", Namespace: "prod", NotAfter: at(now, 40), Source: SourceTLSSecret}, // deduped (owned by prod cert)
{Name: "tls", Namespace: "staging", NotAfter: at(now, 12), Source: SourceTLSSecret}, // survives (different ns)
},
})
if find(got, "prod", "tls") != nil {
t.Error("prod/tls secret should be deduped against the cert-manager Certificate")
}
if find(got, "staging", "tls") == nil {
t.Error("staging/tls secret must survive — dedup must be namespace-scoped, not by bare secretName")
}
}
// Health thresholds are `< 7` and `< 30`, and DaysLeft truncates Hours()/24.
// Pin the exact boundaries so a `<`→`<=` or truncation regression is caught.
func TestAggregate_HealthThresholdBoundaries(t *testing.T) {
now := time.Now().UTC()
got := Aggregate(Sources{
Now: now,
TLSSecrets: []Input{
{Name: "d6", Namespace: "a", NotAfter: at(now, 6), Source: SourceTLSSecret},
{Name: "d7", Namespace: "a", NotAfter: at(now, 7), Source: SourceTLSSecret},
{Name: "d29", Namespace: "a", NotAfter: at(now, 29), Source: SourceTLSSecret},
{Name: "d30", Namespace: "a", NotAfter: at(now, 30), Source: SourceTLSSecret},
},
})
want := map[string]Health{
"d6": HealthUnhealthy, // 6 < 7
"d7": HealthDegraded, // 7 is NOT < 7
"d29": HealthDegraded, // 29 < 30
"d30": HealthHealthy, // 30 is NOT < 30
}
for name, wantHealth := range want {
c := find(got, "a", name)
if c == nil {
t.Fatalf("cert %q missing", name)
}
if c.Health != wantHealth {
t.Errorf("cert %q health = %q, want %q", name, c.Health, wantHealth)
}
}
}