Skip to content

Commit 0ad311c

Browse files
committed
test: add coverage for composite PK migration, repair, and diagnostics
1 parent 4720f93 commit 0ad311c

File tree

2 files changed

+208
-0
lines changed

2 files changed

+208
-0
lines changed

test/commands/cli/fix.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,4 +367,59 @@ describe("sentry cli fix", () => {
367367
// Should NOT print "Could not open database" since permission issues explain it
368368
expect(stderr).not.toContain("Could not open database");
369369
});
370+
371+
test("detects and repairs wrong primary key on pagination_cursors (CLI-72)", async () => {
372+
const dbPath = join(getTestDir(), "cli.db");
373+
const db = new Database(dbPath);
374+
// Create a full schema but with the buggy pagination_cursors table
375+
initSchema(db);
376+
db.exec("DROP TABLE pagination_cursors");
377+
db.exec(
378+
"CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)"
379+
);
380+
db.close();
381+
chmodSync(dbPath, 0o600);
382+
383+
// Warm the DB cache so getRawDatabase() uses this pre-repaired DB
384+
getDatabase();
385+
386+
const { stdout, exitCode } = await runFix(false);
387+
388+
expect(stdout).toContain("schema issue(s)");
389+
expect(stdout).toContain("Wrong primary key");
390+
expect(stdout).toContain("pagination_cursors");
391+
expect(stdout).toContain("Repairing schema");
392+
expect(stdout).toContain("repaired successfully");
393+
expect(exitCode).toBe(0);
394+
});
395+
396+
test("dry-run detects wrong primary key without repairing", async () => {
397+
const dbPath = join(getTestDir(), "cli.db");
398+
const db = new Database(dbPath);
399+
initSchema(db);
400+
db.exec("DROP TABLE pagination_cursors");
401+
db.exec(
402+
"CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)"
403+
);
404+
db.close();
405+
chmodSync(dbPath, 0o600);
406+
407+
getDatabase();
408+
409+
const { stdout } = await runFix(true);
410+
411+
expect(stdout).toContain("Wrong primary key");
412+
expect(stdout).toContain("pagination_cursors");
413+
expect(stdout).toContain("Run 'sentry cli fix' to apply fixes");
414+
// Table should still have the wrong PK
415+
closeDatabase();
416+
const verifyDb = new Database(dbPath);
417+
const row = verifyDb
418+
.query(
419+
"SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'"
420+
)
421+
.get() as { sql: string };
422+
expect(row.sql).not.toContain("PRIMARY KEY (command_key, context)");
423+
verifyDb.close();
424+
});
370425
});

test/lib/db/schema.test.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
initSchema,
1616
isReadonlyError,
1717
repairSchema,
18+
runMigrations,
1819
tableExists,
1920
} from "../../../src/lib/db/schema.js";
2021
import { useTestConfigDir } from "../../helpers.js";
@@ -291,3 +292,155 @@ describe("isReadonlyError", () => {
291292
expect(isReadonlyError(undefined)).toBe(false);
292293
});
293294
});
295+
296+
describe("runMigrations", () => {
297+
test("no-op when already at current version", () => {
298+
const db = new Database(join(getTestDir(), "test.db"));
299+
initSchema(db);
300+
301+
// Should not throw and version stays at current
302+
runMigrations(db);
303+
304+
const version = (
305+
db.query("SELECT version FROM schema_version").get() as {
306+
version: number;
307+
}
308+
).version;
309+
expect(version).toBe(CURRENT_SCHEMA_VERSION);
310+
db.close();
311+
});
312+
313+
test("repairs pagination_cursors with wrong single-column PK (CLI-72)", () => {
314+
const db = new Database(join(getTestDir(), "test.db"));
315+
// Build a full schema but with the bugged pagination_cursors table
316+
initSchema(db);
317+
db.exec("DROP TABLE pagination_cursors");
318+
db.exec(
319+
"CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)"
320+
);
321+
// Set version to 5 so migration 5→6 fires
322+
db.query("UPDATE schema_version SET version = 5").run();
323+
324+
runMigrations(db);
325+
326+
// Table should now have the correct composite PK
327+
const row = db
328+
.query(
329+
"SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'"
330+
)
331+
.get() as { sql: string };
332+
expect(row.sql).toContain("PRIMARY KEY (command_key, context)");
333+
334+
// Version should be bumped to 6
335+
const version = (
336+
db.query("SELECT version FROM schema_version").get() as {
337+
version: number;
338+
}
339+
).version;
340+
expect(version).toBe(CURRENT_SCHEMA_VERSION);
341+
db.close();
342+
});
343+
344+
test("skips pagination_cursors repair when PK is already correct", () => {
345+
const db = new Database(join(getTestDir(), "test.db"));
346+
initSchema(db);
347+
db.query("UPDATE schema_version SET version = 5").run();
348+
349+
// pagination_cursors was created by initSchema with the correct PK
350+
runMigrations(db);
351+
352+
const row = db
353+
.query(
354+
"SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'"
355+
)
356+
.get() as { sql: string };
357+
expect(row.sql).toContain("PRIMARY KEY (command_key, context)");
358+
db.close();
359+
});
360+
361+
test("creates pagination_cursors when missing during migration 4→5", () => {
362+
const db = new Database(join(getTestDir(), "test.db"));
363+
// Set up schema at version 4 without pagination_cursors
364+
const statementsWithoutPagination = Object.entries(EXPECTED_TABLES)
365+
.filter(([name]) => name !== "pagination_cursors")
366+
.map(([, ddl]) => ddl);
367+
db.exec(statementsWithoutPagination.join(";\n"));
368+
db.query("INSERT INTO schema_version (version) VALUES (4)").run();
369+
370+
runMigrations(db);
371+
372+
expect(tableExists(db, "pagination_cursors")).toBe(true);
373+
db.close();
374+
});
375+
});
376+
377+
describe("repairSchema: wrong primary key", () => {
378+
test("detects and repairs pagination_cursors with wrong single-column PK", () => {
379+
const db = new Database(join(getTestDir(), "test.db"));
380+
initSchema(db);
381+
// Simulate the bug: drop and recreate with wrong PK
382+
db.exec("DROP TABLE pagination_cursors");
383+
db.exec(
384+
"CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)"
385+
);
386+
387+
const result = repairSchema(db);
388+
389+
expect(
390+
result.fixed.some((f) => f.includes("pagination_cursors"))
391+
).toBe(true);
392+
expect(result.failed).toEqual([]);
393+
394+
const row = db
395+
.query(
396+
"SELECT sql FROM sqlite_master WHERE type='table' AND name='pagination_cursors'"
397+
)
398+
.get() as { sql: string };
399+
expect(row.sql).toContain("PRIMARY KEY (command_key, context)");
400+
db.close();
401+
});
402+
403+
test("no-op when pagination_cursors already has correct composite PK", () => {
404+
const db = new Database(join(getTestDir(), "test.db"));
405+
initSchema(db);
406+
407+
const result = repairSchema(db);
408+
409+
// Should not report pagination_cursors as fixed
410+
expect(
411+
result.fixed.some((f) => f.includes("pagination_cursors"))
412+
).toBe(false);
413+
db.close();
414+
});
415+
});
416+
417+
describe("getSchemaIssues: wrong primary key", () => {
418+
test("detects wrong_primary_key when pagination_cursors has single-column PK", () => {
419+
const db = new Database(join(getTestDir(), "test.db"));
420+
initSchema(db);
421+
db.exec("DROP TABLE pagination_cursors");
422+
db.exec(
423+
"CREATE TABLE pagination_cursors (command_key TEXT PRIMARY KEY, context TEXT NOT NULL, cursor TEXT NOT NULL, expires_at INTEGER NOT NULL)"
424+
);
425+
426+
const issues = getSchemaIssues(db);
427+
const pkIssues = issues.filter((i) => i.type === "wrong_primary_key");
428+
429+
expect(pkIssues).toContainEqual({
430+
type: "wrong_primary_key",
431+
table: "pagination_cursors",
432+
});
433+
db.close();
434+
});
435+
436+
test("no wrong_primary_key issues for healthy database", () => {
437+
const db = new Database(join(getTestDir(), "test.db"));
438+
initSchema(db);
439+
440+
const issues = getSchemaIssues(db);
441+
const pkIssues = issues.filter((i) => i.type === "wrong_primary_key");
442+
443+
expect(pkIssues).toEqual([]);
444+
db.close();
445+
});
446+
});

0 commit comments

Comments
 (0)