feat(observability): add handleError callback to hooks.server.ts returning errorId
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
58
frontend/src/hooks.server.test.ts
Normal file
58
frontend/src/hooks.server.test.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('@sentry/sveltekit', () => ({
|
||||||
|
init: vi.fn(),
|
||||||
|
handleErrorWithSentry: (fn: (args: unknown) => unknown) => fn,
|
||||||
|
lastEventId: vi.fn(() => 'sentry-event-id-abc123')
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@sveltejs/kit', () => ({ redirect: vi.fn() }));
|
||||||
|
vi.mock('@sveltejs/kit/hooks', () => ({ sequence: vi.fn((...fns: unknown[]) => fns[0]) }));
|
||||||
|
vi.mock('$lib/paraglide/server', () => ({ paraglideMiddleware: vi.fn() }));
|
||||||
|
vi.mock('$lib/paraglide/runtime', () => ({ cookieName: 'locale', cookieMaxAge: 86400 }));
|
||||||
|
vi.mock('$lib/shared/server/locale', () => ({ detectLocale: vi.fn(() => 'de') }));
|
||||||
|
|
||||||
|
const makeEvent = () => ({
|
||||||
|
url: { pathname: '/documents/123' },
|
||||||
|
locals: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hooks.server handleError', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns Sentry lastEventId as errorId', async () => {
|
||||||
|
const Sentry = await import('@sentry/sveltekit');
|
||||||
|
vi.mocked(Sentry.lastEventId).mockReturnValue('sentry-event-id-abc123');
|
||||||
|
|
||||||
|
const { handleError } = await import('./hooks.server');
|
||||||
|
const result = (handleError as (args: unknown) => { message: string; errorId: string })({
|
||||||
|
error: new Error('boom'),
|
||||||
|
event: makeEvent(),
|
||||||
|
status: 500,
|
||||||
|
message: 'Internal Error'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.errorId).toBe('sentry-event-id-abc123');
|
||||||
|
expect(result.message).toBe('An unexpected error occurred');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to crypto.randomUUID when lastEventId returns undefined', async () => {
|
||||||
|
const Sentry = await import('@sentry/sveltekit');
|
||||||
|
vi.mocked(Sentry.lastEventId).mockReturnValue(undefined);
|
||||||
|
|
||||||
|
const { handleError } = await import('./hooks.server');
|
||||||
|
const result = (handleError as (args: unknown) => { message: string; errorId: string })({
|
||||||
|
error: new Error('boom'),
|
||||||
|
event: makeEvent(),
|
||||||
|
status: 500,
|
||||||
|
message: 'Internal Error'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.errorId).toMatch(
|
||||||
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
||||||
|
);
|
||||||
|
expect(result.message).toBe('An unexpected error occurred');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,6 +10,7 @@ Sentry.init({
|
|||||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||||
environment: import.meta.env.MODE,
|
environment: import.meta.env.MODE,
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
|
sendDefaultPii: false,
|
||||||
enabled: !!import.meta.env.VITE_SENTRY_DSN
|
enabled: !!import.meta.env.VITE_SENTRY_DSN
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,4 +123,7 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => {
|
|||||||
|
|
||||||
export const handle = sequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide);
|
export const handle = sequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide);
|
||||||
|
|
||||||
export const handleError = Sentry.handleErrorWithSentry();
|
export const handleError = Sentry.handleErrorWithSentry(() => {
|
||||||
|
const errorId = Sentry.lastEventId() ?? crypto.randomUUID();
|
||||||
|
return { message: 'An unexpected error occurred', errorId };
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user