Files
electric-cherry/tests/client/maintenance-release.test.ts
T
2026-10-17 09:54:01 -04:00

118 lines
5.2 KiB
TypeScript

import { afterEach, expect, it, vi } from 'vitest';
import { flushSync, mount, unmount } from 'svelte';
import { writable } from 'svelte/store';
import MaintenancePage from '../../src/routes/maintenance/+page.svelte';
import type { MaintenanceOverview } from '../../src/lib/maintenance.js';
import type { PlutoniumRelease } from '../../src/lib/plutonium.js';
import type { UserRole } from '../../src/lib/auth.js';
const currentPage = writable({
url: new URL('http://localhost/maintenance'),
data: { user: { role: 'admin' as UserRole } }
});
vi.mock('$app/stores', () => ({
page: {
subscribe: (run: Parameters<typeof currentPage.subscribe>[0]) => currentPage.subscribe(run)
}
}));
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
vi.mock('../../src/lib/ui/poll.js', () => ({
pollVisible: (task: () => Promise<void>) => {
void task();
return () => {};
}
}));
let component: ReturnType<typeof mount> | undefined;
afterEach(async () => {
if (component) await unmount(component);
component = undefined;
document.body.replaceChildren();
vi.unstubAllGlobals();
});
function overview(release: Partial<PlutoniumRelease> = {}): MaintenanceOverview {
return {
policies: { paused: false, health: true, drift: true, updates: false },
recovery: { enabled: true, lastScanAt: null, scanError: null, incidents: [] },
runtime: {
release: {
revision: null,
checkedAt: '2026-10-17T12:00:00Z',
error: 'Updater manifest request failed (HTTP 503).',
...release
},
schedule: { enabled: false, time: '06:00', timeZone: 'UTC', nextAt: null },
servers: [],
jobs: []
}
};
}
async function show(role: UserRole = 'admin') {
currentPage.update((value) => ({ ...value, data: { user: { role } } }));
component = mount(MaintenancePage, { target: document.body });
flushSync();
await vi.waitFor(() => expect(document.querySelector('#updates')).not.toBeNull());
return document.querySelector<HTMLElement>('#updates')!;
}
const button = (panel: HTMLElement, label: string) =>
[...panel.querySelectorAll('button')].find((node) => node.textContent?.trim() === label);
it('shows unavailable release information on the first failed check and keeps diagnostics closed', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(Response.json(overview())));
const panel = await show();
const alert = panel.querySelector('[role="alert"]')!;
expect(alert.textContent).toContain('Could not refresh the published Plutonium release.');
expect(alert.textContent).toContain('The published revision is unavailable.');
expect(alert.textContent).toContain('No previous release information is available.');
expect(panel.querySelector('p strong')?.textContent).toBe('Unavailable');
expect(panel.textContent).not.toMatch(/cached|Showing the last known release/);
const details = panel.querySelector('details')!;
expect(details.open).toBe(false);
expect(details.textContent).toContain('Updater manifest request failed (HTTP 503).');
const primary = panel.cloneNode(true) as HTMLElement;
primary.querySelectorAll('details').forEach((node) => node.remove());
expect(primary.textContent).not.toContain('HTTP 503');
expect(panel.textContent).toContain('Last attempted');
});
it('labels a retained revision honestly and clears the failure after retry succeeds', async () => {
let resolve!: (response: Response) => void;
const fetch = vi
.fn()
.mockResolvedValueOnce(Response.json(overview({ revision: 5354 })))
.mockImplementationOnce(() => new Promise<Response>((done) => (resolve = done)));
vi.stubGlobal('fetch', fetch);
const panel = await show();
expect(panel.textContent).toContain('Last known published revision');
expect(panel.textContent).toContain('r5354');
expect(panel.querySelector('[role="alert"]')!.textContent).toContain(
'Showing the last known release; it may be out of date.'
);
button(panel, 'Retry checks')!.click();
flushSync();
expect(fetch).toHaveBeenLastCalledWith('/api/maintenance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'scan' })
});
expect(button(panel, 'Retry checks')!.disabled).toBe(true);
resolve(
Response.json({ ...overview({ revision: 5355, error: null }), notice: 'Checks finished.' })
);
await vi.waitFor(() => expect(panel.querySelector('[role="alert"]')).toBeNull());
expect(panel.textContent).toContain('r5355');
expect(panel.textContent).toContain('Checked');
expect(panel.textContent).not.toMatch(/Last known|Last attempted|HTTP 503/);
});
it('keeps release failure details available without offering an administrator check to viewers', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(Response.json(overview())));
const panel = await show('viewer');
expect(panel.querySelector('[role="alert"]')!.textContent).toContain(
'Could not refresh the published Plutonium release.'
);
expect(panel.querySelector('details')?.textContent).toContain('HTTP 503');
expect(button(panel, 'Retry checks')).toBeUndefined();
});