package backup import ( "context" "fmt" "io" "log" "os" "path/filepath" "strings" "testing" ) func newReimportTestManager() *Manager { return &Manager{logger: log.New(io.Discard, "", 0)} } func writeDump(t *testing.T, nsRoot, stack string, dbType DBType) string { t.Helper() dir := AppDBDumpPath(nsRoot, stack) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } p := filepath.Join(dir, fmt.Sprintf("%s-%s.sql", stack, dbType)) if err := os.WriteFile(p, []byte("-- dump\nDROP TABLE IF EXISTS t;\n"), 0o644); err != nil { t.Fatal(err) } return p } // TestReimportDBDumps_ImportsWhenDumpAndDBPresent asserts F17: when a captured .sql dump exists and a // matching running DB is discovered, reimportDBDumps replays it. The PRE-FIX restore never called any // import — this orchestration is the fix. func TestReimportDBDumps_ImportsWhenDumpAndDBPresent(t *testing.T) { nsRoot := t.TempDir() wantPath := writeDump(t, nsRoot, "app", DBTypeMariaDB) m := newReimportTestManager() m.discoverDBs = func(ctx context.Context) ([]DiscoveredDB, error) { return []DiscoveredDB{ {StackName: "other", DBType: DBTypePostgres, ContainerName: "other-db"}, {StackName: "app", DBType: DBTypeMariaDB, ContainerName: "app-db", ContainerID: "cid"}, }, nil } var gotPath string var gotDB DiscoveredDB m.importDBDump = func(ctx context.Context, db DiscoveredDB, dumpPath string) error { gotPath, gotDB = dumpPath, db return nil } n, err := m.reimportDBDumps(context.Background(), "app", nsRoot) if err != nil { t.Fatalf("reimportDBDumps: %v", err) } if n != 1 { t.Fatalf("imported = %d, want 1", n) } if gotPath != wantPath { t.Fatalf("imported path = %q, want %q", gotPath, wantPath) } if gotDB.ContainerName != "app-db" { t.Fatalf("imported into %q, want app-db (must match the stack's own DB)", gotDB.ContainerName) } } // TestReimportDBDumps_FailureSurfaces asserts an import failure is RETURNED, not swallowed (a failed // data restore must not read as success). func TestReimportDBDumps_FailureSurfaces(t *testing.T) { nsRoot := t.TempDir() writeDump(t, nsRoot, "app", DBTypeMariaDB) m := newReimportTestManager() m.discoverDBs = func(ctx context.Context) ([]DiscoveredDB, error) { return []DiscoveredDB{{StackName: "app", DBType: DBTypeMariaDB, ContainerName: "app-db"}}, nil } m.importDBDump = func(ctx context.Context, db DiscoveredDB, dumpPath string) error { return fmt.Errorf("boom") } if _, err := m.reimportDBDumps(context.Background(), "app", nsRoot); err == nil || !strings.Contains(err.Error(), "boom") { t.Fatalf("expected the import failure to surface, got %v", err) } } // TestReimportDBDumps_NoDumpNoImport asserts apps with no .sql dump never trigger discovery/import. func TestReimportDBDumps_NoDumpNoImport(t *testing.T) { nsRoot := t.TempDir() m := newReimportTestManager() discoverCalled := false m.discoverDBs = func(ctx context.Context) ([]DiscoveredDB, error) { discoverCalled = true return nil, nil } m.importDBDump = func(ctx context.Context, db DiscoveredDB, dumpPath string) error { t.Fatal("importDBDump must not be called when there is no dump") return nil } n, err := m.reimportDBDumps(context.Background(), "app", nsRoot) if err != nil || n != 0 { t.Fatalf("reimportDBDumps with no dump = (%d, %v), want (0, nil)", n, err) } if discoverCalled { t.Fatalf("discovery should be skipped when there is no .sql dump") } } // TestReimportDBDumps_DumpButNoMatchingDB asserts that a dump with no matching running DB container is a // non-fatal skip (logged), returning 0 imported and no error — the app is up, just no DB matched. func TestReimportDBDumps_DumpButNoMatchingDB(t *testing.T) { nsRoot := t.TempDir() writeDump(t, nsRoot, "app", DBTypeMariaDB) m := newReimportTestManager() m.discoverDBs = func(ctx context.Context) ([]DiscoveredDB, error) { return []DiscoveredDB{{StackName: "different", DBType: DBTypeMariaDB}}, nil } m.importDBDump = func(ctx context.Context, db DiscoveredDB, dumpPath string) error { t.Fatal("must not import when no DB matches the stack") return nil } n, err := m.reimportDBDumps(context.Background(), "app", nsRoot) if err != nil || n != 0 { t.Fatalf("= (%d, %v), want (0, nil)", n, err) } }