Add player moderation workflows and game integration
CI / checks (push) Successful in 7m27s

This commit is contained in:
2026-10-15 21:33:50 -04:00
parent c91d49c298
commit 8b372fa2c1
50 changed files with 4744 additions and 354 deletions
+145
View File
@@ -0,0 +1,145 @@
import { randomUUID } from 'node:crypto';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { newFixture, type Fixture } from '../helpers/fixture.js';
let fixture: Fixture;
let actorId: string;
let hostId: string;
let instanceId: string;
const server = 't5_zombie_theater_1';
beforeEach(async () => {
vi.resetModules();
vi.stubEnv('EC_PUBLIC_URL', 'https://public-controller.example');
fixture = await newFixture([]);
const hosts = await import('../../src/lib/server/hosts/index.js');
const { db } = await import('../../src/lib/server/db/index.js');
hostId = hosts.createHost({ name: 'Moderation host' }).id;
hosts.enrollHost(hosts.createEnrollment(hostId).token, {
capabilities: ['moderation.configure']
});
actorId = randomUUID();
instanceId = randomUUID();
db()
.prepare(
`INSERT INTO auth_users(id,username,display_name,password_hash,role,created_at,updated_at) VALUES(?,'admin','Admin','unused','admin',1,1)`
)
.run(actorId);
db()
.prepare(
`INSERT INTO native_instances(id,server,host_id,generation,library_id,image,port,created_at) VALUES(?,?,?,1,'library','image',28960,?)`
)
.run(instanceId, server, hostId, new Date().toISOString());
db().prepare(`INSERT INTO servers(name,game) VALUES(?,'t5')`).run(server);
});
afterEach(async () => {
await fixture.cleanup();
vi.unstubAllEnvs();
});
describe('moderation runtime configuration', () => {
it('protects the fixed private file even with customized shared config destinations', async () => {
const { config } = await import('../../src/lib/server/config.js');
(config as { relDestConfig: string }).relDestConfig = 'custom-configs';
const { isPrivateConfigPath } =
await import('../../src/lib/server/fleet/private-config.js');
expect(isPrivateConfigPath('configs/electric_cherry_moderation.cfg')).toBe(true);
expect(isPrivateConfigPath('custom-configs/electric_cherry_moderation.cfg')).toBe(true);
expect(isPrivateConfigPath('custom-configs/nested/electric_cherry_moderation.cfg')).toBe(
false
);
});
it('queues private configuration and returns only an operation identifier', async () => {
const { configureModerationServer } =
await import('../../src/lib/server/moderation/configure.js');
const hosts = await import('../../src/lib/server/hosts/index.js');
const { moderationCredential } =
await import('../../src/lib/server/moderation/credentials.js');
const result = configureModerationServer(actorId, server);
const credential = moderationCredential(instanceId, 1);
expect(Object.keys(result)).toEqual(['id']);
const operation = hosts.getHostOperation(result.id)!;
expect(operation).toMatchObject({
kind: 'instance.configure-moderation',
instanceId,
generation: 1,
payload: {
moderation: {
token: credential,
configPath: 'configs/electric_cherry_moderation.cfg',
url: 'https://public-controller.example/api/moderation/runtime',
generation: 1
}
}
});
expect(JSON.stringify(result)).not.toContain(credential);
expect(JSON.stringify(hosts.listHostOperationHistory())).not.toContain(credential);
const { db } = await import('../../src/lib/server/db/index.js');
expect(JSON.stringify(db().prepare('SELECT * FROM moderation_events').all())).not.toContain(
credential
);
expect(
db()
.prepare('SELECT count(*) AS n FROM file_blobs WHERE instr(content,?)>0')
.get(Buffer.from(credential))
).toEqual({ n: 0 });
hosts.reportHostOperationStatus(hostId, result.id, {
state: 'completed',
result: {
configured: true,
generation: 1,
liveLoad: 'sent',
text: credential,
contentBase64: Buffer.from(credential).toString('base64')
},
error: `Unexpected echo ${credential}`
});
const receipt = hosts.getHostOperationStatus(result.id)!;
expect(JSON.stringify(receipt)).not.toContain(credential);
expect(receipt.result).toEqual({ configured: true, generation: 1, liveLoad: 'sent' });
});
it.each(['viewer', 'operator', 'moderator', 'super_viewer'])(
'rejects %s configuration requests',
async (role) => {
const { db } = await import('../../src/lib/server/db/index.js');
db().prepare('UPDATE auth_users SET role=? WHERE id=?').run(role, actorId);
const { configureModerationServer } =
await import('../../src/lib/server/moderation/configure.js');
expect(() => configureModerationServer(actorId, server)).toThrow('access');
expect(db().prepare('SELECT COUNT(*) AS n FROM ec_host_operations').get()).toEqual({
n: 0
});
}
);
it('revalidates account state, host capability and instance identity before queueing', async () => {
const { configureModerationServer } =
await import('../../src/lib/server/moderation/configure.js');
const { db } = await import('../../src/lib/server/db/index.js');
db().prepare('UPDATE auth_users SET disabled=1 WHERE id=?').run(actorId);
expect(() => configureModerationServer(actorId, server)).toThrow('access');
db().prepare('UPDATE auth_users SET disabled=0 WHERE id=?').run(actorId);
const hosts = await import('../../src/lib/server/hosts/index.js');
hosts.heartbeat(hostId, { capabilities: [] });
expect(() => configureModerationServer(actorId, server)).toThrow('Update the host agent');
hosts.heartbeat(hostId, { capabilities: ['moderation.configure'] });
db()
.prepare('UPDATE native_instances SET retired_at=? WHERE id=?')
.run(new Date().toISOString(), instanceId);
expect(() => configureModerationServer(actorId, server)).toThrow('active native server');
expect(db().prepare('SELECT COUNT(*) AS n FROM ec_host_operations').get()).toEqual({
n: 0
});
});
it.each([
'',
'http://public-controller.example',
'https://user:password@public-controller.example',
'https://public-controller.example/prefix',
'https://public-controller.example/?token=secret'
])('rejects unsafe controller origin %s', async (url) => {
const { config } = await import('../../src/lib/server/config.js');
(config as { publicUrl: string }).publicUrl = url;
const { moderationRuntimeUrl } =
await import('../../src/lib/server/moderation/configuration.js');
expect(() => moderationRuntimeUrl()).toThrow('EC_PUBLIC_URL');
});
});