feat(geschichten): wire the ?documentId list filter the drawer already links to
DocumentMetadataDrawer links to /geschichten?documentId={id}, but the list
loader silently dropped the param — the user got the unfiltered list. The
loader now validates the UUID and forwards it to GET /api/geschichten,
returning it as documentIdFilter in page data.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
115
frontend/src/routes/geschichten/page.server.test.ts
Normal file
115
frontend/src/routes/geschichten/page.server.test.ts
Normal file
@@ -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<string, string | string[]> = {}) {
|
||||
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]
|
||||
})
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user