Files
Dune/tests/coalition-funding.test.ts
T
Quintine e529cc2d7a
Verify and publish checkpoint container / container (push) Successful in 11m0s
Extend Truthtrance bidding and prepare alliance controls
2026-10-28 22:06:55 +11:00

123 lines
8.6 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { quoteCoalitionFunding, reconcileCoalitionPledges, settleCoalitionFunding, type CoalitionFundingInput } from '../game/coalition-funding';
import type { CoalitionSeat, CoalitionState } from '../game/coalition-alliance';
function fixture(): CoalitionFundingInput {
const seats: CoalitionSeat[] = ['a', 'b', 'c', 'd'].map(id => ({ id, ally: null }));
const coalitionState: CoalitionState = { version: 1, revision: 3, proposal: null, usedEvents: [], groups: [{
id: 'abc', members: ['a', 'b', 'c'], formedTurn: 2, edges: [
{ members: ['a', 'b'], formedTurn: 2 }, { members: ['a', 'c'], formedTurn: 3 },
{ members: ['b', 'c'], formedTurn: 3 },
],
}] };
return { seats, coalitionState, purpose: 'bid', payer: 'a', cost: 10, ownAvailable: 4,
authorizations: [{ members: ['a', 'b'], epoch: 'ab1' }, { members: ['a', 'c'], epoch: 'ac1' },
{ members: ['b', 'c'], epoch: 'bc1' }],
pledges: [
{ donor: 'b', recipient: 'a', amount: 4, epoch: 'ab1' }, { donor: 'c', recipient: 'a', amount: 5, epoch: 'ac1' },
{ donor: 'b', recipient: 'c', amount: 2, epoch: 'bc1' },
], contributions: [{ donor: 'b', amount: 3 }, { donor: 'c', amount: 4 }] };
}
void test('exact split uses each donor escrow and preserves unrelated pledges', () => {
const input = fixture(), original = structuredClone(input);
const quote = quoteCoalitionFunding(input);
assert.deepEqual(quote, { purpose: 'bid', payer: 'a', cost: 10, own: 3,
contributions: [{ donor: 'b', amount: 3 }, { donor: 'c', amount: 4 }] });
assert.deepEqual(settleCoalitionFunding(input), { quote, pledges: [
{ donor: 'b', recipient: 'a', amount: 1, epoch: 'ab1' }, { donor: 'c', recipient: 'a', amount: 1, epoch: 'ac1' },
{ donor: 'b', recipient: 'c', amount: 2, epoch: 'bc1' },
] });
assert.deepEqual(input, original);
});
void test('pair and own-only payments retain exact split including zero-cost shipments', () => {
const seats: CoalitionSeat[] = [{ id: 'a', ally: 'b' }, { id: 'b', ally: 'a' }, { id: 'c', ally: null }];
const input: CoalitionFundingInput = { seats, purpose: 'shipment', payer: 'a', cost: 6, ownAvailable: 4,
authorizations: [{ members: ['a', 'b'], epoch: 'ab1' }],
pledges: [{ donor: 'b', recipient: 'a', amount: 3, epoch: 'ab1' }], contributions: [{ donor: 'b', amount: 2 }] };
assert.deepEqual(settleCoalitionFunding(input), { quote: { purpose: 'shipment', payer: 'a', cost: 6,
own: 4, contributions: [{ donor: 'b', amount: 2 }] }, pledges: [{ donor: 'b', recipient: 'a', amount: 1, epoch: 'ab1' }] });
assert.equal(quoteCoalitionFunding({ ...input, cost: 4, contributions: [] }).own, 4);
assert.equal(quoteCoalitionFunding({ ...input, cost: 0, ownAvailable: 0, contributions: [] }).own, 0);
});
void test('settlement revalidates current pledges, coalition and payer budget', () => {
const input = fixture();
quoteCoalitionFunding(input);
assert.throws(() => settleCoalitionFunding({ ...input, ownAvailable: 2 }), /remaining cost/);
assert.throws(() => settleCoalitionFunding({ ...input, pledges: [
{ donor: 'b', recipient: 'a', amount: 2, epoch: 'ab1' }, ...input.pledges.slice(1),
] }), /current escrow/);
const seats: CoalitionSeat[] = [{ id: 'a', ally: 'b' }, { id: 'b', ally: 'a' },
{ id: 'c', ally: null }, { id: 'd', ally: null }];
assert.throws(() => settleCoalitionFunding({ ...input, seats, coalitionState: undefined,
authorizations: [{ members: ['a', 'b'], epoch: 'ab1' }] }), /Reconcile and refund stale/);
assert.throws(() => settleCoalitionFunding({ ...input, coalitionState: null as unknown as CoalitionState }), /ledger cannot be null/);
});
void test('only the stated recipient can use a pledge and no split silently reallocates', () => {
const input = fixture();
assert.throws(() => quoteCoalitionFunding({ ...input, contributions: [{ donor: 'd', amount: 1 }] }), /current escrow/);
assert.throws(() => quoteCoalitionFunding({ ...input, payer: 'c', contributions: [{ donor: 'c', amount: 1 }] }), /current escrow/);
assert.throws(() => quoteCoalitionFunding({ ...input, cost: 2 }), /exceed the cost/);
assert.throws(() => quoteCoalitionFunding({ ...input, contributions: [{ donor: 'b', amount: 3 }, { donor: 'b', amount: 1 }] }), /each donating ally once/i);
assert.throws(() => quoteCoalitionFunding({ ...input, contributions: [{ donor: 'b', amount: 0 }] }), /positive safe/);
assert.throws(() => quoteCoalitionFunding({ ...input, pledges: [...input.pledges, input.pledges[0]] }), /only one pledge/);
assert.throws(() => quoteCoalitionFunding({ ...input, pledges: [{ donor: 'd', recipient: 'a', amount: 1, epoch: 'ad1' }] }), /Reconcile and refund stale/);
});
void test('safe integer boundaries avoid overflow and JSON round trips keep the split', () => {
const input = fixture(), max = Number.MAX_SAFE_INTEGER;
const large: CoalitionFundingInput = { ...input, cost: max, ownAvailable: 0,
pledges: [{ donor: 'b', recipient: 'a', amount: max, epoch: 'ab1' }, { donor: 'c', recipient: 'a', amount: max, epoch: 'ac1' }],
contributions: [{ donor: 'b', amount: max }] };
assert.equal(settleCoalitionFunding(large).quote.own, 0);
assert.deepEqual(settleCoalitionFunding(JSON.parse(JSON.stringify(input)) as CoalitionFundingInput), settleCoalitionFunding(input));
for (const bad of [-1, 0.5, NaN, Infinity, max + 1]) {
assert.throws(() => quoteCoalitionFunding({ ...input, cost: bad }), /safe spice/);
assert.throws(() => quoteCoalitionFunding({ ...input, contributions: [{ donor: 'b', amount: bad }] }), /positive safe/);
assert.throws(() => quoteCoalitionFunding({ ...input, pledges: [{ donor: 'b', recipient: 'a', amount: bad, epoch: 'ab1' }] }), /safe escrow/);
}
});
void test('a broken and re-formed alliance cannot spend an old pledge even in the same turn', () => {
const seats: CoalitionSeat[] = [{ id: 'a', ally: 'b' }, { id: 'b', ally: 'a' }, { id: 'c', ally: null }];
const original: CoalitionFundingInput = { seats, purpose: 'bid', payer: 'a', cost: 4, ownAvailable: 0,
authorizations: [{ members: ['a', 'b'], epoch: 'first-edge' }],
pledges: [{ donor: 'b', recipient: 'a', amount: 4, epoch: 'first-edge' }],
contributions: [{ donor: 'b', amount: 4 }] };
assert.equal(quoteCoalitionFunding(original).own, 0);
const broken = { ...original, seats: seats.map(seat => ({ ...seat, ally: null })), authorizations: [] };
assert.deepEqual(reconcileCoalitionPledges(broken), { pledges: [], refunds: original.pledges });
const reformed = { ...original, authorizations: [{ members: ['a', 'b'] as [string, string], epoch: 'new-edge' }] };
assert.throws(() => settleCoalitionFunding(reformed), /Reconcile and refund stale/);
assert.deepEqual(reconcileCoalitionPledges(reformed), { pledges: [], refunds: original.pledges });
const fresh = { ...reformed, pledges: [{ donor: 'b', recipient: 'a', amount: 4, epoch: 'new-edge' }] };
assert.equal(settleCoalitionFunding(fresh).pledges[0].amount, 0);
});
void test('unrelated stale pledges require explicit reconciliation without consuming valid escrow', () => {
const input = fixture();
const changed = { ...input, authorizations: [input.authorizations[0], input.authorizations[1],
{ members: ['b', 'c'] as [string, string], epoch: 'bc2' }] };
assert.throws(() => quoteCoalitionFunding(changed), /Reconcile and refund stale/);
const reconciled = reconcileCoalitionPledges(changed);
assert.deepEqual(reconciled.refunds, [input.pledges[2]]);
assert.deepEqual(reconciled.pledges, input.pledges.slice(0, 2));
assert.equal(settleCoalitionFunding({ ...changed, pledges: reconciled.pledges }).quote.own, 3);
assert.deepEqual(input.pledges, fixture().pledges);
});
void test('authorizations must cover exactly the current edges with valid ordered epochs', () => {
const input = fixture();
assert.throws(() => quoteCoalitionFunding({ ...input, authorizations: input.authorizations.slice(1) }), /Every current alliance edge/);
assert.throws(() => quoteCoalitionFunding({ ...input, authorizations: [...input.authorizations, input.authorizations[0]] }), /exactly/);
assert.throws(() => quoteCoalitionFunding({ ...input, authorizations: [input.authorizations[0],
{ members: ['a', 'c'], epoch: 'ab1' }, input.authorizations[2]] }), /uniquely/);
assert.throws(() => quoteCoalitionFunding({ ...input, authorizations: [{ members: ['b', 'a'], epoch: 'bad' }] }), /ordered pair/);
assert.throws(() => quoteCoalitionFunding({ ...input, authorizations: [{ members: ['a', 'b'], epoch: ' ' }] }), /ordered pair/);
assert.throws(() => quoteCoalitionFunding({ ...input, pledges: [{ donor: 'b', recipient: 'a', amount: 1, epoch: '' }] }), /epoch/);
});