Skip to content

Commit afddce3

Browse files
committed
add case for load
1 parent f867447 commit afddce3

File tree

5 files changed

+73
-28
lines changed

5 files changed

+73
-28
lines changed

packages/sveltekit/.eslintrc.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,14 @@ module.exports = {
33
browser: true,
44
node: true,
55
},
6+
overrides: [
7+
{
8+
files: ['*.ts'],
9+
rules: {
10+
// Turning this off because it's not working with @sveltejs/kit
11+
'import/no-unresolved': 'off',
12+
},
13+
},
14+
],
615
extends: ['../../.eslintrc.js'],
716
};

packages/sveltekit/src/server/load.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { captureException } from '@sentry/node';
22
import { addExceptionMechanism, objectify } from '@sentry/utils';
3-
// eslint-disable-next-line import/no-unresolved
4-
import type { ServerLoad } from '@sveltejs/kit';
3+
import type { HttpError, ServerLoad } from '@sveltejs/kit';
4+
5+
function isHttpError(err: unknown): err is HttpError {
6+
return typeof err === 'object' && err !== null && 'status' in err && 'body' in err;
7+
}
58

69
/**
710
* Wrap load function with Sentry
@@ -16,7 +19,14 @@ export function wrapLoadWithSentry(origLoad: ServerLoad): ServerLoad {
1619
} catch (e) {
1720
// In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can
1821
// store a seen flag on it.
19-
const objectifiedErr = objectify(e);
22+
const objectifiedErr = objectify(e) as unknown;
23+
24+
// The error() helper is commonly used to throw errors in load functions: https://kit.svelte.dev/docs/modules#sveltejs-kit-error
25+
// If we detect a thrown error that is an instance of HttpError, we don't want to capture 4xx errors as they
26+
// could be noisy.
27+
if (isHttpError(objectifiedErr) && objectifiedErr.status < 500 && objectifiedErr.status >= 400) {
28+
throw objectifiedErr;
29+
}
2030

2131
captureException(objectifiedErr, scope => {
2232
scope.addEventProcessor(event => {

packages/sveltekit/test/client/handleError.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import { Scope } from '@sentry/svelte';
2-
// For now disable the import/no-unresolved rule, because we don't have a way to
3-
// tell eslint that we are only importing types from the @sveltejs/kit package without
4-
// adding a custom resolver, which will take too much time.
5-
// eslint-disable-next-line import/no-unresolved
62
import type { HandleClientError, NavigationEvent } from '@sveltejs/kit';
73
import { vi } from 'vitest';
84

packages/sveltekit/test/server/handleError.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import { Scope } from '@sentry/node';
2-
// For now disable the import/no-unresolved rule, because we don't have a way to
3-
// tell eslint that we are only importing types from the @sveltejs/kit package without
4-
// adding a custom resolver, which will take too much time.
5-
// eslint-disable-next-line import/no-unresolved
62
import type { HandleServerError, RequestEvent } from '@sveltejs/kit';
73
import { vi } from 'vitest';
84

@@ -80,6 +76,6 @@ describe('handleError', () => {
8076

8177
expect(addEventProcessorSpy).toBeCalledTimes(1);
8278
expect(mockAddExceptionMechanism).toBeCalledTimes(1);
83-
expect(mockAddExceptionMechanism).toBeCalledWith({}, { handled: false, type: 'sveltekit' });
79+
expect(mockAddExceptionMechanism).toBeCalledWith({}, { handled: false, type: 'instrument' });
8480
});
8581
});

packages/sveltekit/test/server/load.test.ts

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { Scope } from '@sentry/node';
2-
// eslint-disable-next-line import/no-unresolved
32
import type { ServerLoad } from '@sveltejs/kit';
3+
import { error } from '@sveltejs/kit';
4+
import { vi } from 'vitest';
45

56
import { wrapLoadWithSentry } from '../../src/server/load';
67

7-
const mockCaptureException = jest.fn();
8+
const mockCaptureException = vi.fn();
89
let mockScope = new Scope();
910

10-
jest.mock('@sentry/node', () => {
11-
const original = jest.requireActual('@sentry/node');
11+
vi.mock('@sentry/node', async () => {
12+
const original = (await vi.importActual('@sentry/node')) as any;
1213
return {
1314
...original,
1415
captureException: (err: unknown, cb: (arg0: unknown) => unknown) => {
@@ -19,10 +20,10 @@ jest.mock('@sentry/node', () => {
1920
};
2021
});
2122

22-
const mockAddExceptionMechanism = jest.fn();
23+
const mockAddExceptionMechanism = vi.fn();
2324

24-
jest.mock('@sentry/utils', () => {
25-
const original = jest.requireActual('@sentry/utils');
25+
vi.mock('@sentry/utils', async () => {
26+
const original = (await vi.importActual('@sentry/utils')) as any;
2627
return {
2728
...original,
2829
addExceptionMechanism: (...args: unknown[]) => mockAddExceptionMechanism(...args),
@@ -33,12 +34,6 @@ function getById(_id?: string) {
3334
throw new Error('error');
3435
}
3536

36-
async function erroringLoad({ params }: Parameters<ServerLoad>[0]): Promise<ReturnType<ServerLoad>> {
37-
return {
38-
post: getById(params.id),
39-
};
40-
}
41-
4237
describe('wrapLoadWithSentry', () => {
4338
beforeEach(() => {
4439
mockCaptureException.mockClear();
@@ -47,20 +42,59 @@ describe('wrapLoadWithSentry', () => {
4742
});
4843

4944
it('calls captureException', async () => {
50-
const wrappedLoad = wrapLoadWithSentry(erroringLoad);
45+
async function load({ params }: Parameters<ServerLoad>[0]): Promise<ReturnType<ServerLoad>> {
46+
return {
47+
post: getById(params.id),
48+
};
49+
}
50+
51+
const wrappedLoad = wrapLoadWithSentry(load);
5152
const res = wrappedLoad({ params: { id: '1' } } as any);
5253
await expect(res).rejects.toThrow();
5354

5455
expect(mockCaptureException).toHaveBeenCalledTimes(1);
5556
});
5657

58+
describe('with error() helper', () => {
59+
it.each([
60+
// [statusCode, timesCalled]
61+
[400, 0],
62+
[401, 0],
63+
[403, 0],
64+
[404, 0],
65+
[409, 0],
66+
[429, 0],
67+
[499, 0],
68+
[500, 1],
69+
[501, 1],
70+
[503, 1],
71+
[504, 1],
72+
])('error with status code %s calls captureException %s times', async (code, times) => {
73+
async function load({ params }: Parameters<ServerLoad>[0]): Promise<ReturnType<ServerLoad>> {
74+
throw error(code, params.id);
75+
}
76+
77+
const wrappedLoad = wrapLoadWithSentry(load);
78+
const res = wrappedLoad({ params: { id: '1' } } as any);
79+
await expect(res).rejects.toThrow();
80+
81+
expect(mockCaptureException).toHaveBeenCalledTimes(times);
82+
});
83+
});
84+
5785
it('adds an exception mechanism', async () => {
58-
const addEventProcessorSpy = jest.spyOn(mockScope, 'addEventProcessor').mockImplementationOnce(callback => {
86+
const addEventProcessorSpy = vi.spyOn(mockScope, 'addEventProcessor').mockImplementationOnce(callback => {
5987
void callback({}, { event_id: 'fake-event-id' });
6088
return mockScope;
6189
});
6290

63-
const wrappedLoad = wrapLoadWithSentry(erroringLoad);
91+
async function load({ params }: Parameters<ServerLoad>[0]): Promise<ReturnType<ServerLoad>> {
92+
return {
93+
post: getById(params.id),
94+
};
95+
}
96+
97+
const wrappedLoad = wrapLoadWithSentry(load);
6498
const res = wrappedLoad({ params: { id: '1' } } as any);
6599
await expect(res).rejects.toThrow();
66100

0 commit comments

Comments
 (0)