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
+164 -25
View File
@@ -1,37 +1,138 @@
import { describe, expect, it } from 'vitest';
import { render } from 'svelte/server';
import PolicyCard from '../../src/lib/ui/components/autoscaling/PolicyCard.svelte';
import { lobbyGroups, lobbyLabel, policyStatus, scalingActivity, observationAge } from '../../src/lib/ui/autoscaling.js';
import {
lobbyGroups,
lobbyLabel,
policyStatus,
scalingActivity,
observationAge
} from '../../src/lib/ui/autoscaling.js';
import { zombieMaps } from '../../src/lib/map-aliases.js';
import type { ScalingHistory, ScalingView } from '../../src/lib/autoscaling.js';
const now = Date.parse('2026-10-07T20:00:00Z');
function view(): ScalingView {
return {
policy: { id: 'policy', game: 't5', mapSlug: '*', mode: 'create-destroy', minimum: 1, maximum: 3, spares: 1, emptyMinutes: 15, revision: 1, pausedReason: null, updatedAt: '' },
snapshot: { members: [
{ name: 't5_zombie_theater_1', mapSlug: 'theater', identity: 'one', state: 'empty', players: 0, observedAt: now, source: 'rcon', emptySince: now, blocker: null, operationId: null },
{ name: 't5_zombie_cosmodrome_1', mapSlug: 'cosmodrome', identity: 'two', state: 'creating', players: null, observedAt: null, source: null, emptySince: null, blocker: 'Provisioning job-1: Checking container readiness', operationId: 'job-1' }
], reservedNames: [], keyPool: { available: 13, total: 15 }, blockers: [] },
decision: { action: 'wait', reason: 'Provisioning job-1: Checking container readiness', server: null, occupied: 0, empty: 1, pending: 1, unknown: 0, deficit: 9, desired: 10 },
unsettled: [{ id: 'job-1', action: 'create', state: 'awaiting-health', stage: 'Checking container readiness' }],
blockers: [], ownsMap: true, history: [], lastEvaluatedAt: now,
policy: {
id: 'policy',
game: 't5',
mapSlug: '*',
mode: 'create-destroy',
minimum: 1,
maximum: 3,
spares: 1,
emptyMinutes: 15,
revision: 1,
pausedReason: null,
updatedAt: ''
},
snapshot: {
members: [
{
name: 't5_zombie_theater_1',
mapSlug: 'theater',
identity: 'one',
state: 'empty',
players: 0,
observedAt: now,
source: 'rcon',
emptySince: now,
blocker: null,
operationId: null
},
{
name: 't5_zombie_cosmodrome_1',
mapSlug: 'cosmodrome',
identity: 'two',
state: 'creating',
players: null,
observedAt: null,
source: null,
emptySince: null,
blocker: 'Provisioning job-1: Checking container readiness',
operationId: 'job-1'
}
],
reservedNames: [],
keyPool: { available: 13, total: 15 },
blockers: []
},
decision: {
action: 'wait',
reason: 'Provisioning job-1: Checking container readiness',
server: null,
occupied: 0,
empty: 1,
pending: 1,
unknown: 0,
deficit: 9,
desired: 10
},
unsettled: [
{
id: 'job-1',
action: 'create',
state: 'awaiting-health',
stage: 'Checking container readiness'
}
],
blockers: [],
ownsMap: true,
history: [],
lastEvaluatedAt: now,
totals: { observedMinutes: 12, emptyLobbyHours: 0.2, deficitMinutes: 5, unknownMinutes: 0 },
operations: { creates: 1, removals: 0, cancelled: 0, failed: 0, readinessSamples: 0, readinessMeanSeconds: null, readinessMaxSeconds: null }
operations: {
creates: 1,
removals: 0,
cancelled: 0,
failed: 0,
readinessSamples: 0,
readinessMeanSeconds: null,
readinessMaxSeconds: null
}
};
}
function body(data = view(), canOperate = true, globallyPaused = false) {
return render(PolicyCard, { props: { view: data, canOperate, globallyPaused, busy: false, onedit: () => {}, ontoggle: () => {}, ondelete: () => {} } }).body;
return render(PolicyCard, {
props: {
view: data,
canOperate,
globallyPaused,
busy: false,
onedit: () => {},
ontoggle: () => {},
ondelete: () => {}
}
}).body;
}
function history(id: string, at: number, action: ScalingHistory['decision']['action'], reason: string, operationId: string | null = null): ScalingHistory {
return { id, at, lastSeenAt: at, decision: { ...view().decision, action, reason, server: action === 'create' ? 't5_zombie_cosmodrome_1' : null }, operationId, operationState: operationId ? 'done' : null };
function history(
id: string,
at: number,
action: ScalingHistory['decision']['action'],
reason: string,
operationId: string | null = null
): ScalingHistory {
return {
id,
at,
lastSeenAt: at,
decision: {
...view().decision,
action,
reason,
server: action === 'create' ? 't5_zombie_cosmodrome_1' : null
},
operationId,
operationState: operationId ? 'done' : null
};
}
describe('autoscaling presentation', () => {
it('shows a provisioning stage once and keeps diagnostic job links without routine deletion warnings', () => {
const html = body();
expect(html.split('Checking container readiness')).toHaveLength(2);
expect(html).toContain('Creating Ascension · Lobby 1');
expect(html.replace(/\s+/g, ' ')).toContain('Creating Ascension · Lobby 1');
expect(html).toContain('href="/provisioning/job-1"');
expect(html).not.toContain('Work is still settling');
expect(html).not.toContain('Provisioning job-1:');
@@ -41,11 +142,24 @@ describe('autoscaling presentation', () => {
});
it('includes uncovered maps and preserves lobbies with unrecognized identities', () => {
const data = view();
data.snapshot.members.push({ ...data.snapshot.members[0], name: 'legacy-server', mapSlug: undefined });
data.snapshot.members.push({
...data.snapshot.members[0],
name: 'legacy-server',
mapSlug: undefined
});
const groups = lobbyGroups(data);
expect(groups.find(group => group.slug === 'theater')).toMatchObject({ ready: 1, target: 1 });
expect(groups.find(group => group.slug === 'moon')).toMatchObject({ ready: 0, target: 1, members: [] });
expect(groups.find(group => group.slug === 'unassigned')?.members[0].name).toBe('legacy-server');
expect(groups.find((group) => group.slug === 'theater')).toMatchObject({
ready: 1,
target: 1
});
expect(groups.find((group) => group.slug === 'moon')).toMatchObject({
ready: 0,
target: 1,
members: []
});
expect(groups.find((group) => group.slug === 'unassigned')?.members[0].name).toBe(
'legacy-server'
);
expect(body()).toContain('1 / 10 maps meet spare target');
});
it('does not imply global readiness from one satisfied map', () => {
@@ -61,11 +175,13 @@ describe('autoscaling presentation', () => {
data.unsettled[0].state = 'failed';
const html = body(data);
expect(html).toContain(data.policy.pausedReason);
expect(html).toContain('Review Ascension · Lobby 1 job');
expect(html.replace(/\s+/g, ' ')).toContain('Review Ascension · Lobby 1 job');
expect(body(view(), true, true)).toContain('Globally paused');
data.policy.pausedReason = null;
data.policy.mode = 'off';
expect(body(data)).toContain('No new scaling requests. Outstanding jobs are still tracked.');
expect(body(data)).toContain(
'No new scaling requests. Outstanding jobs are still tracked.'
);
});
it('groups job wait decisions with their original request without swallowing unrelated blockers', () => {
const data = view();
@@ -77,14 +193,31 @@ describe('autoscaling presentation', () => {
];
const activity = scalingActivity(data);
expect(activity).toHaveLength(2);
expect(activity[0]).toMatchObject({ operationId: 'job-1', action: 'create', at: now, lastSeenAt: now + 30, evaluations: 3, operationState: 'done' });
expect(activity[0]).toMatchObject({
operationId: 'job-1',
action: 'create',
at: now,
lastSeenAt: now + 30,
evaluations: 3,
operationState: 'done'
});
expect(activity[1].reason).toBe('No available game keys.');
});
it('consolidates a fully covered game into one healthy summary', () => {
const data = view();
data.unsettled = [];
data.snapshot.members = zombieMaps('t5').map(map => ({ ...data.snapshot.members[0], name: `t5_zombie_${map.slug}_1`, mapSlug: map.slug }));
data.decision = { ...data.decision, reason: 'Empty spare target is satisfied.', deficit: 0, empty: 10, pending: 0 };
data.snapshot.members = zombieMaps('t5').map((map) => ({
...data.snapshot.members[0],
name: `t5_zombie_${map.slug}_1`,
mapSlug: map.slug
}));
data.decision = {
...data.decision,
reason: 'Empty spare target is satisfied.',
deficit: 0,
empty: 10,
pending: 0
};
const html = body(data);
expect(html).toContain('All 10 maps meet their spare target.');
expect(html).not.toContain('10 / 10 maps meet spare target');
@@ -95,8 +228,14 @@ describe('autoscaling presentation', () => {
expect(observationAge(now - 5000, now)).toEqual({ label: '5s ago', stale: false });
expect(observationAge(now - 90000, now).stale).toBe(false);
expect(observationAge(now - 90001, now)).toEqual({ label: 'Stale · 1m ago', stale: true });
expect(observationAge(now, now + 3600000)).toEqual({ label: 'Stale · 1h ago', stale: true });
expect(observationAge(now + 5000, now)).toEqual({ label: 'Check observation time', stale: true });
expect(observationAge(now, now + 3600000)).toEqual({
label: 'Stale · 1h ago',
stale: true
});
expect(observationAge(now + 5000, now)).toEqual({
label: 'Check observation time',
stale: true
});
});
it('renders accessible tabs and hides mutation controls from viewers', () => {
const html = body(view(), false);