/** * Two independent SQLite connections on one file — one writer wins, no corrupt cash. * Run: npm run test:dual-connection */ import assert from 'node:assert/strict' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Worker } from 'node:worker_threads' import Database from 'better-sqlite3' import { runSchemaMigrations } from '../src/database/migration.service' import { createDefaultUserState } from '../src/shared/catalog/default-user-state' const testDir = mkdtempSync(join(tmpdir(), 'gold-land-dual-sqlite-')) const dbPath = join(testDir, 'dual.db') const userId = 'dual-user' function seedUser(db: Database.Database) { const state = createDefaultUserState(userId) state.cash = 1000 state.goldGrams = 0 db.prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( userId, JSON.stringify(state), new Date().toISOString(), ) } function readCash(): number { const db = new Database(dbPath, { readonly: true }) const row = db.prepare('SELECT state_json FROM users WHERE id = ?').get(userId) as { state_json: string } db.close() return (JSON.parse(row.state_json) as { cash: number }).cash } function spendInWorker(amount: number): Promise<'ok' | 'fail'> { return new Promise((resolve, reject) => { const worker = new Worker( ` const Database = require('better-sqlite3'); const { parentPort, workerData } = require('worker_threads'); const db = new Database(workerData.dbPath); db.pragma('journal_mode = WAL'); db.pragma('busy_timeout = 5000'); const spend = db.transaction(() => { const row = db.prepare('SELECT state_json FROM users WHERE id = ?').get(workerData.userId); const state = JSON.parse(row.state_json); if (state.cash < workerData.amount) return false; state.cash -= workerData.amount; db.prepare('UPDATE users SET state_json = ?, updated_at = ? WHERE id = ?').run( JSON.stringify(state), new Date().toISOString(), workerData.userId, ); return true; }).immediate; parentPort.postMessage(spend() ? 'ok' : 'fail'); db.close(); `, { eval: true, workerData: { dbPath, userId, amount } }, ) worker.on('message', (msg) => resolve(msg as 'ok' | 'fail')) worker.on('error', reject) }) } async function runTests() { const bootstrap = new Database(dbPath) bootstrap.pragma('journal_mode = WAL') runSchemaMigrations(bootstrap) seedUser(bootstrap) bootstrap.close() const [a, b] = await Promise.all([spendInWorker(700), spendInWorker(700)]) const outcomes = [a, b] assert.equal(outcomes.filter((x) => x === 'ok').length, 1) assert.equal(outcomes.filter((x) => x === 'fail').length, 1) const finalCash = readCash() assert.equal(finalCash, 300) assert.ok(finalCash >= 0) try { rmSync(testDir, { recursive: true, force: true }) } catch { // ignore } console.log('dual-connection tests passed') } runTests().catch((error) => { console.error(error) process.exit(1) })