Files
Dune/tests/richese-auction-review.test.ts
2026-09-08 07:37:52 +10:00

114 lines
4.1 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
createRicheseAuction,
moveRicheseBidderLast,
projectRicheseAuction,
submitRicheseBid,
type RicheseAuction,
type RicheseAuctionInput,
} from '../game/richese-auction';
const create = (overrides: Partial<RicheseAuctionInput> = {}) =>
createRicheseAuction({
event: 'public-lot-event',
cardId: 'treachery-8',
owner: 'richese',
source: 'blackMarket',
method: 'silent',
eligible: ['a', 'b', 'richese'],
order: ['a', 'b', 'richese'],
tieOrder: ['b', 'a', 'richese'],
...overrides,
});
const bid = (state: RicheseAuction, actor: string, amount: number | null) =>
submitRicheseBid(state, { event: state.event, actor, amount }, 20);
void test('concealed Black Market projections do not disclose face-encoding physical card IDs', () => {
const lasgun = bid(create({ cardId: 'treachery-8' }), 'a', 3);
const shield = bid(create({ cardId: 'treachery-9' }), 'a', 3);
for (const viewer of ['a', 'b', 'observer', 'atreides']) {
assert.deepEqual(
projectRicheseAuction(lasgun, viewer),
projectRicheseAuction(shield, viewer),
);
assert.equal(projectRicheseAuction(lasgun, viewer).cardId, null);
assert.equal(
JSON.stringify(projectRicheseAuction(lasgun, viewer)).includes(
'treachery-8',
),
false,
);
}
assert.equal(projectRicheseAuction(lasgun, 'richese').cardId, 'treachery-8');
assert.equal(projectRicheseAuction(shield, 'richese').cardId, 'treachery-9');
});
void test('a sold or retained Black Market lot does not automatically publish its card identity', () => {
for (const amount of [0, 4]) {
let state = bid(create(), 'a', amount);
state = bid(state, 'b', 0);
state = bid(state, 'richese', 0);
assert.ok(state.outcome);
for (const viewer of ['a', 'b', 'observer'])
assert.equal(projectRicheseAuction(state, viewer).cardId, null);
assert.equal(projectRicheseAuction(state, 'richese').cardId, 'treachery-8');
}
});
void test('revealed cache identities remain public while ordinary cache bidding is rejected', () => {
assert.throws(
() => create({ source: 'cache', method: 'normal' }),
/Once Around or Silent/,
);
for (const method of ['onceAround', 'silent'] as const) {
const state = create({ source: 'cache', method });
assert.equal(
projectRicheseAuction(state, 'observer').cardId,
'treachery-8',
);
}
assert.equal(create({ source: 'blackMarket', method: 'normal' }).active, 'a');
});
void test('Sapho ordering survives reload, preserves completed bids and leaves Silent tie order unchanged', () => {
let state = bid(create({ source: 'cache', method: 'onceAround' }), 'a', 2);
state = moveRicheseBidderLast(state, state.event, 'b');
const saved = JSON.stringify(state);
state = JSON.parse(saved);
assert.deepEqual(state.acted, ['a']);
assert.deepEqual(state.tieOrder, ['b', 'a', 'richese']);
assert.equal(state.active, 'richese');
state = bid(state, 'richese', null);
assert.equal(state.active, 'b');
assert.throws(() => bid(state, 'a', 5), /Wait/);
state = bid(state, 'b', 3);
assert.deepEqual(state.outcome, { kind: 'sold', winner: 'b', amount: 3 });
assert.throws(() => moveRicheseBidderLast(state, state.event, 'a'), /open/);
assert.throws(
() => moveRicheseBidderLast(create(), 'public-lot-event', 'b'),
/Once Around/,
);
});
void test('input lists and revealed bid projections cannot mutate auction authority', () => {
const eligible = ['a', 'b'];
const order = ['b', 'a'];
const tieOrder = ['a', 'b'];
let state = create({ eligible, order, tieOrder });
eligible.pop();
order.reverse();
tieOrder.reverse();
assert.deepEqual(state.eligible, ['a', 'b']);
assert.deepEqual(state.order, ['b', 'a']);
assert.deepEqual(state.tieOrder, ['a', 'b']);
state = bid(state, 'b', 3);
state = bid(state, 'a', 3);
const view = projectRicheseAuction(state, 'a');
view.revealedBids!.a = 19;
view.eligible.pop();
assert.deepEqual(state.sealed, { b: 3, a: 3 });
assert.deepEqual(state.outcome, { kind: 'sold', winner: 'a', amount: 3 });
assert.deepEqual(state.eligible, ['a', 'b']);
});