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
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>
78 lines
2.8 KiB
TypeScript
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' });
|
|
});
|
|
});
|