0fe315b759
gates / gates (push) Successful in 15s
MinAgent: 0.131.0 (unchanged). Requires hub v0.117.0 for restore_interrupted. R-550 (operator ruling: fix). A design reversed and recorded: the restore op-status was in memory by choice. Now restore-status.json in DataDir, written atomically at both ends of an op. At startup a record still marked running becomes a failed, interrupted result kept per app until that app's next restore, shown on /backups/restore and the off-site wizard, and raised once as restore_interrupted. Cooldowns stay in memory. R-546. The R-543 reminder bar consults the agent's own preflight ok (every blocking item, not a copy of pbs_storage_id), cached 60 s, probed only while paused. /backup/escrow shows a waiting card that polls and reloads instead of red crosses and English diagnostics. POST /api/escrow/start refuses 409 before staging or starting - the direct path chaos night used. Unknown readiness keeps the bar. Red-proofs (each seen failing): restore record across restart; main() calls both startup functions; startup helper with loading skipped; restore page card; bar held back; waiting card; start refusal. go build/vet/test ./... green, 28 packages; controller_gates --fast all OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
58 lines
2.4 KiB
Go
58 lines
2.4 KiB
Go
package web
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// ── R-550 — an interrupted restore is SHOWN on the restore page ─────────────────────────────────
|
|
//
|
|
// Chaos night round 10: a restore interrupted by a hard reset left the page blank. The record now
|
|
// survives (internal/backup/restore_record.go); these drive the REAL page through ServeHTTP, so they
|
|
// bite on the wiring — a record that exists but that no page renders is the inert-seam shape.
|
|
|
|
const interruptedCardFragment = "Megszakadt vissza" // ASCII fragment of the card heading
|
|
|
|
// RED-PROOF: drop `data["InterruptedRestores"]` from backupsRestoreHandler → the card never renders →
|
|
// "the restore page does not show the interrupted restore".
|
|
func TestR550_RestorePageShowsInterruptedRestore(t *testing.T) {
|
|
s := newDashboardServer(t, time.Time{})
|
|
if s.backupMgr == nil {
|
|
t.Fatal("fixture invalid: no backup manager, so nothing here measures the restore page")
|
|
}
|
|
s.backupMgr.SetRestoreRecordPath(filepath.Join(t.TempDir(), "restore-status.json"))
|
|
s.backupMgr.BeginRestoreOp("restore", "gokapi") // the box stops here …
|
|
if s.backupMgr.LoadRestoreRecord() == nil { // … and this is the next start
|
|
t.Fatal("fixture invalid: the record did not convert to an interrupted restore")
|
|
}
|
|
|
|
rec := getPage(t, s, "/backups/restore")
|
|
if rec.Code != 200 {
|
|
t.Fatalf("GET /backups/restore = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
body := rec.Body.String()
|
|
if !strings.Contains(body, interruptedCardFragment) || !strings.Contains(body, "megszakadt") {
|
|
t.Fatalf("the restore page does not show the interrupted restore — the household is left not knowing, which is R-550")
|
|
}
|
|
if !strings.Contains(body, "gokapi") {
|
|
t.Fatalf("the interrupted card does not name the app")
|
|
}
|
|
if strings.Contains(body, "zzzz-not-present") {
|
|
t.Fatal("negative control matched — the fragment search is broken")
|
|
}
|
|
}
|
|
|
|
// The other branch: a box with no interrupted restore shows no such card.
|
|
func TestR550_NoInterruptedRestoreNoCard(t *testing.T) {
|
|
s := newDashboardServer(t, time.Time{})
|
|
rec := getPage(t, s, "/backups/restore")
|
|
if rec.Code != 200 {
|
|
t.Fatalf("GET /backups/restore = %d", rec.Code)
|
|
}
|
|
if strings.Contains(rec.Body.String(), interruptedCardFragment) {
|
|
t.Fatalf("a box with no interrupted restore shows the interrupted card")
|
|
}
|
|
}
|