Files
Dune/tests/admin-seat-ai-client.test.ts
T
Quintine 2cddf03932 Prototype paused admin participant AI support
Enable an existing AI difficulty for a human seat through admin controls without granting private access or advancing paused play. Preserve custody, credentials, takeback and separate paced resume. Bind atomic writes and outgoing-grant revocation to live authority, room/pause versions and active seat access with durable exact receipts.

Validation: types/lint, 5408 offline tests, build and 52 HTTP tests passed on source fingerprint 2157de0f4bb0b74d9760d52102601c52344cf674dee8ccd1d559f0a246a1d4ff; 22 authenticated admin HTTP groups, independent SQL/privacy/UI review, desktop/mobile and refresh checks. All 2098 baseline rooms and 4455 seats preserved. Migration 0015 is additive. Positive production admin QA awaits authorized access; full admin/rules scope remains unfinished.
2026-09-26 14:04:17 +10:00

55 lines
4.6 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { adminSeatAiConfirmation, adminSeatAiResponse, clearAdminSeatAiRequest, newAdminSeatAiRequest, readAdminSeatAiRequest, saveAdminSeatAiRequest } from '../lib/admin-seat-ai-client';
import type { AdminSeatAiView } from '../lib/admin-seat-ai';
const view = (): AdminSeatAiView => ({ code: 'SEATQAAB', version: 7, controlRevision: 2, status: 'playing', paused: true, closed: false, removed: false, archived: false,
editable: true, blockedReason: null, players: [{ id: 'owner', name: 'QA Owner', faction: 'atreides', control: 'human', difficulty: null, eligible: true }] });
function storage() {
const values = new Map<string, string>();
return { values, getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => { values.set(key, value); }, removeItem: (key: string) => { values.delete(key); } };
}
void test('participant retry stores exact intent before dispatch, survives refresh, and isolates room/account', () => {
const tab = storage(), room = view(), input = newAdminSeatAiRequest(room, 'owner', 'Hard', ' QA support ');
assert.equal(input.reason, 'QA support');
saveAdminSeatAiRequest(tab, 'admin', room.code, input);
assert.deepEqual(readAdminSeatAiRequest(tab, 'admin', room.code), input);
assert.equal(readAdminSeatAiRequest(tab, 'other', room.code), null);
assert.equal(readAdminSeatAiRequest(tab, 'admin', 'OTHERQAA'), null);
for (const change of [{ operationId: crypto.randomUUID() }, { expectedVersion: 8 }, { expectedControlRevision: 3 }, { target: 'other' }, { difficulty: 'Easy' as const }, { reason: 'Different' }])
assert.throws(() => saveAdminSeatAiRequest(tab, 'admin', room.code, { ...input, ...change }), /Resolve the saved/);
saveAdminSeatAiRequest(tab, 'admin', room.code, input);
clearAdminSeatAiRequest(tab, 'admin', room.code); assert.equal(readAdminSeatAiRequest(tab, 'admin', room.code), null);
});
void test('bad participant retry storage cannot lose or overwrite unresolved intent', () => {
const tab = storage(), room = view(), input = newAdminSeatAiRequest(room, 'owner', 'Easy', 'QA');
assert.throws(() => saveAdminSeatAiRequest({ ...tab, setItem() {} }, 'admin', room.code, input), /Nothing was sent/);
saveAdminSeatAiRequest(tab, 'admin', room.code, input);
const key = [...tab.values.keys()][0];
for (const raw of ['{broken', JSON.stringify({ ...input, token: 'not accepted' }), ' '.repeat(2049), JSON.stringify({ ...input, expectedControlRevision: -1 })]) {
tab.values.set(key, raw);
assert.throws(() => readAdminSeatAiRequest(tab, 'admin', room.code), /unreadable/);
assert.throws(() => saveAdminSeatAiRequest(tab, 'admin', room.code, input), /unreadable/);
assert.equal(tab.values.get(key), raw);
}
assert.throws(() => clearAdminSeatAiRequest({ ...tab, removeItem() {} }, 'admin', room.code), /could not be cleared/);
clearAdminSeatAiRequest(tab, 'admin', room.code);
for (const reason of [' ', 'a'.repeat(301), 'two\nlines', 'control\u007f']) assert.throws(() => newAdminSeatAiRequest(room, 'owner', 'Easy', reason));
assert.throws(() => newAdminSeatAiRequest({ ...room, editable: false, blockedReason: 'Pause first' }, 'owner', 'Easy', 'QA'), /Pause first/);
assert.throws(() => newAdminSeatAiRequest(room, 'absent', 'Easy', 'QA'), /eligible/);
});
void test('participant confirmation binds original operation/version but accepts later human takeback', () => {
const room = view(), input = newAdminSeatAiRequest(room, 'owner', 'Hard', 'QA');
const result = { operationId: input.operationId, appliedVersion: 8, replayed: true, room: { ...room, version: 10, paused: false, editable: false, blockedReason: 'Pause first' } };
assert.deepEqual(adminSeatAiConfirmation(result, room.code, input), result);
for (const bad of [{ ...result, operationId: crypto.randomUUID() }, { ...result, appliedVersion: 9 }, { ...result, replayed: 'yes' },
{ ...result, room: { ...room, version: 7 } }, { ...result, token: 'unexpected' }]) assert.throws(() => adminSeatAiConfirmation(bad, room.code, input));
});
void test('participant responses reject unexpected private fields and malformed operational values', () => {
const room = view(); assert.deepEqual(adminSeatAiResponse(room, room.code), room);
for (const bad of [null, [], { ...room, state: {} }, { ...room, auditReason: 'private' }, { ...room, code: 'bad' },
{ ...room, version: -1 }, { ...room, paused: 'yes' }, { ...room, controlRevision: 1.2 },
{ ...room, players: [{ ...room.players[0], hand: [] }] }, { ...room, players: [{ ...room.players[0], difficulty: 'Impossible' }] }])
assert.throws(() => adminSeatAiResponse(bad, room.code));
});