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[0]) => currentPage.subscribe(run) } })); vi.mock('$app/navigation', () => ({ goto: vi.fn() })); vi.mock('../../src/lib/ui/poll.js', () => ({ pollVisible: (task: () => Promise) => { void task(); return () => {}; } })); let component: ReturnType | undefined; afterEach(async () => { if (component) await unmount(component); component = undefined; document.body.replaceChildren(); vi.unstubAllGlobals(); }); function overview(release: Partial = {}): 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('#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((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(); });