package web import ( "context" "html" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/stacks" ) // heldGuards holds every app; nothing else is exercised by the page. type heldGuards struct{} func (heldGuards) HoldFor(string) (bool, string) { return true, "HOLD SENTENCE" } func (heldGuards) Busy(string) (bool, string) { return false, "" } func (heldGuards) RestorePoints(context.Context, string, func(stacks.UpdateRestorePoint) bool) (stacks.UpdateRestorePoint, bool, []stacks.UpdateRestorePoint) { return stacks.UpdateRestorePoint{}, false, nil } func (heldGuards) CanBackUp(string) (bool, string) { return false, "" } func (heldGuards) BackupNow(context.Context, string) error { return nil } func (heldGuards) SafetyDump(context.Context, string) ([]string, error) { return nil, nil } func (heldGuards) HoldAfterFailedUpdate(string, time.Time, stacks.UpdateRestorePoint, string) error { return nil } // R-621 (the last half): the app page's HOLD PANEL points at the log the hold kept. Driven through the // REAL handler (appDetailHandler) and the REAL template, both languages, with the kept-log file present // and absent — a render per branch of the gate (seam-wiring rule). // // COMPANION RED-PROOF: drop the HoldLogKept block from appDetailHandler — the "kept" branch fails. func TestR621_HoldPanelPointsAtTheKeptLog(t *testing.T) { s, _ := credsHarness(t) s.stackMgr.SetUpdateGuards(heldGuards{}) s.loadTemplates() sd := filepath.Join(s.cfg.Paths.StacksDir, "crafty") render := func(lang string) string { rr := httptest.NewRecorder() s.appDetailHandler(rr, httptest.NewRequest("GET", "/apps/crafty?lang="+lang, nil), "crafty") return html.UnescapeString(rr.Body.String()) } const link = `` const huLine, enLine = "Az alkalmazás saját naplója megmaradt", "The app's own log from before it was stopped was kept" // No kept log: the panel is there (positive control) and says nothing about a log. h := render("hu") if !strings.Contains(h, `data-held="true"`) { t.Fatal("positive control: the held panel must render — nothing below would be judged") } if strings.Contains(h, `data-hold-log="true"`) { t.Error("with no kept log the panel must not claim one") } p := filepath.Join(sd, "hold-logs", "20260922T080000Z") if err := os.MkdirAll(p, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(p, "compose-logs.txt"), []byte("web-1 | never bound :8000\n"), 0o644); err != nil { t.Fatal(err) } for _, tc := range []struct{ lang, want, notWant string }{{"hu", huLine, enLine}, {"en", enLine, huLine}} { h := render(tc.lang) i := strings.Index(h, `data-held="true"`) if i < 0 { t.Fatalf("%s: the held panel is gone", tc.lang) } panel := h[i:] panel = panel[:strings.Index(panel, "")] if !strings.Contains(panel, `data-hold-log="true"`) || !strings.Contains(panel, tc.want) || !strings.Contains(panel, link) { t.Errorf("%s: the hold panel must say the log was kept and link the logs page; panel=%q", tc.lang, panel) } if strings.Contains(panel, tc.notWant) { t.Errorf("%s: the other language's sentence is on the page", tc.lang) } } }