88 lines
4.8 KiB
TypeScript
88 lines
4.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
PublicDealError, createPublicDeal, releasePublicDeal, respondPublicDeal,
|
|
validatePublicDeals, withdrawPublicDeal,
|
|
} from '../game/public-deals';
|
|
|
|
const players = [
|
|
{ id: 'a', ally: null }, { id: 'b', ally: null }, { id: 'c', ally: null },
|
|
{ id: 'bot', bot: 'Medium', ally: null },
|
|
];
|
|
const offer = () => createPublicDeal(players, 'a', 'b', ' Trade two spice later. ', 2, 'event-1');
|
|
|
|
void test('an explicit offer and recipient response preserve input and do not form an alliance', () => {
|
|
const before = structuredClone(players);
|
|
const pending = offer();
|
|
assert.deepEqual(pending, { id: 'event-1', from: 'a', to: 'b', terms: 'Trade two spice later.', turn: 2, status: 'offered', releaseVotes: [] });
|
|
const original = structuredClone(pending);
|
|
assert.throws(() => respondPublicDeal(pending, players, 'a', true), PublicDealError);
|
|
assert.throws(() => respondPublicDeal(pending, players, 'c', false), PublicDealError);
|
|
const active = respondPublicDeal(pending, players, 'b', true);
|
|
assert.equal(active.status, 'active');
|
|
assert.deepEqual(pending, original);
|
|
assert.deepEqual(players, before);
|
|
validatePublicDeals([pending, { ...active, id: 'event-2' }], players);
|
|
assert.throws(() => respondPublicDeal(active, players, 'b', true), PublicDealError);
|
|
});
|
|
|
|
void test('decline and withdrawal require their respective participant and are terminal', () => {
|
|
const pending = offer();
|
|
assert.throws(() => withdrawPublicDeal(pending, players, 'b'), PublicDealError);
|
|
const declined = respondPublicDeal(pending, players, 'b', false);
|
|
const withdrawn = withdrawPublicDeal(pending, players, 'a');
|
|
assert.equal(declined.status, 'declined');
|
|
assert.equal(withdrawn.status, 'withdrawn');
|
|
for (const deal of [declined, withdrawn]) {
|
|
validatePublicDeals([deal], players);
|
|
assert.throws(() => releasePublicDeal(deal, players, 'a', true), PublicDealError);
|
|
}
|
|
});
|
|
|
|
void test('only both participants can release an accepted deal and a prior vote may be withdrawn', () => {
|
|
const initial = respondPublicDeal(offer(), players, 'b', true);
|
|
const aVotes = releasePublicDeal(initial, players, 'a', true);
|
|
assert.deepEqual(aVotes.releaseVotes, ['a']);
|
|
assert.deepEqual(initial.releaseVotes, []);
|
|
assert.throws(() => releasePublicDeal(aVotes, players, 'c', true), PublicDealError);
|
|
const aWithdraws = releasePublicDeal(aVotes, players, 'a', false);
|
|
assert.deepEqual(aWithdraws.releaseVotes, []);
|
|
const bVotes = releasePublicDeal(aWithdraws, players, 'b', true);
|
|
assert.deepEqual(bVotes.releaseVotes, ['b']);
|
|
const released = releasePublicDeal(bVotes, players, 'a', true);
|
|
assert.equal(released.status, 'released');
|
|
assert.deepEqual(new Set(released.releaseVotes), new Set(['a', 'b']));
|
|
validatePublicDeals([released], players);
|
|
assert.throws(() => releasePublicDeal(released, players, 'a', false), PublicDealError);
|
|
});
|
|
|
|
void test('alliance of the two parties prevents new and accepted deals, but not decline or release', () => {
|
|
const pending = offer();
|
|
const between = players.map(player => player.id === 'a' ? { ...player, ally: 'b' } : player);
|
|
assert.throws(() => createPublicDeal(between, 'a', 'b', 'terms', 2, 'x'), PublicDealError);
|
|
assert.throws(() => respondPublicDeal(pending, between, 'b', true), PublicDealError);
|
|
assert.equal(respondPublicDeal(pending, between, 'b', false).status, 'declined');
|
|
const active = respondPublicDeal(pending, players, 'b', true);
|
|
const first = releasePublicDeal(active, between, 'a', true);
|
|
assert.equal(releasePublicDeal(first, between, 'b', true).status, 'released');
|
|
const otherAlliance = players.map(player => player.id === 'a' ? { ...player, ally: 'c' } : player);
|
|
assert.equal(createPublicDeal(otherAlliance, 'a', 'b', 'terms', 2, 'y').status, 'offered');
|
|
});
|
|
|
|
void test('strict ledger and creation validation reject malformed or forged records', () => {
|
|
validatePublicDeals([], [players[0]]);
|
|
for (const [actor, target, terms, turn, event] of [
|
|
['a', 'a', 'x', 1, 'e'], ['a', 'bot', 'x', 1, 'e'], ['bot', 'a', 'x', 1, 'e'],
|
|
['a', 'missing', 'x', 1, 'e'], ['a', 'b', ' ', 1, 'e'], ['a', 'b', 'x'.repeat(501), 1, 'e'],
|
|
['a', 'b', 'x', 0, 'e'], ['a', 'b', 'x', 1, ' e '],
|
|
] as Array<[string, string, string, number, string]>)
|
|
assert.throws(() => createPublicDeal(players, actor, target, terms, turn, event), PublicDealError);
|
|
const pending = offer();
|
|
for (const ledger of [
|
|
[pending, pending], [{ ...pending, unexpected: true }], [{ ...pending, releaseVotes: ['c'] }],
|
|
[{ ...pending, status: 'active', releaseVotes: ['a', 'a'] }],
|
|
[{ ...pending, status: 'released', releaseVotes: ['a'] }],
|
|
[{ ...pending, status: 'offered', releaseVotes: ['a'] }],
|
|
]) assert.throws(() => validatePublicDeals(ledger, players), PublicDealError);
|
|
});
|