Use friendly UI labels and release feedback (Refs #120)
CI / checks (push) Successful in 6m39s
CI / checks (push) Successful in 6m39s
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user