Files
familienarchiv/frontend/src/routes/briefwechsel/page.server.spec.ts
Marcel 0c4b22291f
All checks were successful
CI / Unit & Component Tests (push) Successful in 3m31s
CI / OCR Service Tests (push) Successful in 19s
CI / Backend Unit Tests (push) Successful in 3m29s
CI / fail2ban Regex (push) Successful in 40s
CI / Semgrep Security Scan (push) Successful in 20s
CI / Compose Bucket Idempotency (push) Successful in 1m0s
fix(frontend): add extractErrorCode to all api.server vi.mock factories
All route spec files that mock $lib/shared/api.server were missing
extractErrorCode from the mock factory, causing a vitest "No export defined"
error after the refactor introduced the new export.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:31:53 +02:00

206 lines
5.9 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from 'vitest';
import { load } from './+page.server';
vi.mock('$lib/shared/api.server', () => ({
createApiClient: vi.fn(),
extractErrorCode: (e: unknown) => (e as { code?: string } | undefined)?.code
}));
vi.mock('$lib/shared/errors', () => ({
getErrorMessage: (code: string) => code ?? 'Unknown error'
}));
import { createApiClient } from '$lib/shared/api.server';
const writeUser = { groups: [{ permissions: ['WRITE_ALL'] }] };
const readUser = { groups: [{ permissions: ['READ_ALL'] }] };
function makeUrl(params: Record<string, string> = {}): URL {
const url = new URL('http://x/korrespondenz');
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
return url;
}
function mockApi(calls: { ok: boolean; data?: unknown; status?: number }[]) {
const GET = vi.fn();
for (const call of calls) {
GET.mockResolvedValueOnce({
response: { ok: call.ok, status: call.status ?? (call.ok ? 200 : 500) },
data: call.data,
error: call.ok ? undefined : { code: 'INTERNAL_ERROR' }
});
}
vi.mocked(createApiClient).mockReturnValue({ GET } as ReturnType<typeof createApiClient>);
return GET;
}
beforeEach(() => vi.clearAllMocks());
// ─── No senderId ──────────────────────────────────────────────────────────────
describe('korrespondenz load — no senderId', () => {
it('returns empty documents without calling the conversation endpoint', async () => {
const GET = mockApi([]);
const result = await load({
url: makeUrl(),
request: new Request('http://localhost/briefwechsel'),
fetch: vi.fn() as unknown as typeof fetch,
locals: { user: readUser }
});
expect(result.documents).toEqual([]);
expect(GET).not.toHaveBeenCalled();
});
});
// ─── With senderId, no receiverId ────────────────────────────────────────────
describe('korrespondenz load — senderId set, no receiverId', () => {
it('calls the conversation endpoint and the sender person endpoint', async () => {
const docs = [{ id: 'd1', title: 'Testbrief' }];
const GET = mockApi([
{ ok: true, data: docs },
{
ok: true,
data: {
firstName: 'Hans',
lastName: 'Müller',
displayName: 'Hans Müller',
personType: 'PERSON'
}
}
]);
const result = await load({
url: makeUrl({ senderId: 'p1' }),
request: new Request('http://localhost/briefwechsel'),
fetch: vi.fn() as unknown as typeof fetch,
locals: { user: readUser }
});
expect(result.documents).toEqual(docs);
expect(result.initialValues.senderName).toBe('Hans Müller');
expect(result.initialValues.receiverName).toBe('');
expect(GET).toHaveBeenCalledTimes(2);
});
});
// ─── With senderId and receiverId ────────────────────────────────────────────
describe('korrespondenz load — senderId and receiverId set', () => {
it('calls conversation, sender person, and receiver person endpoints', async () => {
const GET = mockApi([
{ ok: true, data: [] },
{
ok: true,
data: {
firstName: 'Hans',
lastName: 'Müller',
displayName: 'Hans Müller',
personType: 'PERSON'
}
},
{
ok: true,
data: {
firstName: 'Anna',
lastName: 'Schmidt',
displayName: 'Anna Schmidt',
personType: 'PERSON'
}
}
]);
const result = await load({
url: makeUrl({ senderId: 'p1', receiverId: 'p2' }),
request: new Request('http://localhost/briefwechsel'),
fetch: vi.fn() as unknown as typeof fetch,
locals: { user: readUser }
});
expect(result.initialValues.senderName).toBe('Hans Müller');
expect(result.initialValues.receiverName).toBe('Anna Schmidt');
expect(GET).toHaveBeenCalledTimes(3);
});
});
// ─── canWrite derivation ─────────────────────────────────────────────────────
describe('korrespondenz load — canWrite', () => {
it('derives canWrite true from WRITE_ALL permission', async () => {
mockApi([
{ ok: true, data: [] },
{
ok: true,
data: {
firstName: 'Hans',
lastName: 'Müller',
displayName: 'Hans Müller',
personType: 'PERSON'
}
}
]);
const result = await load({
url: makeUrl({ senderId: 'p1' }),
request: new Request('http://localhost/briefwechsel'),
fetch: vi.fn() as unknown as typeof fetch,
locals: { user: writeUser }
});
expect(result.canWrite).toBe(true);
});
it('derives canWrite false when user lacks WRITE_ALL', async () => {
mockApi([
{ ok: true, data: [] },
{
ok: true,
data: {
firstName: 'Hans',
lastName: 'Müller',
displayName: 'Hans Müller',
personType: 'PERSON'
}
}
]);
const result = await load({
url: makeUrl({ senderId: 'p1' }),
request: new Request('http://localhost/briefwechsel'),
fetch: vi.fn() as unknown as typeof fetch,
locals: { user: readUser }
});
expect(result.canWrite).toBe(false);
});
});
// ─── Backend error propagation ────────────────────────────────────────────────
describe('korrespondenz load — backend error', () => {
it('throws when the conversation endpoint returns non-ok', async () => {
mockApi([
{ ok: false, status: 500 },
{
ok: true,
data: {
firstName: 'Hans',
lastName: 'Müller',
displayName: 'Hans Müller',
personType: 'PERSON'
}
}
]);
await expect(
load({
url: makeUrl({ senderId: 'p1' }),
request: new Request('http://localhost/briefwechsel'),
fetch: vi.fn() as unknown as typeof fetch,
locals: { user: readUser }
})
).rejects.toMatchObject({ status: 500 });
});
});