pkg/audit/cnpg_test.go
Rama main · réplica de lectura del repositorio original.
package audit
import (
"strings"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func cnpgObj(kind, ns, name string, spec map[string]any) *unstructured.Unstructured {
if spec == nil {
spec = map[string]any{}
}
return &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "postgresql.cnpg.io/v1",
"kind": kind,
"metadata": map[string]any{"name": name, "namespace": ns},
"spec": spec,
}}
}
func cnpgScheduledBackup(ns, name, clusterName, method string) *unstructured.Unstructured {
return cnpgObj("ScheduledBackup", ns, name, map[string]any{
"cluster": map[string]any{"name": clusterName},
"schedule": "0 0 2 * * *", // CNPG crons are six-field
"method": method,
})
}
func TestCNPGDeclarativeBackup_FlagsClusterWithNoSchedule(t *testing.T) {
input := &CheckInput{
CNPGClusters: []*unstructured.Unstructured{cnpgObj("Cluster", "pg", "unprotected", nil)},
CNPGScheduledBackups: nil,
CNPGScheduledBackupsAuthoritative: true,
}
tr := newEvalTracker()
findings := checkCNPGDeclarativeBackup(tr, input)
if len(findings) != 1 {
t.Fatalf("expected 1 finding, got %d: %+v", len(findings), findings)
}
f := findings[0]
if f.CheckID != checkCNPGNoDeclarativeBackup {
t.Errorf("CheckID = %q, want %q", f.CheckID, checkCNPGNoDeclarativeBackup)
}
if f.Severity != SeverityWarning {
t.Errorf("Severity = %q, want %q (posture, not breakage)", f.Severity, SeverityWarning)
}
if f.Category != CategoryReliability {
t.Errorf("Category = %q, want %q", f.Category, CategoryReliability)
}
if f.Kind != "Cluster" || f.Namespace != "pg" || f.Name != "unprotected" {
t.Errorf("wrong subject: %+v", f)
}
if f.Group != "postgresql.cnpg.io" {
t.Errorf("Group = %q, want postgresql.cnpg.io", f.Group)
}
if tr.counts[checkCNPGNoDeclarativeBackup]["pg"] != 1 {
t.Errorf("expected the cluster to be counted as evaluated once")
}
}
func TestCNPGDeclarativeBackup_ScheduleSatisfiesCheck(t *testing.T) {
// All three methods are declarative schedules; the plugin one is the case
// that a barmanObjectStore-only heuristic would miss.
for _, method := range []string{"barmanObjectStore", "volumeSnapshot", "plugin"} {
t.Run(method, func(t *testing.T) {
input := &CheckInput{
CNPGClusters: []*unstructured.Unstructured{cnpgObj("Cluster", "pg", "protected", nil)},
CNPGScheduledBackups: []*unstructured.Unstructured{cnpgScheduledBackup("pg", "nightly", "protected", method)},
CNPGScheduledBackupsAuthoritative: true,
}
tr := newEvalTracker()
if findings := checkCNPGDeclarativeBackup(tr, input); len(findings) != 0 {
t.Fatalf("expected no findings, got %+v", findings)
}
if tr.counts[checkCNPGNoDeclarativeBackup]["pg"] != 1 {
t.Errorf("a passing cluster must still count as evaluated")
}
})
}
}
func TestCNPGDeclarativeBackup_SuspendedScheduleStillCounts(t *testing.T) {
// Suspension is deliberate operator intent; re-reporting it here would
// duplicate intent as a defect.
sb := cnpgScheduledBackup("pg", "nightly", "protected", "barmanObjectStore")
_ = unstructured.SetNestedField(sb.Object, true, "spec", "suspend")
input := &CheckInput{
CNPGClusters: []*unstructured.Unstructured{cnpgObj("Cluster", "pg", "protected", nil)},
CNPGScheduledBackups: []*unstructured.Unstructured{sb},
CNPGScheduledBackupsAuthoritative: true,
}
if findings := checkCNPGDeclarativeBackup(newEvalTracker(), input); len(findings) != 0 {
t.Fatalf("suspended schedule should still count as declared, got %+v", findings)
}
}
func TestCNPGDeclarativeBackup_ScheduleInOtherNamespaceDoesNotCount(t *testing.T) {
input := &CheckInput{
CNPGClusters: []*unstructured.Unstructured{cnpgObj("Cluster", "pg", "app", nil)},
CNPGScheduledBackups: []*unstructured.Unstructured{cnpgScheduledBackup("other", "nightly", "app", "barmanObjectStore")},
CNPGScheduledBackupsAuthoritative: true,
}
if findings := checkCNPGDeclarativeBackup(newEvalTracker(), input); len(findings) != 1 {
t.Fatalf("a schedule in a different namespace targets a different cluster, got %+v", findings)
}
}
// The coverage gate: without a synced cluster-wide ScheduledBackup informer the
// inventory may cover a subset of namespaces, so absence proves nothing.
func TestCNPGDeclarativeBackup_NonAuthoritativeInventoryIsNotEvaluated(t *testing.T) {
input := &CheckInput{
CNPGClusters: []*unstructured.Unstructured{cnpgObj("Cluster", "pg", "unprotected", nil)},
CNPGScheduledBackups: nil,
CNPGScheduledBackupsAuthoritative: false,
}
tr := newEvalTracker()
if findings := checkCNPGDeclarativeBackup(tr, input); len(findings) != 0 {
t.Fatalf("expected no findings when absence isn't provable, got %+v", findings)
}
if len(tr.counts) != 0 {
t.Fatalf("a check that couldn't run must not record evaluations, got %+v", tr.counts)
}
}
func TestCNPGDeclarativeBackup_NoClustersIsNotEvaluated(t *testing.T) {
tr := newEvalTracker()
if findings := checkCNPGDeclarativeBackup(tr, &CheckInput{CNPGScheduledBackupsAuthoritative: true}); len(findings) != 0 {
t.Fatalf("expected no findings without CNPG, got %+v", findings)
}
if len(tr.counts) != 0 {
t.Fatalf("CNPG-absent cluster must record nothing, got %+v", tr.counts)
}
}
func TestCNPGDeclarativeBackup_MessageNamesTheBackupMode(t *testing.T) {
cases := []struct {
name string
spec map[string]any
want string
}{
{
name: "plugin",
spec: map[string]any{"plugins": []any{map[string]any{"name": cnpgBarmanPluginName}}},
want: "spec.method: plugin",
},
{
name: "in-tree",
spec: map[string]any{"backup": map[string]any{"barmanObjectStore": map[string]any{"destinationPath": "s3://b"}}},
want: "spec.backup.barmanObjectStore",
},
{
name: "nothing configured",
spec: map[string]any{},
want: "No backup destination is configured",
},
{
name: "explicitly disabled plugin reads as unconfigured",
spec: map[string]any{"plugins": []any{map[string]any{"name": cnpgBarmanPluginName, "enabled": false}}},
want: "No backup destination is configured",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
input := &CheckInput{
CNPGClusters: []*unstructured.Unstructured{cnpgObj("Cluster", "pg", "c", tc.spec)},
CNPGScheduledBackupsAuthoritative: true,
}
findings := checkCNPGDeclarativeBackup(newEvalTracker(), input)
if len(findings) != 1 {
t.Fatalf("expected 1 finding, got %+v", findings)
}
if !strings.Contains(findings[0].Message, tc.want) {
t.Errorf("message %q should mention %q", findings[0].Message, tc.want)
}
// Never assert backups don't exist — only that none is declared.
if strings.Contains(findings[0].Message, "is not backed up") {
t.Errorf("message overclaims: %q", findings[0].Message)
}
})
}
}
func TestCNPGDeclarativeBackup_CountsRollupAndRegistry(t *testing.T) {
input := &CheckInput{
CNPGClusters: []*unstructured.Unstructured{
cnpgObj("Cluster", "pg", "protected", nil),
cnpgObj("Cluster", "pg", "unprotected", nil),
},
CNPGScheduledBackups: []*unstructured.Unstructured{cnpgScheduledBackup("pg", "nightly", "protected", "plugin")},
CNPGScheduledBackupsAuthoritative: true,
}
results := RunChecks(input)
got, ok := results.CheckCounts[checkCNPGNoDeclarativeBackup]
if !ok {
t.Fatalf("check missing from CheckCounts: %+v", results.CheckCounts)
}
if got.Evaluated != 2 || got.Passed != 1 {
t.Errorf("counts = %+v, want Evaluated 2 / Passed 1", got)
}
if _, ok := CheckRegistry[checkCNPGNoDeclarativeBackup]; !ok {
t.Errorf("check has no CheckRegistry entry — it would be untoggleable and uncatalogued")
}
for _, in := range results.MissingInputs {
if strings.Contains(strings.ToLower(in), "cnpg") {
t.Errorf("a self-gated check must not report a missing input, got %q", in)
}
}
}