You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/apps/hq/test/concurrency.test.ts

47 lines
2.4 KiB
TypeScript

// 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()
})
})