Queue server creation and verify health and selected map before completion
This commit is contained in:
@@ -13,7 +13,11 @@ vi.mock('../../src/lib/server/integrations/komodo.js', () => ({
|
||||
komodoEnsureTag: vi.fn(async (name: string) => ({ ok: true, id: name })),
|
||||
komodoUpdateTagsOnResource: vi.fn(async () => ({ ok: true }))
|
||||
}));
|
||||
vi.mock('../../src/lib/server/integrations/rcon.js', () => ({ rconGetPlayerCount: vi.fn(async () => 0) }));
|
||||
vi.mock('../../src/lib/server/integrations/rcon.js', () => ({
|
||||
rconGetPlayerCount: vi.fn(async () => 0),
|
||||
rconStatusResult: vi.fn(async () => ({ ok: true, endpoint: { host: 'localhost', port: 27016 }, rawReply: '',
|
||||
status: { players: 0, map: 'zombie_cosmodrome', gametype: null, roster: [] } }))
|
||||
}));
|
||||
vi.mock('../../src/lib/server/pipeline/worker.js', () => ({ kick: vi.fn(async () => {}) }));
|
||||
|
||||
let fixture: Fixture;
|
||||
@@ -54,6 +58,62 @@ async function promotedConfig() {
|
||||
}
|
||||
|
||||
describe('server creation map selection', () => {
|
||||
it('returns a queued creation before provisioning and reserves its name and port', async () => {
|
||||
const { POST } = await import('../../src/routes/api/servers/+server.js');
|
||||
const { queueCreateServer, processCreations } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { getDeployment, interruptDeployments } = await import('../../src/lib/server/fleet/deployments.js');
|
||||
const { komodoCreateDeployment, komodoInspectHealthy } = await import('../../src/lib/server/integrations/komodo.js');
|
||||
const { closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
const response = await POST({ request: new Request('http://localhost/api/servers', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
||||
}) });
|
||||
expect(response.status).toBe(202);
|
||||
const body = await response.json();
|
||||
expect(body).toMatchObject({ name: serverName, deploymentId: expect.any(String) });
|
||||
expect(komodoCreateDeployment).not.toHaveBeenCalled();
|
||||
expect(getDeployment(body.deploymentId)?.deployment).toMatchObject({ state: 'queued', expected_map: 'zombie_cosmodrome' });
|
||||
expect(queueCreateServer({ ...input, expectedName: serverName })).toMatchObject({ ok: false, kind: 'no-slot' });
|
||||
const second = queueCreateServer(input);
|
||||
expect(second).toMatchObject({ ok: true, name: 't5_zombie_cosmodrome_2' });
|
||||
if (second.ok) expect(second.port).not.toBe(body.port);
|
||||
closeDb();
|
||||
interruptDeployments();
|
||||
expect(getDeployment(body.deploymentId)?.deployment).toMatchObject({ state: 'queued' });
|
||||
vi.mocked(komodoInspectHealthy).mockResolvedValueOnce({ ok: false, reason: 'starting' }).mockResolvedValueOnce({ ok: false, reason: 'starting' });
|
||||
await Promise.all([processCreations(), processCreations()]);
|
||||
expect(komodoCreateDeployment).toHaveBeenCalledTimes(2);
|
||||
expect(getDeployment(body.deploymentId)?.deployment).toMatchObject({ state: 'awaiting-health', finished_at: null });
|
||||
});
|
||||
|
||||
it('completes creation only when Komodo, RCON, and the selected map all pass', async () => {
|
||||
const { createServer, processCreations } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { getDeployment, interruptDeployments } = await import('../../src/lib/server/fleet/deployments.js');
|
||||
const { komodoInspectHealthy } = await import('../../src/lib/server/integrations/komodo.js');
|
||||
const { rconStatusResult } = await import('../../src/lib/server/integrations/rcon.js');
|
||||
const created = await createServer(input);
|
||||
if (!created.ok) throw new Error('fixture creation failed');
|
||||
const id = created.deploymentId;
|
||||
expect(getDeployment(id)?.deployment).toMatchObject({ state: 'awaiting-health', finished_at: null });
|
||||
vi.mocked(komodoInspectHealthy).mockResolvedValueOnce({ ok: false, reason: 'starting' });
|
||||
await processCreations();
|
||||
expect(rconStatusResult).not.toHaveBeenCalled();
|
||||
vi.mocked(komodoInspectHealthy).mockResolvedValueOnce({ ok: true, state: 'running' });
|
||||
vi.mocked(rconStatusResult).mockResolvedValueOnce({ ok: false, endpoint: null, reason: 'timeout', detail: 'not ready' });
|
||||
await processCreations();
|
||||
expect(getDeployment(id)?.deployment).toMatchObject({ state: 'awaiting-health', stage: 'Waiting for RCON health: timeout' });
|
||||
for (const map of [null, 'zombie_moon']) {
|
||||
vi.mocked(komodoInspectHealthy).mockResolvedValueOnce({ ok: true, state: 'running' });
|
||||
vi.mocked(rconStatusResult).mockResolvedValueOnce({ ok: true, endpoint: { host: 'localhost', port: created.port }, rawReply: '', status: { players: 0, map, gametype: null, roster: [] } });
|
||||
await processCreations();
|
||||
expect(getDeployment(id)?.deployment).toMatchObject({ state: 'awaiting-health', finished_at: null, stage: expect.stringContaining('expected zombie_cosmodrome') });
|
||||
}
|
||||
interruptDeployments();
|
||||
expect(getDeployment(id)?.deployment).toMatchObject({ state: 'awaiting-health' });
|
||||
vi.mocked(komodoInspectHealthy).mockResolvedValueOnce({ ok: true, state: 'running' });
|
||||
await processCreations();
|
||||
expect(getDeployment(id)?.deployment).toMatchObject({ state: 'done', finished_at: expect.any(String), stage: 'Verified Komodo healthy, RCON healthy, and map zombie_cosmodrome' });
|
||||
});
|
||||
|
||||
it('returns a deployment URL identifier immediately and serves its lifecycle detail', async () => {
|
||||
const { createServer } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { DELETE } = await import('../../src/routes/api/servers/[name]/+server.js');
|
||||
@@ -116,7 +176,7 @@ describe('server creation map selection', () => {
|
||||
});
|
||||
|
||||
it('records in-flight creation and retains both operations after destruction', async () => {
|
||||
const { createServer, destroyServer, processDestructions } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { createServer, destroyServer, processDestructions, processCreations } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { listDeployments } = await import('../../src/lib/server/fleet/deployments.js');
|
||||
const { komodoDeploy, komodoInspectHealthy, komodoDestroyDeployment } = await import('../../src/lib/server/integrations/komodo.js');
|
||||
vi.mocked(komodoDeploy).mockImplementationOnce(async () => {
|
||||
@@ -124,6 +184,8 @@ describe('server creation map selection', () => {
|
||||
return { ok: true };
|
||||
});
|
||||
expect((await createServer(input)).ok).toBe(true);
|
||||
vi.mocked(komodoInspectHealthy).mockResolvedValueOnce({ ok: true, state: 'running' });
|
||||
await processCreations();
|
||||
vi.mocked(komodoInspectHealthy).mockResolvedValueOnce({ ok: false, reason: 'gone' });
|
||||
vi.mocked(komodoDestroyDeployment).mockImplementationOnce(async () => {
|
||||
expect(listDeployments()).toContainEqual(expect.objectContaining({ action: 'destroy', state: 'running' }));
|
||||
|
||||
Reference in New Issue
Block a user