Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,9 @@ jobs:
with:
node-version-file: 'dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}/package.json'
- name: Set up Bun
if: contains(fromJSON('["node-exports-test-app","nextjs-16-bun", "elysia-bun"]'), matrix.test-application)
if:
contains(fromJSON('["node-exports-test-app","nextjs-16-bun", "elysia-bun", "hono-4"]'),
matrix.test-application)
uses: oven-sh/setup-bun@v2
- name: Set up AWS SAM
if: matrix.test-application == 'aws-serverless'
Expand Down
36 changes: 36 additions & 0 deletions dev-packages/e2e-tests/test-applications/hono-4/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# prod
dist/

# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf

# deps
node_modules/
.wrangler

# env
.env
.env.production
.dev.vars

# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# test
test-results

# misc
.DS_Store
2 changes: 2 additions & 0 deletions dev-packages/e2e-tests/test-applications/hono-4/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://127.0.0.1:4873
@sentry-internal:registry=http://127.0.0.1:4873
43 changes: 43 additions & 0 deletions dev-packages/e2e-tests/test-applications/hono-4/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "hono-4",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"dev:cf": "wrangler dev --var \"E2E_TEST_DSN:$E2E_TEST_DSN\" --log-level=$(test $CI && echo 'none' || echo 'log')",
"dev:node": "node --import tsx/esm --import @sentry/node/preload src/entry.node.ts",
"dev:bun": "bun src/entry.bun.ts",
"build": "wrangler deploy --dry-run",
"test:build": "pnpm install && pnpm build",
"test:assert": "TEST_ENV=production playwright test"
},
"dependencies": {
"@sentry/hono": "latest || *",
"@sentry/node": "latest || *",
"@hono/node-server": "^1.19.10",
"hono": "^4.12.14"
},
"devDependencies": {
"@playwright/test": "~1.56.0",
"@cloudflare/workers-types": "^4.20240725.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"tsx": "^4.20.3",
"typescript": "^5.5.2",
"wrangler": "^4.61.0"
},
"volta": {
"extends": "../../package.json"
},
"sentryTest": {
"variants": [
{
"assert-command": "RUNTIME=node pnpm test:assert",
"label": "hono-4 (node)"
},
{
"assert-command": "RUNTIME=bun pnpm test:assert",
"label": "hono-4 (bun)"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

type Runtime = 'cloudflare' | 'node' | 'bun';

const RUNTIME = (process.env.RUNTIME || 'cloudflare') as Runtime;

const testEnv = process.env.TEST_ENV;

if (!testEnv) {
throw new Error('No test env defined');
}

const APP_PORT = 38787;

const startCommands: Record<Runtime, string> = {
cloudflare: `pnpm dev:cf --port ${APP_PORT}`,
node: `pnpm dev:node`,
bun: `pnpm dev:bun`,
};

const config = getPlaywrightConfig(
{
startCommand: startCommands[RUNTIME],
port: APP_PORT,
},
{
workers: '100%',
retries: 0,
},
);

export default config;
24 changes: 24 additions & 0 deletions dev-packages/e2e-tests/test-applications/hono-4/src/entry.bun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Hono } from 'hono';
import { sentry } from '@sentry/hono/bun';
import { addRoutes } from './routes';

const app = new Hono<{ Bindings: { E2E_TEST_DSN: string } }>();

app.use(
// @ts-expect-error - Env is not yet in type
sentry(app, {
dsn: process.env.E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
tunnel: 'http://localhost:3031/',
}),
);

addRoutes(app);

const port = Number(process.env.PORT || 38787);

export default {
port,
fetch: app.fetch,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Hono } from 'hono';
import { sentry } from '@sentry/hono/cloudflare';
import { addRoutes } from './routes';

const app = new Hono<{ Bindings: { E2E_TEST_DSN: string } }>();

app.use(
sentry(app, env => ({
dsn: env.E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
tunnel: 'http://localhost:3031/',
})),
);

addRoutes(app);

export default app;
24 changes: 24 additions & 0 deletions dev-packages/e2e-tests/test-applications/hono-4/src/entry.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Hono } from 'hono';
import { sentry } from '@sentry/hono/node';
import { serve } from '@hono/node-server';
import { addRoutes } from './routes';

const app = new Hono<{ Bindings: { E2E_TEST_DSN: string } }>();

app.use(
// @ts-expect-error - Env is not yet in type
sentry(app, {
dsn: process.env.E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
tunnel: 'http://localhost:3031/',
}),
);

addRoutes(app);

const port = Number(process.env.PORT || 38787);

serve({ fetch: app.fetch, port }, () => {
console.log(`Hono (Node) listening on port ${port}`);
});
24 changes: 24 additions & 0 deletions dev-packages/e2e-tests/test-applications/hono-4/src/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';

export function addRoutes(app: Hono<{ Bindings: { E2E_TEST_DSN: string } }>): void {
app.get('/', c => {
return c.text('Hello Hono!');
});

app.get('/test-param/:paramId', c => {
return c.json({ paramId: c.req.param('paramId') });
});

app.get('/error/:cause', c => {
throw new Error('This is a test error for Sentry!', {
cause: c.req.param('cause'),
});
});

app.get('/http-exception/:code', c => {
// oxlint-disable-next-line typescript/no-explicit-any
const code = Number(c.req.param('code')) as any;
throw new HTTPException(code, { message: `HTTPException ${code}` });
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { startEventProxyServer } from '@sentry-internal/test-utils';

startEventProxyServer({
port: 3031,
proxyServerName: 'hono-4',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { expect, test } from '@playwright/test';
import { waitForError } from '@sentry-internal/test-utils';

const APP_NAME = 'hono-4';

test('captures error thrown in route handler', async ({ baseURL }) => {
const errorWaiter = waitForError(APP_NAME, event => {
return event.exception?.values?.[0]?.value === 'This is a test error for Sentry!';
});

const response = await fetch(`${baseURL}/error/test-cause`);
expect(response.status).toBe(500);

const event = await errorWaiter;
expect(event.exception?.values?.[0]?.value).toBe('This is a test error for Sentry!');
});

test('captures HTTPException with 502 status', async ({ baseURL }) => {
const errorWaiter = waitForError(APP_NAME, event => {
return event.exception?.values?.[0]?.value === 'HTTPException 502';
});

const response = await fetch(`${baseURL}/http-exception/502`);
expect(response.status).toBe(502);

const event = await errorWaiter;
expect(event.exception?.values?.[0]?.value).toBe('HTTPException 502');
});

// TODO: 401 and 404 HTTPExceptions should not be captured by Sentry by default,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Is this some todo for this PR or another one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another one

// but currently they are. Fix the filtering and update these tests accordingly.
test('captures HTTPException with 401 status', async ({ baseURL }) => {
const errorWaiter = waitForError(APP_NAME, event => {
return event.exception?.values?.[0]?.value === 'HTTPException 401';
});

const response = await fetch(`${baseURL}/http-exception/401`);
expect(response.status).toBe(401);

const event = await errorWaiter;
expect(event.exception?.values?.[0]?.value).toBe('HTTPException 401');
});

test('captures HTTPException with 404 status', async ({ baseURL }) => {
const errorWaiter = waitForError(APP_NAME, event => {
return event.exception?.values?.[0]?.value === 'HTTPException 404';
});

const response = await fetch(`${baseURL}/http-exception/404`);
expect(response.status).toBe(404);

const event = await errorWaiter;
expect(event.exception?.values?.[0]?.value).toBe('HTTPException 404');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

const APP_NAME = 'hono-4';

test('sends a transaction for the index route', async ({ baseURL }) => {
const transactionWaiter = waitForTransaction(APP_NAME, event => {
return event.transaction === 'GET /';
});

const response = await fetch(`${baseURL}/`);
expect(response.status).toBe(200);

const transaction = await transactionWaiter;
expect(transaction.contexts?.trace?.op).toBe('http.server');
});

test('sends a transaction for a parameterized route', async ({ baseURL }) => {
const transactionWaiter = waitForTransaction(APP_NAME, event => {
return event.transaction === 'GET /test-param/:paramId';
});

const response = await fetch(`${baseURL}/test-param/123`);
expect(response.status).toBe(200);

const transaction = await transactionWaiter;
expect(transaction.contexts?.trace?.op).toBe('http.server');
expect(transaction.transaction).toBe('GET /test-param/:paramId');
});

test('sends a transaction for a route that throws', async ({ baseURL }) => {
const transactionWaiter = waitForTransaction(APP_NAME, event => {
return event.transaction === 'GET /error/:cause';
});

await fetch(`${baseURL}/error/test-cause`);

const transaction = await transactionWaiter;
expect(transaction.contexts?.trace?.op).toBe('http.server');
expect(transaction.contexts?.trace?.status).toBe('internal_error');
});
13 changes: 13 additions & 0 deletions dev-packages/e2e-tests/test-applications/hono-4/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"lib": ["ESNext"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"types": ["@cloudflare/workers-types"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "hono-4",
"main": "src/entry.cloudflare.ts",
"compatibility_date": "2026-04-20",
"compatibility_flags": ["nodejs_compat"],
}
Loading