From 9b25b2225cbe5cb9b41e1f2a217313d26dae3c0d Mon Sep 17 00:00:00 2001 From: nabi-noor Date: Thu, 10 Sep 2026 17:11:32 +0500 Subject: [PATCH] test(e2e): add Bun Express test app Co-Authored-By: OpenAI Codex --- .github/workflows/build.yml | 2 +- .../test-applications/bun-express/.gitignore | 1 + .../test-applications/bun-express/build.ts | 20 +++++++++++ .../bun-express/package.json | 26 +++++++++++++++ .../bun-express/playwright.config.mjs | 5 +++ .../test-applications/bun-express/src/app.ts | 28 ++++++++++++++++ .../bun-express/start-event-proxy.mjs | 6 ++++ .../bun-express/tests/errors.test.ts | 33 +++++++++++++++++++ .../bun-express/tests/spans.test.ts | 30 +++++++++++++++++ .../bun-express/tsconfig.json | 11 +++++++ 10 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/build.ts create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/package.json create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/src/app.ts create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/tests/spans.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a9e79c085c5..758add9ba214 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1029,7 +1029,7 @@ jobs: - name: Set up Bun if: contains(fromJSON('["node-exports-test-app","nextjs-16-bun", "elysia-bun", "elysia-bun-static", "hono-4", - "bun-bytecode", "bun-mysql"]'), matrix.test-application) + "bun-bytecode", "bun-express", "bun-mysql"]'), matrix.test-application) uses: oven-sh/setup-bun@v2 with: bun-version: '1.3.14' diff --git a/dev-packages/e2e-tests/test-applications/bun-express/.gitignore b/dev-packages/e2e-tests/test-applications/bun-express/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/.gitignore @@ -0,0 +1 @@ +dist diff --git a/dev-packages/e2e-tests/test-applications/bun-express/build.ts b/dev-packages/e2e-tests/test-applications/bun-express/build.ts new file mode 100644 index 000000000000..a2c2eaefd2a7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/build.ts @@ -0,0 +1,20 @@ +// @ts-ignore -- subpath export resolved by Bun at runtime; the package +// tsconfig's node module resolution can't see `exports` subpaths. +import { sentryBunPlugin } from '@sentry/bun/plugin'; +import { join } from 'path'; + +void (async () => { + const result = await Bun.build({ + entrypoints: [join(__dirname, 'src/app.ts')], + target: 'bun', + outdir: join(__dirname, 'dist'), + external: ['@sentry/bun', 'express'], + plugins: [sentryBunPlugin()], + }); + + if (!result.success) { + // oxlint-disable-next-line no-console + console.error('BUILD_FAILED', result.logs); + process.exit(1); + } +})(); diff --git a/dev-packages/e2e-tests/test-applications/bun-express/package.json b/dev-packages/e2e-tests/test-applications/bun-express/package.json new file mode 100644 index 000000000000..cd2634fe22f4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/package.json @@ -0,0 +1,26 @@ +{ + "name": "bun-express", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "start": "bun run dist/app.js", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml dist", + "test:build": "pnpm install && bun run build.ts", + "test:assert": "pnpm test" + }, + "dependencies": { + "@sentry/bun": "latest || *", + "express": "^5.1.0" + }, + "devDependencies": { + "@playwright/test": "~1.56.0", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@types/express": "^5.0.0", + "bun-types": "^1.2.9" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/bun-express/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/bun-express/playwright.config.mjs new file mode 100644 index 000000000000..ce719004da38 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/playwright.config.mjs @@ -0,0 +1,5 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +export default getPlaywrightConfig({ + startCommand: 'pnpm start', +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-express/src/app.ts b/dev-packages/e2e-tests/test-applications/bun-express/src/app.ts new file mode 100644 index 000000000000..97c8b53a6c26 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/src/app.ts @@ -0,0 +1,28 @@ +import * as Sentry from '@sentry/bun'; +import express from 'express'; + +Sentry.init({ + environment: 'qa', + dsn: process.env.E2E_TEST_DSN, + tunnel: 'http://localhost:3031/', + tracesSampleRate: 1, +}); + +const app = express(); + +app.get('/test-param/:id', (request, response) => { + response.json({ id: request.params.id }); +}); + +app.get('/test-exception/:id', request => { + throw new Error(`This is an exception with id ${request.params.id}`); +}); + +app.use((_error, _request, response, _next) => { + response.status(500).send('Internal Server Error'); +}); + +app.listen(3030, () => { + // oxlint-disable-next-line no-console + console.log('Bun Express app listening on port 3030'); +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-express/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/bun-express/start-event-proxy.mjs new file mode 100644 index 000000000000..61525a11957e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'bun-express', +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-express/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/bun-express/tests/errors.test.ts new file mode 100644 index 000000000000..1fd5c04cbf06 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/tests/errors.test.ts @@ -0,0 +1,33 @@ +import { expect, test } from '@playwright/test'; +import { collectStreamedSpansUntilSegment, waitForError } from '@sentry-internal/test-utils'; + +test('captures an error thrown in an Express route', async ({ baseURL }) => { + const errorEventPromise = waitForError('bun-express', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; + }); + const spansPromise = collectStreamedSpansUntilSegment('bun-express', 'GET /test-exception/:id'); + + const response = await fetch(`${baseURL}/test-exception/123`); + + const errorEvent = await errorEventPromise; + const spans = await spansPromise; + + expect(response.status).toBe(500); + expect(errorEvent.exception?.values).toEqual([ + expect.objectContaining({ + value: 'This is an exception with id 123', + mechanism: { + type: 'auto.http.express', + handled: false, + }, + }), + ]); + expect(errorEvent.transaction).toBe('GET /test-exception/:id'); + expect(errorEvent.contexts?.runtime?.name).toBe('bun'); + + const rootSpan = spans.find(span => span.is_segment); + expect(rootSpan?.name).toBe('GET /test-exception/:id'); + expect(rootSpan?.status).toBe('error'); + expect(rootSpan?.attributes['http.response.status_code']?.value).toBe(500); + expect(errorEvent.contexts?.trace?.trace_id).toBe(rootSpan?.trace_id); +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-express/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/bun-express/tests/spans.test.ts new file mode 100644 index 000000000000..050e42ab35a8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/tests/spans.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; + +test('instruments an Express route under Bun', async ({ baseURL }) => { + const spansPromise = collectStreamedSpansUntilSegment('bun-express', 'GET /test-param/:id'); + + const response = await fetch(`${baseURL}/test-param/123`); + const spans = await spansPromise; + + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ id: '123' }); + + const rootSpan = spans.find(span => span.is_segment); + expect(rootSpan?.name).toBe('GET /test-param/:id'); + expect(rootSpan && getSpanOp(rootSpan)).toBe('http.server'); + expect(rootSpan?.status).toBe('ok'); + expect(rootSpan?.attributes['sentry.segment.name.source']?.value).toBe('route'); + + expect(spans).toContainEqual( + expect.objectContaining({ + name: '/test-param/:id', + attributes: expect.objectContaining({ + 'sentry.origin': { value: 'auto.http.express', type: 'string' }, + 'sentry.op': { value: 'handler', type: 'string' }, + 'http.route': { value: '/test-param/:id', type: 'string' }, + 'express.type': { value: 'request_handler', type: 'string' }, + }), + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-express/tsconfig.json b/dev-packages/e2e-tests/test-applications/bun-express/tsconfig.json new file mode 100644 index 000000000000..ef784cd1e0b4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "types": ["bun-types"], + "esModuleInterop": true, + "lib": ["es2020"], + "strict": true, + "outDir": "dist", + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "build.ts"] +}