211 lines
8.9 KiB
TypeScript
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);
|
|
});
|
|
});
|