Files
gold_land_server/test/dual-connection.test.ts
safavimohadeseh78andCursor dfb20ca92c
Backend CI / type-check, build, tests (push) Successful in 1m0s
feat: prepare six-lesson trial backend with reliable rewards and operations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 14:35:42 +03:30

98 lines
3.1 KiB
TypeScript

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