Files
felhom-controller/controller/internal/web/backup_target_offer_test.go
T
admin 0fe04bb6d5
gates / gates (push) Successful in 28s
v0.259.0 — the claim page and the backup warnings answer in the reader's language (R-596, R-598)
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
2026-09-21 07:45:56 +02:00

99 lines
4.6 KiB
Go

package web
import (
"os"
"strings"
"testing"
)
// SCENARIO E — a healthy configuration must look NORMAL.
//
// The single most likely over-correction in this whole arc: decorating every dashboard with a
// reassurance banner. Eight consecutive fixes here have produced one, and a permanent notice on a
// working box is how a real warning stops being read. When the target is a real drive, the state
// carries no message at all — there is nothing for the UI to render.
func TestHealthyBackupTargetRendersNothing(t *testing.T) {
st := BackupTargetState{Known: true, Degraded: false, TargetID: "felhom-backup", Label: "Külső HDD"}
if msg := degradedMessageFor(st); msg != "" {
t.Fatalf("a healthy target produced customer copy %q — a working configuration must look "+
"normal, with no caution and no decoration (Scenario E)", msg)
}
}
// SCENARIO D — declining is valid; forgetting is not. A degraded box keeps saying so, every time it
// is asked. There is no "dismissed" flag by design: the customer declines by not accepting, and the
// state is recomputed from the agent on the next visit rather than remembered as handled.
func TestDegradedStateKeepsReportingOnEveryVisit(t *testing.T) {
st := BackupTargetState{Known: true, Degraded: true, TargetID: "local"}
for visit := 1; visit <= 3; visit++ {
if msg := degradedMessageFor(st); msg == "" {
t.Fatalf("visit %d produced no message — a declined offer must stay VISIBLE, not go quiet", visit)
}
}
}
// The copy must carry FACT → CONSEQUENCE → REMEDY. A customer told only "your backup is on the same
// disk" cannot act; the sentence has to say what that costs them and what fixes it.
func TestDegradedCopyNamesConsequenceAndRemedy(t *testing.T) {
msg := huText(t, degradedMessageFor(BackupTargetState{Known: true, Degraded: true}))
for _, want := range []struct{ frag, why string }{
{"ugyanazon a lemezen", "the FACT — it shares the disk with the system"},
{"lemezhiba ellen nem", "the CONSEQUENCE — it does not survive a disk failure"},
{"második meghajtót", "the REMEDY — attach a second drive"},
} {
if !strings.Contains(msg, want.frag) {
t.Errorf("degraded copy is missing %s (%q); got: %s", want.why, want.frag, msg)
}
}
}
// UNKNOWN IS NOT DEGRADED. An unreachable or pre-R-82 agent means we could not ask — rendering a
// warning there would put a permanent scare on a box that may be perfectly healthy, and it is the
// absence-read-as-a-value mistake this project keeps closing (R-88 Part 2).
func TestUnknownStateRendersNothing(t *testing.T) {
// Degraded:true is deliberate. With Degraded:false the fixture passes even if the !Known guard is
// deleted, because the second condition catches it — the test would be HOLLOW and a red-proof
// proved exactly that. The meaningful case is "we could not ask, and the other field says
// degraded": the guard must win.
if msg := degradedMessageFor(BackupTargetState{Known: false, Degraded: true}); msg != "" {
t.Fatalf("an UNKNOWN state produced customer copy %q — not being able to ask is not evidence "+
"of degradation", msg)
}
}
// REACHABILITY — the repo's seam-wiring rule, and it already bit once here.
//
// These handlers were first registered as /api/backup-target inside ServeStorageAPI's switch, which
// main.go mounts ONLY at "/api/storage/". The endpoints returned "endpoint not found" on a live box
// while every unit test passed, because the tests called the handlers directly and never travelled
// the mount. A feature is not shipped until its entry point is reachable.
func TestBackupTargetRoutesLiveUnderTheStorageAPIMount(t *testing.T) {
// main.go: mux.Handle("/api/storage/", ... ServeStorageAPI). Anything the switch answers MUST
// therefore begin with that prefix or it is dead on arrival.
for _, p := range []string{
"/api/storage/backup-target",
"/api/storage/backup-target/assign",
} {
if !strings.HasPrefix(p, "/api/storage/") {
t.Fatalf("%s is outside the /api/storage/ mount and would 404 in production", p)
}
}
src := storageAPISource(t)
for _, want := range []string{`"/api/storage/backup-target"`, `"/api/storage/backup-target/assign"`} {
if !strings.Contains(src, want) {
t.Errorf("ServeStorageAPI does not dispatch %s — the handler exists but nothing routes to it", want)
}
}
}
// storageAPISource reads the dispatcher's own source, so the assertion is about what the router
// actually contains rather than about a constant the test itself defines.
func storageAPISource(t *testing.T) string {
t.Helper()
b, err := os.ReadFile("storage_handlers.go")
if err != nil {
t.Fatalf("read storage_handlers.go: %v", err)
}
return string(b)
}