52 lines
3.6 KiB
TypeScript
52 lines
3.6 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { GholaLeaderPromiseError, gholaLeaderClaimText, gholaLeaderRequirements,
|
|
liveGholaLeaderPromises, parseGholaLeaderClaim, recordPromisedGholaLeader,
|
|
validateGholaLeaderPromises, type GholaLeaderPromise } from '../game/ghola-leader-promises';
|
|
|
|
const promise = (event: string, leader = 'leader-a', changes: Partial<GholaLeaderPromise> = {}): GholaLeaderPromise =>
|
|
({ event, player: 'target', asker: 'asker', turn: 2, leader, answer: true, ...changes });
|
|
|
|
void test('named Ghola claims require a current turn and exact identity without coercion', () => {
|
|
assert.deepEqual(parseGholaLeaderClaim({ kind: 'gholaLeader', target: 'target', turn: 2, leader: 'kwisatz' }),
|
|
{ turn: 2, leader: 'kwisatz' });
|
|
assert.match(gholaLeaderClaimText({ turn: 2, leader: 'kwisatz' }), /Kwisatz Haderach.*Ghola/);
|
|
for (const value of [null, [], {}, { turn: 0, leader: 'a' }, { turn: 2.5, leader: 'a' },
|
|
{ turn: '2', leader: 'a' }, { turn: 2, leader: '' }, { turn: 2, leader: ' a' }])
|
|
assert.throws(() => parseGholaLeaderClaim(value), GholaLeaderPromiseError);
|
|
});
|
|
|
|
void test('live obligations combine duplicates, flag conflicts, and do not grant extra Ghola uses', () => {
|
|
const records = [promise('a'), promise('a-again'), promise('no', 'leader-b', { answer: false }),
|
|
promise('old', 'leader-c', { turn: 1 }), promise('other', 'leader-d', { player: 'other' }),
|
|
promise('done', 'leader-e', { fulfilled: true }), promise('released', 'leader-f', { released: true })];
|
|
assert.deepEqual(liveGholaLeaderPromises(records, 'target', 2), records.slice(0, 3));
|
|
assert.deepEqual(gholaLeaderRequirements(records, 'target', 2), { required: ['leader-a'], forbidden: ['leader-b'] });
|
|
assert.equal(gholaLeaderRequirements([...records, promise('contradiction', 'leader-a', { answer: false })], 'target', 2), null);
|
|
});
|
|
|
|
void test('only an actual matching return fulfills Yes, and No rejects without mutation', () => {
|
|
const records = [promise('a'), promise('a-again'), promise('no', 'leader-b', { answer: false }),
|
|
promise('other', 'leader-a', { player: 'other' }), promise('earlier', 'leader-a', { fulfilled: true })];
|
|
const before = structuredClone(records);
|
|
assert.throws(() => recordPromisedGholaLeader(records, 'target', 2, 'leader-b'), GholaLeaderPromiseError);
|
|
assert.deepEqual(records, before);
|
|
const after = recordPromisedGholaLeader(records, 'target', 2, 'leader-a');
|
|
assert.equal(after[0].fulfilled, true); assert.equal(after[1].fulfilled, true);
|
|
assert.equal(after[2], records[2]); assert.equal(after[3], records[3]); assert.equal(after[4], records[4]);
|
|
assert.deepEqual(records, before);
|
|
const unrelated = recordPromisedGholaLeader(records, 'target', 2, 'kwisatz');
|
|
assert.equal(unrelated[0], records[0]);
|
|
assert.throws(() => recordPromisedGholaLeader(records, 'target', 2, ' leader-a'), GholaLeaderPromiseError);
|
|
});
|
|
|
|
void test('ledger validation enforces participants, unique events and terminal state', () => {
|
|
const valid = [promise('a'), promise('b', 'kwisatz', { answer: false }), promise('c', 'leader-c', { released: true })];
|
|
assert.doesNotThrow(() => validateGholaLeaderPromises(JSON.parse(JSON.stringify(valid)), ['target', 'asker']));
|
|
for (const invalid of [null, [null], [promise('a'), promise('a')],
|
|
[promise('bad', 'a', { asker: 'target' })], [promise('bad', 'a', { player: 'absent' })],
|
|
[promise('bad', 'a', { fulfilled: true, released: true })],
|
|
[{ ...promise('bad'), answer: 'yes' }]])
|
|
assert.throws(() => validateGholaLeaderPromises(invalid as GholaLeaderPromise[], ['target', 'asker']), GholaLeaderPromiseError);
|
|
});
|