107 lines
4.3 KiB
TypeScript
107 lines
4.3 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: [] });
|
|
});
|
|
});
|