package web import ( "bytes" "strings" "testing" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/agentapi" "gitea.dooplex.hu/admin/felhom-controller/internal/i18n" ) // R-517 — the whole-guest tile speaks from SUCCESSES per tier. The BIGNIGHT page read, after a local // success and a failed PBS attempt on absent storage: "✗ · 0 B · PBS · Naprakész" and ticked // "Távoli rendszermentés — külön hardveren". var tvNow = time.Date(2026, 9, 14, 19, 15, 36, 0, time.UTC) // RED-PROOF (run 2026-09-15, recorded in REPORT.md): with buildTierViews reading a tier's "success" // from LastAttempt instead of LastSuccess, the shown backup lost its size and this failed at "the // successful local backup is not the one shown: has=true size=0". // huMsg is the Hungarian side of buildTierViews' message seam (v0.259.0, R-598). The tier labels // used to be Hungarian literals inside the builder; they are bundle keys now, and this renders them // exactly as a Hungarian household's request does — so the label assertions below still compare the // WORDS, not a key. func huMsg(t *testing.T) func(string, ...interface{}) string { t.Helper() return func(key string, a ...interface{}) string { b, err := i18n.Shared() if err != nil { t.Fatalf("i18n bundle: %v", err) } if len(a) == 0 { return b.Msg("hu", key) } return b.Msgf("hu", key, a...) } } func TestBuildTierViews_BignightFailedPBSOnAbsentStorage(t *testing.T) { v := &guestBackupView{} tiers := []agentapi.TierBackupState{ {Target: "local", Primary: true, Storage: "present", LastSuccess: &agentapi.BackupRecord{TargetID: "local", Success: true, SizeBytes: 8877619753, StartedAt: "2026-09-14T19:03:23Z"}, LastAttempt: &agentapi.TierAttempt{StartedAt: "2026-09-14T19:03:23Z", Success: true}}, {Target: "felhom-pbs", Storage: "absent", LastAttempt: &agentapi.TierAttempt{StartedAt: "2026-09-14T19:09:59Z", Success: false, Error: "storage 'felhom-pbs' does not exist"}}, } buildTierViews(v, tiers, map[string]int64{"local": 86400, "felhom-pbs": 604800}, tvNow, huMsg(t)) if v.Offsite { t.Fatalf("remote tick shown without a PBS success: %+v", v.Tiers) } if !v.HasBackup || v.SizeBytes != 8877619753 || v.Target != "Helyi tároló (local)" { t.Fatalf("the successful local backup is not the one shown: has=%v size=%d target=%q", v.HasBackup, v.SizeBytes, v.Target) } if v.Due { t.Fatalf("a current local success with an unprovisioned PBS tier must read up to date, got Due") } pbs := v.Tiers[1] if !pbs.NotSetUp || pbs.HasSuccess || !pbs.FailedAfter { t.Fatalf("pbs row wrong (want not set up, no success, failed attempt shown): %+v", pbs) } } // A set-up PBS tier whose last attempt failed: ✗ under the last success, and not current if the // success is outside the window → Due, no remote tick. func TestBuildTierViews_PBSFailedUnderOldSuccess(t *testing.T) { v := &guestBackupView{} tiers := []agentapi.TierBackupState{ {Target: "local", Primary: true, Storage: "present", LastSuccess: &agentapi.BackupRecord{Success: true, SizeBytes: 10, StartedAt: "2026-09-14T01:00:00Z"}}, {Target: "felhom-pbs", Storage: "present", LastSuccess: &agentapi.BackupRecord{Success: true, SizeBytes: 20, StartedAt: "2026-08-20T01:00:00Z"}, LastAttempt: &agentapi.TierAttempt{StartedAt: "2026-09-14T02:00:00Z", Success: false}}, } buildTierViews(v, tiers, map[string]int64{"local": 86400, "felhom-pbs": 604800}, tvNow, huMsg(t)) pbs := v.Tiers[1] if !pbs.HasSuccess || !pbs.FailedAfter || pbs.Current { t.Fatalf("pbs row: want old success kept, failure shown under it, not current: %+v", pbs) } if v.Offsite || !v.Due { t.Fatalf("want no remote tick and Due, got Offsite=%v Due=%v", v.Offsite, v.Due) } } // After an agent restart the success is read back from storage: shown, size unknown. func TestBuildTierViews_SuccessFromStorageAfterRestart(t *testing.T) { v := &guestBackupView{} tiers := []agentapi.TierBackupState{ {Target: "local", Primary: true, Storage: "present", LastSuccessSource: "storage", LastSuccess: &agentapi.BackupRecord{Success: true, StartedAt: "2026-09-14T19:03:23Z"}}, } buildTierViews(v, tiers, map[string]int64{"local": 86400}, tvNow, huMsg(t)) if !v.HasBackup || v.Due || v.Tiers[0].SizeKnown { t.Fatalf("after restart: want the local success shown, up to date, size unknown: %+v / due=%v", v.Tiers[0], v.Due) } // MEASURED LIVE 2026-09-15 on 9201: the top card printed „0 B" for this state. // RED-PROOF (run 2026-09-15, recorded in REPORT.md): without `v.SizeUnknown = !tv.SizeKnown` // this failed at "tile would print a size it does not know". if !v.SizeUnknown { t.Fatal("tile would print a size it does not know (0 B) for a success read back from storage") } } // The rendered tile says „–", not „0 B", when the size is unknown. func TestBackupTile_UnknownSizeRendersDash(t *testing.T) { s := securityHarness(t) s.loadTemplates() v := &guestBackupView{Available: true, HasBackup: true, Success: true, SizeUnknown: true, Target: "Helyi tároló (local)"} var buf bytes.Buffer if err := s.tmpl.ExecuteTemplate(&buf, "backups", map[string]interface{}{"Page": "backups", "Title": "t", "GuestBackup": v, "Backup": map[string]interface{}{}}); err != nil { t.Skipf("backups page needs more data than this harness gives: %v", err) } if strings.Contains(buf.String(), ">0 B<") { t.Fatal("unknown size rendered as 0 B") } }