Files
Dune/tests/public-deals-future-deferred-runtime.test.ts
T
Quintine 1e81ce8ad8
Verify and publish checkpoint container / container (push) Successful in 11m8s
Support scheduled travel agreements
2026-10-29 08:19:52 +11:00

114 lines
6.4 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame, type Action, type Game } from '../game/engine';
import { actTruthTravel as act, createTruthTravelFixture as fixture,
truthTravelInventory as inventory, truthTravelPlayer as player } from './fixture-truth-travel';
function reject(game: Game, actor: string, action: Action) {
const before = structuredClone(game);
assert.throws(() => act(game, actor, action));
assert.deepEqual(game, before, 'Conflicting consent must leave the source game unchanged.');
}
function travelOffer(game: Game, proposer: string, traveler: string, dueTurn: number) {
const next = act(game, proposer, {type:'dealOffer', target:traveler, terms:'Travel in a future turn.',
travel:{kind:'ship', territory:'polar_sink', minimum:1, answer:true, dueTurn}});
const deal = next.deals?.at(-1); assert.ok(deal);
return {game:next, id:deal.id};
}
function paymentOffer(game: Game, proposer: string, target: string, payer: string, recipient: string, dueTurn: number) {
const next = act(game, proposer, {type:'dealOffer', target, terms:'Pay a future bribe.',
payment:{kind:'deferredBribe', payer, recipient, amount:2, dueTurn}});
const deal = next.deals?.at(-1); assert.ok(deal);
return {game:next, id:deal.id};
}
function accept(game: Game, recipient: string, id: string) {
return act(game, recipient, {type:'dealRespond', deal:id, accept:true});
}
function deal(game: Game, id: string) {
const found = game.deals?.find(row => row.id === id); assert.ok(found); return found;
}
/** Each case uses the genuine Basic Atreides/Emperor setup and actual native
* first-turn pre-shipment window. These are independent two-party offers and
* acceptances; no wallet, force, pending debt or travel receipt is staged. */
void test('a same-turn deferred debt cannot be accepted after the payer schedules travel', () => {
const f = fixture(), dueTurn = f.game.turn + 1, originalCards = inventory(f.game);
const future = travelOffer(f.game, f.asker, f.target, dueTurn);
let game = accept(future.game, f.target, future.id);
assert.equal(viewGame(game, f.target).dealTravelStatus[future.id], 'scheduled');
const payment = paymentOffer(game, f.asker, f.target, f.target, f.asker, dueTurn);
game = payment.game;
const purse = player(game, f.target).spice;
reject(game, f.target, {type:'dealRespond', deal:payment.id, accept:true});
assert.equal(deal(game, future.id).status, 'active');
assert.equal(deal(game, payment.id).status, 'offered');
assert.equal(game.travelPromises?.filter(row => row.sourceDeal === future.id).length, 1);
assert.equal(player(game, f.target).spice, purse);
assert.deepEqual(inventory(game), originalCards);
});
void test('a same-turn scheduled journey cannot be accepted after the traveler owes a deferred debt', () => {
const f = fixture(), dueTurn = f.game.turn + 1, originalCards = inventory(f.game);
const payment = paymentOffer(f.game, f.asker, f.target, f.target, f.asker, dueTurn);
let game = accept(payment.game, f.target, payment.id);
assert.equal(deal(game, payment.id).paymentStatus, 'pending');
const future = travelOffer(game, f.asker, f.target, dueTurn);
game = future.game;
const purse = player(game, f.target).spice;
reject(game, f.target, {type:'dealRespond', deal:future.id, accept:true});
assert.equal(deal(game, payment.id).status, 'active');
assert.equal(deal(game, payment.id).paymentStatus, 'pending');
assert.equal(deal(game, future.id).status, 'offered');
assert.equal(game.travelPromises?.some(row => row.sourceDeal === future.id) ?? false, false);
assert.equal(player(game, f.target).spice, purse);
assert.deepEqual(inventory(game), originalCards);
});
for (const order of ['travel-first', 'payment-first'] as const) void test(
`different future due turns coexist when accepted ${order}`, () => {
const f = fixture(), travelDue = f.game.turn + 1, paymentDue = travelDue + 1;
let game = f.game, travelId: string, paymentId: string;
if (order === 'travel-first') {
const future = travelOffer(game, f.asker, f.target, travelDue); travelId = future.id;
game = accept(future.game, f.target, travelId);
const payment = paymentOffer(game, f.asker, f.target, f.target, f.asker, paymentDue); paymentId = payment.id;
game = accept(payment.game, f.target, paymentId);
} else {
const payment = paymentOffer(game, f.asker, f.target, f.target, f.asker, paymentDue); paymentId = payment.id;
game = accept(payment.game, f.target, paymentId);
const future = travelOffer(game, f.asker, f.target, travelDue); travelId = future.id;
game = accept(future.game, f.target, travelId);
}
assert.equal(deal(game, travelId).status, 'active');
assert.equal(viewGame(game, f.target).dealTravelStatus[travelId], 'scheduled');
assert.equal(game.travelPromises?.find(row => row.sourceDeal === travelId)?.turn, travelDue);
assert.equal(deal(game, paymentId).paymentStatus, 'pending');
assert.equal(player(game, f.target).spice, player(f.game, f.target).spice);
assert.deepEqual(inventory(game), f.originalCards);
},
);
for (const order of ['travel-first', 'payment-first'] as const) void test(
`the same future due turn can involve a different payment payer when accepted ${order}`, () => {
const f = fixture(), dueTurn = f.game.turn + 1;
let game = f.game, travelId: string, paymentId: string;
if (order === 'travel-first') {
const future = travelOffer(game, f.asker, f.target, dueTurn); travelId = future.id;
game = accept(future.game, f.target, travelId);
const payment = paymentOffer(game, f.asker, f.target, f.asker, f.target, dueTurn); paymentId = payment.id;
game = accept(payment.game, f.target, paymentId);
} else {
const payment = paymentOffer(game, f.asker, f.target, f.asker, f.target, dueTurn); paymentId = payment.id;
game = accept(payment.game, f.target, paymentId);
const future = travelOffer(game, f.asker, f.target, dueTurn); travelId = future.id;
game = accept(future.game, f.target, travelId);
}
assert.equal(deal(game, travelId).status, 'active');
assert.equal(viewGame(game, f.target).dealTravelStatus[travelId], 'scheduled');
assert.equal(deal(game, paymentId).paymentStatus, 'pending');
assert.equal(player(game, f.asker).spice, player(f.game, f.asker).spice);
assert.equal(player(game, f.target).spice, player(f.game, f.target).spice);
assert.deepEqual(inventory(game), f.originalCards);
},
);