Files
electric-cherry/tests/unit/server-keys-api.test.ts
T

71 lines
3.9 KiB
TypeScript

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: [] });
});
});