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)
}
}
}