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.RCON_PASSWORD; }); afterEach(async () => { await fixture.cleanup(); }); const plugin = { name: 't6-gsc-utils', kind: 'plugin' as const, targetRelPath: 't6-gsc-utils.dll', content: Buffer.from([0, 1, 2, 3]) }; describe('plugin release versions', () => { it('keeps release tags independent of upload order and preserves binary identity', async () => { const { createScript, uploadNewVersion, listVersions, getVersion } = await import('../../src/lib/server/fleet/scripts.js'); const { script, version } = createScript({ ...plugin, releaseVersion: ' v3.1.0 ' }); expect(version).toMatchObject({ version: 1, release_version: 'v3.1.0' }); const older = uploadNewVersion(script.id, Buffer.from([4, 5]), 'Known good release', 'v3.0.4'); const rebuild = uploadNewVersion(script.id, Buffer.from([6, 7]), 'Rebuilt', 'v3.0.4'); expect(listVersions(script.id).map(v => [v.version, v.release_version])).toEqual([ [3, 'v3.0.4'], [2, 'v3.0.4'], [1, 'v3.1.0'] ]); expect(older.sha256).not.toBe(rebuild.sha256); expect((await import('../../src/lib/server/fleet/scripts.js')).getVersionContent(version.id)).toEqual(plugin.content); }); it('allows unlabeled uploads and non-semver release tags', async () => { const { createScript, uploadNewVersion } = await import('../../src/lib/server/fleet/scripts.js'); const { script, version } = createScript(plugin); expect(version.release_version).toBeNull(); expect(uploadNewVersion(script.id, plugin.content, undefined, ' ').release_version).toBeNull(); expect(uploadNewVersion(script.id, plugin.content, undefined, '2026.09-rc1').release_version).toBe('2026.09-rc1'); }); it('rejects invalid labels before storing an artifact or new upload', async () => { const { createScript, uploadNewVersion, listScripts, listVersions } = await import('../../src/lib/server/fleet/scripts.js'); expect(() => createScript({ ...plugin, releaseVersion: 'x'.repeat(81) })).toThrow(/release version/); expect(listScripts()).toHaveLength(0); const { script } = createScript(plugin); expect(() => uploadNewVersion(script.id, plugin.content, undefined, 'v3\n.1')).toThrow(/release version/); expect(listVersions(script.id)).toHaveLength(1); }); it('migrates an existing database without changing its stored versions', async () => { const { createScript, getVersion } = await import('../../src/lib/server/fleet/scripts.js'); const { db, closeDb } = await import('../../src/lib/server/db/index.js'); const { version } = createScript(plugin); db().exec('ALTER TABLE script_versions DROP COLUMN release_version'); closeDb(); expect(getVersion(version.id)).toMatchObject({ id: version.id, version: 1, release_version: null, sha256: version.sha256 }); expect((await import('../../src/lib/server/fleet/scripts.js')).getVersionContent(version.id)).toEqual(plugin.content); closeDb(); expect(getVersion(version.id)!.release_version).toBeNull(); }); it('round trips release versions through plugin creation, version upload, list and detail APIs', async () => { const plugins = await import('../../src/routes/api/plugins/+server.js'); const detail = await import('../../src/routes/api/scripts/[id]/+server.js'); const uploads = await import('../../src/routes/api/scripts/[id]/versions/+server.js'); const url = new URL('http://localhost/api/plugins'); const form = new FormData(); form.set('name', plugin.name); form.set('releaseVersion', 'v3.1.0'); form.set('content', new File([plugin.content], plugin.targetRelPath)); const created = await plugins.POST({ url, request: new Request(url, { method: 'POST', body: form }) }); expect(created.status).toBe(200); const { script } = await created.json(); const listed = await (await plugins.GET({ url })).json(); expect(listed.scripts[0].latestVersion.release_version).toBe('v3.1.0'); const next = new FormData(); next.set('releaseVersion', 'v3.1.1'); next.set('content', new File([new Uint8Array([4, 5])], plugin.targetRelPath)); const uploaded = await uploads.POST({ params: { id: String(script.id) }, request: new Request(url, { method: 'POST', body: next }) }); expect(uploaded.status).toBe(200); expect((await uploaded.json()).version).toMatchObject({ version: 2, release_version: 'v3.1.1' }); const result = await (await detail.GET({ params: { id: String(script.id) } })).json(); expect(result.versions.map((v: { release_version: string }) => v.release_version)).toEqual(['v3.1.1', 'v3.1.0']); expect(result.versions[0]).not.toHaveProperty('content'); }); });