Files
felhom-controller/controller/internal/web/backup_target_render_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

146 lines
6.8 KiB
Go

package web
import (
"context"
"net/http/httptest"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// R-112 — SCENARIO E: THE SEAM. This is the test whose absence let E-2 ship.
//
// The endpoint was byte-correct the whole time. `resolveBackupTargetState` produced the right state,
// `degradedMessageFor` produced the right Hungarian, and v0.185.1 even added a test proving the route
// dispatched. None of that was ever reachable by a customer, because NOTHING fetched or rendered it:
// the controller's templates fetch 18 distinct /api/storage/* endpoints and backup-target was one of
// only two with zero references (felhom.eu audits/E2D-fresh-vm-2026-07-29.md §5.1).
//
// So these tests deliberately do NOT call the resolver and assert its string — that proves the
// resolver, which was never broken. They drive `backupsHandler` over httptest and assert the RENDERED
// HTML, travelling handler → backupTargetView → resolveBackupTargetState → degradedMessageFor →
// the production "backups" template. Delete the one line in backupsHandler that sets
// data["BackupTarget"] and every assertion below fails, which is precisely the class of regression
// that shipped five times in this project.
// renderBackupsPage drives the REAL page handler and returns the HTML a browser would receive.
func renderBackupsPage(t *testing.T, primary string, disks []agentapi.DiskInfo, registerPath string) string {
t.Helper()
s := absentHarness(t, primary, disks, registerPath)
s.loadTemplates()
rr := httptest.NewRecorder()
s.backupsHandler(rr, httptest.NewRequest("GET", "/backups", nil))
if rr.Code != 200 {
t.Fatalf("backups page returned %d, want 200", rr.Code)
}
return rr.Body.String()
}
// C — configured, drive absent: the truthful copy reaches the page, and no offer does.
func TestBackupsPageRendersTheAbsentDriveCopy(t *testing.T) {
html := renderBackupsPage(t, "felhom-backup", []agentapi.DiskInfo{theVanishedDrive}, "/mnt/felhom-drives/mentes2")
if !strings.Contains(html, huText(t, backupTargetAbsentKey)) {
t.Error("the absent-drive copy never reached the page — the customer is told nothing while " +
"their backup drive is missing (R-112: the state had no consumer at all)")
}
if strings.Contains(html, huText(t, backupTargetDegradedKey)) {
t.Error("the page shows the SYSTEM-DISK copy for an absent configured drive (R-114)")
}
// Assert the CONTROL's markup, not the bare id: the page script always contains
// getElementById('backup-target-assign'), so a substring match on the id alone matches the
// script and reports a control that is not there. (This test caught exactly that on itself.)
if strings.Contains(html, `id="backup-target-assign"`) {
t.Error("an offer control rendered while the configured target is absent — the remedy is to " +
"reconnect that drive, not to pick another")
}
if strings.Contains(html, `id="backup-target-offer"`) {
t.Error("the offer block rendered while the configured target is absent")
}
}
// A — never configured: the copy AND the offer control both render, and the control carries the
// drive's path so accepting it can only ever assign that one.
func TestBackupsPageRendersTheOfferWhenNeverConfigured(t *testing.T) {
html := renderBackupsPage(t, builtinLocalTarget, []agentapi.DiskInfo{{
Name: "hdd1", MountPath: "/mnt/hdd1", GuestPath: "/mnt/felhom-drives/hdd1", Role: "user-data",
}}, "/mnt/felhom-drives/hdd1")
if !strings.Contains(html, huText(t, backupTargetDegradedKey)) {
t.Error("the degraded copy never reached the page")
}
if !strings.Contains(html, huText(t, backupTargetOfferKey)) {
t.Error("the offer copy never reached the page")
}
if !strings.Contains(html, `id="backup-target-assign"`) {
t.Error("no offer control rendered — the customer is told to attach a drive but given no way " +
"to assign the one they already have")
}
if !strings.Contains(html, `data-path="/mnt/felhom-drives/hdd1"`) {
t.Error("the offer control does not carry the offered drive's path")
}
// It is an OFFER: it must not submit itself.
if strings.Contains(html, "backup-target-assign.click()") || strings.Contains(html, "autosubmit") {
t.Error("the offer control auto-submits — declining must be possible by doing nothing")
}
}
// B — healthy renders NOTHING. Assert the absence of all three strings, not the absence of an error.
func TestBackupsPageRendersNothingWhenHealthy(t *testing.T) {
html := renderBackupsPage(t, "felhom-backup", []agentapi.DiskInfo{{
Name: "mentes2", MountPath: "/mnt/mentes2", GuestPath: "/mnt/felhom-drives/mentes2",
Role: "user-data", BackupTarget: true,
}}, "/mnt/felhom-drives/mentes2")
assertNoBackupTargetCopy(t, html, "a HEALTHY box grew a banner — a working configuration must "+
"look normal, or every dashboard carries a permanent notice and warnings stop being read")
}
// D — unknown renders NOTHING. Not being able to ask the agent is not evidence of degradation.
func TestBackupsPageRendersNothingWhenAgentUnreachable(t *testing.T) {
s := testServer(t)
if err := s.settings.AddStoragePath(settings.StoragePath{Path: "/mnt/felhom-drives/hdd1"}); err != nil {
t.Fatalf("register: %v", err)
}
// No tiersFn/disksFn seams and no configured endpoint → agentClient() fails → Known:false.
s.loadTemplates()
rr := httptest.NewRecorder()
s.backupsHandler(rr, httptest.NewRequest("GET", "/backups", nil))
assertNoBackupTargetCopy(t, rr.Body.String(), "an UNREACHABLE agent produced a customer warning — "+
"absence of an answer is not evidence of degradation (R-88 Part 2's mistake)")
}
func assertNoBackupTargetCopy(t *testing.T, html, why string) {
t.Helper()
for _, s := range []struct{ frag, name string }{
{huText(t, backupTargetDegradedKey), "the system-disk copy"},
{huText(t, backupTargetAbsentKey), "the absent-drive copy"},
{huText(t, backupTargetOfferKey), "the offer copy"},
} {
if strings.Contains(html, s.frag) {
t.Errorf("%s rendered: %s", s.name, why)
}
}
if strings.Contains(html, `id="backup-target-alert"`) {
t.Errorf("the banner element rendered: %s", why)
}
}
// The view is nil — not an empty struct — in the two silent states, so a template typo cannot
// accidentally decorate a working box with an empty alert box.
func TestBackupTargetViewIsNilWhenNothingShouldRender(t *testing.T) {
healthy := absentHarness(t, "felhom-backup", []agentapi.DiskInfo{{
MountPath: "/mnt/mentes2", GuestPath: "/mnt/felhom-drives/mentes2", Role: "user-data", BackupTarget: true,
}}, "")
if v := healthy.backupTargetView(context.Background(), "hu"); v != nil {
t.Errorf("healthy returned a non-nil view %+v — nil is what makes the template render nothing", v)
}
unknown := testServer(t)
if v := unknown.backupTargetView(context.Background(), "hu"); v != nil {
t.Errorf("unknown returned a non-nil view %+v", v)
}
}