import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { queueCreateServer, destroyServer, requestLifecycle } from '../../src/lib/server/provisioning/index.js'; import { kick, kickProvisioning } from '../../src/lib/server/pipeline/worker.js'; import { POST as create } from '../../src/routes/api/servers/+server.js'; import { DELETE as destroy } from '../../src/routes/api/servers/[name]/+server.js'; import { POST as lifecycle } from '../../src/routes/api/provisioning/+server.js'; vi.mock('../../src/lib/server/provisioning/index.js', () => ({ queueCreateServer: vi.fn(), destroyServer: vi.fn(), requestLifecycle: vi.fn() })); vi.mock('../../src/lib/server/pipeline/worker.js', () => ({ kick: vi.fn(async () => {}), kickProvisioning: vi.fn(async () => {}) })); vi.mock('../../src/lib/server/fleet/snapshot.js', () => ({ fleetSnapshot: vi.fn(), serverSnapshot: vi.fn() })); vi.mock('../../src/lib/server/provisioning/history.js', () => ({ deploymentListPage: vi.fn() })); beforeEach(() => { vi.clearAllMocks(); vi.useFakeTimers({ toFake: ['setImmediate'] }); }); afterEach(() => vi.useRealTimers()); const request = (body: unknown) => new Request('http://localhost/api/servers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const routes = [ { name: 'create', queue: queueCreateServer, invoke: () => create({ request: request({ game: 't5', gameMode: 'zombie', mapSlug: 'theater' }) }) }, { name: 'destroy', queue: destroyServer, invoke: () => destroy({ params: { name: 'server' } }) }, { name: 'restart', queue: requestLifecycle, invoke: () => lifecycle({ request: request({ server: 'server', action: 'restart' }) }) } ]; it.each(routes)( 'returns the queued $name job before starting provisioning or unrelated observers', async ({ queue, invoke }) => { vi.mocked(queue).mockReturnValue({ ok: true, name: 'server', port: 28960, deploymentId: 'job', warnings: [] }); const response = await invoke(); expect(response.status).toBe(202); expect(await response.json()).toMatchObject({ ok: true, deploymentId: 'job' }); expect(queue).toHaveBeenCalledTimes(1); expect(kickProvisioning).not.toHaveBeenCalled(); expect(kick).not.toHaveBeenCalled(); await vi.runAllTimersAsync(); expect(kickProvisioning).toHaveBeenCalledTimes(1); expect(kick).not.toHaveBeenCalled(); } ); it.each(routes)('does not wake workers when $name admission fails', async ({ queue, invoke }) => { vi.mocked(queue).mockReturnValue({ ok: false, kind: 'conflict', message: 'Busy', error: 'Busy', status: 409, warnings: [] }); const response = await invoke(); expect(response.status).toBe(409); expect(await response.json()).toMatchObject({ error: 'Busy' }); await vi.runAllTimersAsync(); expect(kickProvisioning).not.toHaveBeenCalled(); expect(kick).not.toHaveBeenCalled(); });