import { describe, expect, it, vi, beforeEach } from 'vitest'; vi.mock('$lib/api.server', () => ({ createApiClient: vi.fn() })); import { load } from './+page.server'; import { createApiClient } from '$lib/api.server'; beforeEach(() => vi.clearAllMocks()); function makeUrl(params: Record = {}) { const url = new URL('http://localhost/'); for (const [key, value] of Object.entries(params)) { if (Array.isArray(value)) { value.forEach((v) => url.searchParams.append(key, v)); } else { url.searchParams.set(key, value); } } return url; } // ─── always-dashboard behaviour ─────────────────────────────────────────────── it('never calls /api/documents/search regardless of URL params', async () => { const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl({ q: 'Urlaub', from: '2020-01-01' }), fetch: vi.fn() as unknown as typeof fetch }); const calledEndpoints = mockGet.mock.calls.map((c: unknown[]) => c[0]); expect(calledEndpoints).not.toContain('/api/documents/search'); }); it('always fetches dashboard data regardless of URL params', async () => { const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl({ q: 'Urlaub' }), fetch: vi.fn() as unknown as typeof fetch }); const calledEndpoints = mockGet.mock.calls.map((c: unknown[]) => c[0]); expect(calledEndpoints).toContain('/api/stats'); expect(calledEndpoints).toContain('/api/dashboard/resume'); expect(calledEndpoints).toContain('/api/dashboard/pulse'); expect(calledEndpoints).toContain('/api/dashboard/activity'); }); // ─── dashboard mode ──────────────────────────────────────────────────────────── describe('home page load — dashboard', () => { it('fetches stats, resume, pulse, and activity APIs', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons .mockResolvedValueOnce({ response: { ok: true }, data: { totalDocuments: 42, totalPersons: 7 } }) // stats .mockResolvedValueOnce({ response: { ok: true }, data: { documentId: 'd1', title: 'T', caption: '', excerpt: '', totalBlocks: 2, pct: 50, collaborators: [] } }) // resume .mockResolvedValueOnce({ response: { ok: true }, data: { pages: 5, annotated: 1, transcribed: 2, uploaded: 1, yourPages: 3, contributors: [] } }) // pulse .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // activity .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // segmentation-queue .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // transcription-queue .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // ready-to-read .mockResolvedValueOnce({ response: { ok: true }, data: { segmentationCount: 0, transcriptionCount: 0, readyCount: 0 } }) // weekly-stats .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // incomplete .mockResolvedValueOnce({ response: { ok: true }, data: { count: 0 } }); // incomplete-count vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); expect(result.stats).toEqual({ totalDocuments: 42, totalPersons: 7 }); expect(result.resumeDoc).not.toBeNull(); expect(result.resumeDoc?.totalBlocks).toBe(2); expect(result.pulse).not.toBeNull(); expect(result.activityFeed).toEqual([]); }); it('returns stats with totalDocuments from /api/stats', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons .mockResolvedValueOnce({ response: { ok: true }, data: { totalDocuments: 248, totalPersons: 34 } }) // stats .mockResolvedValueOnce({ response: { ok: true }, data: null }) // resume .mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // activity .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // segmentation-queue .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // transcription-queue .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // ready-to-read .mockResolvedValueOnce({ response: { ok: true }, data: { segmentationCount: 0, transcriptionCount: 0, readyCount: 0 } }) // weekly-stats .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // incomplete .mockResolvedValueOnce({ response: { ok: true }, data: { count: 0 } }); // incomplete-count vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); expect(result.stats?.totalDocuments).toBe(248); expect(result.stats?.totalPersons).toBe(34); }); it('returns stats: null when /api/stats rejects', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons .mockRejectedValueOnce(new Error('network')) // stats .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // resume .mockResolvedValueOnce({ response: { ok: true }, data: [] }); // pulse vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); expect(result.stats).toBeNull(); }); it('defaults resumeDoc to null when resume API rejects', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons .mockResolvedValueOnce({ response: { ok: true }, data: { content: [] } }) // stats .mockRejectedValueOnce(new Error('network')) // resume .mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse .mockResolvedValueOnce({ response: { ok: true }, data: [] }); // activity vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); expect(result.resumeDoc).toBeNull(); }); it('defaults activityFeed to [] when activity API rejects', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons .mockResolvedValueOnce({ response: { ok: true }, data: { totalDocuments: 0, totalPersons: 0 } }) // stats .mockResolvedValueOnce({ response: { ok: true }, data: null }) // resume .mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse .mockRejectedValueOnce(new Error('network')); // activity vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); expect(result.activityFeed).toEqual([]); }); }); // ─── 401 redirect ───────────────────────────────────────────────────────────── describe('home page load — auth redirect', () => { it('redirects to /login when persons API returns 401', async () => { vi.mocked(createApiClient).mockReturnValue({ GET: vi.fn().mockResolvedValueOnce({ response: { ok: false, status: 401 }, data: null }) } as ReturnType); await expect( load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }) ).rejects.toMatchObject({ location: '/login' }); }); }); // ─── network error fallback ─────────────────────────────────────────────────── describe('home page load — network error fallback', () => { it('returns error string instead of throwing when API call throws', async () => { vi.mocked(createApiClient).mockReturnValue({ GET: vi.fn().mockRejectedValue(new Error('Network failure')) } as ReturnType); const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); expect(result.error).toBe('Daten konnten nicht geladen werden.'); }); });