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([]); }); afterEach(async () => { await fixture.cleanup(); vi.unstubAllEnvs(); }); function request(method: string, body: unknown) { return { request: new Request('http://localhost/api/server-keys', { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) }; } describe('game key pool API', () => { it('returns pool metadata and assigned servers without credentials', async () => { const route = await import('../../src/routes/api/server-keys/+server.js'); const { reserveServerKey } = await import('../../src/lib/server/fleet/server-keys.js'); const saved = await route.PUT( request('PUT', { game: 't5', label: 'Cherry hostname 5', plutoKey: 'private-t5-credential' }) ); expect(saved.status).toBe(200); const { id } = await saved.json(); reserveServerKey('t5_zombie_theater_2', 't5'); const response = route.GET(); expect(response.headers.get('cache-control')).toBe('no-store'); const text = await response.text(); expect(text).not.toContain('private-t5-credential'); expect(text).not.toContain('pluto_key'); expect(text).not.toContain('plutoKey'); expect(JSON.parse(text)).toEqual({ keys: [ { id, game: 't5', label: 'Cherry hostname 5', assigned_servers: ['t5_zombie_theater_2'], updated_at: expect.any(String) } ] }); }); it.each([ { game: 't4', plutoKey: 'private-key' }, { serverId: 't5_zombie_theater_2', plutoKey: 'private-key' }, { game: 't5', plutoKey: 'contains whitespace' }, { game: 't5', plutoKey: 'private-key', label: 'x'.repeat(161) } ])('rejects invalid pool input without saving it: %j', async (body) => { const route = await import('../../src/routes/api/server-keys/+server.js'); const response = await route.PUT(request('PUT', body)); expect(response.status).toBe(400); expect(await response.json()).toMatchObject({ ok: false, error: expect.any(String) }); expect(await route.GET().json()).toEqual({ keys: [] }); }); it('deduplicates a credential in its game pool', async () => { const route = await import('../../src/routes/api/server-keys/+server.js'); const first = await route.PUT( request('PUT', { game: 't6', plutoKey: 'private-key', label: 'First label' }) ); const repeated = await route.PUT( request('PUT', { game: 't6', plutoKey: ' private-key ', label: 'Updated label' }) ); expect(await repeated.json()).toEqual(await first.json()); expect((await route.GET().json()).keys).toEqual([ expect.objectContaining({ game: 't6', label: 'Updated label', assigned_servers: [] }) ]); }); it('rejects removal while assigned and permits it after the key is released', async () => { const route = await import('../../src/routes/api/server-keys/+server.js'); const { reserveServerKey, releaseServerKey, getServerKey } = await import('../../src/lib/server/fleet/server-keys.js'); const saved = await route.PUT(request('PUT', { game: 't5', plutoKey: 'private-key' })); const { id } = await saved.json(); reserveServerKey('t5_zombie_moon_1', 't5'); const blocked = await route.DELETE(request('DELETE', { id })); expect(blocked.status).toBe(400); expect(await blocked.json()).toMatchObject({ ok: false, error: expect.stringMatching(/reserved|assigned|in use/i) }); expect(getServerKey('t5_zombie_moon_1')).toBe('private-key'); releaseServerKey('t5_zombie_moon_1'); expect(await (await route.DELETE(request('DELETE', { id }))).json()).toEqual({ ok: true }); expect(await route.GET().json()).toEqual({ keys: [] }); }); });