From 108dc3104df270e5a01a28b131d34f4068293daf Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 10 May 2026 09:12:17 +0200 Subject: [PATCH] test(admin): cover all import + thumbnail status branches FAILED import-status with error message, RUNNING thumbnail with progress count, RUNNING thumbnail without progress (total=0), trigger thumbnail backfill, trigger import from idle. 5 new tests covering ~10 branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../routes/admin/system/page.svelte.test.ts | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/frontend/src/routes/admin/system/page.svelte.test.ts b/frontend/src/routes/admin/system/page.svelte.test.ts index 641470f5..9a028cee 100644 --- a/frontend/src/routes/admin/system/page.svelte.test.ts +++ b/frontend/src/routes/admin/system/page.svelte.test.ts @@ -238,4 +238,114 @@ describe('admin/system page', () => { const banner = document.querySelector('[data-testid="thumbnails-status-done"]'); expect(banner).not.toBeNull(); }); + + it('renders the FAILED state for import-status with retry button', async () => { + fetchSpy.mockImplementation(async (url: RequestInfo | URL) => { + const u = url.toString(); + if (u.includes('import-status')) { + return new Response( + JSON.stringify({ + state: 'FAILED', + message: 'database error', + processed: 0, + startedAt: null + }), + { status: 200, headers: { 'Content-Type': 'application/json' } } + ); + } + return new Response(JSON.stringify({ state: 'IDLE' }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + }); + + render(AdminSystemPage, { props: {} }); + + await new Promise((r) => setTimeout(r, 100)); + expect(document.body.textContent).toContain('database error'); + }); + + it('renders the running thumbnail status with progress count', async () => { + fetchSpy.mockImplementation(async (url: RequestInfo | URL) => { + const u = url.toString(); + if (u.includes('thumbnail-status')) { + return new Response( + JSON.stringify({ + state: 'RUNNING', + message: '', + total: 100, + processed: 30, + skipped: 5, + failed: 1, + startedAt: null + }), + { status: 200, headers: { 'Content-Type': 'application/json' } } + ); + } + return new Response(JSON.stringify({ state: 'IDLE' }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + }); + + render(AdminSystemPage, { props: {} }); + + await new Promise((r) => setTimeout(r, 100)); + // Total 100, processed+skipped+failed = 36 + expect(document.body.textContent).toMatch(/36|100/); + }); + + it('triggers thumbnail backfill when its button is clicked', async () => { + render(AdminSystemPage, { props: {} }); + + await new Promise((r) => setTimeout(r, 50)); + const btns = Array.from(document.querySelectorAll('[data-thumbnails-trigger]')); + (btns[0] as HTMLButtonElement)?.click(); + + await new Promise((r) => setTimeout(r, 50)); + const calls = fetchSpy.mock.calls.map((c) => c[0].toString()); + expect(calls.some((c) => c.includes('generate-thumbnails'))).toBe(true); + }); + + it('triggers import when import button is clicked from idle state', async () => { + render(AdminSystemPage, { props: {} }); + + await new Promise((r) => setTimeout(r, 50)); + const btns = Array.from(document.querySelectorAll('[data-import-trigger]')); + (btns[0] as HTMLButtonElement)?.click(); + + await new Promise((r) => setTimeout(r, 50)); + const calls = fetchSpy.mock.calls.map((c) => c[0].toString()); + expect(calls.some((c) => c.includes('trigger-import'))).toBe(true); + }); + + it('renders the running thumbnail status without progress when total is 0', async () => { + fetchSpy.mockImplementation(async (url: RequestInfo | URL) => { + const u = url.toString(); + if (u.includes('thumbnail-status')) { + return new Response( + JSON.stringify({ + state: 'RUNNING', + message: '', + total: 0, + processed: 0, + skipped: 0, + failed: 0, + startedAt: null + }), + { status: 200, headers: { 'Content-Type': 'application/json' } } + ); + } + return new Response(JSON.stringify({ state: 'IDLE' }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + }); + + render(AdminSystemPage, { props: {} }); + + await new Promise((r) => setTimeout(r, 100)); + // running state shown but no progress count when total=0 + expect(document.body.textContent).toMatch(/läuft|wird|generier/i); + }); });