Ctrl K
ComunidadGuías…

pkg/certs/certs_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
		}
	}
}