Files
familienarchiv/frontend/src/routes/geschichten/new/page.server.test.ts
Marcel 994772564a
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m39s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 3m43s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m8s
fix(geschichten-new): add request to makeEvent and vi.fn wrapper to createApiClient mock
Sentry's wrapLoadWithSentry reads event.request.method — the test's makeEvent
now provides a real Request object. createApiClient mock was a plain function;
wrapping with vi.fn() enables vi.mocked(...).mockReturnValue in individual tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 23:25:56 +02:00

78 lines
2.8 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from 'vitest';
vi.mock('$env/dynamic/private', () => ({
env: { API_INTERNAL_URL: 'http://backend:8080' }
}));
vi.mock('$lib/shared/api.server', () => ({
createApiClient: vi.fn(() => ({
GET: vi.fn().mockResolvedValue({ response: { ok: false }, data: null })
}))
}));
import { load } from './+page.server';
function makeEvent(search: string, canBlogWrite = true) {
return {
url: new URL(`http://localhost/geschichten/new${search}`),
request: new Request(`http://localhost/geschichten/new${search}`),
fetch: vi.fn(),
parent: vi.fn().mockResolvedValue({ canBlogWrite })
} as never;
}
describe('geschichten/new load — selectedType validation (security regression)', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('returns selectedType: STORY for ?type=STORY', async () => {
const result = await load(makeEvent('?type=STORY'));
expect(result.selectedType).toBe('STORY');
});
it('returns selectedType: JOURNEY for ?type=JOURNEY', async () => {
const result = await load(makeEvent('?type=JOURNEY'));
expect(result.selectedType).toBe('JOURNEY');
});
it('returns selectedType: null when ?type param is absent', async () => {
const result = await load(makeEvent(''));
expect(result.selectedType).toBeNull();
});
it('returns selectedType: null for invalid ?type param (security regression)', async () => {
const result = await load(makeEvent('?type=ADMIN'));
expect(result.selectedType).toBeNull();
});
it('returns selectedType: null for ?type=STORY%00JOURNEY (null-byte encoded — strict equality rejects it)', async () => {
// Strict equality rejects encoded variants; .includes/.startsWith would not.
const result = await load(makeEvent('?type=STORY%00JOURNEY'));
expect(result.selectedType).toBeNull();
});
it('returns selectedType: STORY for repeated ?type=STORY&type=JOURNEY (first-value semantics — intentional)', async () => {
// url.searchParams.get() returns the first value; this is intentional and documented.
const result = await load(makeEvent('?type=STORY&type=JOURNEY'));
expect(result.selectedType).toBe('STORY');
});
it('returns BOTH selectedType: STORY AND initialPersons when ?type=STORY&personId=p1 (no coupling)', async () => {
const { createApiClient } = await import('$lib/shared/api.server');
vi.mocked(createApiClient).mockReturnValue({
GET: vi
.fn()
.mockResolvedValue({ response: { ok: true }, data: { id: 'p1', displayName: 'Anna' } })
} as never);
const result = await load(makeEvent('?type=STORY&personId=p1'));
expect(result.selectedType).toBe('STORY');
expect(result.initialPersons).toHaveLength(1);
});
it('redirects non-BLOG_WRITE users to /geschichten', async () => {
await expect(load(makeEvent('', false))).rejects.toMatchObject({ location: '/geschichten' });
});
});