Files
Dune/tests/admin-boundary-http.test.ts
T
Quintine 07c20ae806 Diagnose stalled administrator decisions from the operations page
Add an owner-only, read-only sample of at most ten live rooms whose public
pending interaction — decision, response, Truthtrance or phase opening — has
been idle longest, with the room code, idle duration, interaction kind, turn
and phase. GET /api/admin/operations?stalled=1 repeats live owner authority,
answers no-store, rejects any other query combination and repairs nothing;
paused, closed, removed and archived rooms are excluded and no private game
content is selected. The aggregate sample adds stalledRooms and
oldestStalledChange for the same predicate.

Owner-only unit coverage asserts the counters, the oldest stalled write, the
bounded oldest-first order, every exclusion boundary, idle arithmetic, the
absence of private content and the owner recheck after demotion. The anonymous
HTTP boundary denies the new read, and the full HTTP integration suite passes
55/55 against a local build of this revision. The panel was verified in the
real browser against six hours of genuine idle rooms, which caught and fixed a
missing threshold field in the response.

No game, seat, saved state, deployment, public start or played-save conversion
changes.
2026-10-07 17:51:08 +11:00

129 lines
8.4 KiB
TypeScript

// @dune-suite integration
import test from 'node:test';
import assert from 'node:assert/strict';
const base = process.env.DUNE_TEST_URL ?? 'http://localhost:3000';
void test('admin HTTP denies anonymous, forged admin, room-host and spoofed identity-header access', async () => {
const created = await fetch(base + '/api/rooms', {
method: 'POST', headers: { 'Content-Type': 'application/json', origin: base },
body: JSON.stringify({ name: 'Admin boundary QA', faction: 'atreides', advanced: false, expansions: [] }),
});
assert.equal(created.status, 201);
const cookie = created.headers.get('set-cookie')?.split(';')[0];
assert.ok(cookie);
const room = (await created.json() as { code: string }).code;
for (const headers of [{}, { cookie }, { cookie: 'dune_admin_session=' + 'a'.repeat(64) }, { 'oai-authenticated-user-id': 'owner', 'oai-authenticated-user-email': 'owner@example.test' }] as Record<string, string>[]) {
for (const path of ['/api/admin/session', '/api/admin/accounts', '/api/admin/audit', '/api/admin/operations?integrity=1', '/api/admin/operations?stalled=1', '/api/admin/rooms', `/api/admin/rooms/${room}/control`, `/api/admin/rooms/${room}/lobby`, `/api/admin/rooms/${room}/removal`, `/api/admin/rooms/${room}/closure`, `/api/admin/rooms/${room}/archive`, `/api/admin/rooms/${room}/seat-ai`, `/api/admin/rooms/${room}/discussion`]) {
const result = await fetch(base + path, { headers, signal: AbortSignal.timeout(15000) });
assert.equal(result.status, 401, path);
assert.equal(result.headers.get('cache-control'), 'no-store');
assert.deepEqual(Object.keys(await result.json()), ['error']);
}
const account = await fetch(base + '/api/admin/accounts', {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json', 'X-Dune-Admin-Id': 'forged-owner' },
body: JSON.stringify({ action: 'provision', operationId: crypto.randomUUID(), id: crypto.randomUUID(), key: 'forged', name: 'Forbidden', role: 'owner', reason: 'Denied boundary request' }),
signal: AbortSignal.timeout(15000),
});
assert.equal(account.status, 401);
assert.equal(account.headers.get('set-cookie'), null);
assert.deepEqual(Object.keys(await account.json()), ['error']);
const target=crypto.randomUUID();
const rotate=await fetch(base + '/api/admin/accounts', {
method: 'POST',headers:{ ...headers,origin:base,'Content-Type':'application/json' },
body:JSON.stringify({ action:'rotate',operationId:crypto.randomUUID(),target,expectedUpdatedAt:1000,
key:`dune-admin.${target}.${'a'.repeat(64)}`,reason:'Denied rotation request' }),
signal:AbortSignal.timeout(15000),
});
assert.equal(rotate.status,401);
assert.equal(rotate.headers.get('set-cookie'),null);
assert.deepEqual(Object.keys(await rotate.json()),['error']);
const write = await fetch(`${base}/api/admin/rooms/${room}/control`, {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json' },
body: JSON.stringify({ operationId: crypto.randomUUID(), expectedRevision: 0, paused: true, joinLocked: true, reason: 'Denied QA request' }),
signal: AbortSignal.timeout(15000),
});
assert.equal(write.status, 401);
assert.deepEqual(Object.keys(await write.json()), ['error']);
const lobby = await fetch(`${base}/api/admin/rooms/${room}/lobby`, {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json' },
body: JSON.stringify({ operationId: crypto.randomUUID(), expectedVersion: 0, action: { type: 'rules', advanced: true }, reason: 'Denied lobby QA' }),
signal: AbortSignal.timeout(15000),
});
assert.equal(lobby.status, 401);
assert.equal(lobby.headers.get('set-cookie'), null);
assert.deepEqual(Object.keys(await lobby.json()), ['error']);
const removal = await fetch(`${base}/api/admin/rooms/${room}/removal`, {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json' },
body: JSON.stringify({ operationId: crypto.randomUUID(), expectedVersion: 0, expectedRevision: 0, removed: true, reason: 'Denied removal QA' }),
signal: AbortSignal.timeout(15000),
});
assert.equal(removal.status, 401);
assert.equal(removal.headers.get('set-cookie'), null);
assert.deepEqual(Object.keys(await removal.json()), ['error']);
const closure = await fetch(`${base}/api/admin/rooms/${room}/closure`, {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json' },
body: JSON.stringify({ operationId: crypto.randomUUID(), expectedVersion: 0, expectedRevision: 0, closed: true, reason: 'Denied closure QA' }),
signal: AbortSignal.timeout(15000),
});
assert.equal(closure.status, 401);
assert.equal(closure.headers.get('set-cookie'), null);
assert.deepEqual(Object.keys(await closure.json()), ['error']);
const archive = await fetch(`${base}/api/admin/rooms/${room}/archive`, {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json' },
body: JSON.stringify({ operationId: crypto.randomUUID(), expectedVersion: 0, expectedRevision: 0, archived: true, reason: 'Denied archive QA' }),
});
assert.equal(archive.status, 401);
assert.equal(archive.headers.get('set-cookie'), null);
assert.deepEqual(Object.keys(await archive.json()), ['error']);
const participant = await fetch(`${base}/api/admin/rooms/${room}/seat-ai`, {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json' },
body: JSON.stringify({ operationId: crypto.randomUUID(), expectedVersion: 0, expectedControlRevision: 0, target: 'owner', difficulty: 'Easy', reason: 'Denied participant QA' }),
});
assert.equal(participant.status, 401);
assert.equal(participant.headers.get('set-cookie'), null);
assert.deepEqual(Object.keys(await participant.json()), ['error']);
const discussion = await fetch(`${base}/api/admin/rooms/${room}/discussion`, {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json' },
body: JSON.stringify({ operationId: crypto.randomUUID(), expectedVersion: 0, expectedRevision: 0, target: 'owner', muted: true, reason: 'Denied moderation QA' }),
});
assert.equal(discussion.status, 401);
assert.equal(discussion.headers.get('set-cookie'), null);
assert.deepEqual(Object.keys(await discussion.json()), ['error']);
const create = await fetch(base + '/api/admin/rooms', {
method: 'POST', headers: { ...headers, origin: base, 'Content-Type': 'application/json' },
body: JSON.stringify({ operationId: crypto.randomUUID(), sessionToken: 'b'.repeat(64), name: 'Denied creation QA', faction: 'atreides', advanced: false, techTokens: false, strongholdCards: false, bots: [], reason: 'Must not create a room' }),
signal: AbortSignal.timeout(15000),
});
assert.equal(create.status, 401);
assert.equal(create.headers.get('set-cookie'), null);
assert.deepEqual(Object.keys(await create.json()), ['error']);
}
});
void test('admin HTTP rejects login CSRF, oversized bodies and invalid credentials without setting sessions', async () => {
for (const [origin, body, expected] of [
['', '{}', 403], ['https://foreign.invalid', '{}', 403],
[base, ' '.repeat(2049), 413], [base, JSON.stringify({ action: 'login', key: 'invalid' }), 401],
] as const) {
const result = await fetch(base + '/api/admin/session', {
method: 'POST', headers: { 'Content-Type': 'application/json', ...(origin ? { origin } : {}) }, body,
signal: AbortSignal.timeout(15000),
});
assert.equal(result.status, expected);
assert.equal(result.headers.get('set-cookie'), null);
if (expected !== 403) assert.equal(result.headers.get('cache-control'), 'no-store');
await result.text();
}
});
void test('account writes reject foreign origins before any credential or account lookup', async () => {
for (const origin of ['', 'https://foreign.invalid']) {
const result = await fetch(base + '/api/admin/accounts', {
method: 'POST', headers: { 'Content-Type': 'application/json', ...(origin ? { origin } : {}), 'X-Dune-Admin-Id': 'forged-owner' },
body: JSON.stringify({ action:'disable',operationId:crypto.randomUUID(),target:crypto.randomUUID(),expectedUpdatedAt:1,reason:'Forbidden' }),
signal: AbortSignal.timeout(15000),
});
assert.equal(result.status,403);
assert.equal(result.headers.get('set-cookie'),null);
await result.text();
}
});