Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+202 -78
View File
@@ -5,7 +5,8 @@ import { join } from 'node:path';
import { newFixture, type Fixture } from '../helpers/fixture.js';
vi.mock('../../src/lib/server/integrations/rcon.js', () => ({
rconGetPlayerCount: vi.fn(), rconStatusResult: vi.fn()
rconGetPlayerCount: vi.fn(),
rconStatusResult: vi.fn()
}));
vi.mock('../../src/lib/server/pipeline/worker.js', () => ({ kick: vi.fn(async () => {}) }));
@@ -14,14 +15,19 @@ const otherServer = 't5_zombie_cosmodrome_1';
const server = 't5_zombie_theater_1';
beforeEach(async () => {
vi.resetModules(); vi.clearAllMocks();
vi.resetModules();
vi.clearAllMocks();
fixture = await newFixture([otherServer, server]);
vi.stubEnv('RCON_PASSWORD', 'test');
const { db } = await import('../../src/lib/server/db/index.js');
for (const [index, name] of [otherServer, server].entries()) {
const map = name === server ? 'theater' : 'cosmodrome';
db().prepare(`INSERT INTO servers (name, game, map, game_mode, map_slug, game_port, host_dir)
VALUES (?, 't5', ?, 'zombie', ?, ?, ?)`).run(name, 'zombie_' + map, map, 28960 + index, join(fixture.t5, name));
db()
.prepare(
`INSERT INTO servers (name, game, map, game_mode, map_slug, game_port, host_dir)
VALUES (?, 't5', ?, 'zombie', ?, ?, ?)`
)
.run(name, 'zombie_' + map, map, 28960 + index, join(fixture.t5, name));
}
await prepareNativeHost([otherServer, server]);
const { mockAgentFiles } = await import('../helpers/agent-files.js');
@@ -29,25 +35,50 @@ beforeEach(async () => {
const { rconGetPlayerCount } = await import('../../src/lib/server/integrations/rcon.js');
vi.mocked(rconGetPlayerCount).mockReset().mockResolvedValue(0);
});
afterEach(async () => { await fixture.cleanup(); vi.unstubAllEnvs(); });
afterEach(async () => {
await fixture.cleanup();
vi.unstubAllEnvs();
});
async function attachBeta(state = 'running', outcome?: string, deleted = false) {
const { createScript } = await import('../../src/lib/server/fleet/scripts.js');
const { db } = await import('../../src/lib/server/db/index.js');
const { script, version } = createScript({ name: 'Protected beta', targetRelPath: 'protected.gsc',
scopeType: 'game', scopeValue: 't5', content: Buffer.from('beta content') });
const id = Number(db().prepare(`INSERT INTO betas (script_id, script_version_id, trigger_kind, state)
VALUES (?, ?, 'manual', ?)`).run(script.id, version.id, state).lastInsertRowid);
db().prepare(`INSERT INTO beta_servers (beta_id, server, had_original, stage, server_deleted_at)
VALUES (?, ?, 0, ?, ?)`).run(id, server, outcome === 'failed' ? 'failed' : 'ready', deleted ? '2026-09-01' : null);
if (outcome) db().prepare('INSERT INTO beta_test_plans (beta_id, plan, outcome) VALUES (?, ?, ?)').run(id, '{}', outcome);
const { script, version } = createScript({
name: 'Protected beta',
targetRelPath: 'protected.gsc',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from('beta content')
});
const id = Number(
db()
.prepare(
`INSERT INTO betas (script_id, script_version_id, trigger_kind, state)
VALUES (?, ?, 'manual', ?)`
)
.run(script.id, version.id, state).lastInsertRowid
);
db()
.prepare(
`INSERT INTO beta_servers (beta_id, server, had_original, stage, server_deleted_at)
VALUES (?, ?, 0, ?, ?)`
)
.run(id, server, outcome === 'failed' ? 'failed' : 'ready', deleted ? '2026-09-01' : null);
if (outcome)
db()
.prepare('INSERT INTO beta_test_plans (beta_id, plan, outcome) VALUES (?, ?, ?)')
.run(id, '{}', outcome);
return id;
}
async function expectServerPreserved() {
const { db } = await import('../../src/lib/server/db/index.js');
expect(db().prepare("SELECT id FROM ec_host_operations WHERE kind LIKE 'instance.%'").all()).toEqual([]);
expect(db().prepare('SELECT name FROM servers WHERE name = ?').get(server)).toEqual({ name: server });
expect(
db().prepare("SELECT id FROM ec_host_operations WHERE kind LIKE 'instance.%'").all()
).toEqual([]);
expect(db().prepare('SELECT name FROM servers WHERE name = ?').get(server)).toEqual({
name: server
});
await expect(access(join(fixture.t5, server))).resolves.toBeUndefined();
}
@@ -67,7 +98,9 @@ describe('active beta destruction guard', () => {
expect(result.message).toContain(String(id));
expect(result.message).toMatch(/promot/i);
expect(result.message).toMatch(/roll.?back/i);
expect(db().prepare("SELECT id FROM server_deployments WHERE action = 'destroy'").all()).toEqual([]);
expect(
db().prepare("SELECT id FROM server_deployments WHERE action = 'destroy'").all()
).toEqual([]);
await expectServerPreserved();
});
@@ -77,7 +110,11 @@ describe('active beta destruction guard', () => {
const { kick } = await import('../../src/lib/server/pipeline/worker.js');
const response = await DELETE({ params: { name: server } });
expect(response.status).toBe(409);
expect(await response.json()).toMatchObject({ ok: false, kind: 'conflict', error: expect.stringContaining(String(id)) });
expect(await response.json()).toMatchObject({
ok: false,
kind: 'conflict',
error: expect.stringContaining(String(id))
});
expect(kick).not.toHaveBeenCalled();
await expectServerPreserved();
});
@@ -86,9 +123,13 @@ describe('active beta destruction guard', () => {
const id = await attachBeta();
const { POST } = await import('../../src/routes/api/provisioning/+server.js');
const { db } = await import('../../src/lib/server/db/index.js');
const response = await POST({ request: new Request('http://localhost/api/provisioning', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ server, action: 'rebuild' })
}) });
const response = await POST({
request: new Request('http://localhost/api/provisioning', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ server, action: 'rebuild' })
})
});
expect(response.status).toBe(409);
expect(await response.json()).toMatchObject({ error: expect.stringContaining(String(id)) });
expect(db().prepare('SELECT * FROM server_creation_jobs').all()).toEqual([]);
@@ -100,63 +141,118 @@ describe('active beta destruction guard', () => {
{ state: 'rolled-back', deleted: false },
{ state: 'running', deleted: true },
{ state: 'ready-promote', deleted: true }
])('allows removal for $state with historical deletion $deleted', async ({ state, deleted }) => {
await attachBeta(state, undefined, deleted);
const { destroyServer } = await import('../../src/lib/server/fleet/create.js');
expect(destroyServer(server)).toMatchObject({ ok: true, deploymentId: expect.any(String) });
});
])(
'allows removal for $state with historical deletion $deleted',
async ({ state, deleted }) => {
await attachBeta(state, undefined, deleted);
const { destroyServer } = await import('../../src/lib/server/fleet/create.js');
expect(destroyServer(server)).toMatchObject({
ok: true,
deploymentId: expect.any(String)
});
}
);
it.each(['destroy', 'rebuild'] as const)('requires cancelling a pending %s before opening a beta', async action => {
const { destroyServer } = await import('../../src/lib/server/fleet/create.js');
const { requestLifecycle } = await import('../../src/lib/server/fleet/lifecycle.js');
const { cancelRemoval } = await import('../../src/lib/server/fleet/deployments.js');
const { cancelProvisioning } = await import('../../src/lib/server/provisioning/requests.js');
const { reserveServerKey } = await import('../../src/lib/server/fleet/server-keys.js');
const { createScript } = await import('../../src/lib/server/fleet/scripts.js');
const { openBeta } = await import('../../src/lib/server/fleet/beta.js');
const { db } = await import('../../src/lib/server/db/index.js');
reserveServerKey(server, 't5', 'saved-key');
const queued = action === 'destroy' ? destroyServer(server) : requestLifecycle(server, 'rebuild');
expect(queued.ok).toBe(true);
if (!queued.ok) throw new Error('Expected queued removal');
const { script, version } = createScript({ name: 'Beta admission', targetRelPath: 'admission.gsc',
scopeType: 'game', scopeValue: 't5', content: Buffer.from('beta content') });
const input = { scripts: [{ scriptId: script.id, scriptVersionId: version.id }], servers: [server],
waitForHealth: false, waitForMatch: false };
await expect(openBeta(input)).rejects.toMatchObject({ kind: 'conflict', message: expect.stringMatching(/cancel or finish/i) });
expect(db().prepare('SELECT id FROM betas').all()).toEqual([]);
expect(action === 'destroy' ? cancelRemoval(queued.deploymentId!) : cancelProvisioning(queued.deploymentId!)).toBe(true);
await expect(openBeta(input)).resolves.toMatchObject({ servers: [server], betaId: expect.any(Number) });
await expectServerPreserved();
});
it.each(['destroy', 'rebuild'] as const)(
'requires cancelling a pending %s before opening a beta',
async (action) => {
const { destroyServer } = await import('../../src/lib/server/fleet/create.js');
const { requestLifecycle } = await import('../../src/lib/server/fleet/lifecycle.js');
const { cancelRemoval } = await import('../../src/lib/server/fleet/deployments.js');
const { cancelProvisioning } =
await import('../../src/lib/server/provisioning/requests.js');
const { reserveServerKey } = await import('../../src/lib/server/fleet/server-keys.js');
const { createScript } = await import('../../src/lib/server/fleet/scripts.js');
const { openBeta } = await import('../../src/lib/server/fleet/beta.js');
const { db } = await import('../../src/lib/server/db/index.js');
reserveServerKey(server, 't5', 'saved-key');
const queued =
action === 'destroy' ? destroyServer(server) : requestLifecycle(server, 'rebuild');
expect(queued.ok).toBe(true);
if (!queued.ok) throw new Error('Expected queued removal');
const { script, version } = createScript({
name: 'Beta admission',
targetRelPath: 'admission.gsc',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from('beta content')
});
const input = {
scripts: [{ scriptId: script.id, scriptVersionId: version.id }],
servers: [server],
waitForHealth: false,
waitForMatch: false
};
await expect(openBeta(input)).rejects.toMatchObject({
kind: 'conflict',
message: expect.stringMatching(/cancel or finish/i)
});
expect(db().prepare('SELECT id FROM betas').all()).toEqual([]);
expect(
action === 'destroy'
? cancelRemoval(queued.deploymentId!)
: cancelProvisioning(queued.deploymentId!)
).toBe(true);
await expect(openBeta(input)).resolves.toMatchObject({
servers: [server],
betaId: expect.any(Number)
});
await expectServerPreserved();
}
);
it.each((['destroy', 'rebuild'] as const).flatMap(action => ['failed', 'interrupted'].flatMap(state =>
['Stopping and removing container', 'Removing server files and registry'].map(stage => ({ action, state, stage }))
)))('rejects beta admission after $action is $state at $stage', async ({ action, state, stage }) => {
const { beginDeployment } = await import('../../src/lib/server/fleet/deployments.js');
const { createScript } = await import('../../src/lib/server/fleet/scripts.js');
const { openBeta } = await import('../../src/lib/server/fleet/beta.js');
const { db } = await import('../../src/lib/server/db/index.js');
const operation = beginDeployment(server, action);
db().prepare('UPDATE server_deployments SET state = ?, stage = ? WHERE id = ?').run(state, stage, operation);
const { script, version } = createScript({ name: 'Beta admission', targetRelPath: 'admission.gsc',
scopeType: 'game', scopeValue: 't5', content: Buffer.from('beta content') });
await expect(openBeta({ scripts: [{ scriptId: script.id, scriptVersionId: version.id }], servers: [server],
waitForHealth: false, waitForMatch: false })).rejects.toMatchObject({ kind: 'conflict' });
expect(db().prepare('SELECT id FROM betas').all()).toEqual([]);
expect(db().prepare('SELECT * FROM staged_file_changes').all()).toEqual([]);
await expectServerPreserved();
});
it.each(
(['destroy', 'rebuild'] as const).flatMap((action) =>
['failed', 'interrupted'].flatMap((state) =>
['Stopping and removing container', 'Removing server files and registry'].map(
(stage) => ({ action, state, stage })
)
)
)
)(
'rejects beta admission after $action is $state at $stage',
async ({ action, state, stage }) => {
const { beginDeployment } = await import('../../src/lib/server/fleet/deployments.js');
const { createScript } = await import('../../src/lib/server/fleet/scripts.js');
const { openBeta } = await import('../../src/lib/server/fleet/beta.js');
const { db } = await import('../../src/lib/server/db/index.js');
const operation = beginDeployment(server, action);
db()
.prepare('UPDATE server_deployments SET state = ?, stage = ? WHERE id = ?')
.run(state, stage, operation);
const { script, version } = createScript({
name: 'Beta admission',
targetRelPath: 'admission.gsc',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from('beta content')
});
await expect(
openBeta({
scripts: [{ scriptId: script.id, scriptVersionId: version.id }],
servers: [server],
waitForHealth: false,
waitForMatch: false
})
).rejects.toMatchObject({ kind: 'conflict' });
expect(db().prepare('SELECT id FROM betas').all()).toEqual([]);
expect(db().prepare('SELECT * FROM staged_file_changes').all()).toEqual([]);
await expectServerPreserved();
}
);
});
describe('bulk removal beta protection', () => {
it('rolls back earlier House Party removals when another target has an active beta', async () => {
const id = await attachBeta();
const { housePartyTargets, queueHouseParty } = await import('../../src/lib/server/fleet/house-party.js');
const { housePartyTargets, queueHouseParty } =
await import('../../src/lib/server/fleet/house-party.js');
const { db } = await import('../../src/lib/server/db/index.js');
expect(housePartyTargets()).toEqual([otherServer, server]);
expect(() => queueHouseParty(housePartyTargets())).toThrow(new RegExp(String(id)));
expect(db().prepare("SELECT id FROM server_deployments WHERE action = 'destroy'").all()).toEqual([]);
expect(
db().prepare("SELECT id FROM server_deployments WHERE action = 'destroy'").all()
).toEqual([]);
await expectServerPreserved();
});
@@ -164,12 +260,21 @@ describe('bulk removal beta protection', () => {
const id = await attachBeta();
const { POST } = await import('../../src/routes/api/provisioning/house-party/+server.js');
const { db } = await import('../../src/lib/server/db/index.js');
const response = await POST({ request: new Request('http://localhost/api/provisioning/house-party', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ servers: [otherServer, server] })
}) });
const response = await POST({
request: new Request('http://localhost/api/provisioning/house-party', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ servers: [otherServer, server] })
})
});
expect(response.status).toBe(409);
expect(await response.json()).toMatchObject({ ok: false, error: expect.stringContaining(String(id)) });
expect(db().prepare("SELECT id FROM server_deployments WHERE action = 'destroy'").all()).toEqual([]);
expect(await response.json()).toMatchObject({
ok: false,
error: expect.stringContaining(String(id))
});
expect(
db().prepare("SELECT id FROM server_deployments WHERE action = 'destroy'").all()
).toEqual([]);
await expectServerPreserved();
});
@@ -177,7 +282,10 @@ describe('bulk removal beta protection', () => {
const id = await attachBeta();
const { saveBlueprint } = await import('../../src/lib/server/fleet/launch-recipes.js');
const { previewCollection } = await import('../../src/lib/server/fleet/collections.js');
const blueprint = saveBlueprint({ name: 'Replace fleet', entries: [{ game: 't5', mapSlug: 'moon', count: 1 }] });
const blueprint = saveBlueprint({
name: 'Replace fleet',
entries: [{ game: 't5', mapSlug: 'moon', count: 1 }]
});
const preview = previewCollection(blueprint.id, 'exact');
expect(preview.ready).toBe(false);
expect(preview.blockers).toContainEqual(expect.stringContaining(String(id)));
@@ -187,17 +295,33 @@ describe('bulk removal beta protection', () => {
it('rolls back an exact collection if a beta starts after its preview', async () => {
const { saveBlueprint } = await import('../../src/lib/server/fleet/launch-recipes.js');
const { previewCollection, queueCollection } = await import('../../src/lib/server/fleet/collections.js');
const { previewCollection, queueCollection } =
await import('../../src/lib/server/fleet/collections.js');
const { db } = await import('../../src/lib/server/db/index.js');
const blueprint = saveBlueprint({ name: 'Replace fleet', entries: [{ game: 't5', mapSlug: 'moon', count: 1 }] });
const blueprint = saveBlueprint({
name: 'Replace fleet',
entries: [{ game: 't5', mapSlug: 'moon', count: 1 }]
});
const preview = previewCollection(blueprint.id, 'exact');
expect(preview.removals.map(target => target.name)).toEqual([otherServer, server]);
expect(preview.removals.map((target) => target.name)).toEqual([otherServer, server]);
const id = await attachBeta();
expect(() => queueCollection({ previewId: preview.id, keys: { t5_zombie_moon_1: 'new-key' } })).toThrow(new RegExp(String(id)));
for (const table of ['server_deployments', 'server_launch_collections', 'server_collection_removals', 'server_creation_jobs', 'server_keys']) {
expect(() =>
queueCollection({ previewId: preview.id, keys: { t5_zombie_moon_1: 'new-key' } })
).toThrow(new RegExp(String(id)));
for (const table of [
'server_deployments',
'server_launch_collections',
'server_collection_removals',
'server_creation_jobs',
'server_keys'
]) {
expect(db().prepare(`SELECT * FROM ${table}`).all()).toEqual([]);
}
expect(db().prepare('SELECT collection_id FROM server_collection_previews WHERE id = ?').get(preview.id)).toEqual({ collection_id: null });
expect(
db()
.prepare('SELECT collection_id FROM server_collection_previews WHERE id = ?')
.get(preview.id)
).toEqual({ collection_id: null });
await expectServerPreserved();
});
});