package web import ( "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" ) // ── R-302 — RENDERED, at the boundary the defect lives at ─────────────────────────────────────── // // The condition is unit-tested in internal/backup; these assert the SENTENCES, because the defect was // always copy that disagreed with what the box could see, and only the rendered bytes show that. // abandonBannerData renders a page carrying the countdown strip. `offered` is the R-302 verdict. func abandonBannerData(offered bool, repoState string) map[string]interface{} { d := splitTestData() d["Offbox"] = &settings.OffboxTarget{ Enabled: true, Host: "nas.local", User: "felhom", RepoPath: "/srv/repo", EscrowState: "escrowed", RepoState: repoState, QuotaGB: 50, StatsKnown: true, } d["OffboxQuotaPct"] = 0 d["RecoveryBanner"] = true d["RecoveryAbandonDays"] = 3 d["RecoveryAbandonDate"] = "2026-08-26" d["RecoveryAbandonRetrievalOffered"] = offered return d } const ( promiseClause = "visszaszerezheted őket a helyreállítási kóddal" cautiousClause = "nem tudjuk megállapítani" deletionClause = "véglegesen töröljük" writeToUs = "írj nekünk a törlés előtt" ) // ── SCENARIO A — package unchanged → the clause stands, byte-identical in meaning to before ───── func TestR302_Render_A_PromiseKeptWhenStillTrue(t *testing.T) { html := renderBackupPage(t, "backups_remote", abandonBannerData(true, "ok")) if !strings.Contains(html, deletionClause) { t.Fatal("the deletion sentence is missing — that half is certain and must always render") } if !strings.Contains(html, promiseClause) { t.Error("R-302: a customer who can genuinely still change their mind lost the retrieval clause. " + "The grace period is explicitly NOT decorative; hedging a true sentence is its own dishonesty") } } // ── SCENARIO B/D/E (rendered) — cautious branch says what is true and names a route ───────────── // // RED-PROOF: remove the `{{if .RecoveryAbandonRetrievalOffered}}` conditional from layout.html and // this fails on the first assertion — the false promise returns. func TestR302_Render_B_CautiousBranchWhenNotKnowable(t *testing.T) { html := renderBackupPage(t, "backups_remote", abandonBannerData(false, "ok")) if strings.Contains(html, promiseClause) { t.Error("R-302: the banner still promises retrieval when the box cannot see that it is true — " + "this is the sentence a customer reads after giving up their history") } if !strings.Contains(html, cautiousClause) { t.Error("R-302: the cautious branch does not say we cannot determine it — silence is not the " + "same as declining a claim") } if !strings.Contains(html, writeToUs) { t.Error("R-302: the cautious branch names no route, and it is time-bounded — the customer must " + "be told to write in BEFORE the deletion date") } // The certain half is unconditional. if !strings.Contains(html, deletionClause) { t.Error("R-302: the deletion sentence was lost with the promise — it is the part we DO know") } } // ── SCENARIO C — THE CO-RENDER. The card and the banner must not contradict each other ────────── // // Reachable per yesterday's reading: ResetOrphanedRepo clears RepoState then starts the countdown, but // markOrphaned (offbox.go:804) has NO guard against an active countdown, so a later run finding the // FRESH store unopenable re-raises the card while the countdown runs. // // RED-PROOF — THE ONE THAT MATTERS: replace the condition with the rejected proxy (hub fingerprint vs // the CURRENT key, compared at render). In this state those differ, so the proxy answers "promise it" // and the false promise returns on the very screen the card is declining it. That is why the pin was // chosen over the obvious condition. func TestR302_Render_C_CoRenderDoesNotContradictItself(t *testing.T) { html := renderBackupPage(t, "backups_remote", abandonBannerData(false, "orphaned")) if !strings.Contains(html, "offbox-orphan-card") { t.Fatal("the orphan card did not render — this test would then prove nothing about the co-render") } if !strings.Contains(html, deletionClause) { t.Fatal("the banner did not render — likewise") } // The card says we cannot tell. The banner must not say the opposite one strip above it. if strings.Contains(html, promiseClause) { t.Error("R-302 CO-RENDER: the orphan card says we cannot determine whether the set-aside copies " + "can be opened, and the banner above it tells the customer they can still retrieve them. " + "One page, two answers, and the confident one is the wrong one") } } // ── SCENARIO F — no countdown → the undecided reminder ladder is untouched ────────────────────── func TestR302_Render_F_NoCountdownLeavesTheLadderAlone(t *testing.T) { d := splitTestData() d["RecoveryBanner"] = true d["RecoveryReminderTier"] = 14 // an undecided box, two weeks waiting html := renderBackupPage(t, "backups_remote", d) for _, s := range []string{deletionClause, promiseClause, cautiousClause} { if strings.Contains(html, s) { t.Errorf("abandonment copy %q leaked onto a box with no countdown running", s) } } if !strings.Contains(html, "Két hete") { t.Error("the undecided reminder ladder changed — it is not in scope and must be byte-identical") } }