Files
felhom-controller/controller/internal/web/r621_hold_panel_test.go
T
admin 0f2eab7a3e R-621: the hold panel points at the log the hold kept
A held app's page now says, inside the hold panel, that the app's own log from
before it was stopped was kept, with a Napló link to the logs page that serves
it (0b93e1a). Shown only for an update hold with a non-empty kept log
(Manager.HoldLogKept). New copy through the bundle (hu + en), parity case
app_info_hold_log captured when born.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-06 02:01:12 +02:00

82 lines
3.2 KiB
Go

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 = `<a href="/stacks/crafty/logs" class="btn btn-sm btn-outline">`
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, "</div>")]
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)
}
}
}