7c4a33b6d1
gates / gates (push) Successful in 24s
R-589 the update badge, R-590 the data-folder backup sentence, R-573 the two channel banners, R-572 two dead helpers. All four were built in Go, which is why neither the template parity fixtures nor TestI18nEnglishPages could see them; slice 5's LIVE proof is what found them. R-590 is the one that matters most: it is a promise about the customer's files. It said, in Hungarian and under an already-English folder label, that a drop-zone is temporary and unbacked. The test now asserts the CONSEQUENCE in both languages — and that the two languages do not produce the same string, which would mean the English fell back. R-572 is not what its row said. The row claimed a template renders "vasárnap" on an English page; measured, NO template and no Go file called pruneLabel or nextPruneLabel. They were dead func-map entries returning Hungarian, so they are deleted rather than translated — translating dead code would add machinery with no reader and a test pinning a fiction. Deletion is fail-loud and that was proven: a template naming the removed function panics loadTemplates at startup. Five red-proofs. One of them says something about the gate rather than the code: the Go-parity gate does not measure localeFuncs keys against the base capture, so the citation to TestLocaleFuncsHungarianBundleMatchesFuncMap is what carries them — the test was extended to make that citation true. MinAgent: 0.131.0 (unchanged). Hungarian byte-identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
157 lines
6.6 KiB
Go
157 lines
6.6 KiB
Go
package web
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
|
)
|
|
|
|
// appInfoWithCards overlays the R-75 folder card onto the shared app_info fixture. It reuses
|
|
// appInfoData (lifecycle_test.go) deliberately — that helper passes a *Metadata for a reason
|
|
// documented there (value vs pointer receivers at render time); do not fork it.
|
|
func appInfoWithCards(st stacks.Stack, cards []DataPathCard) map[string]interface{} {
|
|
d := appInfoData(st)
|
|
if len(cards) > 0 {
|
|
d["DataPathCards"] = cards
|
|
}
|
|
return d
|
|
}
|
|
|
|
// SEAM-WIRING RULE: a conditional affordance ships with a render test PER BRANCH of its gate.
|
|
// Branch 1 — cards present: the block renders, with the label, the deep link and the copy.
|
|
func TestAppInfo_DataPathCardRenders(t *testing.T) {
|
|
st := stacks.Stack{Name: "paperless-ngx", Deployed: true, State: stacks.StateRunning,
|
|
Meta: stacks.Metadata{Slug: "paperless-ngx", DisplayName: "Paperless-ngx"}}
|
|
cards := []DataPathCard{{
|
|
Label: "Beolvasandó dokumentumok",
|
|
Link: importFolderLink("demo-felhom.eu", "paperless"),
|
|
Consequence: consequenceFor(appbackup.ClassExcluded, stacks.RoleImport, bundleMsg(t, "hu")),
|
|
IsImport: true,
|
|
FreeSpace: "44.8 GB szabad",
|
|
}}
|
|
html := renderBackupPage(t, "app_info", appInfoWithCards(st, cards))
|
|
|
|
if !strings.Contains(html, "Hova tegyem a fájlokat?") {
|
|
t.Error("the folder card heading must render")
|
|
}
|
|
if !strings.Contains(html, "Beolvasandó dokumentumok") {
|
|
t.Error("the catalog label must render")
|
|
}
|
|
// The deep link must reach the page INTACT — this is the assertion the brief asks for, and it is
|
|
// what would have caught the v0.150.0 class of bug (a key rendered where another belonged).
|
|
if !strings.Contains(html, "https://files.demo-felhom.eu/files/Beolvas%C3%A1s/paperless") {
|
|
t.Errorf("the FileBrowser deep link must render intact; body:\n%s", excerpt(html, "datapath"))
|
|
}
|
|
if !strings.Contains(html, "nem készül róla biztonsági mentés") {
|
|
t.Error("an excluded drop-zone must say it is unbacked")
|
|
}
|
|
if !strings.Contains(html, "44.8 GB szabad") {
|
|
t.Error("the system-drive free space must render on an import row")
|
|
}
|
|
// The copy must not promise a single click — a cold deep link goes through the FileBrowser login.
|
|
if !strings.Contains(html, "be kell jelentkezned") {
|
|
t.Error("the card must warn that a FileBrowser sign-in may be needed")
|
|
}
|
|
}
|
|
|
|
// Branch 2 — no cards: nothing renders, and in particular no empty heading.
|
|
func TestAppInfo_NoDataPathCardsRendersNothing(t *testing.T) {
|
|
st := stacks.Stack{Name: "docmost", Deployed: true, State: stacks.StateRunning,
|
|
Meta: stacks.Metadata{Slug: "docmost", DisplayName: "Docmost"}}
|
|
html := renderBackupPage(t, "app_info", appInfoWithCards(st, nil))
|
|
if strings.Contains(html, "Hova tegyem a fájlokat?") {
|
|
t.Error("an app with no data_paths must not render the folder card")
|
|
}
|
|
if strings.Contains(html, "datapath-row") {
|
|
t.Error("no folder rows must render")
|
|
}
|
|
}
|
|
|
|
// bundleMsg is the production lookup the handler uses, bound to one language.
|
|
func bundleMsg(t *testing.T, lang string) func(string) string {
|
|
t.Helper()
|
|
b, err := i18n.Shared()
|
|
if err != nil {
|
|
t.Fatalf("bundle: %v", err)
|
|
}
|
|
return func(key string) string { return b.Msg(lang, key) }
|
|
}
|
|
|
|
// Fork-4: the consequence line is CLASS-driven, so the UI can never promise a backup the engines do
|
|
// not make. `excluded` is dropped at every tier — it must say so.
|
|
//
|
|
// R-590 (v0.258.0): the sentence now follows the household's language, so the CONSEQUENCE is
|
|
// asserted in BOTH — the mechanism (does it read the bundle?) is not the property that matters. It
|
|
// is a promise about the customer's files: an English household that cannot read „nem készül róla
|
|
// biztonsági mentés" may leave originals in a drop-zone the backup filter discards at every tier.
|
|
func TestConsequenceIsClassDriven(t *testing.T) {
|
|
cases := []struct {
|
|
lang string
|
|
tempAndUnbacked []string // an excluded import folder must say BOTH of these
|
|
promise string // a kept folder must say this
|
|
denial string // and must NOT say this
|
|
}{
|
|
{"hu", []string{"törli innen", "nem készül róla biztonsági mentés"},
|
|
"Biztonsági mentés készül", "nem készül"},
|
|
{"en", []string{"deletes them from here", "is not backed up"},
|
|
"It is backed up", "not backed up"},
|
|
}
|
|
for _, c := range cases {
|
|
msg := bundleMsg(t, c.lang)
|
|
imp := consequenceFor(appbackup.ClassExcluded, stacks.RoleImport, msg)
|
|
for _, want := range c.tempAndUnbacked {
|
|
if !strings.Contains(imp, want) {
|
|
t.Errorf("[%s] an excluded import folder must say %q: %q", c.lang, want, imp)
|
|
}
|
|
}
|
|
for _, cls := range []appbackup.BindClass{appbackup.ClassMandatory, appbackup.ClassOptional} {
|
|
lib := consequenceFor(cls, stacks.RoleLibrary, msg)
|
|
if !strings.Contains(lib, c.promise) {
|
|
t.Errorf("[%s] class %q must promise a backup: %q", c.lang, cls, lib)
|
|
}
|
|
if strings.Contains(lib, c.denial) {
|
|
t.Errorf("[%s] class %q must NOT say unbacked: %q", c.lang, cls, lib)
|
|
}
|
|
}
|
|
// An unclassified bind says NOTHING rather than guessing — an unverified backup promise
|
|
// about a customer's files is worse than no sentence. In every language.
|
|
if got := consequenceFor("", stacks.RoleLibrary, msg); got != "" {
|
|
t.Errorf("[%s] an unclassified path must produce no promise, got %q", c.lang, got)
|
|
}
|
|
}
|
|
// And the two languages must not say the same thing — that would mean the bundle fell back and
|
|
// the English household is reading Hungarian, which is the defect this row was opened for.
|
|
hu := consequenceFor(appbackup.ClassMandatory, stacks.RoleLibrary, bundleMsg(t, "hu"))
|
|
en := consequenceFor(appbackup.ClassMandatory, stacks.RoleLibrary, bundleMsg(t, "en"))
|
|
if hu == en {
|
|
t.Errorf("the English consequence line fell back to Hungarian: %q", en)
|
|
}
|
|
}
|
|
|
|
// A card is only built for a DEPLOYED app (Fork-2: deployed-only in the UI).
|
|
func TestBuildDataPathCards_UndeployedYieldsNothing(t *testing.T) {
|
|
s := testServer(t)
|
|
st := &stacks.Stack{Name: "paperless-ngx", Deployed: false,
|
|
Meta: stacks.Metadata{Slug: "paperless-ngx", DataPaths: []stacks.DataPath{
|
|
{Path: "paperless", Root: appbackup.RootImport, Role: stacks.RoleImport, Label: "x"},
|
|
}}}
|
|
if got := s.buildDataPathCards(st, "hu"); got != nil {
|
|
t.Errorf("an undeployed app must get no folder card, got %v", got)
|
|
}
|
|
}
|
|
|
|
func excerpt(html, needle string) string {
|
|
i := strings.Index(html, needle)
|
|
if i < 0 {
|
|
return "(needle not found)"
|
|
}
|
|
end := i + 400
|
|
if end > len(html) {
|
|
end = len(html)
|
|
}
|
|
return html[i:end]
|
|
}
|