package web import ( "errors" "testing" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/appbackup" "gitea.dooplex.hu/admin/felhom-controller/internal/backup" ) // R-258 — the per-app tier-1 tick must answer about THIS app, and say nothing when it knows nothing. // // Driven through appDumpVerdict, which is the seam buildAppBackupRows now calls. The defect was a // verdict derived from a GLOBAL field, so the test that matters is the one with two apps where the // global answer and the per-app answer disagree. func res(stack string, err error) appbackup.DumpResult { return appbackup.DumpResult{DB: appbackup.DiscoveredDB{StackName: stack}, Error: err} } // SCENARIO F — X's own dump failed; Y's succeeded and is the most recent on the box. func TestAppDumpVerdict_IsPerApp_NotTheBoxsMostRecentRun(t *testing.T) { dump := &backup.DBDumpStatus{ LastRun: time.Now(), // Y ran last and succeeded, so the box-level Success is true — which is exactly the value // the old code used for every app. Success: true, Results: []appbackup.DumpResult{ res("appX", errors.New("pg_dump: connection refused")), res("appY", nil), }, } if got := appDumpVerdict(dump, "appX"); got != "error" { t.Errorf("app X's own dump FAILED but its tick is %q, want \"error\".\n"+ "This is R-258: the verdict was read from the box's most recent dump run — app Y's — so a "+ "failed backup showed a green tick to the customer.", got) } if got := appDumpVerdict(dump, "appY"); got != "ok" { t.Errorf("app Y succeeded but its tick is %q, want \"ok\"", got) } } // SCENARIO G — nothing known is not the same as fine. func TestAppDumpVerdict_NoResultForThisApp_IsNoVerdict(t *testing.T) { dump := &backup.DBDumpStatus{Success: true, Results: []appbackup.DumpResult{res("other", nil)}} if got := appDumpVerdict(dump, "appWithNoDatabase"); got != "" { t.Errorf("an app with no dump result of its own got the verdict %q; want \"\" (no icon).\n"+ "A green tick standing for \"a restore point file exists\" is the presence-is-not-success "+ "rule as a UI badge.", got) } // and with no dump run recorded at all if got := appDumpVerdict(nil, "anything"); got != "" { t.Errorf("no dump status at all gave the verdict %q; want \"\"", got) } } // An app with SEVERAL databases: any failure among them makes the app's backup a failure. A partial // dump is not a success, and reporting the last-listed result would make the verdict order-dependent. func TestAppDumpVerdict_AnyFailingDatabaseFailsTheApp(t *testing.T) { for _, tc := range []struct { name string results []appbackup.DumpResult }{ {"failure first", []appbackup.DumpResult{res("app", errors.New("boom")), res("app", nil)}}, {"failure last", []appbackup.DumpResult{res("app", nil), res("app", errors.New("boom"))}}, } { t.Run(tc.name, func(t *testing.T) { if got := appDumpVerdict(&backup.DBDumpStatus{Results: tc.results}, "app"); got != "error" { t.Errorf("one of the app's databases failed to dump, verdict = %q, want \"error\"", got) } }) } } // All of this app's databases dumped cleanly → ok. func TestAppDumpVerdict_AllCleanIsOK(t *testing.T) { dump := &backup.DBDumpStatus{Results: []appbackup.DumpResult{res("app", nil), res("app", nil)}} if got := appDumpVerdict(dump, "app"); got != "ok" { t.Errorf("verdict = %q, want \"ok\"", got) } }