Refresh script library with batch uploads and beta handoff
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { betaSelectionHref, canTest, mapLabel, parseBetaSelection, scriptStatus, type LibraryScript } from '../../src/lib/script-library.js';
|
||||
|
||||
const script: LibraryScript = {
|
||||
id: 1, name: 'HUD', target_rel_path: 'hud.gsc', scope_type: null, scope_value: null,
|
||||
current_version_id: null, currentVersion: null, versionCount: 1, servers: [], runningBetaId: null,
|
||||
latestVersion: { id: 10, version: 1, sha256: 'abc', size: 12, state: 'untested', uploaded_at: '2026-09-18' }
|
||||
};
|
||||
|
||||
describe('script library state', () => {
|
||||
it.each([['untested', 'untested'], ['tested', 'tested'], ['test-failed', 'failed']] as const)(
|
||||
'preserves %s for a never-promoted version', (state, expected) => {
|
||||
expect(scriptStatus({ ...script, latestVersion: { ...script.latestVersion!, state } })).toBe(expected);
|
||||
});
|
||||
it('distinguishes an active beta, a promoted release, and a newer upload', () => {
|
||||
expect(scriptStatus({ ...script, runningBetaId: 5 })).toBe('testing');
|
||||
expect(canTest({ ...script, runningBetaId: 5 })).toBe(false);
|
||||
expect(scriptStatus({ ...script, current_version_id: 10 })).toBe('promoted');
|
||||
expect(canTest({ ...script, current_version_id: 10 })).toBe(false);
|
||||
expect(scriptStatus({ ...script, current_version_id: 9 })).toBe('untested');
|
||||
expect(canTest({ ...script, current_version_id: 9 })).toBe(true);
|
||||
expect(scriptStatus({ ...script, latestVersion: null })).toBe('empty');
|
||||
});
|
||||
it('does not describe shared T6 engine maps as a single variant', () => {
|
||||
expect(mapLabel('zombie_moon')).toBe('Moon');
|
||||
expect(mapLabel('zm_transit')).toMatch(/variants/);
|
||||
expect(mapLabel('custom_map')).toBe('custom_map');
|
||||
});
|
||||
});
|
||||
|
||||
describe('beta selection links', () => {
|
||||
it('round-trips exact versions in a multi-script selection', () => {
|
||||
const picks = [{ scriptId: 4, versionId: 20 }, { scriptId: 7, versionId: 25 }];
|
||||
expect(parseBetaSelection(new URL(betaSelectionHref(picks), 'http://localhost').searchParams)).toEqual(picks);
|
||||
});
|
||||
it('preserves existing plugin import links', () => {
|
||||
expect(parseBetaSelection(new URLSearchParams('pluginId=8&versionId=32'))).toEqual([{ scriptId: 8, versionId: 32 }]);
|
||||
});
|
||||
it.each(['artifact=0:1', 'artifact=1:NaN', 'artifact=1:2junk', 'artifact=1:2&artifact=1:3', 'artifact=9007199254740992:1', 'pluginId=8'])(
|
||||
'rejects malformed or ambiguous selections: %s', query => {
|
||||
expect(() => parseBetaSelection(new URLSearchParams(query))).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
||||
|
||||
let fixture: Fixture;
|
||||
beforeEach(async () => {
|
||||
vi.resetModules(); fixture = await newFixture();
|
||||
delete process.env.KOMODO_ADDRESS; delete process.env.RCON_PASSWORD;
|
||||
});
|
||||
afterEach(async () => { await fixture.cleanup(); });
|
||||
function request(content: string, reuse = false) {
|
||||
const form = new FormData(); form.append('content', new File([content], 'hud.gsc'));
|
||||
if (reuse) form.append('reuseExisting', 'true');
|
||||
return new Request('http://localhost/api/scripts/inspect', { method: 'POST', body: form });
|
||||
}
|
||||
async function seed() {
|
||||
const mod = await import('../../src/lib/server/fleet/scripts.js');
|
||||
return { ...mod, ...mod.createScript({ name: 'HUD', targetRelPath: 'hud.gsc', scopeType: 'game', scopeValue: 't5', content: Buffer.from('main(){}') }) };
|
||||
}
|
||||
|
||||
describe('script upload review', () => {
|
||||
it('inspects content against all versions without creating records or including plugins', async () => {
|
||||
const { script, version, uploadNewVersion, createScript, listVersions } = await seed();
|
||||
uploadNewVersion(script.id, Buffer.from('changed(){}'));
|
||||
createScript({ name: 'Plugin', targetRelPath: 'plugin.dll', kind: 'plugin', content: Buffer.from('main(){}') });
|
||||
const { POST } = await import('../../src/routes/api/scripts/inspect/+server.js');
|
||||
const res = await POST({ request: request('main(){}') });
|
||||
expect(await res.json()).toEqual({ sha256: version.sha256, duplicates: [{ scriptId: script.id, versionId: version.id, version: 1 }] });
|
||||
expect(listVersions(script.id)).toHaveLength(2);
|
||||
expect((await POST({ request: request('') })).status).toBe(400);
|
||||
});
|
||||
it('reuses identical historical content and creates changed versions while preserving scope', async () => {
|
||||
const { script, version, uploadNewVersion, listVersions, getScript } = await seed();
|
||||
uploadNewVersion(script.id, Buffer.from('changed(){}'));
|
||||
const { POST } = await import('../../src/routes/api/scripts/[id]/versions/+server.js');
|
||||
const invoke = (content: string) => POST({ params: { id: String(script.id) }, request: request(content, true) });
|
||||
const res = await invoke('main(){}');
|
||||
expect(await res.json()).toMatchObject({ ok: true, reused: true, version: { id: version.id, version: 1 } });
|
||||
expect(listVersions(script.id)).toHaveLength(2);
|
||||
const added = await invoke('third(){}');
|
||||
expect(await added.json()).toMatchObject({ ok: true, version: { version: 3 } });
|
||||
expect(listVersions(script.id)).toHaveLength(3);
|
||||
expect(getScript(script.id)).toMatchObject({ scope_type: 'game', scope_value: 't5' });
|
||||
expect((await invoke('')).status).toBe(400);
|
||||
expect(listVersions(script.id)).toHaveLength(3);
|
||||
});
|
||||
it('rejects new versions when a beta becomes active after the upload preview', async () => {
|
||||
const { script, version, listVersions } = await seed();
|
||||
const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js');
|
||||
await seedServerRegistry();
|
||||
const { openBeta } = await import('../../src/lib/server/fleet/beta.js');
|
||||
await openBeta({ scripts: [{ scriptId: script.id, scriptVersionId: version.id }], servers: ['t5_zombie_theater_1'], waitForHealth: false, waitForMatch: false });
|
||||
const { POST } = await import('../../src/routes/api/scripts/[id]/versions/+server.js');
|
||||
const res = await POST({ params: { id: String(script.id) }, request: request('changed(){}', true) });
|
||||
expect(res.status).toBe(409);
|
||||
expect(listVersions(script.id)).toHaveLength(1);
|
||||
});
|
||||
it('lists promoted and latest version metadata separately without file contents', async () => {
|
||||
const { script, version, uploadNewVersion } = await seed();
|
||||
const latest = uploadNewVersion(script.id, Buffer.from('changed(){}'));
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
db().prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?').run(version.id, script.id);
|
||||
const { GET } = await import('../../src/routes/api/scripts/+server.js');
|
||||
const body = await (await GET({ url: new URL('http://localhost/api/scripts?kind=script') })).json();
|
||||
expect(body.scripts[0]).toMatchObject({ currentVersion: { id: version.id, version: 1 }, latestVersion: { id: latest.id, version: 2 } });
|
||||
expect(body.scripts[0].currentVersion).not.toHaveProperty('content');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user