Files
electric-cherry/tests/unit/config-workbench-api.test.ts
2026-10-12 18:46:57 -04:00

211 lines
8.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { newFixture, type Fixture } from '../helpers/fixture.js';
let fixture: Fixture;
const server = 't5_zombie_theater_1';
beforeEach(async () => {
vi.resetModules();
fixture = await newFixture([server]);
const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js');
await seedServerRegistry();
});
afterEach(async () => {
await fixture.cleanup();
});
async function seed() {
const scripts = await import('../../src/lib/server/fleet/scripts.js');
const { db } = await import('../../src/lib/server/db/index.js');
const baseline = scripts.createScript({
name: 'Base',
kind: 'baseline',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from('set g_inactivity 190')
});
db()
.prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?')
.run(baseline.version.id, baseline.script.id);
return { ...scripts, baseline, db };
}
describe('config workbench API', () => {
it('previews a draft without writing versions or changing the promoted config', async () => {
const { createScript, baseline, listVersions, getScript } = await seed();
const overlay = createScript({
name: 'AFK',
kind: 'config',
scopeType: 'map',
scopeValue: 'zombie_theater',
content: Buffer.from('set g_inactivity 0')
});
const { POST, GET } = await import('../../src/routes/api/configs/inspect/+server.js');
const before = await (
await GET({ url: new URL(`http://localhost/api/configs/inspect?server=${server}`) })
).json();
expect(
before.inspection.settings.find((s: { key: string }) => s.key === 'g_inactivity').value
).toBe('190');
const response = await POST({
request: new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({
server,
artifactId: overlay.script.id,
text: 'set g_inactivity 60'
})
})
});
expect(response.status).toBe(200);
const result = await response.json();
expect(
result.inspection.settings.find((s: { key: string }) => s.key === 'g_inactivity')
).toMatchObject({ value: '60', source: { id: overlay.script.id, draft: true } });
expect(
result.promoted.settings.find((s: { key: string }) => s.key === 'g_inactivity').value
).toBe('190');
expect(listVersions(overlay.script.id)).toHaveLength(1);
expect(getScript(baseline.script.id)?.current_version_id).toBe(baseline.version.id);
const { getPromotedServerConfig } =
await import('../../src/lib/server/fleet/server-config.js');
expect(before.inspection.text).toBe(
getPromotedServerConfig('t5', 'zombie_theater')!.toString('utf8')
);
});
it('uses promoted versions even when a newer untested version exists', async () => {
const { baseline, uploadNewVersion } = await seed();
uploadNewVersion(baseline.script.id, Buffer.from('set g_inactivity 0'));
const { GET } = await import('../../src/routes/api/configs/inspect/+server.js');
const result = await (
await GET({ url: new URL(`http://localhost/?server=${server}`) })
).json();
expect(
result.inspection.settings.find((s: { key: string }) => s.key === 'g_inactivity').value
).toBe('190');
expect(result.inspection.sources[0].version).toBe(1);
});
it('handles missing baselines and allows previewing an unpromoted baseline draft', async () => {
const { createScript } = await import('../../src/lib/server/fleet/scripts.js');
const { GET, POST } = await import('../../src/routes/api/configs/inspect/+server.js');
const missing = await (
await GET({ url: new URL(`http://localhost/?server=${server}`) })
).json();
expect(missing.inspection.reason).toMatch(/No promoted baseline/);
const baseline = createScript({
name: 'Base',
kind: 'baseline',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from('set a 1')
});
const response = await POST({
request: new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ server, artifactId: baseline.script.id, text: 'set a 2' })
})
});
expect(
(await response.json()).inspection.settings.find((s: { key: string }) => s.key === 'a')
.value
).toBe('2');
});
it('rejects mismatched scopes and malformed preview requests', async () => {
const { createScript } = await seed();
const config = createScript({
name: 'T6',
kind: 'config',
scopeType: 'game',
scopeValue: 't6',
content: Buffer.from('set a 1')
});
const { POST } = await import('../../src/routes/api/configs/inspect/+server.js');
const response = await POST({
request: new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ server, artifactId: config.script.id, text: 'set a 2' })
})
});
expect(response.status).toBe(400);
expect((await response.json()).error).toMatch(/does not apply/);
expect(
(
await POST({
request: new Request('http://localhost', { method: 'POST', body: '{' })
})
).status
).toBe(400);
});
it('reads only versions owned by the requested config and rejects other artifact kinds', async () => {
const { baseline, createScript } = await seed();
const other = createScript({
name: 'Other',
kind: 'config',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from('set b 1')
});
const script = createScript({
releaseVersion: '1.0.0',
name: 'Script',
targetRelPath: 'test.gsc',
content: Buffer.from('code')
});
const { GET } = await import('../../src/routes/api/configs/[id]/+server.js');
const read = (id: number | string, version?: number) =>
GET({
params: { id: String(id) },
url: new URL(`http://localhost/${version ? `?version=${version}` : ''}`)
});
expect((await (await read(baseline.script.id)).json()).text).toBe('set g_inactivity 190');
expect((await read(baseline.script.id, other.version.id)).status).toBe(404);
expect((await read(script.script.id)).status).toBe(404);
expect((await read('1oops')).status).toBe(400);
});
it('saves editor content as an untested version without promoting it', async () => {
const { baseline, getScript, getLatestVersion } = await seed();
const { POST } = await import('../../src/routes/api/scripts/[id]/versions/+server.js');
const form = new FormData();
form.append('content', new Blob(['set g_inactivity 0']), 'config.cfg');
form.append('notes', 'Disable timeout');
const response = await POST({
params: { id: String(baseline.script.id) },
request: new Request('http://localhost', { method: 'POST', body: form })
});
expect(response.status).toBe(200);
expect(getLatestVersion(baseline.script.id)).toMatchObject({
version: 2,
state: 'untested',
notes: 'Disable timeout'
});
expect(getScript(baseline.script.id)?.current_version_id).toBe(baseline.version.id);
});
it('blocks config saves during an active beta', async () => {
const { baseline, db, listVersions } = await seed();
const beta = db()
.prepare(
"INSERT INTO betas (script_id, script_version_id, trigger_kind, state) VALUES (?, ?, 'manual', 'running')"
)
.run(baseline.script.id, baseline.version.id);
db()
.prepare(
'INSERT INTO beta_scripts (beta_id, script_id, script_version_id) VALUES (?, ?, ?)'
)
.run(beta.lastInsertRowid, baseline.script.id, baseline.version.id);
const { POST } = await import('../../src/routes/api/scripts/[id]/versions/+server.js');
const form = new FormData();
form.append('content', new Blob(['set g_inactivity 0']), 'config.cfg');
const response = await POST({
params: { id: String(baseline.script.id) },
request: new Request('http://localhost', { method: 'POST', body: form })
});
expect(response.status).toBe(409);
expect(listVersions(baseline.script.id)).toHaveLength(1);
});
});