98 lines
6.1 KiB
TypeScript
98 lines
6.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
FutureBattleLeaderError,
|
|
bindFutureBattleLeader,
|
|
createFutureBattleLeader,
|
|
expireFutureBattleLeader,
|
|
releaseFutureBattleLeader,
|
|
settleFutureBattleLeader,
|
|
type FutureBattleLeaderReceipt,
|
|
type FutureBattleLeaderSource,
|
|
} from '../game/future-battle-leader';
|
|
|
|
const question = { turn: 2, asker: 'atreides', player: 'emperor', leader: 'fenring', answer: true };
|
|
const names = ['fenring', 'aramsham'];
|
|
const battle: FutureBattleLeaderSource = {
|
|
turn: 2, event: 'battle-1', attacker: 'emperor', defender: 'guild', native: true, plansOpen: true,
|
|
};
|
|
const revealed = { turn: 2, battle: 'battle-1', player: 'emperor', revealed: true, leader: 'fenring' };
|
|
|
|
void test('conditional question waits for the first actual qualifying native battle', () => {
|
|
const waiting = createFutureBattleLeader(question, names);
|
|
const original = structuredClone(waiting);
|
|
assert.equal(waiting.status, 'waiting');
|
|
for (const unrelated of [
|
|
{ ...battle, turn: 3 },
|
|
{ ...battle, attacker: 'fremen' },
|
|
{ ...battle, native: false },
|
|
]) assert.deepEqual(bindFutureBattleLeader(waiting, unrelated), waiting);
|
|
assert.throws(() => bindFutureBattleLeader(waiting, { ...battle, plansOpen: false }), FutureBattleLeaderError,
|
|
'Missing an already-opened qualifying battle must not silently bind a later one.');
|
|
const bound = bindFutureBattleLeader(waiting, battle);
|
|
assert.deepEqual(bound, { ...original, battle: 'battle-1', status: 'bound' });
|
|
assert.deepEqual(waiting, original, 'Binding cannot mutate the original receipt.');
|
|
assert.deepEqual(bindFutureBattleLeader(bound, { ...battle, event: 'battle-2' }), bound,
|
|
'A later battle must not replace the original qualifying battle.');
|
|
assert.deepEqual(JSON.parse(JSON.stringify(bound)), bound);
|
|
});
|
|
|
|
void test('yes settles only on an actual compliant revealed plan and the original battle', () => {
|
|
const bound = bindFutureBattleLeader(createFutureBattleLeader(question, names), battle);
|
|
assert.throws(() => settleFutureBattleLeader(bound, { ...revealed, revealed: false }), FutureBattleLeaderError);
|
|
assert.throws(() => settleFutureBattleLeader(bound, { ...revealed, battle: 'battle-2' }), FutureBattleLeaderError);
|
|
assert.throws(() => settleFutureBattleLeader(bound, { ...revealed, player: 'guild' }), FutureBattleLeaderError);
|
|
assert.throws(() => settleFutureBattleLeader(bound, { ...revealed, leader: 'aramsham' }), FutureBattleLeaderError);
|
|
const settled = settleFutureBattleLeader(bound, revealed);
|
|
assert.equal(settled.status, 'settled');
|
|
assert.equal(bound.status, 'bound');
|
|
assert.deepEqual(settleFutureBattleLeader(settled, revealed), settled);
|
|
assert.throws(() => settleFutureBattleLeader(settled, { ...revealed, leader: null }), FutureBattleLeaderError);
|
|
});
|
|
|
|
void test('no forbids only the named leader at that first battle', () => {
|
|
const waiting = createFutureBattleLeader({ ...question, answer: false }, names);
|
|
const bound = bindFutureBattleLeader(waiting, battle);
|
|
assert.throws(() => settleFutureBattleLeader(bound, revealed), FutureBattleLeaderError);
|
|
assert.equal(settleFutureBattleLeader(bound, { ...revealed, leader: 'aramsham' }).status, 'settled');
|
|
assert.equal(settleFutureBattleLeader(bound, { ...revealed, leader: null }).status, 'settled');
|
|
});
|
|
|
|
void test('a turn with no eligible battle expires without inventing a battle', () => {
|
|
const waiting = createFutureBattleLeader(question, names);
|
|
assert.deepEqual(expireFutureBattleLeader(waiting, { turn: 2, battlePhaseEnded: false }), waiting);
|
|
const expired = expireFutureBattleLeader(waiting, { turn: 2, battlePhaseEnded: true });
|
|
assert.equal(expired.status, 'expired');
|
|
assert.equal(expired.battle, undefined);
|
|
assert.deepEqual(bindFutureBattleLeader(expired, battle), expired);
|
|
assert.throws(() => expireFutureBattleLeader(waiting, { turn: 3, battlePhaseEnded: true }), FutureBattleLeaderError);
|
|
const bound = bindFutureBattleLeader(waiting, battle);
|
|
assert.deepEqual(expireFutureBattleLeader(bound, { turn: 2, battlePhaseEnded: true }), bound,
|
|
'An opened battle must be resolved or released, not treated as no opportunity.');
|
|
});
|
|
|
|
void test('release requires the correct turn and original bound battle', () => {
|
|
const waiting = createFutureBattleLeader(question, names);
|
|
const bound = bindFutureBattleLeader(waiting, battle);
|
|
assert.throws(() => releaseFutureBattleLeader(bound, { turn: 2, battle: 'battle-2' }), FutureBattleLeaderError);
|
|
assert.throws(() => releaseFutureBattleLeader(bound, { turn: 3, battle: 'battle-1' }), FutureBattleLeaderError);
|
|
assert.throws(() => releaseFutureBattleLeader(bound, { turn: 2 }), FutureBattleLeaderError);
|
|
const released = releaseFutureBattleLeader(bound, { turn: 2, battle: 'battle-1' });
|
|
assert.equal(released.status, 'released');
|
|
assert.deepEqual(releaseFutureBattleLeader(released, { turn: 2, battle: 'battle-1' }), released);
|
|
assert.throws(() => settleFutureBattleLeader(released, revealed), FutureBattleLeaderError);
|
|
assert.equal(releaseFutureBattleLeader(waiting, { turn: 2 }).status, 'released');
|
|
});
|
|
|
|
void test('malformed questions, saved receipts and battle identities are rejected', () => {
|
|
assert.throws(() => createFutureBattleLeader({ ...question, leader: 'unknown' }, names), FutureBattleLeaderError);
|
|
assert.throws(() => createFutureBattleLeader({ ...question, asker: question.player }, names), FutureBattleLeaderError);
|
|
assert.throws(() => createFutureBattleLeader({ ...question, turn: 0 }, names), FutureBattleLeaderError);
|
|
assert.throws(() => createFutureBattleLeader(question, ['fenring', 'fenring']), FutureBattleLeaderError);
|
|
const waiting = createFutureBattleLeader(question, names);
|
|
assert.throws(() => bindFutureBattleLeader(waiting, { ...battle, attacker: battle.defender }), FutureBattleLeaderError);
|
|
assert.throws(() => bindFutureBattleLeader(waiting, { ...battle, event: '' }), FutureBattleLeaderError);
|
|
assert.throws(() => bindFutureBattleLeader({ ...waiting, status: 'bound' } as FutureBattleLeaderReceipt, battle), FutureBattleLeaderError);
|
|
assert.throws(() => bindFutureBattleLeader({ ...waiting, status: 'waiting', battle: 'old' }, battle), FutureBattleLeaderError);
|
|
});
|