diff --git a/internal/backup/preflight.go b/internal/backup/preflight.go index b3c09d1a..065de9b3 100644 --- a/internal/backup/preflight.go +++ b/internal/backup/preflight.go @@ -92,9 +92,11 @@ func RunPreflight(ctx context.Context, dsn, dataDir, workspace string) *Prefligh } // Collect warnings from non-ok checks (use make to avoid JSON null). + // Both "warning" and "missing" surface Detail (the problem) so the user + // sees the actual cause, not just the Hint (the fix). warnings := make([]string, 0) for _, c := range checks { - if c.Status == "warning" { + if (c.Status == "warning" || c.Status == "missing") && c.Detail != "" { warnings = append(warnings, c.Detail) } if c.Hint != "" { diff --git a/internal/backup/preflight_test.go b/internal/backup/preflight_test.go index 1e4908d3..0a9d021e 100644 --- a/internal/backup/preflight_test.go +++ b/internal/backup/preflight_test.go @@ -1,6 +1,7 @@ package backup import ( + "context" "os" "path/filepath" "testing" @@ -157,7 +158,7 @@ func TestPreflightResult_FlatFields(t *testing.T) { } // We can't easily test with a real DSN, so test the structure with empty DSN - result := RunPreflight(nil, "", tmpDir, "") + result := RunPreflight(context.Background(), "", tmpDir, "") // Verify flat fields exist if !result.DiskSpaceOK && result.DiskSpaceOK { @@ -198,6 +199,46 @@ func TestPreflightResult_FlatFields(t *testing.T) { } } +// TestCollectWarnings_IncludesDetailAndHint verifies that both "warning" +// and "missing" checks surface their Detail (the problem) alongside their +// Hint (the fix). Regression guard: earlier versions only surfaced Detail +// for "warning" status, leaving "missing" checks to display the fix with +// no explanation of the cause. +func TestCollectWarnings_IncludesDetailAndHint(t *testing.T) { + checks := []PreflightCheck{ + {Name: "ok_check", Status: "ok", Detail: "all good"}, + {Name: "warn_check", Status: "warning", Detail: "degraded service", Hint: "do X to recover"}, + {Name: "missing_check", Status: "missing", Detail: "pg_dump 17 cannot dump PostgreSQL 18 server", Hint: "Install postgresql18-client"}, + {Name: "ok_with_hint", Status: "ok", Detail: "informational", Hint: "optional tune-up"}, + } + + warnings := make([]string, 0) + for _, c := range checks { + if (c.Status == "warning" || c.Status == "missing") && c.Detail != "" { + warnings = append(warnings, c.Detail) + } + if c.Hint != "" { + warnings = append(warnings, c.Hint) + } + } + + want := []string{ + "degraded service", + "do X to recover", + "pg_dump 17 cannot dump PostgreSQL 18 server", + "Install postgresql18-client", + "optional tune-up", + } + if len(warnings) != len(want) { + t.Fatalf("warnings len = %d, want %d\ngot: %v", len(warnings), len(want), warnings) + } + for i, w := range want { + if warnings[i] != w { + t.Errorf("warnings[%d] = %q, want %q", i, warnings[i], w) + } + } +} + func TestFormatBytes_EdgeCases(t *testing.T) { // Test boundary values tests := []struct {