0fe04bb6d5
gates / gates (push) Successful in 28s
The 2026-09-20 English drill ended one screen short: the claim page was English and its answers were Hungarian, so a household who mistyped the code from their e-mail could not tell a typo from a dead code. Fourteen call sites carrying nine messages now go through s.msg; the backup page's two protection warnings — which are promises about whether the customer's files are safe — follow the same route. Hungarian is byte-identical, proved structurally by the go-parity gate against the frozen base capture and red-proofed on a single added full stop. data["Title"] was DEAD (claim.html is standalone; .Title is layout.html's) and is deleted rather than translated — a translated dead field is a permanent false signal about where the page's title comes from. Six existing copy-contract tests were kept, not weakened: each now resolves its key through the real bundle, so it still convicts on a reworded Hungarian sentence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
166 lines
6.8 KiB
Go
166 lines
6.8 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
|
|
)
|
|
|
|
// R-598 — THE TWO SENTENCES THAT SAY WHETHER THE CUSTOMER'S FILES ARE SAFE.
|
|
//
|
|
// The 2026-09-20 English drill found the Backup page's protection warnings still Hungarian on an
|
|
// otherwise English page. They are not decoration: they say "only one copy is being made" and "the
|
|
// system backup is on the same disk as the system, so it protects against bad files but not against
|
|
// a disk failure". A household who cannot read them believes they are protected when they are not.
|
|
//
|
|
// These tests render the REAL /backups page through the real handler in both languages, because
|
|
// that is the only instrument that can see this defect — the sentences arrive as struct fields, so
|
|
// the template parity fixtures render them faithfully whatever language they are in.
|
|
|
|
// renderBackupsPageLang is renderBackupsPage with a language cookie. Same production handler.
|
|
func renderBackupsPageLang(t *testing.T, lang, primary string, disks []agentapi.DiskInfo, registerPath string) string {
|
|
t.Helper()
|
|
s := absentHarness(t, primary, disks, registerPath)
|
|
s.loadTemplates()
|
|
req := httptest.NewRequest("GET", "/backups", nil)
|
|
if lang != "" {
|
|
req.AddCookie(&http.Cookie{Name: langCookieName, Value: lang})
|
|
}
|
|
rr := httptest.NewRecorder()
|
|
s.backupsHandler(rr, req)
|
|
if rr.Code != 200 {
|
|
t.Fatalf("backups page returned %d, want 200", rr.Code)
|
|
}
|
|
return rr.Body.String()
|
|
}
|
|
|
|
func enText(t *testing.T, key string) string {
|
|
t.Helper()
|
|
b, err := i18n.Shared()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := b.Msg("en", key)
|
|
if got == key {
|
|
t.Fatalf("en.json does not know %q", key)
|
|
}
|
|
return got
|
|
}
|
|
|
|
// S2 — the degraded warning and the offer follow the reader, in the state the drill actually saw:
|
|
// never configured, backup on the system disk, one drive attached.
|
|
func TestBackupWarningsFollowLanguage(t *testing.T) {
|
|
disks := []agentapi.DiskInfo{{
|
|
Name: "hdd1", MountPath: "/mnt/hdd1", GuestPath: "/mnt/felhom-drives/hdd1", Role: "user-data",
|
|
}}
|
|
const reg = "/mnt/felhom-drives/hdd1"
|
|
|
|
huHTML := renderBackupsPageLang(t, "hu", builtinLocalTarget, disks, reg)
|
|
for _, key := range []string{backupTargetDegradedKey, backupTargetOfferKey} {
|
|
want := huText(t, key)
|
|
if !strings.Contains(huHTML, want) {
|
|
t.Errorf("the HUNGARIAN backups page lost %q — the pre-v0.259.0 wording must be byte-identical", want)
|
|
}
|
|
}
|
|
|
|
enHTML := renderBackupsPageLang(t, "en", builtinLocalTarget, disks, reg)
|
|
for _, key := range []string{backupTargetDegradedKey, backupTargetOfferKey} {
|
|
if want := enText(t, key); !strings.Contains(enHTML, want) {
|
|
t.Errorf("the ENGLISH backups page is missing %q", want)
|
|
}
|
|
// The decisive half: the Hungarian sentence must be GONE, not merely accompanied.
|
|
if hu := huText(t, key); strings.Contains(enHTML, hu) {
|
|
t.Errorf("the HUNGARIAN sentence %q is still on the ENGLISH backups page", hu)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The absent-drive state has the OPPOSITE remedy, and its English must promise exactly what the
|
|
// Hungarian promises: the FACT, the CONSEQUENCE, the REMEDY, in that order (§8's retrieval-promise
|
|
// rule applied to the English side).
|
|
func TestAbsentDriveWarningFollowsLanguageAndKeepsThePromise(t *testing.T) {
|
|
html := renderBackupsPageLang(t, "en", "felhom-backup",
|
|
[]agentapi.DiskInfo{theVanishedDrive}, "/mnt/felhom-drives/mentes2")
|
|
|
|
en := enText(t, backupTargetAbsentKey)
|
|
if !strings.Contains(html, en) {
|
|
t.Fatalf("the English absent-drive copy never reached the page; want %q", en)
|
|
}
|
|
if strings.Contains(html, huText(t, backupTargetAbsentKey)) {
|
|
t.Error("the Hungarian absent-drive copy is still on the English page")
|
|
}
|
|
for _, want := range []struct{ frag, why string }{
|
|
{"cannot be reached", "the FACT — the drive cannot be reached"},
|
|
{"is not made", "the CONSEQUENCE — the full system backup does not happen"},
|
|
{"reconnect it", "the REMEDY — reconnect THAT drive"},
|
|
} {
|
|
if !strings.Contains(en, want.frag) {
|
|
t.Errorf("the English absent copy is missing %s (%q); got: %s", want.why, want.frag, en)
|
|
}
|
|
}
|
|
// The English must not accidentally offer a different drive either.
|
|
if strings.Contains(html, `id="backup-target-assign"`) {
|
|
t.Error("an offer control rendered on the English page while the configured target is absent")
|
|
}
|
|
}
|
|
|
|
// The degraded sentence is a PROMISE about protection. Its English must draw the same distinction
|
|
// the Hungarian draws — corrupted files yes, disk failure no. An English text that promised
|
|
// protection against a disk failure would be worse than no translation at all.
|
|
func TestEnglishDegradedWarningPromisesExactlyWhatTheHungarianPromises(t *testing.T) {
|
|
en := enText(t, backupTargetDegradedKey)
|
|
for _, want := range []string{"same disk as the system", "corrupted files", "not against a disk failure", "second drive"} {
|
|
if !strings.Contains(en, want) {
|
|
t.Errorf("the English degraded warning is missing %q; got: %s", want, en)
|
|
}
|
|
}
|
|
// Hungarian says „hibás fájlok ellen véd, lemezhiba ellen nem" — protection is CONDITIONAL. A
|
|
// bare "protects against a disk failure" would invert it, so pin the negation's presence.
|
|
if !strings.Contains(en, "but not against a disk failure") {
|
|
t.Errorf("the English warning does not NEGATE disk-failure protection; got: %s", en)
|
|
}
|
|
}
|
|
|
|
// The tier labels on the same page: the builder is language-free now, and the words come from the
|
|
// reader's bundle. Asserted through buildTierViews rather than the page so a box with no agent
|
|
// still exercises it.
|
|
func TestTierLabelsFollowLanguage(t *testing.T) {
|
|
tiers := []agentapi.TierBackupState{
|
|
{Target: "local", Primary: true, Storage: "present"},
|
|
{Target: "felhom-pbs", Storage: "present"},
|
|
}
|
|
b, err := i18n.Shared()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, lang := range []string{"hu", "en"} {
|
|
v := &guestBackupView{}
|
|
msg := func(key string, a ...interface{}) string {
|
|
if len(a) == 0 {
|
|
return b.Msg(lang, key)
|
|
}
|
|
return b.Msgf(lang, key, a...)
|
|
}
|
|
buildTierViews(v, tiers, map[string]int64{"local": 86400, "felhom-pbs": 604800}, tvNow, msg)
|
|
if len(v.Tiers) != 2 {
|
|
t.Fatalf("[%s] want 2 tier rows, got %d", lang, len(v.Tiers))
|
|
}
|
|
wantLocal, wantPBS := b.Msgf(lang, "backup.tier.local", "local"), b.Msg(lang, "backup.tier.pbs")
|
|
if v.Tiers[0].Label != wantLocal {
|
|
t.Errorf("[%s] local tier label = %q, want %q", lang, v.Tiers[0].Label, wantLocal)
|
|
}
|
|
if v.Tiers[1].Label != wantPBS {
|
|
t.Errorf("[%s] PBS tier label = %q, want %q", lang, v.Tiers[1].Label, wantPBS)
|
|
}
|
|
}
|
|
// And the two languages must actually differ — otherwise the loop above passes on an
|
|
// untranslated bundle and proves nothing.
|
|
if b.Msg("hu", "backup.tier.pbs") == b.Msg("en", "backup.tier.pbs") {
|
|
t.Error("backup.tier.pbs is identical in both languages — untranslated")
|
|
}
|
|
}
|