// apps/hq/test/concurrency.test.ts — D24 (red-team): SQLite transaction serialization. import { describe, it, expect } from 'vitest' import { openDb } from '../src/db' describe('SqliteDb.transaction serialization (D24)', () => { it('overlapping transactions do not interleave — a concurrent counter stays consistent', async () => { const db = openDb(':memory:') await db.exec(`CREATE TABLE ctr (id INTEGER PRIMARY KEY, n INTEGER NOT NULL)`) await db.run(`INSERT INTO ctr (id, n) VALUES (1, 0)`) // 50 read-modify-write transactions fired concurrently. Without serialization the // read-then-write would race and lose increments; the mutex makes the final count exact. const bump = () => db.transaction(async () => { const row = (await db.get<{ n: number }>(`SELECT n FROM ctr WHERE id=1`))! // Yield the event loop mid-transaction — the moment a naive impl would interleave. await Promise.resolve() await db.run(`UPDATE ctr SET n=? WHERE id=1`, row.n + 1) }) await Promise.all(Array.from({ length: 50 }, bump)) expect((await db.get<{ n: number }>(`SELECT n FROM ctr WHERE id=1`))!.n).toBe(50) await db.close() }) it('a nested transaction still commits with its parent (savepoint), and rolls back alone on inner failure', async () => { const db = openDb(':memory:') await db.exec(`CREATE TABLE t (k TEXT PRIMARY KEY)`) // Outer commits; a caught inner failure rolls back only the savepoint. await db.transaction(async () => { await db.run(`INSERT INTO t (k) VALUES ('outer')`) await db.transaction(async () => { await db.run(`INSERT INTO t (k) VALUES ('inner')`) throw new Error('inner boom') }).catch(() => undefined) }) expect(await db.get(`SELECT k FROM t WHERE k='outer'`)).toBeDefined() expect(await db.get(`SELECT k FROM t WHERE k='inner'`)).toBeUndefined() // An outer failure rolls back everything including the nested savepoint work. await db.transaction(async () => { await db.run(`INSERT INTO t (k) VALUES ('a')`) await db.transaction(async () => { await db.run(`INSERT INTO t (k) VALUES ('b')`) }) throw new Error('outer boom') }).catch(() => undefined) expect(await db.get(`SELECT k FROM t WHERE k='a'`)).toBeUndefined() expect(await db.get(`SELECT k FROM t WHERE k='b'`)).toBeUndefined() await db.close() }) })