Files
felhom-controller/controller/internal/web/r537_tier_contents_test.go
T
admin 5270bad76e
gates / gates (push) Successful in 23s
v0.252.0 — the sentences the program builds follow the language (R-557 slice 2 release A)
Slice 1 translated the dashboard's markup. The sentences the program BUILDS were still
Hungarian literals in Go, so an English household clicked an English button and was
answered in Hungarian. 226 of them move into the bundle here.

A flash was the hard part: it travels inside the redirect URL and is rendered by a
DIFFERENT request, so it now carries a bundle key plus its parameters. A link minted by
an older controller carries prose and is shown verbatim — never a raw key, never dropped.

Also converted: page data and view-model text, the internal/api JSON answers, the alert
banners (Alert.MessageKey, rendered on the way out of GetAlerts), 237 country names at
display, and the four page titles built around an app name (R-566 closed).

Hungarian is byte-identical, and that is measured rather than read:
scripts/i18n_go_parity.py freezes every Go literal at the base commit (7 467) and refuses
a key whose Hungarian is not that text, byte for byte. Three decoys, each seen to convict.
Its own first version filtered the capture through an ASCII-Hungarian word list and missed
seven real literals — the R-565 class. The filter is gone.

Nothing on the wire moved, and wire goldens now hold it there: the report's health
warnings and every notify event message stay Hungarian, because the hub MAILS the
controller's sentence when it has no entry of its own. Slice 3 (R-558) owns those.

MinAgent: 0.131.0 (unchanged). No hub release needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-18 10:15:56 +02:00

73 lines
3.0 KiB
Go

package web
import (
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
)
// fileLegProvider declares a MANDATORY drive-side bind for one app and nothing for the others — the
// shape of a class-A app (calibre-web, immich, nextcloud, paperless-ngx) against the 45 whose data
// lives entirely in Docker volumes.
type fileLegProvider struct {
blockProvider
withLegs string
}
func (p *fileLegProvider) GetStackComposePath(string) (string, bool) { return "compose", true }
func (p *fileLegProvider) GetStackClassifiedBinds(n string) ([]backup.ClassifiedBind, bool) {
if n != p.withLegs {
return nil, false
}
return []backup.ClassifiedBind{{
ComposeBind: appbackup.ComposeBind{Root: appbackup.RootHDD, RelPath: "appdata/" + n},
Class: appbackup.ClassMandatory,
}}, true
}
// R-537 — the backup page must describe what each TIER captured, not what the app is shaped like.
//
// The defect this pins, measured live on 2026-09-16 on a fresh box: the Tier-1 row read
// „DB + Konfig + Adatok" for Nextcloud, over a unit that held a database dump, three volume tars and
// no copy of the customer's files at all. The five photos in that folder were in no backup on the
// box, and the page said they were.
//
// Red-proof: put the old single label back (`if app.HasHDDData || app.HasVolumeData → "Adatok"`,
// rendered on all three tier rows) → the Tier-1 assertion fails.
func TestAppBackupRows_Tier1LabelDoesNotClaimFilesItCannotHold(t *testing.T) {
s, _, m := newOffboxWebServer(t)
drive := t.TempDir()
m.SetStackProvider(&fileLegProvider{blockProvider: blockProvider{hdd: drive}, withLegs: "nextcloud"})
rows := s.buildAppBackupRows(&backup.FullBackupStatus{AppDataInfo: []backup.AppBackupInfo{
{StackName: "nextcloud", DisplayName: "Nextcloud", HasHDDData: true, HasVolumeData: true},
{StackName: "privatebin", DisplayName: "PrivateBin", HasVolumeData: true},
}}, "hu")
nc := findRow(rows, "nextcloud")
if nc == nil {
t.Fatal("no row for nextcloud")
}
if strings.Contains(nc.Tier1Contents, "Adatok") {
t.Fatalf("the Tier-1 label claims it holds the app's data: %q — the unit has no file leg and the customer's files are on the drive", nc.Tier1Contents)
}
if !strings.Contains(nc.Tier23Contents, "Adatok") {
t.Fatalf("Tier 2/3 DO carry the file legs; their label must say so: %q", nc.Tier23Contents)
}
if nc.DriveFilesNote == "" {
t.Fatal("an app whose files a local backup cannot hold must be told where they ARE protected")
}
// NEGATIVE CONTROL: an app whose data really is inside the volumes the unit captured keeps its
// „Adatok". Without this, "never say Adatok" would pass and would be a different lie.
pb := findRow(rows, "privatebin")
if pb == nil || !strings.Contains(pb.Tier1Contents, "Adatok") {
t.Fatalf("an app whose data IS in the captured volumes must keep its Adatok label: %+v", pb)
}
if pb.DriveFilesNote != "" {
t.Fatalf("an app with no drive-side files needs no note about them: %q", pb.DriveFilesNote)
}
}