Files
Dune/tests/choam-market-auto-waiver-guards.test.ts
T
Quintine 825f903cce
Verify and publish checkpoint container / container (push) Successful in 10m42s
Add opt-in CHOAM market skip for the current turn
2026-10-29 07:08:53 +11:00

96 lines
6.1 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, normalizeAutomaticGame, viewGame, type Action, type Game } from '../game/engine';
import { actChoamCharityFunding, advanceChoamCharityFunding, choamCharityFundingInventory,
createChoamCharityFundingFixture, passChoamCharityFundingResponses } from './fixture-choam-charity-funding';
import { actTruthtranceChoamPromises, advanceTruthtranceChoamPromises,
allowTruthtranceChoamPromisesIncome, createTruthtranceChoamPromisesFixture,
finishTruthtranceChoamPromises, truthtranceChoamPromisesInventory } from './fixture-truthtrance-choam-promises';
function reject(game: Game, actor: string, action: Action) {
const before = structuredClone(game);
assert.throws(() => applyAction(game, actor, action));
assert.deepEqual(game, before);
}
/** Genuine public Basic setup and first Storm closing. */
function firstMarket() {
const source = createChoamCharityFundingFixture();
return advanceChoamCharityFunding(structuredClone(source.afterSetup), state =>
state.turn === 1 && state.phase === 0 && state.decision?.kind === 'choamMarket');
}
void test('a pending sale and its Karama response finish exactly once before a standing waiver can close the market', () => {
const game = firstMarket();
const owner = game.players.find(player => player.id === 'choam'); assert.ok(owner);
// Preserve all 35 physical cards while staging a saleable original card in
// place of CHOAM's original deal. Atreides already genuinely holds Karama.
const worthless = game.deck.findIndex(card => card.kind === 'worthless'); assert.ok(worthless >= 0);
[owner.hand[0], game.deck[worthless]] = [game.deck[worthless], owner.hand[0]];
const card = owner.hand[0], beforeCards = choamCharityFundingInventory(game), beforeSpice = owner.spice;
const pending = actChoamCharityFunding(game, owner.id, {type:'decision', mode:'sell', card:card.id});
assert.equal(pending.response?.kind, 'choamSale');
assert.equal(pending.choamMarket?.sale?.card, card.id);
// The sole later-state staging is a previously accepted same-turn preference.
pending.choamMarketSkip = {owner:owner.id, turn:pending.turn};
const normalized = normalizeAutomaticGame(structuredClone(pending));
assert.equal(normalized.response?.kind, 'choamSale');
assert.equal(normalized.choamMarket?.sale?.card, card.id);
assert.equal(normalized.players.find(player => player.id === owner.id)!.spice, beforeSpice);
assert.equal(viewGame(normalized, owner.id).choamMarketSkipAvailable, false);
reject(normalized, owner.id, {type:'decision', done:true, skipMarketsThisTurn:true});
reject(normalized, owner.id, {type:'cancelChoamMarketSkip'});
const settled = passChoamCharityFundingResponses(normalized);
assert.equal(settled.response, null);
assert.equal(settled.choamMarket, null);
assert.equal(settled.phase, 1);
assert.equal(settled.players.find(player => player.id === owner.id)!.spice, beforeSpice + 2);
assert.equal(settled.discard.filter(item => item.id === card.id).length, 1);
assert.deepEqual(choamCharityFundingInventory(settled), beforeCards);
});
void test('a real binding Truthtrance shipment prevents market skipping until the commitment can proceed', () => {
const f = createTruthtranceChoamPromisesFixture({advanced:false, kind:'shipment'});
let game = actTruthtranceChoamPromises(structuredClone(f.game), f.asker, f.declarationStep.action);
game = advanceTruthtranceChoamPromises(game, state => state.truthtrance?.stage === 'ask');
game = actTruthtranceChoamPromises(game, f.asker, f.askStep.action);
game = actTruthtranceChoamPromises(game, f.target, {type:'truthAnswer', answer:'yes'});
assert.equal(game.shipmentPromises?.[0]?.answer, true);
assert.equal(game.shipmentPromises?.[0]?.fulfilled, undefined);
// Only the previously accepted same-turn preference is staged; the card,
// question and binding answer all came from actual native actions above.
game.choamMarketSkip = {owner:f.target, turn:game.turn};
game = advanceTruthtranceChoamPromises(game, state => state.phase === 0 && state.decision?.kind === 'choamMarket');
const commitment = structuredClone(game.shipmentPromises);
const cards = truthtranceChoamPromisesInventory(game);
assert.equal(viewGame(game, f.target).choamMarketSkipActive, true);
assert.equal(viewGame(game, f.target).choamMarketSkipAvailable, false);
const normalized = normalizeAutomaticGame(structuredClone(game));
assert.equal(normalized.decision?.kind, 'choamMarket');
assert.deepEqual(normalized.shipmentPromises, commitment);
reject(normalized, f.target, {type:'decision', done:true, skipMarketsThisTurn:true});
game = actTruthtranceChoamPromises(normalized, f.target, {type:'decision', done:true});
assert.deepEqual(game.shipmentPromises, commitment);
game = advanceTruthtranceChoamPromises(game, state => state.phase === 2 && state.response?.kind === 'choamCharity');
game = allowTruthtranceChoamPromisesIncome(game);
const completed = finishTruthtranceChoamPromises(f, game);
assert.equal(completed.delivered.shipmentPromises?.[0]?.fulfilled, true);
assert.equal(completed.delivered.shipmentPromises?.[0]?.released, undefined);
assert.deepEqual(truthtranceChoamPromisesInventory(completed.delivered), cards);
});
void test('malformed opt-in packets cannot waive a market; switching to autopilot cancels an accepted waiver', () => {
const market = firstMarket();
for (const action of [
{type:'decision', done:true, skipMarketsThisTurn:false},
{type:'decision', done:true, skipMarketsThisTurn:'true'},
{type:'decision', done:true, skipMarketsThisTurn:true, extra:true},
]) reject(market, 'choam', action);
let game = actChoamCharityFunding(market, 'choam', {type:'decision', done:true, skipMarketsThisTurn:true});
assert.equal(viewGame(game, 'choam').choamMarketSkipActive, true);
game = actChoamCharityFunding(game, 'choam', {type:'setAutopilot', difficulty:'Easy'});
assert.equal(game.choamMarketSkip, undefined);
game = actChoamCharityFunding(game, 'choam', {type:'setAutopilot', difficulty:null});
assert.equal(viewGame(game, 'choam').choamMarketSkipActive, false);
});