Files
Dune/tests/sapho-revival-suspension-quote.test.ts

71 lines
3.9 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
createRevivalOrder, finishRevivalOrder, reorderSaphoRevivalOrder,
saphoRevivalOrderEvent, startRevivalOrder, validateRevivalOrder,
type RevivalOrder, type RevivalOrderContext,
} from '../game/revival-order';
const context = (ready: string[] = []): RevivalOrderContext => ({ turn: 4,
order: ['a', 'b', 'c', 'd'], ready });
function reorder(ctx: RevivalOrderContext, order: RevivalOrder, player: string, mode: 'first' | 'last') {
return reorderSaphoRevivalOrder(ctx, order, { event: saphoRevivalOrderEvent(ctx, order), player, mode });
}
void test('started Revival suspends last and resumes the same started opportunity after intervening turns', () => {
const ctx = context(), original = startRevivalOrder(ctx, createRevivalOrder(ctx), 'a');
const choice = reorder(ctx, original, 'a', 'last');
assert.equal(choice.suspended, 'a'); assert.equal(choice.afterCurrent, undefined);
assert.equal(choice.order.version, 3); assert.ok(choice.order.version === 3);
assert.deepEqual(choice.order.remaining, ['b', 'c', 'd', 'a']);
assert.deepEqual(choice.order.paused, ['a']); assert.equal(choice.order.started, false);
assert.equal(original.version, 1); assert.equal(original.started, true);
let order: RevivalOrder = choice.order;
for (const [index, player] of ['b', 'c', 'd'].entries()) {
const before = context(['b', 'c', 'd'].slice(0, index));
order = finishRevivalOrder(before, startRevivalOrder(before, order, player), player);
}
assert.equal(order.version, 3); assert.ok(order.version === 3);
assert.deepEqual(order.remaining, ['a']); assert.deepEqual(order.paused, ['a']);
assert.equal(order.started, true);
const finished = finishRevivalOrder(context(['b', 'c', 'd']), order, 'a');
validateRevivalOrder(context(['b', 'c', 'd', 'a']), finished);
assert.ok(finished.version === 3); assert.deepEqual(finished.paused, []);
assert.equal(finished.started, false);
});
void test('suspension preserves earlier version-two uses and a later reordered paused turn retains its started lock', () => {
const ctx = context();
const first = reorder(ctx, createRevivalOrder(ctx), 'd', 'first').order;
assert.equal(first.version, 2);
const active = startRevivalOrder(ctx, first, 'd');
const suspended = reorder(ctx, active, 'd', 'last').order;
assert.ok(first.version === 2 && suspended.version === 3);
assert.deepEqual(suspended.uses[0], first.uses[0]);
assert.deepEqual(suspended.paused, ['d']);
const resumed = reorder(ctx, suspended, 'd', 'first').order;
assert.ok(resumed.version === 3); assert.equal(resumed.started, true);
assert.equal(resumed.remaining[0], 'd'); assert.deepEqual(resumed.paused, ['d']);
const restored = JSON.parse(JSON.stringify(resumed)) as RevivalOrder;
validateRevivalOrder(ctx, restored);
const done = finishRevivalOrder(ctx, restored, 'd');
assert.ok(done.version === 3); assert.deepEqual(done.paused, []);
validateRevivalOrder(context(['d']), done);
});
void test('suspension rejects stale, no-op and forged receipts without changing either input', () => {
const ctx = context(), fresh = createRevivalOrder(ctx);
const old = saphoRevivalOrderEvent(ctx, fresh), active = startRevivalOrder(ctx, fresh, 'a');
const before = structuredClone(active);
assert.throws(() => reorderSaphoRevivalOrder(ctx, active, { event: old, player: 'a', mode: 'last' }));
assert.throws(() => reorder(ctx, active, 'a', 'first'));
assert.deepEqual(active, before);
const frame = reorder(ctx, active, 'a', 'last').order; assert.ok(frame.version === 3);
for (const bad of [
{ ...frame, paused: [] }, { ...frame, paused: ['a', 'a'] },
{ ...frame, paused: ['foreign'] }, { ...frame, remaining: ['a', 'b', 'c', 'd'] },
{ ...frame, uses: frame.uses.map(use => ({ ...use, suspended: undefined })) },
]) assert.throws(() => validateRevivalOrder(ctx, bad as RevivalOrder));
assert.deepEqual(active, before);
});