Add moderation permissions and durable player records
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
||||
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
||||
import { moderationGuid } from '../../src/lib/moderation.js';
|
||||
|
||||
let fixture: Fixture;
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
fixture = await newFixture();
|
||||
});
|
||||
afterEach(async () => fixture.cleanup());
|
||||
|
||||
it('keeps large identities exact and normalizes only known representations', () => {
|
||||
expect(moderationGuid('t6', '110000100000001', 'rcon')).toBe('76561197960265729');
|
||||
expect(moderationGuid('t6', '076561197960265729')).toBe('76561197960265729');
|
||||
expect(moderationGuid('t5', 'DEADbeef', 'rcon')).toBe('DEADbeef');
|
||||
expect(moderationGuid('t5', 'Some:opaque_ID')).toBe('Some:opaque_ID');
|
||||
});
|
||||
|
||||
it('preserves aliases and moderation identity independently of gameplay retention', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const { observeModerationPlayers } = await import('../../src/lib/server/moderation/core.js');
|
||||
const handle = db();
|
||||
observeModerationPlayers(
|
||||
handle,
|
||||
't6',
|
||||
[{ guid: '000123', name: 'Old name' }],
|
||||
'2026-10-01T00:00:00.000Z'
|
||||
);
|
||||
observeModerationPlayers(
|
||||
handle,
|
||||
't6',
|
||||
[{ guid: '123', name: 'New name' }],
|
||||
'2026-10-02T00:00:00.000Z'
|
||||
);
|
||||
observeModerationPlayers(
|
||||
handle,
|
||||
't6',
|
||||
[{ guid: '123', name: 'Delayed name' }],
|
||||
'2026-09-30T00:00:00.000Z'
|
||||
);
|
||||
handle.prepare('DELETE FROM leaderboard_players').run();
|
||||
expect(handle.prepare('SELECT * FROM moderation_players').get()).toMatchObject({
|
||||
game: 't6',
|
||||
guid: '123',
|
||||
name: 'New name',
|
||||
first_seen_at: '2026-09-30T00:00:00.000Z',
|
||||
last_seen_at: '2026-10-02T00:00:00.000Z'
|
||||
});
|
||||
expect(handle.prepare('SELECT name FROM moderation_aliases ORDER BY name').all()).toEqual([
|
||||
{ name: 'Delayed name' },
|
||||
{ name: 'New name' },
|
||||
{ name: 'Old name' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('records mutation results atomically and rejects reused keys or revoked access', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const { moderationMutation, moderationEvent } =
|
||||
await import('../../src/lib/server/moderation/core.js');
|
||||
const handle = db();
|
||||
handle
|
||||
.prepare(
|
||||
`INSERT INTO auth_users(id,username,display_name,password_hash,role,created_at,updated_at)
|
||||
VALUES('moderator','moderator','Moderator','unused','moderator',0,0)`
|
||||
)
|
||||
.run();
|
||||
const body = { op: 'note', idempotencyKey: 'request-1', text: 'A note' };
|
||||
const work = vi.fn((actor, now) => {
|
||||
moderationEvent(actor, 'note', { game: 't5', guid: '123' }, null, 'A note', now);
|
||||
return { id: 'one' };
|
||||
});
|
||||
expect(moderationMutation('moderator', body, work)).toEqual({ ok: true, id: 'one' });
|
||||
expect(moderationMutation('moderator', body, work)).toEqual({ ok: true, id: 'one' });
|
||||
expect(work).toHaveBeenCalledTimes(1);
|
||||
expect(() => moderationMutation('moderator', { ...body, text: 'Changed' }, work)).toThrow(
|
||||
'already used'
|
||||
);
|
||||
expect(() =>
|
||||
moderationMutation('moderator', { ...body, idempotencyKey: 'failing' }, (actor, now) => {
|
||||
moderationEvent(actor, 'note', {}, null, 'Rolled back', now);
|
||||
throw new Error('Failed');
|
||||
})
|
||||
).toThrow('Failed');
|
||||
expect(handle.prepare('SELECT COUNT(*) AS count FROM moderation_events').get()).toEqual({
|
||||
count: 1
|
||||
});
|
||||
handle.prepare("UPDATE auth_users SET role='viewer' WHERE id='moderator'").run();
|
||||
expect(() => moderationMutation('moderator', body, work)).toThrow('Moderation access');
|
||||
});
|
||||
Reference in New Issue
Block a user