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; } // ─── dashboard mode (no search filters) ────────────────────────────────────── describe('home page load — dashboard mode', () => { it('sets isDashboard true and fetches stats, incomplete, and recent 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: [{ id: 'd1' }] }) // incomplete .mockResolvedValueOnce({ response: { ok: true }, data: [{ id: 'd2' }] }); // recent 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.isDashboard).toBe(true); expect(result.stats).toEqual({ totalDocuments: 42, totalPersons: 7 }); expect(result.incompleteDocs).toHaveLength(1); expect(result.recentDocs).toHaveLength(1); expect(result.documents).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: [] }) // incomplete .mockResolvedValueOnce({ response: { ok: true }, data: [] }); // recent 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: [] }) // incomplete .mockResolvedValueOnce({ response: { ok: true }, data: [] }); // recent 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 incompleteDocs to [] when incomplete API rejects', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons .mockResolvedValueOnce({ response: { ok: true }, data: { content: [] } }) // notifications .mockRejectedValueOnce(new Error('network')) // incomplete .mockResolvedValueOnce({ response: { ok: true }, data: [] }); // recent 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.incompleteDocs).toEqual([]); }); it('defaults recentDocs to [] when recent-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: [] }) // incomplete .mockRejectedValueOnce(new Error('network')); // recent 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.recentDocs).toEqual([]); }); }); // ─── search mode (any filter active) ───────────────────────────────────────── describe('home page load — search mode', () => { it('sets isDashboard false and skips widget APIs when q is set', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: { documents: [{ id: 'd1' }], total: 1 } }) // search docs .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }); // persons vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl({ q: 'Urlaub' }), fetch: vi.fn() as unknown as typeof fetch }); expect(result.isDashboard).toBe(false); expect(result.documents).toHaveLength(1); expect(result.stats).toBeNull(); expect(result.incompleteDocs).toEqual([]); expect(result.recentDocs).toEqual([]); // Only two API calls — no widget calls expect(mockGet).toHaveBeenCalledTimes(2); }); it('passes search params from the URL to the documents API', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: { documents: [], total: 0 } }) .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }); 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 firstCall = mockGet.mock.calls[0]; expect(firstCall[1].params.query.q).toBe('Urlaub'); expect(firstCall[1].params.query.from).toBe('2020-01-01'); }); it('sets isDashboard false when only tagQ is set', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: { documents: [{ id: 'd1' }], total: 1 } }) // search docs .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }); // persons vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl({ tagQ: 'fam' }), fetch: vi.fn() as unknown as typeof fetch }); expect(result.isDashboard).toBe(false); expect(result.documents).toHaveLength(1); }); }); it('passes sort, dir, and tagQ params to the documents API', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: { documents: [], total: 0 } }) .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl({ q: 'test', sort: 'TITLE', dir: 'asc', tagQ: 'fam' }), fetch: vi.fn() as unknown as typeof fetch }); const firstCall = mockGet.mock.calls[0]; expect(firstCall[1].params.query.sort).toBe('TITLE'); expect(firstCall[1].params.query.dir).toBe('asc'); expect(firstCall[1].params.query.tagQ).toBe('fam'); }); it('returns total from the DocumentSearchResult envelope', async () => { const mockGet = vi .fn() .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: { documents: [{ id: 'd1' }], total: 42 } }) .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl({ q: 'test' }), fetch: vi.fn() as unknown as typeof fetch }); expect(result.documents).toHaveLength(1); expect(result.total).toBe(42); }); // ─── 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.'); expect(result.documents).toEqual([]); }); });