import { prepareNativeHost } from '../helpers/native-host.js'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { newFixture, type Fixture } from '../helpers/fixture.js'; vi.mock('../../src/lib/server/integrations/rcon.js', () => ({ rconGetPlayerCount: vi.fn(async () => 0), rconPlutoniumVersion: vi.fn(async () => ({ revision: 5354, error: null })), rconStatusResult: vi.fn(async () => ({ ok: true, endpoint: { host: 'localhost', port: 28960 }, status: { players: 0, map: 'zombie_theater', gametype: null, roster: [] } })) })); vi.mock('../../src/lib/server/pipeline/worker.js', () => ({ kick: vi.fn(async () => {}), kickProvisioning: vi.fn(async () => {}) })); let fixture: Fixture; const selection = { game: 't5' as const, mapSlug: 'theater' }; const input = { ...selection, gameMode: 'zombie' as const, plutoKey: 'private-pluto-key' }; beforeEach(async () => { vi.resetModules(); vi.clearAllMocks(); fixture = await newFixture([]); await prepareNativeHost(); vi.stubGlobal( 'fetch', vi.fn(async () => Response.json({ product: 'plutonium-core-prod', revision: 5354, files: [] }) ) ); vi.stubEnv('RCON_PASSWORD', 'private-rcon-password'); }); afterEach(async () => { await fixture.cleanup(); vi.unstubAllEnvs(); vi.unstubAllGlobals(); }); async function seedArtifacts() { const { createScript } = await import('../../src/lib/server/fleet/scripts.js'); const { db } = await import('../../src/lib/server/db/index.js'); const baseline = createScript({ name: 'Classic settings', kind: 'baseline', scopeType: 'game', scopeValue: 't5', content: Buffer.from('set sv_voice "1"\n') }); const script = createScript({ name: 'Classic perks', kind: 'script', scopeType: 'game', scopeValue: 't5', targetRelPath: 'perks.gsc', content: Buffer.from('main(){ old_perks(); }') }); for (const item of [baseline, script]) db() .prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?') .run(item.version.id, item.script.id); return { baseline, script }; } describe('launch recipes and previews', () => { it('previews without reserving capacity, saving keys, exposing bytes, or submitting host commands', async () => { await seedArtifacts(); const { previewCreation } = await import('../../src/lib/server/fleet/create.js'); const { db } = await import('../../src/lib/server/db/index.js'); const first = previewCreation(selection); const second = previewCreation(selection); expect(first).toMatchObject({ name: 't5_zombie_theater_1', port: 28960, hasSavedKey: false, ready: true }); expect(second.name).toBe(first.name); expect(second.port).toBe(first.port); expect(first.recipe.files).toHaveLength(3); expect(first.recipe.files[0]).not.toHaveProperty('content'); expect(db().prepare('SELECT * FROM server_keys').all()).toEqual([]); expect(db().prepare('SELECT * FROM server_creation_jobs').all()).toEqual([]); expect(JSON.stringify(first)).not.toContain('private-'); }); it('returns the same operation for repeated submission, even after expiry and restart', async () => { const { previewCreation, queueCreateServer } = await import('../../src/lib/server/fleet/create.js'); const { db, closeDb } = await import('../../src/lib/server/db/index.js'); const { getServerKey } = await import('../../src/lib/server/fleet/server-keys.js'); const preview = previewCreation(selection); const queued = queueCreateServer({ ...input, previewId: preview.id }); expect(queued.ok).toBe(true); db() .prepare( "UPDATE server_launch_previews SET expires_at = '2000-01-01T00:00:00Z' WHERE id = ?" ) .run(preview.id); closeDb(); expect( queueCreateServer({ ...input, previewId: preview.id, plutoKey: 'replacement-key' }) ).toEqual(queued); expect(db().prepare('SELECT * FROM server_creation_jobs').all()).toHaveLength(1); expect(getServerKey(preview.name!)).toBe('private-pluto-key'); }); it('carries preview identity through the public creation API and rejects malformed requests', async () => { const { POST: preview } = await import('../../src/routes/api/deployments/preview/+server.js'); const { POST: launch } = await import('../../src/routes/api/servers/+server.js'); const request = (body: unknown) => ({ request: new Request('http://localhost/api', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) }); for (const body of [null, [], { game: 't7', mapSlug: 'theater' }]) expect((await preview(request(body))).status).toBe(400); const response = await preview(request(selection)); const plan = await response.json(); expect(response.status).toBe(200); expect((await launch(request({ ...input, previewId: '' }))).status).toBe(400); const first = await launch( request({ ...input, previewId: plan.id, expectedName: plan.name }) ); expect(first.status).toBe(202); const retry = await launch( request({ ...input, previewId: plan.id, expectedName: plan.name }) ); expect(await retry.json()).toEqual(await first.json()); }); it('rejects expired, missing, and mismatched previews', async () => { const { previewCreation, queueCreateServer } = await import('../../src/lib/server/fleet/create.js'); const { db } = await import('../../src/lib/server/db/index.js'); const preview = previewCreation(selection); expect( queueCreateServer({ ...input, mapSlug: 'moon', previewId: preview.id }) ).toMatchObject({ ok: false, kind: 'bad-request' }); db() .prepare( "UPDATE server_launch_previews SET expires_at = '2000-01-01T00:00:00Z' WHERE id = ?" ) .run(preview.id); expect(queueCreateServer({ ...input, previewId: preview.id })).toMatchObject({ ok: false, kind: 'bad-request' }); expect(queueCreateServer({ ...input, previewId: 'missing' })).toMatchObject({ ok: false, kind: 'bad-request' }); expect(db().prepare('SELECT * FROM server_keys').all()).toEqual([]); }); it('validates blueprint names, sources, and case-insensitive duplicates', async () => { const { previewCreation } = await import('../../src/lib/server/fleet/create.js'); const { saveBlueprint } = await import('../../src/lib/server/fleet/launch-recipes.js'); const previewId = previewCreation(selection).id; expect(() => saveBlueprint({ name: '', previewId })).toThrow(/name/); expect(() => saveBlueprint({ name: 'Valid', previewId, deploymentId: 'also' })).toThrow( /exactly one/ ); expect(() => saveBlueprint({ name: 'Valid', previewId: 'missing' })).toThrow(/unavailable/); saveBlueprint({ name: 'Classic Night', previewId }); expect(() => saveBlueprint({ name: ' classic night ', previewId })).toThrow( /already exists/ ); }); });