try to fix this spaghetti of a code

This commit is contained in:
Frederik Spirgi
2026-09-09 10:55:40 +02:00
parent 2194ea4f0d
commit 101116472a
26 changed files with 1938 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
const {test} = require('node:test');
const assert = require('node:assert/strict');
const crypto = require('node:crypto');
const fs = require('node:fs/promises');
const os = require('node:os');
const path = require('node:path');
const {createApplication} = require('../gambling');
const {createRepository} = require('../lib/repository');
const {createLobbyService} = require('../lib/lobbies');
const secret = 'test-secret-with-at-least-thirty-two-characters';
const command = (service, username, type, fields = {}) => service.command(username, {type, requestId: crypto.randomUUID(), ...fields});
async function setup(t, overrides = {}) {
let now = 100000;
const api = await createApplication({secret, databasePath: ':memory:', bcryptRounds: 4, now: () => now, intermissionMs: 60000, ...overrides});
t.after(() => api.close());
for (const username of ['a','b','c']) await api.sessions.register({username, password: 'password'});
if (overrides.startingBalances) await api.repository.transaction(async tx => {
for (const [username, money] of Object.entries(overrides.startingBalances)) await tx.run('UPDATE users SET money = ? WHERE username = ?', money, username);
});
const {lobbyId} = await command(api.lobbies, 'a', 'lobby:create');
for (const username of ['b','c']) await command(api.lobbies, username, 'lobby:join', {lobbyId});
await command(api.lobbies, 'a', 'poker:start', {lobbyId});
const intermission = api.lobbies.get(lobbyId).game; now = intermission.nextRoundStartsAt;
await command(api.lobbies, null, 'timer', {lobbyId, handId: intermission.id, phase: 'intermission', deadline: now});
return {api, lobbyId};
}
test('accepted actions persist once; failed commits leave both memory and database unchanged', async t => {
let fail = false;
const {api, lobbyId} = await setup(t, {repositoryHooks: {beforeCommit: async () => {if (fail) {fail = false; throw new Error('Injected disk failure');}}}});
const message = {type: 'poker:action', lobbyId, requestId: crypto.randomUUID(), action: 'bet', payload: {amount: 20}};
const before = JSON.stringify(api.lobbies.get(lobbyId)); fail = true;
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)]);
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');
assert.deepEqual(await api.repository.totals(), {users: 550, escrow: 50});
});
test('concurrent account joins and table starts serialize; simultaneous leaves conserve chips', async t => {
const {api, lobbyId} = await setup(t);
const results = await Promise.allSettled([command(api.lobbies, 'a', 'lobby:create'), command(api.lobbies, 'a', 'poker:start', {lobbyId})]);
assert.ok(results.every(result => result.status === 'rejected'));
await Promise.all(['a', 'b', 'c'].map(username => command(api.lobbies, username, 'lobby:leave', {lobbyId})));
assert.deepEqual(await api.repository.totals(), {users: 600, escrow: 0}); assert.equal(api.lobbies.get(lobbyId), undefined);
});
test('stale timer cannot act on a new turn', async t => {
const {api, lobbyId} = await setup(t);
const original = api.lobbies.get(lobbyId);
const game = original.game;
await command(api.lobbies, 'b', 'poker:action', {lobbyId, action: 'hit'});
const before = JSON.stringify(api.lobbies.get(lobbyId));
await command(api.lobbies, null, 'timer', {lobbyId, handId: game.id, phase: 'betting', deadline: game.turnEndsAt, revision: original.revision});
assert.equal(JSON.stringify(api.lobbies.get(lobbyId)), before);
});
test('unequal all-ins settle correct side pots atomically even after a settlement write fails', async t => {
let fail = false;
const draws = 'AS AH KS KH QS QH 2D 3C 7S 8H 9D'.split(' ').map(value => ({rank:value.slice(0,-1),suit:value.slice(-1)}));
const {api, lobbyId} = await setup(t, {startingBalances:{a:60,b:200,c:100}, createDeck:()=>[
...require('../lib/poker').createDeck().filter(card=>!draws.some(d=>d.rank===card.rank && d.suit===card.suit)), ...draws.toReversed()
], repositoryHooks:{beforeCommit:async()=>{if(fail){fail=false;throw new Error('Settlement disk failure');}}}});
await command(api.lobbies,'b','poker:action',{lobbyId,action:'bet',payload:{amount:190}});
await command(api.lobbies,'c','poker:action',{lobbyId,action:'hit'});
const message={type:'poker:action',lobbyId,requestId:crypto.randomUUID(),action:'hit'};
fail=true;
await assert.rejects(api.lobbies.command('a',message),/Settlement disk failure/);
assert.equal(api.lobbies.get(lobbyId).game.phase,'betting');
assert.deepEqual(await api.repository.totals(),{users:50,escrow:310});
await api.lobbies.command('a',message);
await api.lobbies.command('a',message);
const game=api.lobbies.get(lobbyId).game;
assert.equal(game.phase,'intermission');
assert.deepEqual({...game.payouts},{a:180,b:180});
for(const username of ['a','b','c']) assert.equal((await api.repository.user(username)).money,game.stacks[username]);
assert.deepEqual(await api.repository.totals(),{users:360,escrow:0});
});
test('reopening a database refunds interrupted escrow exactly once', async t => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'gambling-recovery-'));
t.after(() => fs.rm(directory, {recursive: true, force: true}));
const filename = path.join(directory, 'test.sqlite');
const repo = await createRepository(filename);
for (const username of ['a','b']) await repo.register(username, 'unused-test-hash', null);
let now = 1;
const service = createLobbyService(repo, {ante: 10, intermissionMs: 60000, turnMs: 30000, now: () => now});
const {lobbyId} = await command(service, 'a', 'lobby:create');
await command(service, 'b', 'lobby:join', {lobbyId}); await command(service, 'a', 'poker:start', {lobbyId});
const game = service.get(lobbyId).game; now = game.nextRoundStartsAt;
await command(service, null, 'timer', {lobbyId, handId: game.id, phase: 'intermission', deadline: now});
await command(service, 'b', 'poker:action', {lobbyId, action: 'bet', payload: {amount: 30}});
assert.deepEqual(await repo.totals(), {users: 350, escrow: 50});
await service.close(); await repo.close();
const restarted = await createRepository(filename);
try {
await restarted.recover(); await restarted.recover();
assert.deepEqual(await restarted.totals(), {users: 400, escrow: 0});
assert.equal((await restarted.user('a')).money, 200); assert.equal((await restarted.user('b')).money, 200);
} finally {await restarted.close();}
});