mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-08 01:37:54 +00:00
test(recovery): cover downstream malformed metadata consumers
This commit is contained in:
parent
dc61333e57
commit
2dac3bedef
2 changed files with 153 additions and 0 deletions
65
internal/api/reporting_recovery_resilience_test.go
Normal file
65
internal/api/reporting_recovery_resilience_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
88
internal/monitoring/recovery_rollups_resilience_test.go
Normal file
88
internal/monitoring/recovery_rollups_resilience_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue