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; /** 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)); });