From 7c27a74c4299c321452f72e019021c6a6dcbe7b9 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 1 Dec 2025 22:23:47 +0000 Subject: [PATCH] test: Add comprehensive tests for PBS snapshot conversion Add 13 test cases for convertPBSSnapshots covering: - Empty/nil input handling - Basic snapshot field mapping - Files as string array - Files as object array with filename field - Invalid file objects ignored gracefully - Verification status as string ("ok" vs other) - Verification status as object with state field - Verification object without state field - Multiple snapshots with unique IDs - Empty namespace handling in ID generation - Container (ct) backup type Coverage: convertPBSSnapshots 0% -> 100% --- internal/monitoring/pbs_convert_test.go | 322 ++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 internal/monitoring/pbs_convert_test.go diff --git a/internal/monitoring/pbs_convert_test.go b/internal/monitoring/pbs_convert_test.go new file mode 100644 index 0000000..c8818fd --- /dev/null +++ b/internal/monitoring/pbs_convert_test.go @@ -0,0 +1,322 @@ +package monitoring + +import ( + "testing" + "time" + + "github.com/rcourtman/pulse-go-rewrite/pkg/pbs" +) + +func TestConvertPBSSnapshots(t *testing.T) { + t.Run("empty input returns empty slice", func(t *testing.T) { + result := convertPBSSnapshots("pbs-1", "backup-store", "ns1", nil) + if len(result) != 0 { + t.Errorf("expected empty slice, got %d items", len(result)) + } + + result = convertPBSSnapshots("pbs-1", "backup-store", "ns1", []pbs.BackupSnapshot{}) + if len(result) != 0 { + t.Errorf("expected empty slice, got %d items", len(result)) + } + }) + + t.Run("basic snapshot conversion", func(t *testing.T) { + backupTime := int64(1700000000) + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "100", + BackupTime: backupTime, + Size: 1024000, + Protected: true, + Comment: "Test backup", + Owner: "root@pam", + }, + } + + result := convertPBSSnapshots("pbs-prod", "datastore1", "production", snapshots) + + if len(result) != 1 { + t.Fatalf("expected 1 backup, got %d", len(result)) + } + + backup := result[0] + expectedID := "pbs-pbs-prod-datastore1-production-vm-100-1700000000" + if backup.ID != expectedID { + t.Errorf("ID: expected %q, got %q", expectedID, backup.ID) + } + if backup.Instance != "pbs-prod" { + t.Errorf("Instance: expected %q, got %q", "pbs-prod", backup.Instance) + } + if backup.Datastore != "datastore1" { + t.Errorf("Datastore: expected %q, got %q", "datastore1", backup.Datastore) + } + if backup.Namespace != "production" { + t.Errorf("Namespace: expected %q, got %q", "production", backup.Namespace) + } + if backup.BackupType != "vm" { + t.Errorf("BackupType: expected %q, got %q", "vm", backup.BackupType) + } + if backup.VMID != "100" { + t.Errorf("VMID: expected %q, got %q", "100", backup.VMID) + } + expectedTime := time.Unix(backupTime, 0) + if !backup.BackupTime.Equal(expectedTime) { + t.Errorf("BackupTime: expected %v, got %v", expectedTime, backup.BackupTime) + } + if backup.Size != 1024000 { + t.Errorf("Size: expected %d, got %d", 1024000, backup.Size) + } + if !backup.Protected { + t.Error("Protected: expected true") + } + if backup.Verified { + t.Error("Verified: expected false (no verification data)") + } + if backup.Comment != "Test backup" { + t.Errorf("Comment: expected %q, got %q", "Test backup", backup.Comment) + } + if backup.Owner != "root@pam" { + t.Errorf("Owner: expected %q, got %q", "root@pam", backup.Owner) + } + }) + + t.Run("files as string array", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "ct", + BackupID: "200", + BackupTime: 1700000000, + Files: []interface{}{"file1.img", "file2.pxar", "file3.conf"}, + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + if len(result[0].Files) != 3 { + t.Fatalf("expected 3 files, got %d", len(result[0].Files)) + } + if result[0].Files[0] != "file1.img" { + t.Errorf("Files[0]: expected %q, got %q", "file1.img", result[0].Files[0]) + } + if result[0].Files[1] != "file2.pxar" { + t.Errorf("Files[1]: expected %q, got %q", "file2.pxar", result[0].Files[1]) + } + if result[0].Files[2] != "file3.conf" { + t.Errorf("Files[2]: expected %q, got %q", "file3.conf", result[0].Files[2]) + } + }) + + t.Run("files as object array with filename field", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "300", + BackupTime: 1700000000, + Files: []interface{}{ + map[string]interface{}{"filename": "disk-0.raw", "size": 10737418240}, + map[string]interface{}{"filename": "vm.conf", "size": 1024}, + }, + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + if len(result[0].Files) != 2 { + t.Fatalf("expected 2 files, got %d", len(result[0].Files)) + } + if result[0].Files[0] != "disk-0.raw" { + t.Errorf("Files[0]: expected %q, got %q", "disk-0.raw", result[0].Files[0]) + } + if result[0].Files[1] != "vm.conf" { + t.Errorf("Files[1]: expected %q, got %q", "vm.conf", result[0].Files[1]) + } + }) + + t.Run("files with missing filename field ignored", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "400", + BackupTime: 1700000000, + Files: []interface{}{ + map[string]interface{}{"filename": "valid.raw"}, + map[string]interface{}{"name": "invalid.raw"}, // Wrong field name + map[string]interface{}{}, // Empty object + }, + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + if len(result[0].Files) != 1 { + t.Fatalf("expected 1 file (only valid one), got %d", len(result[0].Files)) + } + if result[0].Files[0] != "valid.raw" { + t.Errorf("Files[0]: expected %q, got %q", "valid.raw", result[0].Files[0]) + } + }) + + t.Run("verification as string ok", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "500", + BackupTime: 1700000000, + Verification: "ok", + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + if !result[0].Verified { + t.Error("Verified: expected true when verification is 'ok'") + } + }) + + t.Run("verification as string not ok", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "501", + BackupTime: 1700000000, + Verification: "failed", + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + if result[0].Verified { + t.Error("Verified: expected false when verification is not 'ok'") + } + }) + + t.Run("verification as object with state ok", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "600", + BackupTime: 1700000000, + Verification: map[string]interface{}{ + "state": "ok", + "upid": "UPID:pbs:00001234", + "snapshot": "2023-11-14T12:00:00Z", + }, + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + if !result[0].Verified { + t.Error("Verified: expected true when verification.state is 'ok'") + } + }) + + t.Run("verification as object with state failed", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "601", + BackupTime: 1700000000, + Verification: map[string]interface{}{ + "state": "failed", + "upid": "UPID:pbs:00005678", + }, + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + if result[0].Verified { + t.Error("Verified: expected false when verification.state is 'failed'") + } + }) + + t.Run("verification as object without state field", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "602", + BackupTime: 1700000000, + Verification: map[string]interface{}{ + "upid": "UPID:pbs:00009999", + }, + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + if result[0].Verified { + t.Error("Verified: expected false when verification object has no state field") + } + }) + + t.Run("multiple snapshots", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "100", + BackupTime: 1700000000, + }, + { + BackupType: "ct", + BackupID: "200", + BackupTime: 1700001000, + }, + { + BackupType: "vm", + BackupID: "100", + BackupTime: 1700002000, // Same VM, different time + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "ns", snapshots) + + if len(result) != 3 { + t.Fatalf("expected 3 backups, got %d", len(result)) + } + + // Verify each has unique ID + ids := make(map[string]bool) + for _, backup := range result { + if ids[backup.ID] { + t.Errorf("duplicate ID found: %s", backup.ID) + } + ids[backup.ID] = true + } + }) + + t.Run("empty namespace in ID", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "vm", + BackupID: "100", + BackupTime: 1700000000, + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "", snapshots) + + expectedID := "pbs-pbs-1-store--vm-100-1700000000" + if result[0].ID != expectedID { + t.Errorf("ID with empty namespace: expected %q, got %q", expectedID, result[0].ID) + } + }) + + t.Run("container backup type", func(t *testing.T) { + snapshots := []pbs.BackupSnapshot{ + { + BackupType: "ct", + BackupID: "101", + BackupTime: 1700000000, + Size: 512000, + }, + } + + result := convertPBSSnapshots("pbs-1", "store", "ns", snapshots) + + if result[0].BackupType != "ct" { + t.Errorf("BackupType: expected %q, got %q", "ct", result[0].BackupType) + } + }) +}