120332103a
After a completed escrow ceremony the Távoli mentés page showed the yellow "Helyreállítási kód szükséges" card for ~15 min until the next hub-report ACK flipped pending→escrowed. Phase-0 diagnosis (read-only) = verdict A (report-cycle lag), already resolved on the demo box (escrow_state:"escrowed"); hub Hypothesis B verified false (SaveHostEscrow ON CONFLICT already clears stale_at on upload) → no hub change. - settings.OffboxTarget.CeremonyCompletedAt: stamped on the recovery-code claim, zeroed on the auto-confirmer Flip + the deprecated manual confirm; persisted. - web/handlers.go: offboxCeremonyWaitState + escrowCeremonyGraceWindow (35m). - backups_remote.html: info "megerősítésre vár, legfeljebb 15 perc" card → warn "a megerősítés nem érkezett meg" past the window. Existing branches untouched. - backups_escrow.html: "Mi történik ezután?" note on the wizard's final step. - Test web/escrow_wait_state_test.go (truth table + red-proof recorded in REPORT). No scheduler/agent/hub/endpoint changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017qDiBqKKQ5vPB5fXBqu7Kp
55 lines
3.0 KiB
Go
55 lines
3.0 KiB
Go
package web
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// TestOffboxCeremonyWaitState — truth table for the post-ceremony escrow card pick (v0.138.0).
|
|
// The awaiting card bridges the report-cycle gap between a completed ceremony and the hub-verified
|
|
// pending→escrowed flip; it must degrade to a warning after the grace window and must NEVER show
|
|
// once escrowed, unstamped, or on an unparseable stamp (those fall back to the plain pending CTA).
|
|
//
|
|
// COMPANION red-proof (run → fail → revert, recorded in REPORT): change the escrowed guard so it
|
|
// no longer short-circuits (e.g. drop `t.EscrowState == "escrowed"` from the early return) → the
|
|
// "escrowed clears the wait" case below FAILS (it would report awaiting on an already-confirmed
|
|
// target, resurfacing the interim card after the healthy state). Alternatively flip `>=` to `>` at
|
|
// the boundary and the exact-boundary case FAILS.
|
|
func TestOffboxCeremonyWaitState(t *testing.T) {
|
|
now := time.Now()
|
|
within := now.Add(-10 * time.Minute).Format(time.RFC3339) // inside the 35m grace window
|
|
past := now.Add(-40 * time.Minute).Format(time.RFC3339) // past the grace window
|
|
boundary := now.Add(-escrowCeremonyGraceWindow).Format(time.RFC3339) // exactly at the window → timed out (>=)
|
|
|
|
cases := []struct {
|
|
name string
|
|
target *settings.OffboxTarget
|
|
wantAwaiting bool
|
|
wantTimedOut bool
|
|
}{
|
|
{"nil target", nil, false, false},
|
|
{"pending, no stamp (plain CTA)", &settings.OffboxTarget{EscrowState: "pending"}, false, false},
|
|
{"pending, stamped within window → awaiting", &settings.OffboxTarget{EscrowState: "pending", CeremonyCompletedAt: within}, true, false},
|
|
{"pending, stamped past window → timed out", &settings.OffboxTarget{EscrowState: "pending", CeremonyCompletedAt: past}, false, true},
|
|
{"pending, stamped at boundary → timed out", &settings.OffboxTarget{EscrowState: "pending", CeremonyCompletedAt: boundary}, false, true},
|
|
{"escrowed clears the wait (stamp ignored)", &settings.OffboxTarget{EscrowState: "escrowed", CeremonyCompletedAt: within}, false, false},
|
|
{"empty state, stamped within window → awaiting", &settings.OffboxTarget{EscrowState: "", CeremonyCompletedAt: within}, true, false},
|
|
{"pending, unparseable stamp → plain CTA", &settings.OffboxTarget{EscrowState: "pending", CeremonyCompletedAt: "not-a-timestamp"}, false, false},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
gotAwaiting, gotTimedOut := offboxCeremonyWaitState(c.target)
|
|
if gotAwaiting != c.wantAwaiting || gotTimedOut != c.wantTimedOut {
|
|
t.Errorf("offboxCeremonyWaitState() = (awaiting=%v, timedOut=%v), want (awaiting=%v, timedOut=%v)",
|
|
gotAwaiting, gotTimedOut, c.wantAwaiting, c.wantTimedOut)
|
|
}
|
|
// Mutual exclusion invariant — the two card branches must never both fire.
|
|
if gotAwaiting && gotTimedOut {
|
|
t.Errorf("%s: both awaiting and timedOut true — the card branches are not mutually exclusive", c.name)
|
|
}
|
|
})
|
|
}
|
|
}
|