fd28d70bff
Operator rulings 2026-10-12, 09 §3 decisions 196, 204-207, 223, 227. MinAgent 0.131.0 unchanged. R-255's sweep found one leak, fixed here: an optional-config secret_input was rendered into its settings page; it is now revealed on demand. Hungarian parity fixtures re-captured once for R-562. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
145 lines
6.5 KiB
Go
145 lines
6.5 KiB
Go
package web
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// ── SCENARIO F (R-225) — AN UNREAD STORE SAYS "UNKNOWN", NEVER "ZERO" ───────────────────────────
|
|
//
|
|
// Measured live on 2026-08-05 (CAMPAIGN-11): after a rebuild this page rendered
|
|
//
|
|
// Tároló méret · 0 pillanatkép Tárhelykeret: 0 / 50 GB (0%)
|
|
//
|
|
// directly above a card stating the store contains backups made under another key. An SFTP listing
|
|
// of the remote — read-only, no decryption — found snapshot `f3d9cd67` and **12 535 KB** really
|
|
// there. `snapshot_count` and `repo_size_bytes` were simply ABSENT from settings.json, and the zero
|
|
// value spoke for them.
|
|
//
|
|
// This is R-217's defect class one card over: a field whose zero is indistinguishable from a real
|
|
// measurement, defaulted past on an unknown path. `StatsKnown` is the named state, for the same
|
|
// reason `OffsiteInventory.Empty` is named rather than inferred from `len(Apps)==0`.
|
|
//
|
|
// Render tests per branch of the gate, because a template gate without one is the v0.70.1 lesson.
|
|
|
|
func remoteStatsData(known bool, snaps int, human string) map[string]interface{} {
|
|
d := splitTestData()
|
|
d["Offbox"] = &settings.OffboxTarget{
|
|
Enabled: true, Host: "nas.local", User: "felhom", RepoPath: "/srv/repo",
|
|
LastStatus: "error", EscrowState: "escrowed", QuotaGB: 50,
|
|
SnapshotCount: snaps, RepoSizeHuman: human, StatsKnown: known,
|
|
}
|
|
d["OffboxQuotaPct"] = 0
|
|
return d
|
|
}
|
|
|
|
// The branch the finding was measured on: nothing has ever been read.
|
|
func TestBackupsRemote_UnknownStoreStatsSayUnknownNotZero(t *testing.T) {
|
|
html := renderBackupPage(t, "backups_remote", remoteStatsData(false, 0, ""))
|
|
|
|
// RED-PROOF: set StatsKnown true in this fixture (or drop the template guard) → the page renders
|
|
// "0 pillanatkép" and "0 / 50 GB (0%)" again → this FAILS. Demonstrated failing before it was kept.
|
|
if strings.Contains(html, "0 pillanatkép") {
|
|
t.Fatalf("R-225 RETURNED: an unread store reports a snapshot COUNT of zero")
|
|
}
|
|
if !strings.Contains(html, "a pillanatképek száma még ismeretlen") {
|
|
t.Fatalf("an unread store must say the count is not known")
|
|
}
|
|
if strings.Contains(html, "0 / 50 GB") {
|
|
t.Fatalf("R-225 RETURNED: an unread store reports a used figure of zero")
|
|
}
|
|
if !strings.Contains(html, "még nem tudjuk, mennyi van a tárolóban") {
|
|
t.Fatalf("an unread store must say the used figure is not known")
|
|
}
|
|
// A 0%-wide fill bar is a PICTURE of emptiness, and a picture is a claim. Scoped to the quota
|
|
// bar's own container — the page has other zero-width elements and a bare "width:0%" search
|
|
// would pass or fail for unrelated reasons.
|
|
if bar := between(html, `id="offbox-quota-bar"`, "</div>\n </div>"); strings.Contains(bar, "background:var(--border") {
|
|
t.Fatalf("the fill bar rendered over an unread store — a 0%% bar asserts emptiness")
|
|
}
|
|
}
|
|
|
|
// The other branch: a store that WAS read and is genuinely empty must still say zero. Without this,
|
|
// the fix could be "never show a number", which loses real information.
|
|
func TestBackupsRemote_KnownEmptyStoreStillSaysZero(t *testing.T) {
|
|
html := renderBackupPage(t, "backups_remote", remoteStatsData(true, 0, ""))
|
|
|
|
if !strings.Contains(html, "0 pillanatkép") {
|
|
t.Fatalf("a store that was READ and holds nothing must say zero — that is knowledge")
|
|
}
|
|
if strings.Contains(html, "még ismeretlen") {
|
|
t.Fatalf("a measured zero must not be dressed up as unknown")
|
|
}
|
|
}
|
|
|
|
// And a store with real content renders it unchanged.
|
|
func TestBackupsRemote_KnownNonEmptyStoreRendersTheNumbers(t *testing.T) {
|
|
html := renderBackupPage(t, "backups_remote", remoteStatsData(true, 2, "12.0 MB"))
|
|
|
|
if !strings.Contains(html, "2 pillanatkép") {
|
|
t.Fatalf("a measured count must render")
|
|
}
|
|
if !strings.Contains(html, "12,0 MB / 50 GB") { // R-562: decimal comma on a Hungarian page
|
|
t.Fatalf("a measured size must render against the quota")
|
|
}
|
|
if strings.Contains(html, "még ismeretlen") || strings.Contains(html, "még nem tudjuk") {
|
|
t.Fatalf("measured stats must not read as unknown")
|
|
}
|
|
}
|
|
|
|
// between returns the slice of s after the first `from` and before the next `to` (empty when either
|
|
// marker is missing) — so an assertion can be scoped to one card instead of the whole page.
|
|
func between(s, from, to string) string {
|
|
i := strings.Index(s, from)
|
|
if i < 0 {
|
|
return ""
|
|
}
|
|
rest := s[i+len(from):]
|
|
if j := strings.Index(rest, to); j >= 0 {
|
|
return rest[:j]
|
|
}
|
|
return rest
|
|
}
|
|
|
|
// ── SCENARIO H (R-228) — THE SET-ASIDE HISTORY IS VISIBLE AND HONESTLY DESCRIBED ────────────────
|
|
//
|
|
// Measured 2026-08-05 (CAMPAIGN-11 F7): a customer chose "I do not want the old data", was told the
|
|
// backups would be KEPT and not deleted, and the move-aside did exactly that — 12 535 KB, byte-exact,
|
|
// at `/home/felhom-repo.orphaned-20260805`. The box recorded the path in `orphaned_renamed_to` and a
|
|
// census found **zero** references to it in any template or handler. The promise was kept and shown
|
|
// to nobody.
|
|
func TestBackupsRemote_SetAsideHistoryIsSurfaced(t *testing.T) {
|
|
d := splitTestData()
|
|
d["Offbox"] = &settings.OffboxTarget{
|
|
Enabled: true, Host: "nas.local", User: "felhom", RepoPath: "/srv/repo",
|
|
LastStatus: "ok", EscrowState: "escrowed", StatsKnown: true,
|
|
OrphanedRenamedTo: "/home/felhom-repo.orphaned-20260805",
|
|
}
|
|
html := renderBackupPage(t, "backups_remote", d)
|
|
|
|
// RED-PROOF: delete the {{if .Offbox.OrphanedRenamedTo}} block → this FAILS, and the set-aside
|
|
// history is invisible again. Demonstrated failing before this test was kept.
|
|
if !strings.Contains(html, "félre vannak téve") {
|
|
t.Fatalf("R-228 RETURNED: the set-aside history is not mentioned at all")
|
|
}
|
|
if !strings.Contains(html, "nem töröltük") {
|
|
t.Fatalf("the customer must be told it was NOT deleted — that is the promise being kept")
|
|
}
|
|
// §7.6 — it must NOT promise the history can be reopened. It cannot be, by anyone, today.
|
|
for _, forbidden := range []string{"visszaállítható lehet", "vissza tudod állítani", "megnyithatod", "kóddal később"} {
|
|
if strings.Contains(html, forbidden) {
|
|
t.Errorf("the set-aside notice promises the history can be reopened (%q) — the read path does not exist", forbidden)
|
|
}
|
|
}
|
|
}
|
|
|
|
// And a box that never set anything aside must not claim it did.
|
|
func TestBackupsRemote_NoSetAsideNoClaim(t *testing.T) {
|
|
html := renderBackupPage(t, "backups_remote", remoteStatsData(true, 1, "12.0 MB"))
|
|
if strings.Contains(html, "félre vannak téve") {
|
|
t.Fatal("a box with no set-aside history must not claim one exists")
|
|
}
|
|
}
|