test(recovery): cover downstream malformed metadata consumers

This commit is contained in:
rcourtman 2026-03-26 22:58:27 +00:00
parent dc61333e57
commit 2dac3bedef
2 changed files with 153 additions and 0 deletions

View file

@ -0,0 +1,65 @@
package api
import (
"context"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/recovery"
)
func TestReportingHandlers_ListBackupsForReport_ToleratesMalformedPersistedMetadata(t *testing.T) {
boolPtr := func(v bool) *bool { return &v }
recoveryHandler, dbPath := newRecoveryHandlerWithPersistedPoint(t, recovery.RecoveryPoint{
ID: "report-point-bad-json",
Provider: recovery.ProviderProxmoxPBS,
Kind: recovery.KindBackup,
Mode: recovery.ModeRemote,
Outcome: recovery.OutcomeSuccess,
SubjectResourceID: "vm-123",
SubjectRef: &recovery.ExternalRef{
Type: "proxmox-vm",
Name: "Archive VM",
},
RepositoryRef: &recovery.ExternalRef{
Type: "pbs-datastore",
Name: "fast-store",
},
CompletedAt: timePtr(time.Date(2026, 2, 20, 9, 0, 0, 0, time.UTC)),
Verified: boolPtr(true),
Immutable: boolPtr(true),
Details: map[string]any{
"storage": "fast-store",
"volid": "vm/123/2026-02-20T09:00:00Z",
},
})
corruptRecoveryRowJSON(t, dbPath, "report-point-bad-json", true, true, true)
handler := NewReportingHandlers(nil, recoveryHandler.manager)
start := time.Date(2026, 2, 20, 0, 0, 0, 0, time.UTC)
end := time.Date(2026, 2, 20, 23, 59, 59, 0, time.UTC)
backups := handler.listBackupsForReport(context.Background(), "default", "vm-123", start, end)
if len(backups) != 1 {
t.Fatalf("expected exactly 1 backup, got %d", len(backups))
}
if backups[0].Type != "pbs" {
t.Fatalf("backup type = %q, want %q", backups[0].Type, "pbs")
}
if backups[0].Storage != "" {
t.Fatalf("backup storage = %q, want empty after malformed details degradation", backups[0].Storage)
}
if backups[0].VolID != "" {
t.Fatalf("backup volid = %q, want empty after malformed details degradation", backups[0].VolID)
}
if !backups[0].Verified {
t.Fatal("expected verified flag to survive malformed metadata degradation")
}
if !backups[0].Protected {
t.Fatal("expected protected flag to survive malformed metadata degradation")
}
if !backups[0].Timestamp.Equal(time.Date(2026, 2, 20, 9, 0, 0, 0, time.UTC)) {
t.Fatalf("backup timestamp = %s, want %s", backups[0].Timestamp, time.Date(2026, 2, 20, 9, 0, 0, 0, time.UTC))
}
}

View file

@ -0,0 +1,88 @@
package monitoring
import (
"context"
"database/sql"
"path/filepath"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/recovery"
recoverymanager "github.com/rcourtman/pulse-go-rewrite/internal/recovery/manager"
_ "modernc.org/sqlite"
)
func TestMonitor_ListBackupRollupsForAlerts_ToleratesMalformedPersistedMetadata(t *testing.T) {
baseDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(baseDir)
manager := recoverymanager.New(mtp)
store, err := manager.StoreForOrg("default")
if err != nil {
t.Fatalf("StoreForOrg(default): %v", err)
}
completedAt := time.Date(2026, 2, 21, 8, 30, 0, 0, time.UTC)
if err := store.UpsertPoints(context.Background(), []recovery.RecoveryPoint{
{
ID: "monitor-point-bad-json",
Provider: recovery.ProviderTrueNAS,
Kind: recovery.KindBackup,
Mode: recovery.ModeRemote,
Outcome: recovery.OutcomeSuccess,
SubjectRef: &recovery.ExternalRef{
Type: "truenas-dataset",
Name: "tank/apps",
},
CompletedAt: &completedAt,
},
}); err != nil {
t.Fatalf("UpsertPoints(): %v", err)
}
persistence, err := mtp.GetPersistence("default")
if err != nil {
t.Fatalf("GetPersistence(default): %v", err)
}
corruptMonitorRecoveryRowJSON(
t,
filepath.Join(persistence.DataDir(), "recovery", "recovery.db"),
"monitor-point-bad-json",
)
m := &Monitor{recoveryManager: manager}
rollups, err := m.listBackupRollupsForAlerts(context.Background())
if err != nil {
t.Fatalf("listBackupRollupsForAlerts() error = %v, want graceful degradation", err)
}
if len(rollups) != 1 {
t.Fatalf("expected exactly 1 rollup, got %d", len(rollups))
}
if rollups[0].SubjectRef != nil {
t.Fatalf("expected malformed item ref to be omitted, got %#v", rollups[0].SubjectRef)
}
if rollups[0].Display.SubjectLabel != "tank/apps" {
t.Fatalf("display.subjectLabel = %q, want %q", rollups[0].Display.SubjectLabel, "tank/apps")
}
if rollups[0].Display.ItemType != "dataset" {
t.Fatalf("display.itemType = %q, want %q", rollups[0].Display.ItemType, "dataset")
}
if rollups[0].LastOutcome != recovery.OutcomeSuccess {
t.Fatalf("lastOutcome = %q, want %q", rollups[0].LastOutcome, recovery.OutcomeSuccess)
}
}
func corruptMonitorRecoveryRowJSON(t *testing.T, dbPath string, rowID string) {
t.Helper()
db, err := sql.Open("sqlite", dbPath)
if err != nil {
t.Fatalf("sql.Open(%q): %v", dbPath, err)
}
t.Cleanup(func() { _ = db.Close() })
if _, err := db.ExecContext(context.Background(), "UPDATE recovery_points SET subject_ref_json = '{' WHERE id = ?", rowID); err != nil {
t.Fatalf("corrupt recovery row json: %v", err)
}
}