History

Add coverage for corrupt journals, increase timeout for SIGKILL recovery sweep

Griffin Evans committed 4 months ago

Commit 30f87651 parent 1663b1a1 file · +54 −1Browse files

Unified · Split

apps/local/src/db/v1-v2-migration.test.ts+54−1
@@ -1161,6 +1161,59 @@ describe("local v1 -> v2 migration", () => { · 1160 unmodified lines
11611161 expect(auth[migratedItemId(scopeId, "stripe-key")]).toBe("sk_test_recovered");
11621162 });
11631163
1164+ it("restarts from an unreadable journal when the canonical v1 database is intact", async () => {
1165+ const scopeId = "executor-workspace-corruptjournal";
1166+ const dataDir = join(workDir, "data");
1167+ const dbPath = join(dataDir, "data.db");
1168+ const journalPath = `${dbPath}.v1-v2-migration.json`;
1169+ mkdirSync(dataDir, { recursive: true });
1170+ await seedV1Db(dbPath, scopeId);
1171+ await writeLiveWalMarker(dbPath, "wal-corrupt-journal");
1172+
1173+ const authDir = join(process.env.XDG_DATA_HOME!, "executor");
1174+ mkdirSync(authDir, { recursive: true });
1175+ writeFileSync(
1176+ join(authDir, "auth.json"),
1177+ JSON.stringify({ [scopeId]: { "stripe-key": "sk_test_corrupt_journal" } }, null, 2),
1178+ );
1179+ writeFileSync(journalPath, "{ this is not valid json", { mode: 0o600 });
1180+
1181+ const result = await migrateLocalV1ToV2IfNeeded({
1182+ sqlitePath: dbPath,
1183+ tables: collectTables(),
1184+ namespace: "executor_local",
1185+ tenantId: scopeId,
1186+ });
1187+
1188+ expect(result.migrated).toBe(true);
1189+ expect(existsSync(journalPath)).toBe(false);
1190+ await assertMigratedStripeDbAndSecret({
1191+ dbPath,
1192+ scopeId,
1193+ secret: "sk_test_corrupt_journal",
1194+ walMarker: "wal-corrupt-journal",
1195+ });
1196+ });
1197+
1198+ it("fails closed on an unreadable journal when the canonical database is missing", async () => {
1199+ const dataDir = join(workDir, "data");
1200+ const dbPath = join(dataDir, "data.db");
1201+ const journalPath = `${dbPath}.v1-v2-migration.json`;
1202+ mkdirSync(dataDir, { recursive: true });
1203+ writeFileSync(journalPath, "{ this is not valid json", { mode: 0o600 });
1204+
1205+ await expect(
1206+ migrateLocalV1ToV2IfNeeded({
1207+ sqlitePath: dbPath,
1208+ tables: collectTables(),
1209+ namespace: "executor_local",
1210+ tenantId: "executor-workspace-missingdb",
1211+ }),
1212+ ).rejects.toMatchObject({ _tag: "LocalV1V2MigrationError" });
1213+ expect(existsSync(dbPath)).toBe(false);
1214+ expect(existsSync(journalPath)).toBe(true);
1215+ });
1216+
11641217 it("recovers after SIGKILL at every migration journal boundary", async () => {
11651218 // Each iteration rewrites the process-global XDG auth.json; keep this sweep
11661219 // sequential so phase secrets cannot bleed across concurrent migrations.
@@ -1191,7 +1244,7 @@ describe("local v1 -> v2 migration", () => { · 24 unmodified lines
11911244 await assertMigratedStripeDbAndSecret({ dbPath, scopeId, secret, walMarker });
11921245 expect(existsSync(`${dbPath}.v1-v2-migration.json`)).toBe(false);
11931246 }
1194− });
1247+ }, 30_000);
11951248
11961249 it("completes a built journal recovery before opening the canonical DB", async () => {
11971250 const scopeId = "executor-workspace-abcd1234";