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) } }