Skip to content

Commit a1b3cf0

Browse files
committed
add tests
1 parent f9ace4a commit a1b3cf0

File tree

2 files changed

+117
-10
lines changed

2 files changed

+117
-10
lines changed

packages/cloudflare/src/integrations/hono.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,35 @@ export function getHonoIntegration(): ReturnType<typeof _honoIntegration> | unde
2222
if (!client) {
2323
return undefined;
2424
} else {
25-
return client.getIntegrationByName(_honoIntegration.name);
25+
return client.getIntegrationByName(INTEGRATION_NAME) as ReturnType<typeof _honoIntegration> | undefined;
2626
}
2727
}
2828

29-
// todo: implement this
3029
function isHonoError(err: unknown): err is HonoError {
31-
// @ts-ignore
32-
return 'status' in err;
30+
if (err instanceof Error) {
31+
return true;
32+
}
33+
return typeof err === 'object' && err !== null && 'status' in (err as Record<string, unknown>);
3334
}
3435

35-
const _honoIntegration = ((options: Partial<Options> = {}) => {
36-
let _shouldHandleError: (error: Error) => boolean;
36+
// outside of integration to prevent resetting the variable
37+
let _shouldHandleError: (error: Error) => boolean;
3738

39+
const _honoIntegration = ((options: Partial<Options> = {}) => {
3840
return {
3941
name: INTEGRATION_NAME,
4042
setupOnce() {
4143
_shouldHandleError = options.shouldHandleError || defaultShouldHandleError;
4244
},
4345
handleHonoException(err: HonoError): void {
44-
if (!isHonoError) {
45-
DEBUG_BUILD && debug.log('Hono integration could not detect a Hono error');
46+
if (!isHonoError(err)) {
47+
DEBUG_BUILD && debug.log("[Hono] Won't capture exception in `onError` because it's not a Hono error.", err);
4648
return;
4749
}
4850
if (_shouldHandleError(err)) {
4951
captureException(err, { mechanism: { handled: false, type: 'auto.faas.cloudflare.error_handler' } });
52+
} else {
53+
DEBUG_BUILD && debug.log('[Hono] Not capturing exception because `shouldHandleError` returned `false`.', err);
5054
}
5155
},
5256
};
@@ -55,7 +59,14 @@ const _honoIntegration = ((options: Partial<Options> = {}) => {
5559
/**
5660
* Automatically captures exceptions caught with the `onError` handler in Hono.
5761
*
58-
* The integration is added by default.
62+
* The integration is enabled by default.
63+
*
64+
* @example
65+
* integrations: [
66+
* honoIntegration({
67+
* shouldHandleError: (err) => true; // always capture exceptions in onError
68+
* })
69+
* ]
5970
*/
6071
export const honoIntegration = defineIntegration(_honoIntegration);
6172

@@ -65,7 +76,6 @@ export const honoIntegration = defineIntegration(_honoIntegration);
6576
* 3xx and 4xx errors are not sent by default.
6677
*/
6778
function defaultShouldHandleError(error: HonoError): boolean {
68-
// todo: add test for checking error without status
6979
const statusCode = error?.status;
7080
// 3xx and 4xx errors are not sent by default.
7181
return statusCode ? statusCode >= 500 || statusCode <= 299 : true;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as sentryCore from '@sentry/core';
2+
import { type Client, createStackParser } from '@sentry/core';
3+
import { beforeEach, describe, expect, it, vi } from 'vitest';
4+
import { CloudflareClient } from '../../src/client';
5+
import { honoIntegration } from '../../src/integrations/hono';
6+
7+
class FakeClient extends CloudflareClient {
8+
public getIntegrationByName(name: string) {
9+
return name === 'Hono' ? (honoIntegration() as any) : undefined;
10+
}
11+
}
12+
13+
vi.mock('../../src/debug-build', () => ({
14+
DEBUG_BUILD: true,
15+
}));
16+
17+
type MockHonoIntegrationType = { handleHonoException: (err: Error) => void };
18+
19+
describe('Hono integration', () => {
20+
let client: FakeClient;
21+
22+
beforeEach(() => {
23+
vi.clearAllMocks();
24+
client = new FakeClient({
25+
dsn: 'https://[email protected]/1337',
26+
integrations: [],
27+
transport: () => ({ send: () => Promise.resolve({}), flush: () => Promise.resolve(true) }),
28+
stackParser: createStackParser(),
29+
});
30+
vi.spyOn(sentryCore, 'getClient').mockImplementation(() => client as Client);
31+
});
32+
33+
it('captures in errorHandler when onError exists', () => {
34+
const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
35+
const integration = honoIntegration();
36+
integration.setupOnce?.();
37+
38+
const error = new Error('hono boom');
39+
// simulate withSentry wrapping of errorHandler calling back into integration
40+
(integration as unknown as MockHonoIntegrationType).handleHonoException(error);
41+
42+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
43+
expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, {
44+
mechanism: { handled: false, type: 'auto.faas.cloudflare.error_handler' },
45+
});
46+
});
47+
48+
it('does not capture for 4xx status', () => {
49+
const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
50+
const integration = honoIntegration();
51+
integration.setupOnce?.();
52+
53+
(integration as unknown as MockHonoIntegrationType).handleHonoException(
54+
Object.assign(new Error('client err'), { status: 404 }),
55+
);
56+
expect(captureExceptionSpy).not.toHaveBeenCalled();
57+
});
58+
59+
it('does not capture for 3xx status', () => {
60+
const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
61+
const integration = honoIntegration();
62+
integration.setupOnce?.();
63+
64+
(integration as unknown as MockHonoIntegrationType).handleHonoException(
65+
Object.assign(new Error('redirect'), { status: 302 }),
66+
);
67+
expect(captureExceptionSpy).not.toHaveBeenCalled();
68+
});
69+
70+
it('captures for 5xx status', () => {
71+
const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
72+
const integration = honoIntegration();
73+
integration.setupOnce?.();
74+
75+
const err = Object.assign(new Error('server err'), { status: 500 });
76+
(integration as unknown as MockHonoIntegrationType).handleHonoException(err);
77+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
78+
});
79+
80+
it('captures if no status is present on Error', () => {
81+
const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
82+
const integration = honoIntegration();
83+
integration.setupOnce?.();
84+
85+
(integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('no status'));
86+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
87+
});
88+
89+
it('supports custom shouldHandleError option', () => {
90+
const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
91+
const integration = honoIntegration({ shouldHandleError: () => false });
92+
integration.setupOnce?.();
93+
94+
(integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('blocked'));
95+
expect(captureExceptionSpy).not.toHaveBeenCalled();
96+
});
97+
});

0 commit comments

Comments
 (0)