84 lines
4.1 KiB
TypeScript
84 lines
4.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame } from '../game/engine';
|
|
import type { RoomsClock } from '../db/rooms';
|
|
import { nexusEcazDukePosition } from './fixture-nexus-ecaz-duke';
|
|
import { unitStore } from './fixture-nexus-room-store';
|
|
|
|
const clock: RoomsClock = { now: () => 10000, sleep: async () => {} };
|
|
const rows = (store: ReturnType<typeof unitStore>) =>
|
|
store.sqlite.prepare('SELECT state,version FROM rooms ORDER BY code').all();
|
|
|
|
async function fixture(t: test.TestContext, advanced: boolean) {
|
|
const store = unitStore();
|
|
t.after(() => store.sqlite.close());
|
|
const made = await store.rooms.createRoom('Ecaz Cunning recovery', 'ecaz', advanced, ['ecaz']);
|
|
const code = made.view.code;
|
|
const tokens = [made.token];
|
|
for (const faction of ['moritani', 'fremen'] as const)
|
|
tokens.push((await store.rooms.joinRoom(code, faction, faction)).token!);
|
|
const auths = await Promise.all(tokens.map(token => store.rooms.authenticate(code, token)));
|
|
const ids = auths.map(auth => auth.playerId) as [string, string, string];
|
|
const { game, action } = nexusEcazDukePosition(advanced, ids);
|
|
game.expansions = ['ecaz'];
|
|
game.code = code;
|
|
game.version = (await store.rooms.readRoom(code)).version;
|
|
store.sqlite.prepare('UPDATE rooms SET state = ?, version = ? WHERE code = ?')
|
|
.run(JSON.stringify(game), game.version, code);
|
|
store.writes.length = 0;
|
|
return { ...store, code, tokens, auths, ids, game, action };
|
|
}
|
|
|
|
void test('Ecaz Cunning survives SQLite restart; rejected or concurrent replay cannot spend or duplicate Duke', async t => {
|
|
for (const advanced of [false, true]) {
|
|
const f = await fixture(t, advanced);
|
|
const [owner, rival] = f.ids;
|
|
for (const [i, token] of f.tokens.entries()) {
|
|
const auth = await f.restart().authenticate(f.code, token);
|
|
const view = await f.restart().readSeatView(f.code, auth);
|
|
assert.deepEqual(view, viewGame(f.game, f.ids[i]));
|
|
if (i) assert.equal(view.nexusEcazDuke, null);
|
|
assert.equal('nexusEcazDukeHistory' in view, false);
|
|
}
|
|
const initial = rows(f);
|
|
await assert.rejects(f.restart().act(f.code, f.auths[1], f.game.version, f.action, clock));
|
|
await assert.rejects(f.restart().act(f.code, f.auths[0], f.game.version,
|
|
{ ...f.action, event: 'stale' }, clock));
|
|
assert.deepEqual(rows(f), initial);
|
|
let arrivals = 0;
|
|
let release!: () => void;
|
|
const gate = new Promise<void>(resolve => { release = resolve; });
|
|
f.hooks.beforeWrite = async () => { if (++arrivals === 2) release(); await gate; };
|
|
const timer = setTimeout(release, 2000);
|
|
let outcomes: PromiseSettledResult<unknown>[];
|
|
try {
|
|
outcomes = await Promise.allSettled([
|
|
f.restart().act(f.code, f.auths[0], f.game.version, f.action, clock),
|
|
f.restart().act(f.code, f.auths[0], f.game.version, f.action, clock),
|
|
]);
|
|
} finally {
|
|
clearTimeout(timer);
|
|
delete f.hooks.beforeWrite;
|
|
}
|
|
assert.equal(arrivals, 2);
|
|
assert.equal(outcomes.filter(result => result.status === 'fulfilled').length, 1);
|
|
const done = await f.restart().readRoom(f.code);
|
|
assert.equal(done.version, f.game.version + 1);
|
|
assert.equal(done.dukeVidal!.controller, owner);
|
|
assert.equal(done.dukeVidal!.source, 'ecazNexus');
|
|
assert.equal(done.nexusCards!.cards!.discard.filter(card => card === 'ecaz').length, 1);
|
|
assert.deepEqual(done.nexusEcazDukeEvents, [f.action.event]);
|
|
assert.equal(done.nexusEcazDukeHistory?.length, 1);
|
|
assert.equal(viewGame(done, rival).nexusEcazDuke, null);
|
|
const saved = rows(f);
|
|
await assert.rejects(f.restart().act(f.code, f.auths[0], f.game.version, f.action, clock), /table changed/);
|
|
await assert.rejects(f.restart().act(f.code, f.auths[0], done.version, f.action, clock));
|
|
assert.deepEqual(rows(f), saved);
|
|
for (const [i, token] of f.tokens.entries()) {
|
|
const auth = await f.restart().authenticate(f.code, token);
|
|
assert.deepEqual(await f.restart().readSeatView(f.code, auth), viewGame(done, f.ids[i]));
|
|
}
|
|
assert.deepEqual(f.writes.map(write => write.changes).sort((a, b) => a - b), [0, 1]);
|
|
}
|
|
});
|