diff --git a/frontend/src/routes/geschichten/+page.server.ts b/frontend/src/routes/geschichten/+page.server.ts index ce2b33d8..b0550b91 100644 --- a/frontend/src/routes/geschichten/+page.server.ts +++ b/frontend/src/routes/geschichten/+page.server.ts @@ -6,16 +6,21 @@ import type { PageServerLoad } from './$types'; type Person = components['schemas']['Person']; +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + export const load: PageServerLoad = async ({ url, fetch }) => { const api = createApiClient(fetch); const personIds = url.searchParams.getAll('personId'); + const rawDocumentId = url.searchParams.get('documentId'); + const documentId = rawDocumentId && UUID_PATTERN.test(rawDocumentId) ? rawDocumentId : null; const [listResult, ...personResults] = await Promise.all([ api.GET('/api/geschichten', { params: { query: { status: 'PUBLISHED', - personId: personIds.length ? personIds : undefined + personId: personIds.length ? personIds : undefined, + documentId: documentId ?? undefined } } }), @@ -32,6 +37,7 @@ export const load: PageServerLoad = async ({ url, fetch }) => { return { geschichten: listResult.data ?? [], - personFilters + personFilters, + documentIdFilter: documentId }; }; diff --git a/frontend/src/routes/geschichten/page.server.test.ts b/frontend/src/routes/geschichten/page.server.test.ts new file mode 100644 index 00000000..1a802095 --- /dev/null +++ b/frontend/src/routes/geschichten/page.server.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +vi.mock('$lib/shared/api.server', () => ({ + createApiClient: vi.fn(), + extractErrorCode: (e: unknown) => (e as { code?: string } | undefined)?.code +})); + +import { load } from './+page.server'; +import { createApiClient } from '$lib/shared/api.server'; + +beforeEach(() => vi.clearAllMocks()); + +const VALID_UUID = '11111111-2222-3333-4444-555555555555'; + +function makeUrl(params: Record = {}) { + const url = new URL('http://localhost/geschichten'); + 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; +} + +function mockApi() { + const mockGet = vi.fn().mockResolvedValue({ + response: { ok: true, status: 200 }, + data: [] + }); + vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< + typeof createApiClient + >); + return mockGet; +} + +function callLoad(url: URL) { + return load({ + url, + request: new Request('http://localhost/geschichten'), + fetch: vi.fn() as unknown as typeof fetch + }); +} + +// ─── documentId filter forwarding ───────────────────────────────────────────── + +describe('geschichten page load — documentId filter', () => { + it('passes a valid documentId to the geschichten API', async () => { + const mockGet = mockApi(); + + await callLoad(makeUrl({ documentId: VALID_UUID })); + + expect(mockGet).toHaveBeenCalledWith( + '/api/geschichten', + expect.objectContaining({ + params: expect.objectContaining({ + query: expect.objectContaining({ documentId: VALID_UUID }) + }) + }) + ); + }); + + it('omits documentId from the API call when the value is not a UUID', async () => { + const mockGet = mockApi(); + + await callLoad(makeUrl({ documentId: 'not-a-uuid' })); + + const query = mockGet.mock.calls[0][1].params.query; + expect(query.documentId).toBeUndefined(); + }); + + it('omits documentId from the API call when the param is absent', async () => { + const mockGet = mockApi(); + + await callLoad(makeUrl()); + + const query = mockGet.mock.calls[0][1].params.query; + expect(query.documentId).toBeUndefined(); + }); + + it('returns documentIdFilter in page data when a valid documentId is given', async () => { + mockApi(); + + const result = await callLoad(makeUrl({ documentId: VALID_UUID })); + + expect(result.documentIdFilter).toBe(VALID_UUID); + }); + + it('returns null documentIdFilter when documentId is invalid', async () => { + mockApi(); + + const result = await callLoad(makeUrl({ documentId: 'not-a-uuid' })); + + expect(result.documentIdFilter).toBeNull(); + }); + + it('keeps forwarding personId filters alongside documentId', async () => { + const mockGet = mockApi(); + + await callLoad(makeUrl({ documentId: VALID_UUID, personId: [VALID_UUID] })); + + expect(mockGet).toHaveBeenCalledWith( + '/api/geschichten', + expect.objectContaining({ + params: expect.objectContaining({ + query: expect.objectContaining({ + documentId: VALID_UUID, + personId: [VALID_UUID] + }) + }) + }) + ); + }); +});