71 lines
3.8 KiB
TypeScript
71 lines
3.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
createSaphoArrivalOrder,
|
|
passSaphoArrivalOrder,
|
|
quoteSaphoArrivalOrder,
|
|
reorderSaphoArrivalOrder,
|
|
validateSaphoArrivalOrder,
|
|
} from '../game/sapho-arrival-order';
|
|
|
|
const owners = ['ecaz', 'moritani'] as const;
|
|
const initial = () => createSaphoArrivalOrder('arrival:event', owners[0], owners[1]);
|
|
const request = (player: string, revision: number) => ({ event: 'arrival:event', revision, player });
|
|
|
|
void test('both public arrival owners can pass and retain the original storm order', () => {
|
|
const first = initial();
|
|
assert.deepEqual(first.frame.holders, owners);
|
|
validateSaphoArrivalOrder(first, 'arrival:event', owners, 'ecaz');
|
|
const a = passSaphoArrivalOrder(first, request('ecaz', 0));
|
|
assert.equal(a.done, false);
|
|
const b = passSaphoArrivalOrder(a.order, request('moritani', 1));
|
|
assert.equal(b.done, true);
|
|
assert.deepEqual(b.order.queue.map(row => row.player), owners);
|
|
assert.deepEqual(b.order.frame.passed, owners);
|
|
validateSaphoArrivalOrder(b.order, 'arrival:event', owners, 'ecaz');
|
|
assert.deepEqual(first.frame.passed, [], 'a later response does not mutate the prior frame');
|
|
});
|
|
|
|
for (const [player, mode] of [['moritani', 'first'], ['ecaz', 'last']] as const) {
|
|
void test(`${player} can select ${mode} without displacing a prior protected choice`, () => {
|
|
const start = initial();
|
|
assert.ok(quoteSaphoArrivalOrder(start, player).modes.includes(mode));
|
|
const used = reorderSaphoArrivalOrder(start, { ...request(player, 0), mode });
|
|
assert.equal(used.done, false);
|
|
assert.deepEqual(used.order.queue.map(row => row.player), ['moritani', 'ecaz']);
|
|
validateSaphoArrivalOrder(used.order, 'arrival:event', owners, 'moritani');
|
|
const other = player === 'ecaz' ? 'moritani' : 'ecaz';
|
|
assert.deepEqual(quoteSaphoArrivalOrder(used.order, other).modes, []);
|
|
const passed = passSaphoArrivalOrder(used.order, request(other, 1));
|
|
assert.equal(passed.done, true);
|
|
validateSaphoArrivalOrder(passed.order, 'arrival:event', owners, 'moritani');
|
|
assert.deepEqual(start.queue.map(row => row.player), owners);
|
|
});
|
|
}
|
|
|
|
void test('an arrival frame rejects stale, foreign, duplicate and no-op actions', () => {
|
|
const start = initial();
|
|
assert.throws(() => createSaphoArrivalOrder('arrival:event', 'ecaz', 'ecaz'));
|
|
assert.throws(() => passSaphoArrivalOrder(start, request('outsider', 0)));
|
|
assert.throws(() => passSaphoArrivalOrder(start, request('ecaz', 1)));
|
|
assert.throws(() => reorderSaphoArrivalOrder(start, { ...request('ecaz', 0), mode: 'first' }));
|
|
const passed = passSaphoArrivalOrder(start, request('ecaz', 0));
|
|
assert.throws(() => passSaphoArrivalOrder(passed.order, request('ecaz', 1)));
|
|
assert.throws(() => passSaphoArrivalOrder(passed.order, { ...request('moritani', 1), event: 'old' }));
|
|
});
|
|
|
|
void test('an arrival receipt remains bound to the parent owners, event and chosen first reaction', () => {
|
|
const used = reorderSaphoArrivalOrder(initial(), { ...request('moritani', 0), mode: 'first' }).order;
|
|
const restored = JSON.parse(JSON.stringify(used)) as typeof used;
|
|
validateSaphoArrivalOrder(restored, 'arrival:event', owners, 'moritani');
|
|
assert.throws(() => validateSaphoArrivalOrder(restored, 'other:event', owners, 'moritani'));
|
|
assert.throws(() => validateSaphoArrivalOrder(restored, 'arrival:event', ['moritani', 'ecaz'], 'moritani'));
|
|
assert.throws(() => validateSaphoArrivalOrder(restored, 'arrival:event', owners, 'ecaz'));
|
|
const tampered = structuredClone(restored);
|
|
delete tampered.frame.protectedFirst;
|
|
assert.throws(() => validateSaphoArrivalOrder(tampered, 'arrival:event', owners, 'moritani'));
|
|
const duplicate = structuredClone(restored);
|
|
duplicate.queue[1].player = duplicate.queue[0].player;
|
|
assert.throws(() => validateSaphoArrivalOrder(duplicate, 'arrival:event', owners, 'moritani'));
|
|
});
|