Files
Dune/tests/admin-operations.test.ts
T

56 lines
4.0 KiB
TypeScript

import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';
import test from 'node:test';
import { adminLogin, requireAdmin, AdminError } from '../db/admin-access';
import { readAdminOperations } from '../db/admin-operations';
import { adminStore } from './admin-access-fixture';
const now = 10_000;
void test('owner operations sample counts saved states and backup provenance without leaking contents or changing rooms', async () => {
const store = adminStore();
try {
const owner = store.provision('owner');
const login = await adminLogin(store.database, owner.key, now);
const identity = await requireAdmin(store.database, login.token, ['owner'], now);
store.sqlite.prepare('INSERT INTO rooms(code,state,version,updated_at) VALUES(?,?,?,?)').run('OPERAABC', '{"private":"sealed card"}', 12, 9000);
store.sqlite.prepare('INSERT INTO rooms(code,state,version,updated_at) VALUES(?,?,?,?)').run('OPERAABD', '{broken private state', 3, 9200);
store.sqlite.prepare('INSERT INTO seats(token_hash,room_code,player_id,revoked) VALUES(?,?,?,0)').run('private-session', 'OPERAABC', 'one');
store.sqlite.prepare('INSERT INTO seats(token_hash,room_code,player_id,revoked) VALUES(?,?,?,1)').run('revoked-session', 'OPERAABC', 'two');
store.sqlite.prepare('INSERT INTO room_removals(room_code,removed,revision,removed_at,updated_at) VALUES(?,1,1,?,?)').run('OPERAABD', now, now);
store.sqlite.prepare('INSERT INTO room_archives(room_code,archived,revision,archived_at,updated_at) VALUES(?,1,1,?,?)').run('OPERAABD', now, now);
store.sqlite.prepare('INSERT INTO room_controls(room_code,paused,join_locked,revision,updated_at) VALUES(?,1,0,1,?)').run('OPERAABC', now);
store.sqlite.prepare('INSERT INTO room_closures(room_code,closed,revision,closed_at,updated_at) VALUES(?,1,1,?,?)').run('OPERAABD', now, now);
const backupId = randomUUID();
store.sqlite.prepare(`INSERT INTO admin_room_backups
(operation_id,actor_admin_id,room_code,request_hash,room_version,created_at,size_bytes,digest,payload,reason)
VALUES(?,?,?,'request',12,?,14,'digest','secret payload','private reason')`).run(backupId, owner.id, 'OPERAABC', now);
store.sqlite.prepare('INSERT INTO admin_room_backup_downloads(download_id,operation_id,actor_admin_id,room_code,created_at) VALUES(?,?,?,?,?)').run(randomUUID(), backupId, owner.id, 'OPERAABC', now);
const before = store.sqlite.prepare('SELECT code,state,version FROM rooms ORDER BY code').all();
const result = await readAdminOperations(store.database, identity, now);
assert.deepEqual(result, {
observedAt: now, rooms: 2, removedRooms: 1, archivedRooms: 1,
pausedRooms: 1, closedRooms: 1, activeSeats: 1, unreadableRooms: 1,
lastRoomChange: 9200, backupSnapshots: 1, backupBytes: 14, backupDownloads: 1,
});
assert.equal(JSON.stringify(result).includes('secret'), false);
assert.deepEqual(store.sqlite.prepare('SELECT code,state,version FROM rooms ORDER BY code').all(), before);
} finally { store.sqlite.close(); }
});
void test('stale owner identity, demotion and revoked session cannot sample operational counts', async () => {
const store = adminStore();
try {
const owner = store.provision('owner');
store.provision('owner', 'Other owner');
const login = await adminLogin(store.database, owner.key, now);
const identity = await requireAdmin(store.database, login.token, ['owner'], now);
const denied = (error: unknown) => error instanceof AdminError && error.status === 403;
store.sqlite.prepare("UPDATE admin_accounts SET role='operator' WHERE id=?").run(owner.id);
await assert.rejects(readAdminOperations(store.database, identity, now), denied);
store.sqlite.prepare("UPDATE admin_accounts SET role='owner' WHERE id=?").run(owner.id);
store.sqlite.prepare('UPDATE admin_sessions SET revoked_at=? WHERE token_hash=?').run(now, identity.sessionHash);
await assert.rejects(readAdminOperations(store.database, identity, now), denied);
} finally { store.sqlite.close(); }
});