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