Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a7ef204

Browse files
committed
test(node): Add testing of Hono Instrumentation
1 parent baed387 commit a7ef204

File tree

6 files changed

+366
-4
lines changed

6 files changed

+366
-4
lines changed

‎dev-packages/node-integration-tests/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"dependencies": {
2626
"@aws-sdk/client-s3": "^3.552.0",
2727
"@hapi/hapi": "^21.3.10",
28+
"@hono/node-server": "^1.18.2",
2829
"@nestjs/common": "11.1.3",
2930
"@nestjs/core": "11.1.3",
3031
"@nestjs/platform-express": "11.1.3",
@@ -45,6 +46,7 @@
4546
"express": "^4.21.1",
4647
"generic-pool": "^3.9.0",
4748
"graphql": "^16.3.0",
49+
"hono": "^4.8.12",
4850
"http-terminator": "^3.2.0",
4951
"ioredis": "^5.4.1",
5052
"kafkajs": "2.2.4",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
});
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { serve } from '@hono/node-server';
2+
import * as Sentry from '@sentry/node';
3+
import { sendPortToRunner } from '@sentry-internal/node-core-integration-tests';
4+
import { Hono } from 'hono';
5+
import { HTTPException } from 'hono/http-exception';
6+
7+
const app = new Hono();
8+
9+
Sentry.setupHonoErrorHandler(app);
10+
11+
// Global middleware to capture all requests
12+
app.use(async function global(c, next) {
13+
await next();
14+
});
15+
16+
const basePaths = ['/sync', '/async'];
17+
const methods = ['get', 'post', 'put', 'delete', 'patch'];
18+
19+
basePaths.forEach(basePath => {
20+
// Sub-path middleware to capture all requests under the basePath
21+
app.use(`${basePath}/*`, async function base(c, next) {
22+
await next();
23+
});
24+
25+
const baseApp = new Hono();
26+
methods.forEach(method => {
27+
baseApp[method]('/', c => {
28+
const response = c.text('response 200');
29+
if (basePath === '/sync') return response;
30+
return Promise.resolve(response);
31+
});
32+
33+
baseApp[method](
34+
'/middleware',
35+
// anonymous middleware
36+
async (c, next) => {
37+
await next();
38+
},
39+
c => {
40+
const response = c.text('response 200');
41+
if (basePath === '/sync') return response;
42+
return Promise.resolve(response);
43+
},
44+
);
45+
46+
baseApp.all('/all', c => {
47+
const response = c.text('response 200');
48+
if (basePath === '/sync') return response;
49+
return Promise.resolve(response);
50+
});
51+
52+
baseApp.all(
53+
'/all/middleware',
54+
// anonymous middleware
55+
async (c, next) => {
56+
await next();
57+
},
58+
c => {
59+
const response = c.text('response 200');
60+
if (basePath === '/sync') return response;
61+
return Promise.resolve(response);
62+
},
63+
);
64+
65+
baseApp.on(method, '/on', c => {
66+
const response = c.text('response 200');
67+
if (basePath === '/sync') return response;
68+
return Promise.resolve(response);
69+
});
70+
71+
baseApp.on(
72+
method,
73+
'/on/middleware',
74+
// anonymous middleware
75+
async (c, next) => {
76+
await next();
77+
},
78+
c => {
79+
const response = c.text('response 200');
80+
if (basePath === '/sync') return response;
81+
return Promise.resolve(response);
82+
},
83+
);
84+
85+
baseApp[method]('/401', () => {
86+
const response = new HTTPException(401, { message: 'response 401' });
87+
if (basePath === '/sync') throw response;
88+
return Promise.reject(response);
89+
});
90+
91+
baseApp.all('/all/401', () => {
92+
const response = new HTTPException(401, { message: 'response 401' });
93+
if (basePath === '/sync') throw response;
94+
return Promise.reject(response);
95+
});
96+
97+
baseApp.on(method, '/on/401', () => {
98+
const response = new HTTPException(401, { message: 'response 401' });
99+
if (basePath === '/sync') throw response;
100+
return Promise.reject(response);
101+
});
102+
103+
baseApp[method]('/402', () => {
104+
const response = new HTTPException(402, { message: 'response 402' });
105+
if (basePath === '/sync') throw response;
106+
return Promise.reject(response);
107+
});
108+
109+
baseApp.all('/all/402', () => {
110+
const response = new HTTPException(402, { message: 'response 402' });
111+
if (basePath === '/sync') throw response;
112+
return Promise.reject(response);
113+
});
114+
115+
baseApp.on(method, '/on/402', () => {
116+
const response = new HTTPException(402, { message: 'response 402' });
117+
if (basePath === '/sync') throw response;
118+
return Promise.reject(response);
119+
});
120+
121+
baseApp[method]('/403', () => {
122+
const response = new HTTPException(403, { message: 'response 403' });
123+
if (basePath === '/sync') throw response;
124+
return Promise.reject(response);
125+
});
126+
127+
baseApp.all('/all/403', () => {
128+
const response = new HTTPException(403, { message: 'response 403' });
129+
if (basePath === '/sync') throw response;
130+
return Promise.reject(response);
131+
});
132+
133+
baseApp.on(method, '/on/403', () => {
134+
const response = new HTTPException(403, { message: 'response 403' });
135+
if (basePath === '/sync') throw response;
136+
return Promise.reject(response);
137+
});
138+
139+
baseApp[method]('/500', () => {
140+
const response = new HTTPException(500, { message: 'response 500' });
141+
if (basePath === '/sync') throw response;
142+
return Promise.reject(response);
143+
});
144+
145+
baseApp.all('/all/500', () => {
146+
const response = new HTTPException(500, { message: 'response 500' });
147+
if (basePath === '/sync') throw response;
148+
return Promise.reject(response);
149+
});
150+
151+
baseApp.on(method, '/on/500', () => {
152+
const response = new HTTPException(500, { message: 'response 500' });
153+
if (basePath === '/sync') throw response;
154+
return Promise.reject(response);
155+
});
156+
});
157+
158+
app.route(basePath, baseApp);
159+
});
160+
161+
const port = 8787;
162+
serve({ fetch: app.fetch, port });
163+
sendPortToRunner(port);
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { afterAll, describe, expect } from 'vitest';
2+
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
3+
4+
describe('hono tracing', () => {
5+
afterAll(() => {
6+
cleanupChildProcesses();
7+
});
8+
9+
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
10+
describe.each(['/sync', '/async'] as const)('when using %s route', route => {
11+
describe.each(['get', 'post', 'put', 'delete', 'patch'] as const)('when using %s method', method => {
12+
describe.each(['/', '/all', '/on'])('when using %s path', path => {
13+
test('should handle transaction', async () => {
14+
const runner = createRunner()
15+
.expect({
16+
transaction: {
17+
transaction: `${method.toUpperCase()} ${route}${path === '/' ? '' : path}`,
18+
spans: expect.arrayContaining([
19+
expect.objectContaining({
20+
data: expect.objectContaining({
21+
'hono.name': 'sentryRequestMiddleware',
22+
'hono.type': 'middleware',
23+
}),
24+
description: 'sentryRequestMiddleware',
25+
op: 'middleware.hono',
26+
origin: 'auto.http.otel.hono',
27+
}),
28+
expect.objectContaining({
29+
data: expect.objectContaining({
30+
'hono.name': 'sentryErrorMiddleware',
31+
'hono.type': 'middleware',
32+
}),
33+
description: 'sentryErrorMiddleware',
34+
op: 'middleware.hono',
35+
origin: 'auto.http.otel.hono',
36+
}),
37+
expect.objectContaining({
38+
data: expect.objectContaining({
39+
'hono.name': 'global',
40+
'hono.type': 'middleware',
41+
}),
42+
description: 'global',
43+
op: 'middleware.hono',
44+
origin: 'auto.http.otel.hono',
45+
}),
46+
expect.objectContaining({
47+
data: expect.objectContaining({
48+
'hono.name': 'base',
49+
'hono.type': 'middleware',
50+
}),
51+
description: 'base',
52+
op: 'middleware.hono',
53+
origin: 'auto.http.otel.hono',
54+
}),
55+
expect.objectContaining({
56+
data: expect.objectContaining({
57+
'hono.name': `${route}${path === '/' ? '' : path}`,
58+
'hono.type': 'request_handler',
59+
}),
60+
description: `${route}${path === '/' ? '' : path}`,
61+
op: 'request_handler.hono',
62+
origin: 'auto.http.otel.hono',
63+
}),
64+
]),
65+
},
66+
})
67+
.start();
68+
runner.makeRequest(method, `${route}${path === '/' ? '' : path}`);
69+
await runner.completed();
70+
});
71+
72+
test('should handle transaction with anonymous middleware', async () => {
73+
const runner = createRunner()
74+
.expect({
75+
transaction: {
76+
transaction: `${method.toUpperCase()} ${route}${path === '/' ? '' : path}/middleware`,
77+
spans: expect.arrayContaining([
78+
expect.objectContaining({
79+
data: expect.objectContaining({
80+
'hono.name': 'sentryRequestMiddleware',
81+
'hono.type': 'middleware',
82+
}),
83+
description: 'sentryRequestMiddleware',
84+
op: 'middleware.hono',
85+
origin: 'auto.http.otel.hono',
86+
}),
87+
expect.objectContaining({
88+
data: expect.objectContaining({
89+
'hono.name': 'sentryErrorMiddleware',
90+
'hono.type': 'middleware',
91+
}),
92+
description: 'sentryErrorMiddleware',
93+
op: 'middleware.hono',
94+
origin: 'auto.http.otel.hono',
95+
}),
96+
expect.objectContaining({
97+
data: expect.objectContaining({
98+
'hono.name': 'global',
99+
'hono.type': 'middleware',
100+
}),
101+
description: 'global',
102+
op: 'middleware.hono',
103+
origin: 'auto.http.otel.hono',
104+
}),
105+
expect.objectContaining({
106+
data: expect.objectContaining({
107+
'hono.name': 'base',
108+
'hono.type': 'middleware',
109+
}),
110+
description: 'base',
111+
op: 'middleware.hono',
112+
origin: 'auto.http.otel.hono',
113+
}),
114+
expect.objectContaining({
115+
data: expect.objectContaining({
116+
'hono.name': 'anonymous',
117+
'hono.type': 'middleware',
118+
}),
119+
description: 'anonymous',
120+
op: 'middleware.hono',
121+
origin: 'auto.http.otel.hono',
122+
}),
123+
expect.objectContaining({
124+
data: expect.objectContaining({
125+
'hono.name': `${route}${path === '/' ? '' : path}/middleware`,
126+
'hono.type': 'request_handler',
127+
}),
128+
description: `${route}${path === '/' ? '' : path}/middleware`,
129+
op: 'request_handler.hono',
130+
origin: 'auto.http.otel.hono',
131+
}),
132+
]),
133+
},
134+
})
135+
.start();
136+
runner.makeRequest(method, `${route}${path === '/' ? '' : path}/middleware`);
137+
await runner.completed();
138+
});
139+
140+
test('should handle returned errors for %s path', async () => {
141+
const runner = createRunner()
142+
.ignore('transaction')
143+
.expect({
144+
event: {
145+
exception: {
146+
values: [
147+
{
148+
type: 'Error',
149+
value: 'response 500',
150+
},
151+
],
152+
},
153+
},
154+
})
155+
.start();
156+
runner.makeRequest(method, `${route}${path === '/' ? '' : path}/500`, { expectError: true });
157+
await runner.completed();
158+
});
159+
160+
test.each(['/401', '/402', '/403', '/does-not-exist'])(
161+
'should ignores error %s path by default',
162+
async (subPath: string) => {
163+
const runner = createRunner()
164+
.expect({
165+
transaction: {
166+
transaction: `${method.toUpperCase()} ${route}`,
167+
},
168+
})
169+
.start();
170+
runner.makeRequest(method, `${route}${path === '/' ? '' : path}${subPath}`, { expectError: true });
171+
runner.makeRequest(method, route);
172+
await runner.completed();
173+
},
174+
);
175+
});
176+
});
177+
});
178+
});
179+
});

‎dev-packages/node-integration-tests/utils/runner.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ type StartResult = {
160160
getLogs(): string[];
161161
getPort(): number | undefined;
162162
makeRequest<T>(
163-
method: 'get' | 'post',
163+
method: 'get' | 'post'|'put'|'delete'|'patch',
164164
path: string,
165165
options?: { headers?: Record<string, string>; data?: BodyInit; expectError?: boolean },
166166
): Promise<T | undefined>;
@@ -632,7 +632,7 @@ export function createRunner(...paths: string[]) {
632632
return scenarioServerPort;
633633
},
634634
makeRequest: async function <T>(
635-
method: 'get' | 'post',
635+
method: 'get' | 'post'|'put'|'delete'|'patch',
636636
path: string,
637637
options: { headers?: Record<string, string>; data?: BodyInit; expectError?: boolean } = {},
638638
): Promise<T | undefined> {
@@ -651,7 +651,7 @@ export function createRunner(...paths: string[]) {
651651
if (process.env.DEBUG) log('making request', method, url, headers, body);
652652

653653
try {
654-
const res = await fetch(url, { headers, method, body });
654+
const res = await fetch(url, { headers, method: method.toUpperCase(), body });
655655

656656
if (!res.ok) {
657657
if (!expectError) {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /