Files
ssessesse-ki-battle/tests/peer-protocol.test.ts
T

40 lines
2.6 KiB
TypeScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createTurnExchange } from '../src/shared/peer-protocol.js';
import type { PeerMessage } from '../src/shared/p2p-types.js';
import type { ActionId, Pair } from '../src/shared/types.js';
function pair() {
const toA: PeerMessage[] = [], toB: PeerMessage[] = [], resolvedA: Pair<ActionId | null>[] = [], resolvedB: Pair<ActionId | null>[] = [];
const base = { matchId: 'match', epoch: 0, turnId: 1, energy: [0, 0] as Pair<number> };
const a = createTurnExchange({ ...base, slot: 0, send: m => toB.push(m), onResolved: r => resolvedA.push(r) });
const b = createTurnExchange({ ...base, slot: 1, send: m => toA.push(m), onResolved: r => resolvedB.push(r) });
const pump = async () => { for (let i = 0; i < 20 && (toA.length || toB.length); i++) { while (toA.length) await a.receive(toA.shift()!); while (toB.length) await b.receive(toB.shift()!); } };
return { a, b, toA, toB, resolvedA, resolvedB, pump };
}
test('choices stay secret until both commitments and resolve identically exactly once', async () => {
const h = pair(); await h.a.commit('charge');
assert.equal(h.toB.length, 1); assert.equal(h.toB[0].type, 'turn:commit');
assert.equal('action' in h.toB[0], false); await h.b.receive(h.toB.shift()!);
assert.equal(h.toA.length, 0);
await h.b.commit('guard'); const commit = h.toA[0]; await h.pump();
assert.deepEqual(h.resolvedA, [['charge', 'guard']]); assert.deepEqual(h.resolvedB, h.resolvedA);
await h.a.receive(commit); assert.equal(h.resolvedA.length, 1);
});
test('missing input is committed as null and is distinct from missing network packets', async () => {
const h = pair(); await h.a.commit(null); await h.b.commit('charge'); await h.pump();
assert.deepEqual(h.resolvedA, [[null, 'charge']]);
const silent = pair(); await silent.a.commit(null); assert.equal(silent.resolvedA.length, 0);
});
test('tampered reveal and changed commitment are rejected; stale epochs never resolve', async () => {
const h = pair(); await h.a.commit('charge'); await h.b.commit('guard');
const original = h.toA[0];
await h.a.receive({ ...original, connectionEpoch: 99 }); assert.equal(h.toB.length, 1);
await h.a.receive(h.toA.shift()!);
await h.b.receive(h.toB.shift()!);
const reveal = h.toA.find(m => m.type === 'turn:reveal')!;
await assert.rejects(h.a.receive({ ...reveal, action: 'charge' } as PeerMessage), /COMMITMENT_MISMATCH/);
await assert.rejects(h.a.receive({ ...original, digest: '0'.repeat(64) } as PeerMessage), /COMMITMENT_CHANGED/);
assert.equal(h.resolvedA.length, 0);
});