From c35dd9bc45f944537b1c1f63d52e16980f4acba8 Mon Sep 17 00:00:00 2001 From: Frederik Spirgi Date: Wed, 9 Sep 2026 11:00:17 +0200 Subject: [PATCH] try to fix this spaghetti of a code --- lib/repository.js | 2 +- test/transactions.test.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/repository.js b/lib/repository.js index 52b52f5..5c294b8 100644 --- a/lib/repository.js +++ b/lib/repository.js @@ -47,7 +47,7 @@ async function createRepository(filename, {beforeCommit = async () => {}} = {}) `); if (old) { // Fails and rolls back rather than silently rounding or losing invalid balances. - await tx.exec('INSERT INTO users(username, password_hash, money) SELECT username, password_hash, money FROM legacy_users; INSERT INTO ledger(command_id,username,delta,reason) SELECT 'schema-v1:' || username, username, money, 'Opening balance migration' FROM users; DROP TABLE legacy_users;'); + await tx.exec(`INSERT INTO users(username, password_hash, money) SELECT username, password_hash, money FROM legacy_users; INSERT INTO ledger(command_id,username,delta,reason) SELECT 'schema-v1:' || username, username, money, 'Opening balance migration' FROM users; DROP TABLE legacy_users;`); } await tx.exec('PRAGMA user_version = 1'); }); diff --git a/test/transactions.test.js b/test/transactions.test.js index 9c1c10f..c6715fd 100644 --- a/test/transactions.test.js +++ b/test/transactions.test.js @@ -32,7 +32,8 @@ test('accepted actions persist once; failed commits leave both memory and databa await assert.rejects(api.lobbies.command('b', message), /Injected/); assert.equal(JSON.stringify(api.lobbies.get(lobbyId)), before); assert.equal((await api.repository.user('b')).money, 190); - await Promise.all([api.lobbies.command('b', message), api.lobbies.command('b', message)]); + const reordered = {payload: message.payload, action: message.action, requestId: message.requestId, lobbyId: message.lobbyId, type: message.type}; + await Promise.all([api.lobbies.command('b', message), api.lobbies.command('b', reordered)]); assert.equal((await api.repository.user('b')).money, 170); assert.equal(api.lobbies.get(lobbyId).game.pot, 50); await assert.rejects(api.lobbies.command('b', {...message, action: 'fold'}), error => error.code === 'IDEMPOTENCY_CONFLICT'); @@ -97,3 +98,30 @@ test('reopening a database refunds interrupted escrow exactly once', async t => assert.equal((await restarted.user('a')).money, 200); assert.equal((await restarted.user('b')).money, 200); } finally {await restarted.close();} }); +test('account membership and host starts serialize even when requests arrive together', async t => { + const api = await createApplication({secret,databasePath:':memory:',bcryptRounds:4,intermissionMs:60000}); + t.after(()=>api.close()); + for(const username of ['a','b']) await api.sessions.register({username,password:'password'}); + const creates=await Promise.allSettled([command(api.lobbies,'a','lobby:create'),command(api.lobbies,'a','lobby:create')]); + assert.equal(creates.filter(item=>item.status==='fulfilled').length,1); + const {lobbyId}=creates.find(item=>item.status==='fulfilled').value; + await command(api.lobbies,'b','lobby:join',{lobbyId}); + const starts=await Promise.allSettled([command(api.lobbies,'a','poker:start',{lobbyId}),command(api.lobbies,'a','poker:start',{lobbyId})]); + assert.equal(starts.filter(item=>item.status==='fulfilled').length,1); +}); +test('a departure waits for an in-flight action commit and cannot overwrite its balance', async t => { + let pause=null; + const {api,lobbyId}=await setup(t,{repositoryHooks:{beforeCommit:async()=>{if(pause) await pause();}}}); + let reached,release; + const entered=new Promise(resolve=>{reached=resolve;}); + const gate=new Promise(resolve=>{release=resolve;}); + pause=async()=>{reached();await gate;}; + const action=command(api.lobbies,'b','poker:action',{lobbyId,action:'bet',payload:{amount:20}}); + await entered; + const departure=command(api.lobbies,'b','lobby:leave',{lobbyId}); + pause=null;release(); + await Promise.all([action,departure]); + assert.equal((await api.repository.user('b')).money,170); + assert.equal(api.lobbies.get(lobbyId).game.contributions.b,30); + const totals=await api.repository.totals();assert.equal(totals.users+totals.escrow,600); +});