Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
+132
-37
@@ -1,75 +1,170 @@
|
||||
import { afterEach, expect, it, vi } from 'vitest';
|
||||
import { campaignOutcome, campaignServerStatus, campaignTitle, fetchCampaigns } from '../../src/lib/ui/campaigns.js';
|
||||
import {
|
||||
campaignOutcome,
|
||||
campaignServerStatus,
|
||||
campaignTitle,
|
||||
fetchCampaigns
|
||||
} from '../../src/lib/ui/campaigns.js';
|
||||
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
||||
it('names campaigns with their script or beta and gives maintenance a readable title', () => {
|
||||
const campaign = { action: 'promote', beta_id: 74, rolled_back_by: null, script_name: null, script_version: null, scripts: [], servers: [] };
|
||||
const campaign = {
|
||||
action: 'promote',
|
||||
beta_id: 74,
|
||||
rolled_back_by: null,
|
||||
script_name: null,
|
||||
script_version: null,
|
||||
scripts: [],
|
||||
servers: []
|
||||
};
|
||||
expect(campaignTitle(campaign)).toBe('Promote · Beta #74');
|
||||
expect(campaignTitle({ ...campaign, scripts: [{ script_name: 'Double Tap', script_version: 2 }] })).toBe('Promote · Double Tap v2');
|
||||
expect(campaignTitle({ ...campaign, scripts: Array(7).fill({ script_name: null, script_version: null }) })).toBe('Promote · 7 scripts');
|
||||
expect(
|
||||
campaignTitle({ ...campaign, scripts: [{ script_name: 'Double Tap', script_version: 2 }] })
|
||||
).toBe('Promote · Double Tap v2');
|
||||
expect(
|
||||
campaignTitle({
|
||||
...campaign,
|
||||
scripts: Array(7).fill({ script_name: null, script_version: null })
|
||||
})
|
||||
).toBe('Promote · 7 scripts');
|
||||
expect(campaignTitle({ ...campaign, action: 'rollback' })).toBe('Rollback · Beta #74');
|
||||
expect(campaignTitle({ ...campaign, action: 'drift' })).toBe('Maintenance repair');
|
||||
});
|
||||
|
||||
it('summarizes progress without letting deleted servers create false alarms or false completion', () => {
|
||||
const campaign = { rolled_back_by: null, servers: [{ restart_state: 'done' }, { restart_state: 'failed', server_deleted_at: '2026-10-06' }] };
|
||||
expect(campaignOutcome(campaign)).toMatchObject({ status: 'inactive', label: '1/2 complete', tone: 'neutral' });
|
||||
expect(campaignOutcome({ ...campaign, servers: [{ restart_state: 'done' }, { restart_state: 'pending' }] })).toMatchObject({ status: 'progress', label: '1/2 complete', completed: 1 });
|
||||
expect(campaignOutcome({ ...campaign, servers: [{ restart_state: 'failed' }] })).toMatchObject({ status: 'attention', tone: 'danger' });
|
||||
expect(campaignOutcome({ ...campaign, servers: [{ restart_state: 'held' }] })).toMatchObject({ status: 'attention', tone: 'warning' });
|
||||
expect(campaignOutcome({ ...campaign, rolled_back_by: 'rollback' })).toMatchObject({ status: 'rolled-back', label: 'Rolled back' });
|
||||
const campaign = {
|
||||
rolled_back_by: null,
|
||||
servers: [
|
||||
{ restart_state: 'done' },
|
||||
{ restart_state: 'failed', server_deleted_at: '2026-10-06' }
|
||||
]
|
||||
};
|
||||
expect(campaignOutcome(campaign)).toMatchObject({
|
||||
status: 'inactive',
|
||||
label: '1/2 complete',
|
||||
tone: 'neutral'
|
||||
});
|
||||
expect(
|
||||
campaignOutcome({
|
||||
...campaign,
|
||||
servers: [{ restart_state: 'done' }, { restart_state: 'pending' }]
|
||||
})
|
||||
).toMatchObject({ status: 'progress', label: '1/2 complete', completed: 1 });
|
||||
expect(campaignOutcome({ ...campaign, servers: [{ restart_state: 'failed' }] })).toMatchObject({
|
||||
status: 'attention',
|
||||
tone: 'danger'
|
||||
});
|
||||
expect(campaignOutcome({ ...campaign, servers: [{ restart_state: 'held' }] })).toMatchObject({
|
||||
status: 'attention',
|
||||
tone: 'warning'
|
||||
});
|
||||
expect(campaignOutcome({ ...campaign, rolled_back_by: 'rollback' })).toMatchObject({
|
||||
status: 'rolled-back',
|
||||
label: 'Rolled back'
|
||||
});
|
||||
});
|
||||
|
||||
it('distinguishes queued work, file application, and health verification without treating stage messages as failures', () => {
|
||||
const server = { restart_state: 'pending', restarted_at: null, error: 'Queued to restart; waiting for another server to recover' };
|
||||
expect(campaignServerStatus(server, [{ apply_state: 'pending' }])).toEqual({ group: 'queued', label: 'Queued', tone: 'neutral' });
|
||||
expect(campaignServerStatus(server, [{ apply_state: 'applying' }])).toMatchObject({ group: 'processing', label: 'Applying files' });
|
||||
const verifying = { ...server, restarted_at: '2026-10-06T20:08:29Z', error: 'Checking container state with host agent' };
|
||||
expect(campaignServerStatus(verifying, [{ apply_state: 'applied' }]))
|
||||
.toEqual({ group: 'processing', label: 'Verifying health', tone: 'info' });
|
||||
const server = {
|
||||
restart_state: 'pending',
|
||||
restarted_at: null,
|
||||
error: 'Queued to restart; waiting for another server to recover'
|
||||
};
|
||||
expect(campaignServerStatus(server, [{ apply_state: 'pending' }])).toEqual({
|
||||
group: 'queued',
|
||||
label: 'Queued',
|
||||
tone: 'neutral'
|
||||
});
|
||||
expect(campaignServerStatus(server, [{ apply_state: 'applying' }])).toMatchObject({
|
||||
group: 'processing',
|
||||
label: 'Applying files'
|
||||
});
|
||||
const verifying = {
|
||||
...server,
|
||||
restarted_at: '2026-10-06T20:08:29Z',
|
||||
error: 'Checking container state with host agent'
|
||||
};
|
||||
expect(campaignServerStatus(verifying, [{ apply_state: 'applied' }])).toEqual({
|
||||
group: 'processing',
|
||||
label: 'Verifying health',
|
||||
tone: 'info'
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['failed', 'held', 'manual'])('keeps %s servers needing intervention ahead of ordinary progress', restart_state => {
|
||||
expect(campaignServerStatus({ restart_state, restarted_at: '2026-10-06T20:08:29Z' }, [{ apply_state: 'applying' }]))
|
||||
.toMatchObject({ group: 'attention', tone: restart_state === 'failed' ? 'danger' : 'warning' });
|
||||
});
|
||||
it.each(['failed', 'held', 'manual'])(
|
||||
'keeps %s servers needing intervention ahead of ordinary progress',
|
||||
(restart_state) => {
|
||||
expect(
|
||||
campaignServerStatus({ restart_state, restarted_at: '2026-10-06T20:08:29Z' }, [
|
||||
{ apply_state: 'applying' }
|
||||
])
|
||||
).toMatchObject({
|
||||
group: 'attention',
|
||||
tone: restart_state === 'failed' ? 'danger' : 'warning'
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('preserves completed and inactive outcomes without asking users to intervene on deleted servers', () => {
|
||||
const server = { restart_state: 'done', restarted_at: '2026-10-06T20:08:29Z' };
|
||||
expect(campaignServerStatus(server)).toMatchObject({ group: 'complete', label: 'Complete' });
|
||||
for (const restart_state of ['skipped', 'cancelled']) {
|
||||
expect(campaignServerStatus({ ...server, restart_state })).toMatchObject({ group: 'inactive', tone: 'neutral' });
|
||||
expect(campaignServerStatus({ ...server, restart_state })).toMatchObject({
|
||||
group: 'inactive',
|
||||
tone: 'neutral'
|
||||
});
|
||||
}
|
||||
const deleted = { ...server, server_deleted_at: '2026-10-06T21:00:00Z' };
|
||||
expect(campaignServerStatus(deleted)).toMatchObject({ group: 'complete' });
|
||||
expect(campaignServerStatus({ ...deleted, restart_state: 'failed' })).toEqual({ group: 'inactive', label: 'Deleted before activation', tone: 'neutral' });
|
||||
expect(campaignServerStatus({ ...deleted, restart_state: 'failed' })).toEqual({
|
||||
group: 'inactive',
|
||||
label: 'Deleted before activation',
|
||||
tone: 'neutral'
|
||||
});
|
||||
});
|
||||
|
||||
it('loads history beyond the API page limit without truncating or fetching unwanted older rows', async () => {
|
||||
const requests: URL[] = [];
|
||||
vi.stubGlobal('fetch', vi.fn(async (input: string) => {
|
||||
const url = new URL(input, 'http://localhost');
|
||||
requests.push(url);
|
||||
const start = Number(url.searchParams.get('offset'));
|
||||
const count = Number(url.searchParams.get('limit'));
|
||||
return Response.json({ total: 900, deployments: Array.from({ length: count }, (_, i) => ({ id: start + i })) });
|
||||
}));
|
||||
const result = await fetchCampaigns<{ id: number }>({ server: 'test-server', view: 'history' }, 525);
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async (input: string) => {
|
||||
const url = new URL(input, 'http://localhost');
|
||||
requests.push(url);
|
||||
const start = Number(url.searchParams.get('offset'));
|
||||
const count = Number(url.searchParams.get('limit'));
|
||||
return Response.json({
|
||||
total: 900,
|
||||
deployments: Array.from({ length: count }, (_, i) => ({ id: start + i }))
|
||||
});
|
||||
})
|
||||
);
|
||||
const result = await fetchCampaigns<{ id: number }>(
|
||||
{ server: 'test-server', view: 'history' },
|
||||
525
|
||||
);
|
||||
expect(result.total).toBe(900);
|
||||
expect(result.deployments.map(row => row.id)).toEqual(Array.from({ length: 525 }, (_, i) => i));
|
||||
expect(requests.map(url => Object.fromEntries(url.searchParams))).toEqual([
|
||||
expect(result.deployments.map((row) => row.id)).toEqual(
|
||||
Array.from({ length: 525 }, (_, i) => i)
|
||||
);
|
||||
expect(requests.map((url) => Object.fromEntries(url.searchParams))).toEqual([
|
||||
{ server: 'test-server', view: 'history', offset: '0', limit: '500' },
|
||||
{ server: 'test-server', view: 'history', offset: '500', limit: '25' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('loads all active campaigns independently of the history limit', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn(async (input: string) => {
|
||||
const url = new URL(input, 'http://localhost');
|
||||
const start = Number(url.searchParams.get('offset'));
|
||||
return Response.json({ total: 550, deployments: Array.from({ length: start ? 50 : 500 }, (_, i) => ({ id: start + i })) });
|
||||
}));
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async (input: string) => {
|
||||
const url = new URL(input, 'http://localhost');
|
||||
const start = Number(url.searchParams.get('offset'));
|
||||
return Response.json({
|
||||
total: 550,
|
||||
deployments: Array.from({ length: start ? 50 : 500 }, (_, i) => ({ id: start + i }))
|
||||
});
|
||||
})
|
||||
);
|
||||
const result = await fetchCampaigns<{ id: number }>({ active: 'true' }, Infinity);
|
||||
expect(result.deployments).toHaveLength(550);
|
||||
expect(result.deployments[549].id).toBe(549);
|
||||
|
||||
Reference in New Issue
Block a user