import { describe, expect, it, vi, beforeEach } from 'vitest'; vi.mock('$lib/shared/api.server', () => ({ createApiClient: vi.fn() })); import { load } from './+page.server'; import { createApiClient } from '$lib/shared/api.server'; beforeEach(() => vi.clearAllMocks()); function makeUrl(params: Record = {}) { const url = new URL('http://localhost/documents'); 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; } // ─── search params forwarding ───────────────────────────────────────────────── describe('documents page load — search params', () => { it('passes q, from, to to the search API', async () => { const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl({ q: 'Urlaub', from: '1920-01-01', to: '1950-12-31' }), fetch: vi.fn() as unknown as typeof fetch }); expect(mockGet).toHaveBeenCalledWith( '/api/documents/search', expect.objectContaining({ params: expect.objectContaining({ query: expect.objectContaining({ q: 'Urlaub', from: '1920-01-01', to: '1950-12-31' }) }) }) ); }); it('passes senderId and receiverId to the search API', async () => { const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl({ senderId: 'p-1', receiverId: 'p-2' }), fetch: vi.fn() as unknown as typeof fetch }); expect(mockGet).toHaveBeenCalledWith( '/api/documents/search', expect.objectContaining({ params: expect.objectContaining({ query: expect.objectContaining({ senderId: 'p-1', receiverId: 'p-2' }) }) }) ); }); it('passes sort, dir, tagQ to the search API', async () => { const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); await load({ url: makeUrl({ sort: 'TITLE', dir: 'asc', tagQ: 'fam' }), fetch: vi.fn() as unknown as typeof fetch }); expect(mockGet).toHaveBeenCalledWith( '/api/documents/search', expect.objectContaining({ params: expect.objectContaining({ query: expect.objectContaining({ sort: 'TITLE', dir: 'asc', tagQ: 'fam' }) }) }) ); }); it('returns items and total from the search result', async () => { const item = { document: { id: 'd1' }, matchData: {}, completionPercentage: 0, contributors: [] }; const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: { items: [item], totalElements: 42, pageNumber: 0, pageSize: 50, totalPages: 1 } }); 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.items).toHaveLength(1); expect(result.totalElements).toBe(42); }); it('returns filter values in the result for pre-filling the UI', async () => { const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 } }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); const result = await load({ url: makeUrl({ q: 'Urlaub', from: '1920-01-01', sort: 'TITLE', dir: 'asc' }), fetch: vi.fn() as unknown as typeof fetch }); expect(result.q).toBe('Urlaub'); expect(result.from).toBe('1920-01-01'); expect(result.sort).toBe('TITLE'); expect(result.dir).toBe('asc'); }); }); // ─── 401 redirect ───────────────────────────────────────────────────────────── describe('documents page load — auth redirect', () => { it('redirects to /login when search API returns 401', async () => { vi.mocked(createApiClient).mockReturnValue({ GET: vi.fn().mockResolvedValue({ 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('documents 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).toBeTruthy(); expect(result.items).toEqual([]); }); }); // ─── person name resolution ─────────────────────────────────────────────────── describe('documents page load — person name resolution', () => { function makeSearchMock(personResult?: { ok: boolean; displayName?: string }) { const mockGet = vi.fn().mockImplementation((path: string) => { if (path === '/api/documents/search') { return Promise.resolve({ response: { ok: true, status: 200 }, data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 } }); } // person lookup via api.GET('/api/persons/{id}', ...) if (!personResult?.ok) { return Promise.resolve({ response: { ok: false, status: 404 }, data: undefined }); } return Promise.resolve({ response: { ok: true, status: 200 }, data: { displayName: personResult.displayName ?? '' } }); }); vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); return mockGet; } it('returns initialSenderName from person lookup when senderId is a valid UUID', async () => { makeSearchMock({ ok: true, displayName: 'Max Mustermann' }); const result = await load({ url: makeUrl({ senderId: '11111111-1111-1111-1111-111111111111' }), fetch: vi.fn() as unknown as typeof fetch }); expect(result.initialSenderName).toBe('Max Mustermann'); }); it('returns initialReceiverName from person lookup when receiverId is a valid UUID', async () => { makeSearchMock({ ok: true, displayName: 'Anna Musterfrau' }); const result = await load({ url: makeUrl({ receiverId: '22222222-2222-2222-2222-222222222222' }), fetch: vi.fn() as unknown as typeof fetch }); expect(result.initialReceiverName).toBe('Anna Musterfrau'); }); it('returns empty string when senderId is not a valid UUID', async () => { const mockGet = makeSearchMock(); const result = await load({ url: makeUrl({ senderId: 'not-a-uuid' }), fetch: vi.fn() as unknown as typeof fetch }); expect(result.initialSenderName).toBe(''); // UUID guard fires before any api.GET call — only document search is called expect(mockGet).toHaveBeenCalledTimes(1); }); it('returns empty string when person api returns 404', async () => { makeSearchMock({ ok: false }); const result = await load({ url: makeUrl({ senderId: '11111111-1111-1111-1111-111111111111' }), fetch: vi.fn() as unknown as typeof fetch }); expect(result.initialSenderName).toBe(''); }); });