`@sentry/sveltekit` wraps load functions and reads `event.request.method` and `event.url.pathname`. Mock events that omitted `request` or `url` threw `TypeError: Cannot read properties of undefined` on every invocation, silently masking 86 test failures on main. Two root causes fixed: - Added `request: new Request(...)` (and `url: new URL(...)` where absent) to all mock event objects in 14 `*.server.spec.ts` files - Changed `;` to `&&` in the `test:coverage` npm script so a failing server run propagates its exit code instead of being swallowed by the client run All 576 server-project tests now pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
260 lines
8.7 KiB
TypeScript
260 lines
8.7 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
|
|
vi.mock('$lib/shared/api.server', () => ({ createApiClient: vi.fn() }));
|
|
|
|
import { load } from './+page.server';
|
|
import { createApiClient } from '$lib/shared/api.server';
|
|
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
function makeUrl(params: Record<string, string | string[]> = {}) {
|
|
const url = new URL('http://localhost/documents');
|
|
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;
|
|
}
|
|
|
|
// ─── search params forwarding ─────────────────────────────────────────────────
|
|
|
|
describe('documents page load — search params', () => {
|
|
it('passes q, from, to to the search API', async () => {
|
|
const mockGet = vi.fn().mockResolvedValue({
|
|
response: { ok: true, status: 200 },
|
|
data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
|
|
});
|
|
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
|
typeof createApiClient
|
|
>);
|
|
|
|
await load({
|
|
url: makeUrl({ q: 'Urlaub', from: '1920-01-01', to: '1950-12-31' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(mockGet).toHaveBeenCalledWith(
|
|
'/api/documents/search',
|
|
expect.objectContaining({
|
|
params: expect.objectContaining({
|
|
query: expect.objectContaining({ q: 'Urlaub', from: '1920-01-01', to: '1950-12-31' })
|
|
})
|
|
})
|
|
);
|
|
});
|
|
|
|
it('passes senderId and receiverId to the search API', async () => {
|
|
const mockGet = vi.fn().mockResolvedValue({
|
|
response: { ok: true, status: 200 },
|
|
data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
|
|
});
|
|
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
|
typeof createApiClient
|
|
>);
|
|
|
|
await load({
|
|
url: makeUrl({ senderId: 'p-1', receiverId: 'p-2' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(mockGet).toHaveBeenCalledWith(
|
|
'/api/documents/search',
|
|
expect.objectContaining({
|
|
params: expect.objectContaining({
|
|
query: expect.objectContaining({ senderId: 'p-1', receiverId: 'p-2' })
|
|
})
|
|
})
|
|
);
|
|
});
|
|
|
|
it('passes sort, dir, tagQ to the search API', async () => {
|
|
const mockGet = vi.fn().mockResolvedValue({
|
|
response: { ok: true, status: 200 },
|
|
data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
|
|
});
|
|
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
|
typeof createApiClient
|
|
>);
|
|
|
|
await load({
|
|
url: makeUrl({ sort: 'TITLE', dir: 'asc', tagQ: 'fam' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(mockGet).toHaveBeenCalledWith(
|
|
'/api/documents/search',
|
|
expect.objectContaining({
|
|
params: expect.objectContaining({
|
|
query: expect.objectContaining({ sort: 'TITLE', dir: 'asc', tagQ: 'fam' })
|
|
})
|
|
})
|
|
);
|
|
});
|
|
|
|
it('returns items and total from the search result', async () => {
|
|
const item = {
|
|
document: { id: 'd1' },
|
|
matchData: {},
|
|
completionPercentage: 0,
|
|
contributors: []
|
|
};
|
|
const mockGet = vi.fn().mockResolvedValue({
|
|
response: { ok: true, status: 200 },
|
|
data: { items: [item], totalElements: 42, pageNumber: 0, pageSize: 50, totalPages: 1 }
|
|
});
|
|
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
|
typeof createApiClient
|
|
>);
|
|
|
|
const result = await load({
|
|
url: makeUrl({ q: 'test' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.totalElements).toBe(42);
|
|
});
|
|
|
|
it('returns filter values in the result for pre-filling the UI', async () => {
|
|
const mockGet = vi.fn().mockResolvedValue({
|
|
response: { ok: true, status: 200 },
|
|
data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
|
|
});
|
|
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
|
typeof createApiClient
|
|
>);
|
|
|
|
const result = await load({
|
|
url: makeUrl({ q: 'Urlaub', from: '1920-01-01', sort: 'TITLE', dir: 'asc' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(result.q).toBe('Urlaub');
|
|
expect(result.from).toBe('1920-01-01');
|
|
expect(result.sort).toBe('TITLE');
|
|
expect(result.dir).toBe('asc');
|
|
});
|
|
});
|
|
|
|
// ─── 401 redirect ─────────────────────────────────────────────────────────────
|
|
|
|
describe('documents page load — auth redirect', () => {
|
|
it('redirects to /login when search API returns 401', async () => {
|
|
vi.mocked(createApiClient).mockReturnValue({
|
|
GET: vi.fn().mockResolvedValue({ response: { ok: false, status: 401 }, data: null })
|
|
} as ReturnType<typeof createApiClient>);
|
|
|
|
await expect(
|
|
load({
|
|
url: makeUrl(),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
})
|
|
).rejects.toMatchObject({ location: '/login' });
|
|
});
|
|
});
|
|
|
|
// ─── network error fallback ───────────────────────────────────────────────────
|
|
|
|
describe('documents page load — network error fallback', () => {
|
|
it('returns error string instead of throwing when API call throws', async () => {
|
|
vi.mocked(createApiClient).mockReturnValue({
|
|
GET: vi.fn().mockRejectedValue(new Error('Network failure'))
|
|
} as ReturnType<typeof createApiClient>);
|
|
|
|
const result = await load({
|
|
url: makeUrl(),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(result.error).toBeTruthy();
|
|
expect(result.items).toEqual([]);
|
|
});
|
|
});
|
|
|
|
// ─── person name resolution ───────────────────────────────────────────────────
|
|
|
|
describe('documents page load — person name resolution', () => {
|
|
function makeSearchMock(personResult?: { ok: boolean; displayName?: string }) {
|
|
const mockGet = vi.fn().mockImplementation((path: string) => {
|
|
if (path === '/api/documents/search') {
|
|
return Promise.resolve({
|
|
response: { ok: true, status: 200 },
|
|
data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
|
|
});
|
|
}
|
|
// person lookup via api.GET('/api/persons/{id}', ...)
|
|
if (!personResult?.ok) {
|
|
return Promise.resolve({ response: { ok: false, status: 404 }, data: undefined });
|
|
}
|
|
return Promise.resolve({
|
|
response: { ok: true, status: 200 },
|
|
data: { displayName: personResult.displayName ?? '' }
|
|
});
|
|
});
|
|
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
|
typeof createApiClient
|
|
>);
|
|
return mockGet;
|
|
}
|
|
|
|
it('returns initialSenderName from person lookup when senderId is a valid UUID', async () => {
|
|
makeSearchMock({ ok: true, displayName: 'Max Mustermann' });
|
|
|
|
const result = await load({
|
|
url: makeUrl({ senderId: '11111111-1111-1111-1111-111111111111' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(result.initialSenderName).toBe('Max Mustermann');
|
|
});
|
|
|
|
it('returns initialReceiverName from person lookup when receiverId is a valid UUID', async () => {
|
|
makeSearchMock({ ok: true, displayName: 'Anna Musterfrau' });
|
|
|
|
const result = await load({
|
|
url: makeUrl({ receiverId: '22222222-2222-2222-2222-222222222222' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(result.initialReceiverName).toBe('Anna Musterfrau');
|
|
});
|
|
|
|
it('returns empty string when senderId is not a valid UUID', async () => {
|
|
const mockGet = makeSearchMock();
|
|
|
|
const result = await load({
|
|
url: makeUrl({ senderId: 'not-a-uuid' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(result.initialSenderName).toBe('');
|
|
// UUID guard fires before any api.GET call — only document search is called
|
|
expect(mockGet).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('returns empty string when person api returns 404', async () => {
|
|
makeSearchMock({ ok: false });
|
|
|
|
const result = await load({
|
|
url: makeUrl({ senderId: '11111111-1111-1111-1111-111111111111' }),
|
|
request: new Request('http://localhost/documents'),
|
|
fetch: vi.fn() as unknown as typeof fetch
|
|
});
|
|
|
|
expect(result.initialSenderName).toBe('');
|
|
});
|
|
});
|