Files
Dune/tools/admin-discussion-verification.mjs
Quintine 160fdc1f30 Prototype administrative discussion mute controls
Let owners/operators mute or unmute new public/private sends from a human seat while retaining history, incoming messages, gameplay and credentials. Fence metadata and durable audit writes with live authority, membership and versions. Preserve exact admin/message retries and inherit restrictions through seat recovery.

Validation: types/lint, 5431 offline tests, build, 52 HTTP integration tests, 24 administrator HTTP groups, independent SQL/privacy/UI review and desktop/mobile refresh acceptance. All 2230 prior games and 4746 seats preserved. Additive migration 0016. Verified runtime fingerprint 588967fbb9c2e3b6abe2990c4fce96ee69e2256fb7e3ee416314b87593cebba9; documentation-only grammar/button-label review gives b5e57bdd26812debede91c588e38f247f1fa895ad23e7ae30bc8550eedcd90ec. New-revision deployed acceptance remains open; full admin/rules scope is unfinished.
2026-09-26 14:30:52 +10:00

46 lines
3.7 KiB
JavaScript

// Creates its own QA room; never accepts an existing game for moderation.
import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';
export async function verifyDiscussionModeration(request, adminCookie, report) {
const made = await request('/api/rooms', { name: 'Discussion moderation QA', faction: 'atreides', advanced: false, expansions: [] });
assert.equal(made.status, 201);
const code = made.data.code, host = made.cookie, path = '/api/rooms/' + code, adminPath = '/api/admin/rooms/' + code + '/discussion';
report.discussionRoom = code;
const joined = await request(path, { type: 'join', name: 'Discussion guest QA', faction: 'emperor' });
assert.equal(joined.status, 200);
const guest = joined.cookie, messages = path + '/messages';
const prior = { id: randomUUID(), recipientId: joined.data.me, text: 'QA message before mute' };
assert.equal((await request(messages, prior, host)).status, 200);
const before = (await request(path, undefined, host)).data;
const initial = await request(adminPath, undefined, adminCookie); assert.equal(initial.status, 200);
const input = { operationId: randomUUID(), expectedVersion: initial.data.version, expectedRevision: 0, target: made.data.me, muted: true, reason: 'Discussion moderation QA' };
for (const denied of [
await request(adminPath, input, host), await request(adminPath, input, adminCookie, 'https://foreign.invalid'),
await request(adminPath, input, adminCookie, undefined, randomUUID()),
]) { assert.ok([401,403].includes(denied.status)); assert.equal(denied.rawCookie, null); }
const muted = await request(adminPath, input, adminCookie);
assert.equal(muted.status, 200); assert.equal(muted.rawCookie, null); assert.equal(muted.data.replayed, false);
assert.equal(muted.data.appliedRevision, 1); assert.equal(muted.data.room.version, initial.data.version);
assert.deepEqual(Object.keys(muted.data.room.players[0]).sort(), ['control','faction','id','muted','name','revision']);
assert.deepEqual((await request(path, undefined, host)).data, before);
for (const recipientId of [null, joined.data.me]) {
const denied = await request(messages, { id: randomUUID(), recipientId, text: 'Must be blocked' }, host);
assert.equal(denied.status, 409); assert.equal(denied.data.code, 'SEAT_DISCUSSION_MUTED');
}
const own = await request(messages + '?recipient=' + joined.data.me, undefined, host);
assert.equal(own.status, 200); assert.equal(own.data.muted, true); assert.equal(own.data.messages[0].id, prior.id);
assert.equal((await request(messages, undefined, guest)).data.muted, false);
const retry = await request(messages, prior, host); assert.equal(retry.status, 200); assert.equal(retry.data.replayed, true);
assert.equal((await request(messages, { id: randomUUID(), recipientId: made.data.me, text: 'QA incoming message while muted' }, guest)).status, 200);
report.checks.push('Discussion moderation enforces live admin/account/origin authority, own-only mute status, new-send barrier and exact committed message retry without game changes');
const undo = { ...input, operationId: randomUUID(), expectedRevision: 1, muted: false };
assert.equal((await request(adminPath, undo, adminCookie)).status, 200);
const old = await request(adminPath, input, adminCookie);
assert.equal(old.status, 200); assert.equal(old.data.replayed, true);
assert.equal(old.data.room.players.find(player => player.id === input.target).muted, false);
assert.equal((await request(messages, undefined, host)).data.muted, false);
assert.deepEqual((await request(path, undefined, host)).data, before);
report.checks.push('Unmute restores sending availability; replaying an old admin request cannot mute again and preserves both human seats');
}