Files
Dune/tests/pledge-promises.test.ts
T
Quintine c414a03a19
Verify and publish checkpoint container / container (push) Successful in 10m48s
Prototype Truthtrance ally funding commitments
2026-10-29 05:44:01 +11:00

95 lines
5.3 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
PledgePromiseError, livePledgePromises, parsePledgeClaim, pledgePromiseRequirements,
reconcilePledgePromises, recordPromisedPledge, validatePledgePromises,
type PledgePromise, type PledgeReconciliation,
} from '../game/pledge-promises';
const promise = (overrides: Partial<PledgePromise> = {}): PledgePromise => ({
turn: 2, phase: 3, ally: 'ally', minimum: 3,
event: 'event-1', player: 'donor', asker: 'asker', answer: true, ...overrides,
});
const context = (overrides: Partial<PledgeReconciliation> = {}): PledgeReconciliation => ({
turn: 2, phase: 3, mutualAlly: player => player === 'donor' ? 'ally' : null,
canFulfill: () => true, ...overrides,
});
void test('a pledge question requires a current supported phase, current ally identity and positive safe threshold', () => {
assert.deepEqual(parsePledgeClaim({turn: 2, phase: 5, ally: 'ally', minimum: 4}),
{turn: 2, phase: 5, ally: 'ally', minimum: 4});
for (const value of [null, {}, {turn: 0, phase: 3, ally: 'ally', minimum: 1},
{turn: 2, phase: 4, ally: 'ally', minimum: 1},
{turn: 2, phase: 3, ally: ' ally ', minimum: 1},
{turn: 2, phase: 3, ally: 'ally', minimum: 0},
{turn: 2, phase: 3, ally: 'ally', minimum: Number.MAX_SAFE_INTEGER + 1}])
assert.throws(() => parsePledgeClaim(value), PledgePromiseError);
});
void test('overlapping future Yes and No answers combine into a single bounded update', () => {
const rows = [promise({minimum: 2}), promise({event: 'event-2', minimum: 4}),
promise({event: 'event-3', answer: false, minimum: 6})];
assert.deepEqual(pledgePromiseRequirements(rows, 'donor', 2, 3, 'ally'),
{ally: 'ally', minimum: 4, maximum: 5});
assert.equal(pledgePromiseRequirements([...rows, promise({event: 'event-4', answer: false, minimum: 4})],
'donor', 2, 3, 'ally'), null);
assert.equal(pledgePromiseRequirements(rows, 'donor', 2, 3, 'different'), null);
assert.deepEqual(pledgePromiseRequirements(rows, 'donor', 2, 5, 'ally'), {ally: 'ally', minimum: 0});
});
void test('only a future accepted native funding update satisfies Yes, including an explicit same-value update', () => {
const rows = [promise({minimum: 3}), promise({event: 'event-2', minimum: 5}),
promise({event: 'event-3', answer: false, minimum: 6})];
const before = structuredClone(rows);
const low = recordPromisedPledge(rows, 'donor', 2, 3, {ally: 'ally', amount: 3});
assert.equal(low[0].fulfilled, true);
assert.equal(low[1].fulfilled, undefined);
assert.equal(low[2].fulfilled, undefined);
assert.deepEqual(rows, before);
const full = recordPromisedPledge(low, 'donor', 2, 3, {ally: 'ally', amount: 5});
assert.equal(full[1].fulfilled, true);
assert.equal(full[2].fulfilled, undefined, 'No continues to restrict later same-phase updates');
assert.throws(() => recordPromisedPledge(full, 'donor', 2, 3, {ally: 'ally', amount: 6}), PledgePromiseError);
assert.deepEqual(rows, before);
assert.deepEqual(recordPromisedPledge(rows, 'donor', 2, 3, {ally: 'other', amount: 10}), rows);
assert.deepEqual(recordPromisedPledge(rows, 'donor', 2, 5, {ally: 'ally', amount: 10}), rows);
});
void test('an owner cannot voluntarily evade a feasible Yes while a foreign impossibility releases it', () => {
const rows = [promise(), promise({event: 'no', answer: false, minimum: 5})];
const before = structuredClone(rows);
const keep = reconcilePledgePromises(rows, context({canFulfill: (_row, quote) => {
assert.deepEqual(quote, {ally: 'ally', minimum: 3, maximum: 4}); return true;
}}));
assert.deepEqual(keep, rows);
assert.throws(() => reconcilePledgePromises(rows, context({canFulfill: () => false, voluntaryActor: 'donor'})),
PledgePromiseError);
assert.deepEqual(rows, before);
const released = reconcilePledgePromises(rows, context({canFulfill: () => false, voluntaryActor: 'asker'}));
assert.equal(released[0].released, true);
assert.equal(released[1].fulfilled, undefined, 'The No survives a temporary affordability change');
assert.deepEqual(rows, before);
});
void test('phase or reciprocal alliance changes expire No and release an unfulfilled Yes', () => {
const rows = [promise(), promise({event: 'no', answer: false})];
for (const changed of [context({phase: 5}), context({turn: 3}), context({mutualAlly: () => null})]) {
const result = reconcilePledgePromises(rows, changed);
assert.equal(result[0].released, true);
assert.equal(result[1].fulfilled, true);
assert.deepEqual(livePledgePromises(result, 'donor', changed.turn, changed.phase), []);
}
assert.throws(() => reconcilePledgePromises(rows, context({phase: 5, voluntaryActor: 'donor'})), PledgePromiseError);
assert.throws(() => reconcilePledgePromises(rows, context({mutualAlly: () => null, voluntaryActor: 'donor'})), PledgePromiseError);
});
void test('saved rows retain distinct events, valid participants and noncontradictory terminal flags', () => {
const players = ['donor', 'asker', 'ally'];
assert.doesNotThrow(() => validatePledgePromises([promise()], players));
for (const bad of [
[promise(), promise()], [promise({player: 'ally'})], [promise({asker: 'donor'})],
[promise({event: ''})], [promise({fulfilled: true, released: true})],
[promise({answer: false, released: true})],
]) assert.throws(() => validatePledgePromises(bad, players), PledgePromiseError);
});