Files
Dune/tests/advisor-worm-ride-quote.test.ts
T
Quintine c608d6fea6
Verify and publish checkpoint container / container (push) Canceled after 0s
Handle advisor conversion after worm rides
2026-10-29 09:10:53 +11:00

73 lines
3.9 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
AutomaticAdvisorFlipError, quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip,
type AutomaticAdvisorFlipContext, type AutomaticAdvisorFlipSource, type AutomaticAdvisorWormRide,
} from '../game/advisor-automatic-flip';
type WormSource = Extract<AutomaticAdvisorFlipSource, {producer:'wormRide'}>;
/** A native Fremen ride has already deposited its physical forces in Hagga.
* Bene Gesserit's original Imperial Basin advisors are now alone. */
function source() {
const ride:AutomaticAdvisorWormRide = {event:'ride-1',player:'f',origin:'imperial_basin',
destination:'hagga_basin',sector:12,amount:3,elite:1};
const game:AutomaticAdvisorFlipContext = {status:'playing',advanced:true,turn:2,phase:1,
players:[
{id:'b',faction:'beneGesserit',forces:{'imperial_basin:9':2,'imperial_basin:10':1},
advisors:{imperial_basin:{aloneFlipHold:'flip-1'}}},
{id:'f',faction:'fremen',forces:{'hagga_basin:12':3},elites:{forces:{'hagga_basin:12':1}}},
{id:'a',faction:'atreides',forces:{}},
]};
const flip:WormSource = {event:'flip-1',owner:'b',territory:'imperial_basin',turn:2,phase:1,
forces:{'imperial_basin:9':2,'imperial_basin:10':1},producer:'wormRide',wormRide:ride};
return {game,flip};
}
void test('native committed worm ride binds its held advisor group without mutating it', () => {
const {game,flip}=source(), before=structuredClone({game,flip});
assert.doesNotThrow(() => validateAutomaticAdvisorFlip(game,flip));
assert.deepEqual({game,flip},before);
assert.doesNotThrow(() => validateAutomaticAdvisorFlip(game,JSON.parse(JSON.stringify(flip)) as WormSource));
});
void test('worm ride rejects altered or malformed identity, origin, destination and force custody', () => {
const changes:Array<(game:AutomaticAdvisorFlipContext,flip:WormSource)=>void> = [
game => {game.phase=0;}, game => {game.advanced=false;}, game => {game.turn++;},
(_game,flip) => {flip.wormRide.event='';},
(_game,flip) => {flip.wormRide.player='missing';},
(game) => {game.players[1].faction='atreides';},
(_game,flip) => {flip.wormRide.origin='invalid';},
(_game,flip) => {flip.wormRide.destination='invalid';},
(_game,flip) => {flip.wormRide.destination=flip.wormRide.origin;},
(_game,flip) => {flip.wormRide.sector=10;},
(_game,flip) => {flip.wormRide.amount=0;},
(_game,flip) => {flip.wormRide.amount=4;},
(_game,flip) => {flip.wormRide.elite=4;},
(game) => {game.players[1].elites!.forces={'hagga_basin:12':0};},
(game) => {game.players[1].elites!.forces={'hagga_basin:12':3};},
(_game,flip) => {flip.territory='arrakeen';},
(game) => {game.players[0].forces={'imperial_basin:9':1};},
(game) => {game.players[2].forces={'imperial_basin:11':1};},
];
for (const change of changes) {
const {game,flip}=source(); change(game,flip); const before=structuredClone({game,flip});
assert.throws(() => validateAutomaticAdvisorFlip(game,flip),AutomaticAdvisorFlipError);
assert.deepEqual({game,flip},before);
}
const {game,flip}=source();
assert.throws(() => validateAutomaticAdvisorFlip(game,{...flip,wormRide:{...flip.wormRide,extra:true}} as WormSource),AutomaticAdvisorFlipError);
});
void test('worm ride quote uses public force presence and does not inspect hidden hands', () => {
const {game,flip}=source();
delete game.players[0].advisors!.imperial_basin.aloneFlipHold;
const quote=quoteAutomaticAdvisorFlips(game);
assert.deepEqual(quote,[{player:'b',territory:'imperial_basin',
forces:{'imperial_basin:9':2,'imperial_basin:10':1}}]);
game.players[0].advisors!.imperial_basin.aloneFlipHold=flip.event;
for (const player of game.players) for (const field of ['hand','leaders','traitors','spice'])
Object.defineProperty(player,field,{get() {throw new Error(`Private ${field} inspected`);}});
assert.doesNotThrow(() => validateAutomaticAdvisorFlip(game,flip));
});