Files
TREK/server/tests/unit/db/migration-version-atomicity.test.ts
T
2026-10-04 12:59:55 +02:00

66 lines
2.3 KiB
TypeScript

/**
* The schema_version bump must commit with the migration that earned it.
*
* If it lands in a statement of its own, a crash between the two leaves the
* schema advanced and the version stale, and the next boot replays a step that
* is not idempotent (an INSERT INTO app_settings, say) and exits 1 forever.
*/
import { describe, it, expect, vi } from 'vitest';
import Database from 'better-sqlite3';
vi.mock('../../../src/config', () => ({
ENCRYPTION_KEY: 'migration-atomicity-test-key',
JWT_SECRET: 'migration-atomicity-test-secret',
updateJwtSecret: vi.fn(),
}));
vi.mock('../../../src/db/database', () => {
throw new Error('Migration version atomicity tests must not import the global database');
});
import { createTables } from '../../../src/db/schema';
import { runMigrations } from '../../../src/db/migrations';
function migratedDb(): Database.Database {
const db = new Database(':memory:');
db.exec('PRAGMA journal_mode = WAL');
db.exec('PRAGMA busy_timeout = 5000');
db.exec('PRAGMA foreign_keys = ON');
createTables(db);
runMigrations(db);
return db;
}
describe('migration version bump atomicity', () => {
it('MIGRATE-ATOMIC-001: writes schema_version inside the migration transaction', () => {
const db = migratedDb();
try {
const { version } = db.prepare('SELECT version FROM schema_version LIMIT 1').get() as { version: number };
// Rewind one slot so exactly the last migration replays.
db.prepare('UPDATE schema_version SET version = ?').run(version - 1);
const realPrepare = db.prepare.bind(db);
const inTransaction: boolean[] = [];
vi.spyOn(db, 'prepare').mockImplementation(((sql: string) => {
const stmt = realPrepare(sql);
if (sql.includes('UPDATE schema_version SET version')) {
const realRun = stmt.run.bind(stmt);
stmt.run = ((...args: unknown[]) => {
inTransaction.push(db.inTransaction);
return realRun(...(args as never[]));
}) as typeof stmt.run;
}
return stmt;
}) as typeof db.prepare);
runMigrations(db);
expect(inTransaction).toEqual([true]);
const after = db.prepare('SELECT version FROM schema_version LIMIT 1').get() as { version: number };
expect(after.version).toBe(version);
} finally {
vi.restoreAllMocks();
db.close();
}
});
});