80e6ad8c47
gates / gates (push) Successful in 26s
R-650: internal/dockerexec — every docker exec routed through it; under go test a real docker is refused (opt-in FELHOM_TEST_REAL_DOCKER=1; a stub under the temp dir is allowed). api/stacks/web tests run under a silent stub (TestMain). TestR650_NoBareDockerExec pins it repo-wide. R-640: a dump without its engine's completion marker is refused before the first mutation (unit + off-site restore) and again before any load. R-499: the Tier-2 page's system-disk sentence has four true branches. R-518: the backup button states the measured ~8 min stop. R-626: measured on 9202, not reproduced. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
115 lines
5.0 KiB
Go
115 lines
5.0 KiB
Go
package backup
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// R-640 — a cut-off database copy must never be loaded. Measured 2026-09-23 on 9202: the first half
|
|
// of a real pg_dump loaded with rc 0 into an EMPTY database (42 tables, 0 users). Every test here
|
|
// asserts the CONSEQUENCE — nothing was loaded, nothing was stopped — not only that an error came back.
|
|
|
|
// truncatedPG is pgDump cut off inside its COPY block: header and CREATE TABLEs present, so
|
|
// ValidateDump's header + table checks pass it; only the missing end marker betrays it.
|
|
func truncatedPG() string {
|
|
full := pgDump(50)
|
|
return full[:strings.Index(full, "\\.\n")]
|
|
}
|
|
|
|
// COMPANION RED-PROOF: deleting the CheckDumpComplete call in reimportDBDumpsFrom makes this fail on
|
|
// `a cut-off copy was LOADED`.
|
|
func TestR640_ReplayRefusesACutOffCopyAndLoadsNothing(t *testing.T) {
|
|
m := newReimportTestManager()
|
|
ns := t.TempDir()
|
|
p := writeDump(t, ns, "docmost", DBType("postgres"))
|
|
if err := os.WriteFile(p, []byte(truncatedPG()), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v := ValidateDump(p, DBType("postgres")); !v.Valid {
|
|
t.Fatalf("precondition: the truncated copy must PASS the old structural check (that is the bug), got %+v", v)
|
|
}
|
|
m.discoverDBs = func(context.Context) ([]DiscoveredDB, error) {
|
|
return []DiscoveredDB{{StackName: "docmost", ContainerName: "docmost-postgres", DBType: DBType("postgres")}}, nil
|
|
}
|
|
var loaded []string
|
|
m.importDBDump = func(_ context.Context, _ DiscoveredDB, path string) error { loaded = append(loaded, path); return nil }
|
|
|
|
n, err := m.reimportDBDumps(context.Background(), "docmost", ns)
|
|
if len(loaded) != 0 {
|
|
t.Fatalf("a cut-off copy was LOADED: %v", loaded)
|
|
}
|
|
if err == nil || n != 0 || !strings.Contains(err.Error(), "csonka") {
|
|
t.Fatalf("want the Hungarian cut-off refusal and 0 replayed, got n=%d err=%v", n, err)
|
|
}
|
|
}
|
|
|
|
// The control: the same path with the complete copy loads it — the check refuses nothing whole.
|
|
func TestR640_ReplayLoadsACompleteCopy(t *testing.T) {
|
|
m := newReimportTestManager()
|
|
ns := t.TempDir()
|
|
p := writeDump(t, ns, "docmost", DBType("postgres"))
|
|
if err := os.WriteFile(p, []byte(pgDump(50)), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.discoverDBs = func(context.Context) ([]DiscoveredDB, error) {
|
|
return []DiscoveredDB{{StackName: "docmost", ContainerName: "docmost-postgres", DBType: DBType("postgres")}}, nil
|
|
}
|
|
var loaded []string
|
|
m.importDBDump = func(_ context.Context, _ DiscoveredDB, path string) error { loaded = append(loaded, path); return nil }
|
|
if n, err := m.reimportDBDumps(context.Background(), "docmost", ns); err != nil || n != 1 || len(loaded) != 1 {
|
|
t.Fatalf("a complete copy must load: n=%d err=%v loaded=%v", n, err, loaded)
|
|
}
|
|
}
|
|
|
|
// The local unit restore refuses BEFORE the first mutation: no stop, no volume replay, no definition.
|
|
// COMPANION RED-PROOF: deleting the incompleteDumps gate in RestoreFromRecoveryUnit makes this fail
|
|
// on `the app was stopped`.
|
|
func TestR640_UnitRestoreRefusesACutOffCopyBeforeAnyMutation(t *testing.T) {
|
|
m, prov, imported := r47UnitFixture(t, immichLikeCompose, true)
|
|
drive := prov.hdd
|
|
mustWrite(t, filepath.Join(AppDBDumpPath(drive, "app"), "app-postgres.sql"), truncatedPG())
|
|
|
|
_, err := m.RestoreFromRecoveryUnit("app")
|
|
if err == nil || !strings.Contains(err.Error(), "csonka") {
|
|
t.Fatalf("want the cut-off refusal, got %v", err)
|
|
}
|
|
if prov.stopped || len(prov.calls) != 0 || prov.gotEnv != nil {
|
|
t.Fatalf("ZERO mutations required: stopped=%v calls=%v definition=%v", prov.stopped, prov.calls, prov.gotEnv != nil)
|
|
}
|
|
if len(*imported) != 0 {
|
|
t.Fatalf("a replay happened: %v", *imported)
|
|
}
|
|
}
|
|
|
|
// The off-site restore refuses before the safety dump, the stop and the file copy.
|
|
func TestR640_OffsiteRestoreRefusesACutOffCopyBeforeAnyMutation(t *testing.T) {
|
|
m, prov, imported := reconFixture(t, "run1", "2026-07-19T06:00:00Z", truncatedPG())
|
|
var copied bool
|
|
m.SetOffboxFullPlaceCopier(func(_, _ string) (int, error) { copied = true; return 1, nil })
|
|
|
|
_, err := m.ReconstituteFromOffsite(context.Background(), "immich", false)
|
|
if err == nil || !strings.Contains(err.Error(), "csonka") {
|
|
t.Fatalf("want the cut-off refusal, got %v", err)
|
|
}
|
|
if len(prov.calls) != 0 || copied || len(*imported) != 0 {
|
|
t.Fatalf("ZERO mutations required: calls=%v copied=%v imported=%v", prov.calls, copied, *imported)
|
|
}
|
|
}
|
|
|
|
// A MariaDB copy is judged by its own marker, not PostgreSQL's.
|
|
func TestR640_MariaDBCopyNeedsItsOwnMarker(t *testing.T) {
|
|
dir := t.TempDir()
|
|
good := filepath.Join(dir, "romm-mariadb.sql")
|
|
mustWrite(t, good, "-- MariaDB dump 10.19\nCREATE TABLE `users` (id int);\nINSERT INTO `users` VALUES (1);\n-- Dump completed on 2026-09-23 21:00:00\n")
|
|
if bad := incompleteDumps(dir); len(bad) != 0 {
|
|
t.Fatalf("a complete MariaDB copy was flagged: %v", bad)
|
|
}
|
|
mustWrite(t, good, "-- MariaDB dump 10.19\nCREATE TABLE `users` (id int);\nINSERT INTO `users` VALUES (1);\n-- PostgreSQL database dump complete\n")
|
|
if bad := incompleteDumps(dir); len(bad) != 1 {
|
|
t.Fatalf("a MariaDB copy carrying the WRONG engine's marker must be flagged, got %v", bad)
|
|
}
|
|
}
|