diff --git a/dev-packages/node-integration-tests/suites/express/handle-error-scope-data-loss/test.ts b/dev-packages/node-integration-tests/suites/express/handle-error-scope-data-loss/test.ts deleted file mode 100644 index cb5d20760c00..000000000000 --- a/dev-packages/node-integration-tests/suites/express/handle-error-scope-data-loss/test.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -/** - * Why does this test exist? - * - * We recently discovered that errors caught by global handlers will potentially loose scope data from the active scope - * where the error was originally thrown in. The simple example in this test (see subject.ts) demonstrates this behavior - * (in a Node environment but the same behavior applies to the browser; see the test there). - * - * This test nevertheless covers the behavior so that we're aware. - */ -test('withScope scope is NOT applied to thrown error caught by global handler', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - exception: { - values: [ - { - mechanism: { - type: 'auto.middleware.express', - handled: false, - }, - type: 'Error', - value: 'test_error', - stacktrace: { - frames: expect.arrayContaining([ - expect.objectContaining({ - function: expect.any(String), - lineno: expect.any(Number), - colno: expect.any(Number), - }), - ]), - }, - }, - ], - }, - // 'local' tag is not applied to the event - tags: expect.not.objectContaining({ local: expect.anything() }), - }, - }) - .start(); - - runner.makeRequest('get', '/test/withScope', { expectError: true }); - - await runner.completed(); -}); - -/** - * This test shows that the isolation scope set tags are applied correctly to the error. - */ -test('http requestisolation scope is applied to thrown error caught by global handler', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - exception: { - values: [ - { - mechanism: { - type: 'auto.middleware.express', - handled: false, - }, - type: 'Error', - value: 'isolation_test_error', - stacktrace: { - frames: expect.arrayContaining([ - expect.objectContaining({ - function: expect.any(String), - lineno: expect.any(Number), - colno: expect.any(Number), - }), - ]), - }, - }, - ], - }, - tags: { - global: 'tag', - 'isolation-scope': 'tag', - }, - }, - }) - .start(); - - runner.makeRequest('get', '/test/isolationScope', { expectError: true }); - - await runner.completed(); -}); - -/** - * This test shows that an inner isolation scope, created via `withIsolationScope`, is not applied to the error. - * - * This behaviour occurs because, just like in the test above where we use `getIsolationScope().setTag`, - * this isolation scope again is only valid as long as we're in the callback. - * - * So why _does_ the http isolation scope get applied then? Because express' error handler applies on - * a per-request basis, meaning, it's called while we're inside the isolation scope of the http request, - * created from our `httpIntegration`. - */ -test('withIsolationScope scope is NOT applied to thrown error caught by global handler', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - exception: { - values: [ - { - mechanism: { - type: 'auto.middleware.express', - handled: false, - }, - type: 'Error', - value: 'with_isolation_scope_test_error', - stacktrace: { - frames: expect.arrayContaining([ - expect.objectContaining({ - function: expect.any(String), - lineno: expect.any(Number), - colno: expect.any(Number), - }), - ]), - }, - }, - ], - }, - // 'with-isolation-scope' tag is not applied to the event - tags: expect.not.objectContaining({ 'with-isolation-scope': expect.anything() }), - }, - }) - .start(); - - runner.makeRequest('get', '/test/withIsolationScope', { expectError: true }); - - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-0/server.ts b/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-0/server.ts deleted file mode 100644 index 329d658d905a..000000000000 --- a/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-0/server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, - tracesSampleRate: 0, -}); - -import express from 'express'; - -const app = express(); - -app.get('/test/express/:id', req => { - throw new Error(`test_error with id ${req.params.id}`); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-0/test.ts b/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-0/test.ts deleted file mode 100644 index b8cc2cf53d34..000000000000 --- a/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-0/test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should capture and send Express controller error with txn name if tracesSampleRate is 0', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - exception: { - values: [ - { - mechanism: { - type: 'auto.middleware.express', - handled: false, - }, - type: 'Error', - value: 'test_error with id 123', - stacktrace: { - frames: expect.arrayContaining([ - expect.objectContaining({ - function: expect.any(String), - lineno: expect.any(Number), - colno: expect.any(Number), - }), - ]), - }, - }, - ], - }, - transaction: 'GET /test/express/:id', - }, - }) - .start(); - runner.makeRequest('get', '/test/express/123', { expectError: true }); - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-unset/server.ts b/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-unset/server.ts deleted file mode 100644 index eb7044faee97..000000000000 --- a/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-unset/server.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, -}); - -import express from 'express'; - -const app = express(); - -app.get('/test/express/:id', req => { - throw new Error(`test_error with id ${req.params.id}`); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-unset/test.ts b/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-unset/test.ts deleted file mode 100644 index a5f9a1332866..000000000000 --- a/dev-packages/node-integration-tests/suites/express/handle-error-tracesSampleRate-unset/test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should capture and send Express controller error if tracesSampleRate is not set.', async () => { - const runner = createRunner(__dirname, 'server.ts') - .ignore('transaction') - .expect({ - event: { - exception: { - values: [ - { - mechanism: { - type: 'auto.middleware.express', - handled: false, - }, - type: 'Error', - value: 'test_error with id 123', - stacktrace: { - frames: expect.arrayContaining([ - expect.objectContaining({ - function: expect.any(String), - lineno: expect.any(Number), - colno: expect.any(Number), - }), - ]), - }, - }, - ], - }, - }, - }) - .start(); - - runner.makeRequest('get', '/test/express/123', { expectError: true }); - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error/instrument-no-tracing.mjs b/dev-packages/node-integration-tests/suites/express/handle-error/instrument-no-tracing.mjs new file mode 100644 index 000000000000..9ffde125d498 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/handle-error/instrument-no-tracing.mjs @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error/instrument-sample-rate-0.mjs b/dev-packages/node-integration-tests/suites/express/handle-error/instrument-sample-rate-0.mjs new file mode 100644 index 000000000000..224bb9b211e5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/handle-error/instrument-sample-rate-0.mjs @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, + tracesSampleRate: 0, +}); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error/instrument.mjs b/dev-packages/node-integration-tests/suites/express/handle-error/instrument.mjs new file mode 100644 index 000000000000..c463f38d8f82 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/handle-error/instrument.mjs @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error/scenario-should-handle-error.mjs b/dev-packages/node-integration-tests/suites/express/handle-error/scenario-should-handle-error.mjs new file mode 100644 index 000000000000..335e89107e58 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/handle-error/scenario-should-handle-error.mjs @@ -0,0 +1,24 @@ +import * as Sentry from '@sentry/node'; +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import cors from 'cors'; +import express from 'express'; + +const app = express(); + +app.use(cors()); + +app.get('/test1', (_req, _res) => { + throw new Error('error_1'); +}); + +app.get('/test2', (_req, _res) => { + throw new Error('error_2'); +}); + +Sentry.setupExpressErrorHandler(app, { + shouldHandleError: error => { + return error.message === 'error_2'; + }, +}); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error-scope-data-loss/server.ts b/dev-packages/node-integration-tests/suites/express/handle-error/scenario.mjs similarity index 75% rename from dev-packages/node-integration-tests/suites/express/handle-error-scope-data-loss/server.ts rename to dev-packages/node-integration-tests/suites/express/handle-error/scenario.mjs index 8f594e449162..2ec840387f91 100644 --- a/dev-packages/node-integration-tests/suites/express/handle-error-scope-data-loss/server.ts +++ b/dev-packages/node-integration-tests/suites/express/handle-error/scenario.mjs @@ -1,18 +1,15 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import express from 'express'; const app = express(); Sentry.setTag('global', 'tag'); +app.get('/test/express/:id', req => { + throw new Error(`test_error with id ${req.params.id}`); +}); + app.get('/test/withScope', () => { Sentry.withScope(scope => { scope.setTag('local', 'tag'); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error/server.ts b/dev-packages/node-integration-tests/suites/express/handle-error/server.ts deleted file mode 100644 index ba8fb32cc108..000000000000 --- a/dev-packages/node-integration-tests/suites/express/handle-error/server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1, - transport: loggingTransport, -}); - -import express from 'express'; - -const app = express(); - -app.get('/test/express/:id', req => { - throw new Error(`test_error with id ${req.params.id}`); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/handle-error/test.ts b/dev-packages/node-integration-tests/suites/express/handle-error/test.ts index e66850393c78..5819a322e0c6 100644 --- a/dev-packages/node-integration-tests/suites/express/handle-error/test.ts +++ b/dev-packages/node-integration-tests/suites/express/handle-error/test.ts @@ -1,53 +1,294 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../utils/runner'; -afterAll(() => { - cleanupChildProcesses(); -}); +describe('express error handling', () => { + afterAll(() => { + cleanupChildProcesses(); + }); -test('should capture and send Express controller error with txn name if tracesSampleRate is 1', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /test/express/:id', - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: expect.objectContaining({ - 'http.response.status_code': 500, - }), - }, - }, - }, - }) - .expect({ - event: { - exception: { - values: [ - { - mechanism: { - type: 'auto.middleware.express', - handled: false, - }, - type: 'Error', - value: 'test_error with id 123', - stacktrace: { - frames: expect.arrayContaining([ - expect.objectContaining({ - function: expect.any(String), - lineno: expect.any(Number), - colno: expect.any(Number), + describe('tracesSampleRate: 1', () => { + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('should capture and send Express controller error with txn name if tracesSampleRate is 1', async () => { + const runner = createRunner() + .unordered() + .expect({ + transaction: { + transaction: 'GET /test/express/:id', + contexts: { + trace: { + op: 'http.server', + status: 'internal_error', + data: expect.objectContaining({ + 'http.response.status_code': 500, }), - ]), + }, + }, + }, + }) + .expect({ + event: { + exception: { + values: [ + { + mechanism: { + type: 'auto.middleware.express', + handled: false, + }, + type: 'Error', + value: 'test_error with id 123', + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: expect.any(String), + lineno: expect.any(Number), + colno: expect.any(Number), + }), + ]), + }, + }, + ], + }, + transaction: 'GET /test/express/:id', + }, + }) + .start(); + runner.makeRequest('get', '/test/express/123', { expectError: true }); + await runner.completed(); + }); + }); + }); + + describe('tracesSampleRate: 0', () => { + createCjsTests(__dirname, 'scenario.mjs', 'instrument-sample-rate-0.mjs', (createRunner, test) => { + test('should capture and send Express controller error with txn name if tracesSampleRate is 0', async () => { + const runner = createRunner() + .expect({ + event: { + exception: { + values: [ + { + mechanism: { + type: 'auto.middleware.express', + handled: false, + }, + type: 'Error', + value: 'test_error with id 123', + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: expect.any(String), + lineno: expect.any(Number), + colno: expect.any(Number), + }), + ]), + }, + }, + ], + }, + transaction: 'GET /test/express/:id', + }, + }) + .start(); + runner.makeRequest('get', '/test/express/123', { expectError: true }); + await runner.completed(); + }); + }); + }); + + describe('without tracing', () => { + createCjsTests(__dirname, 'scenario.mjs', 'instrument-no-tracing.mjs', (createRunner, test) => { + test('should capture and send Express controller error if tracesSampleRate is not set.', async () => { + const runner = createRunner() + .ignore('transaction') + .expect({ + event: { + exception: { + values: [ + { + mechanism: { + type: 'auto.middleware.express', + handled: false, + }, + type: 'Error', + value: 'test_error with id 123', + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: expect.any(String), + lineno: expect.any(Number), + colno: expect.any(Number), + }), + ]), + }, + }, + ], + }, + }, + }) + .start(); + + runner.makeRequest('get', '/test/express/123', { expectError: true }); + await runner.completed(); + }); + + /** + * Why does this test exist? + * + * We recently discovered that errors caught by global handlers will potentially loose scope data from the active scope + * where the error was originally thrown in. The simple example in this test (see scenario.mjs) demonstrates this behavior + * (in a Node environment but the same behavior applies to the browser; see the test there). + * + * This test nevertheless covers the behavior so that we're aware. + */ + test('withScope scope is NOT applied to thrown error caught by global handler', async () => { + const runner = createRunner() + .expect({ + event: { + exception: { + values: [ + { + mechanism: { + type: 'auto.middleware.express', + handled: false, + }, + type: 'Error', + value: 'test_error', + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: expect.any(String), + lineno: expect.any(Number), + colno: expect.any(Number), + }), + ]), + }, + }, + ], }, + // 'local' tag is not applied to the event + tags: expect.not.objectContaining({ local: expect.anything() }), }, - ], - }, - transaction: 'GET /test/express/:id', - }, - }) - .start(); - runner.makeRequest('get', '/test/express/123', { expectError: true }); - await runner.completed(); + }) + .start(); + + runner.makeRequest('get', '/test/withScope', { expectError: true }); + + await runner.completed(); + }); + + /** + * This test shows that the isolation scope set tags are applied correctly to the error. + */ + test('http requestisolation scope is applied to thrown error caught by global handler', async () => { + const runner = createRunner() + .expect({ + event: { + exception: { + values: [ + { + mechanism: { + type: 'auto.middleware.express', + handled: false, + }, + type: 'Error', + value: 'isolation_test_error', + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: expect.any(String), + lineno: expect.any(Number), + colno: expect.any(Number), + }), + ]), + }, + }, + ], + }, + tags: { + global: 'tag', + 'isolation-scope': 'tag', + }, + }, + }) + .start(); + + runner.makeRequest('get', '/test/isolationScope', { expectError: true }); + + await runner.completed(); + }); + + /** + * This test shows that an inner isolation scope, created via `withIsolationScope`, is not applied to the error. + * + * This behaviour occurs because, just like in the test above where we use `getIsolationScope().setTag`, + * this isolation scope again is only valid as long as we're in the callback. + * + * So why _does_ the http isolation scope get applied then? Because express' error handler applies on + * a per-request basis, meaning, it's called while we're inside the isolation scope of the http request, + * created from our `httpIntegration`. + */ + test('withIsolationScope scope is NOT applied to thrown error caught by global handler', async () => { + const runner = createRunner() + .expect({ + event: { + exception: { + values: [ + { + mechanism: { + type: 'auto.middleware.express', + handled: false, + }, + type: 'Error', + value: 'with_isolation_scope_test_error', + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: expect.any(String), + lineno: expect.any(Number), + colno: expect.any(Number), + }), + ]), + }, + }, + ], + }, + // 'with-isolation-scope' tag is not applied to the event + tags: expect.not.objectContaining({ 'with-isolation-scope': expect.anything() }), + }, + }) + .start(); + + runner.makeRequest('get', '/test/withIsolationScope', { expectError: true }); + + await runner.completed(); + }); + }); + }); + + describe('setupExpressErrorHandler options', () => { + createCjsTests(__dirname, 'scenario-should-handle-error.mjs', 'instrument-no-tracing.mjs', (createRunner, test) => { + test('allows to pass options to setupExpressErrorHandler', async () => { + const runner = createRunner() + .expect({ + event: { + exception: { + values: [ + { + value: 'error_2', + }, + ], + }, + }, + }) + .start(); + + // this error is filtered & ignored + runner.makeRequest('get', '/test1', { expectError: true }); + // this error is actually captured + runner.makeRequest('get', '/test2', { expectError: true }); + + await runner.completed(); + }); + }); + }); }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-init/instrument.mjs b/dev-packages/node-integration-tests/suites/express/multiple-init/instrument.mjs new file mode 100644 index 000000000000..694a6510c925 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/multiple-init/instrument.mjs @@ -0,0 +1,13 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + // No dsn, means client is disabled + // dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, +}); + +// We add http integration to ensure request isolation etc. works +const initialClient = Sentry.getClient(); +initialClient?.addIntegration(Sentry.httpIntegration()); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-init/scenario.mjs similarity index 83% rename from dev-packages/node-integration-tests/suites/express/multiple-init/server.ts rename to dev-packages/node-integration-tests/suites/express/multiple-init/scenario.mjs index 0f5bf73d7e5b..ed934c5ee08c 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-init/scenario.mjs @@ -1,24 +1,12 @@ import * as Sentry from '@sentry/node'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import express from 'express'; -Sentry.init({ - // No dsn, means client is disabled - // dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, -}); - -// We add http integration to ensure request isolation etc. works -const initialClient = Sentry.getClient(); -initialClient?.addIntegration(Sentry.httpIntegration()); +const app = express(); // Store this so we can update the client later const initialCurrentScope = Sentry.getCurrentScope(); -import express from 'express'; - -const app = express(); - Sentry.setTag('global', 'tag'); app.get('/test/no-init', (_req, res) => { diff --git a/dev-packages/node-integration-tests/suites/express/multiple-init/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-init/test.ts index 7ab331485e7d..edf13aef0477 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-init/test.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-init/test.ts @@ -1,73 +1,77 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../utils/runner'; -afterAll(() => { - cleanupChildProcesses(); -}); +describe('express multiple init', () => { + afterAll(() => { + cleanupChildProcesses(); + }); -test('allows to call init multiple times', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - exception: { - values: [ - { - value: 'This is an exception 2', + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('allows to call init multiple times', async () => { + const runner = createRunner() + .expect({ + event: { + exception: { + values: [ + { + value: 'This is an exception 2', + }, + ], + }, + breadcrumbs: [ + { + message: 'error breadcrumb 2', + timestamp: expect.any(Number), + }, + ], + tags: { + global: 'tag', + error: '2', }, - ], - }, - breadcrumbs: [ - { - message: 'error breadcrumb 2', - timestamp: expect.any(Number), }, - ], - tags: { - global: 'tag', - error: '2', - }, - }, - }) - .expect({ - event: { - exception: { - values: [ - { - value: 'This is an exception 3', + }) + .expect({ + event: { + exception: { + values: [ + { + value: 'This is an exception 3', + }, + ], + }, + breadcrumbs: [ + { + message: 'error breadcrumb 3', + timestamp: expect.any(Number), + }, + ], + tags: { + global: 'tag', + error: '3', }, - ], - }, - breadcrumbs: [ - { - message: 'error breadcrumb 3', - timestamp: expect.any(Number), }, - ], - tags: { - global: 'tag', - error: '3', - }, - }, - }) - .expect({ - event: { - exception: { - values: [ - { - value: 'Final exception was captured', + }) + .expect({ + event: { + exception: { + values: [ + { + value: 'Final exception was captured', + }, + ], }, - ], - }, - }, - }) - .start(); + }, + }) + .start(); - runner - .makeRequest('get', '/test/no-init') - .then(() => runner.makeRequest('get', '/test/error/1')) - .then(() => runner.makeRequest('get', '/test/init')) - .then(() => runner.makeRequest('get', '/test/error/2')) - .then(() => runner.makeRequest('get', '/test/error/3')); + runner + .makeRequest('get', '/test/no-init') + .then(() => runner.makeRequest('get', '/test/error/1')) + .then(() => runner.makeRequest('get', '/test/init')) + .then(() => runner.makeRequest('get', '/test/error/2')) + .then(() => runner.makeRequest('get', '/test/error/3')); - await runner.completed(); + await runner.completed(); + }); + }); }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/test.ts deleted file mode 100644 index 85c3d0a2c353..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { afterAll, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should construct correct url with common infixes with multiple parameterized routers.', async () => { - const runner = createRunner(__dirname, 'server.ts') - .ignore('transaction') - .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/user/:userId' } }) - .start(); - runner.makeRequest('get', '/api/v1/user/3212'); - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/test.ts deleted file mode 100644 index f6a29574e254..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { afterAll, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should construct correct url with common infixes with multiple routers.', async () => { - const runner = createRunner(__dirname, 'server.ts') - .ignore('transaction') - .expect({ event: { message: 'Custom Message', transaction: 'GET /api2/v1/test' } }) - .start(); - runner.makeRequest('get', '/api2/v1/test'); - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/test.ts deleted file mode 100644 index b2b5baabd103..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { afterAll, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should construct correct urls with multiple parameterized routers (use order reversed).', async () => { - const runner = createRunner(__dirname, 'server.ts') - .ignore('transaction') - .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/user/:userId' } }) - .start(); - runner.makeRequest('get', '/api/v1/user/1234/'); - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/test.ts deleted file mode 100644 index f362d49ddd03..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { afterAll, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should construct correct urls with multiple parameterized routers.', async () => { - const runner = createRunner(__dirname, 'server.ts') - .ignore('transaction') - .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/user/:userId' } }) - .start(); - runner.makeRequest('get', '/api/v1/user/1234/'); - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/test.ts deleted file mode 100644 index 3209cde3ea23..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { afterAll, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should construct correct url with multiple parameterized routers of the same length.', async () => { - const runner = createRunner(__dirname, 'server.ts') - .ignore('transaction') - .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/:userId' } }) - .start(); - runner.makeRequest('get', '/api/v1/1234/'); - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts deleted file mode 100644 index 52cce3594b68..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -import cors from 'cors'; -import express from 'express'; - -const app = express(); - -app.use(cors()); - -const APIv1 = express.Router(); - -APIv1.get('/test', function (_req, res) { - Sentry.captureMessage('Custom Message'); - res.send('Success'); -}); - -const root = express.Router(); - -app.use('/api', root); -app.use('/api/v1', APIv1); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/test.ts deleted file mode 100644 index 12c6584b2d3f..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { afterAll, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should construct correct urls with multiple routers.', async () => { - const runner = createRunner(__dirname, 'server.ts') - .ignore('transaction') - .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/test' } }) - .start(); - runner.makeRequest('get', '/api/v1/test'); - await runner.completed(); -}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts deleted file mode 100644 index fbb97cb6b1df..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { afterAll, describe, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -describe('complex-router', () => { - test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route', async () => { - const EXPECTED_TRANSACTION = { - transaction: 'GET /api/api/v1/sub-router/users/:userId/posts/:postId', - transaction_info: { - source: 'route', - }, - }; - - const runner = createRunner(__dirname, 'server.ts') - .ignore('event') - .expect({ transaction: EXPECTED_TRANSACTION as any }) - .start(); - runner.makeRequest('get', '/api/api/v1/sub-router/users/123/posts/456'); - await runner.completed(); - }); - - test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route and original url has query params', async () => { - const EXPECTED_TRANSACTION = { - transaction: 'GET /api/api/v1/sub-router/users/:userId/posts/:postId', - transaction_info: { - source: 'route', - }, - }; - - const runner = createRunner(__dirname, 'server.ts') - .ignore('event') - .expect({ transaction: EXPECTED_TRANSACTION as any }) - .start(); - runner.makeRequest('get', '/api/api/v1/sub-router/users/123/posts/456?param=1'); - await runner.completed(); - }); - - test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route and original url ends with trailing slash and has query params', async () => { - const EXPECTED_TRANSACTION = { - transaction: 'GET /api/api/v1/sub-router/users/:userId/posts/:postId', - transaction_info: { - source: 'route', - }, - }; - - const runner = createRunner(__dirname, 'server.ts') - .ignore('event') - .expect({ transaction: EXPECTED_TRANSACTION as any }) - .start(); - runner.makeRequest('get', '/api/api/v1/sub-router/users/123/posts/456/?param=1'); - await runner.completed(); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/instrument.mjs b/dev-packages/node-integration-tests/suites/express/multiple-routers/instrument.mjs new file mode 100644 index 000000000000..46a27dd03b74 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/instrument.mjs @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts deleted file mode 100644 index 6f24e03bac59..000000000000 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { afterAll, describe, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -// Before Node 16, parametrization is not working properly here -describe('middle-layer-parameterized', () => { - test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route', async () => { - const EXPECTED_TRANSACTION = { - transaction: 'GET /api/v1/users/:userId/posts/:postId', - transaction_info: { - source: 'route', - }, - }; - - const runner = createRunner(__dirname, 'server.ts') - .ignore('event') - .expect({ transaction: EXPECTED_TRANSACTION as any }) - .start(); - runner.makeRequest('get', '/api/v1/users/123/posts/456'); - await runner.completed(); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-infix-parameterized.mjs similarity index 64% rename from dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts rename to dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-infix-parameterized.mjs index 6cbe4a330463..593aa0df7f16 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-infix-parameterized.mjs @@ -1,13 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-infix.mjs similarity index 62% rename from dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts rename to dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-infix.mjs index b07226733bc6..61b328023429 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-infix.mjs @@ -1,14 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - debug: true, - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix-reverse.mjs similarity index 63% rename from dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts rename to dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix-reverse.mjs index e405a5f17d27..f65e2e1e2fcd 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix-reverse.mjs @@ -1,13 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import cors from 'cors'; import express from 'express'; @@ -24,8 +16,8 @@ APIv1.get('/user/:userId', function (_req, res) { const root = express.Router(); -app.use('/api', root); app.use('/api/v1', APIv1); +app.use('/api', root); Sentry.setupExpressErrorHandler(app); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix-same-length.mjs similarity index 63% rename from dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts rename to dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix-same-length.mjs index a5272e134c48..4e0230a7e374 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix-same-length.mjs @@ -1,13 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix.mjs similarity index 64% rename from dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts rename to dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix.mjs index 99792fe8185c..77dd64e0c502 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-common-prefix.mjs @@ -1,13 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import cors from 'cors'; import express from 'express'; @@ -17,6 +9,11 @@ app.use(cors()); const APIv1 = express.Router(); +APIv1.get('/test', function (_req, res) { + Sentry.captureMessage('Custom Message'); + res.send('Success'); +}); + APIv1.get('/user/:userId', function (_req, res) { Sentry.captureMessage('Custom Message'); res.send('Success'); @@ -24,8 +21,8 @@ APIv1.get('/user/:userId', function (_req, res) { const root = express.Router(); -app.use('/api/v1', APIv1); app.use('/api', root); +app.use('/api/v1', APIv1); Sentry.setupExpressErrorHandler(app); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-complex-router.mjs similarity index 65% rename from dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts rename to dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-complex-router.mjs index 7d875b47f13b..9348e16d3334 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-complex-router.mjs @@ -1,13 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-middle-layer.mjs similarity index 63% rename from dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts rename to dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-middle-layer.mjs index 793a84924001..074ba2a68389 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/scenario-middle-layer.mjs @@ -1,13 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/test.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/test.ts new file mode 100644 index 000000000000..4be3172ef2c2 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/test.ts @@ -0,0 +1,145 @@ +import { afterAll, describe } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../utils/runner'; + +describe('express multiple routers', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + createCjsTests(__dirname, 'scenario-common-infix.mjs', 'instrument.mjs', (createRunner, test) => { + test('should construct correct url with common infixes with multiple routers.', async () => { + const runner = createRunner() + .ignore('transaction') + .expect({ event: { message: 'Custom Message', transaction: 'GET /api2/v1/test' } }) + .start(); + runner.makeRequest('get', '/api2/v1/test'); + await runner.completed(); + }); + }); + + createCjsTests(__dirname, 'scenario-common-infix-parameterized.mjs', 'instrument.mjs', (createRunner, test) => { + test('should construct correct url with common infixes with multiple parameterized routers.', async () => { + const runner = createRunner() + .ignore('transaction') + .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/user/:userId' } }) + .start(); + runner.makeRequest('get', '/api/v1/user/3212'); + await runner.completed(); + }); + }); + + createCjsTests(__dirname, 'scenario-common-prefix.mjs', 'instrument.mjs', (createRunner, test) => { + test('should construct correct urls with multiple routers.', async () => { + const runner = createRunner() + .ignore('transaction') + .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/test' } }) + .start(); + runner.makeRequest('get', '/api/v1/test'); + await runner.completed(); + }); + + test('should construct correct urls with multiple parameterized routers.', async () => { + const runner = createRunner() + .ignore('transaction') + .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/user/:userId' } }) + .start(); + runner.makeRequest('get', '/api/v1/user/1234/'); + await runner.completed(); + }); + }); + + createCjsTests(__dirname, 'scenario-common-prefix-reverse.mjs', 'instrument.mjs', (createRunner, test) => { + test('should construct correct urls with multiple parameterized routers (use order reversed).', async () => { + const runner = createRunner() + .ignore('transaction') + .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/user/:userId' } }) + .start(); + runner.makeRequest('get', '/api/v1/user/1234/'); + await runner.completed(); + }); + }); + + createCjsTests(__dirname, 'scenario-common-prefix-same-length.mjs', 'instrument.mjs', (createRunner, test) => { + test('should construct correct url with multiple parameterized routers of the same length.', async () => { + const runner = createRunner() + .ignore('transaction') + .expect({ event: { message: 'Custom Message', transaction: 'GET /api/v1/:userId' } }) + .start(); + runner.makeRequest('get', '/api/v1/1234/'); + await runner.completed(); + }); + }); + + describe('complex-router', () => { + createCjsTests(__dirname, 'scenario-complex-router.mjs', 'instrument.mjs', (createRunner, test) => { + test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route', async () => { + const runner = createRunner() + .ignore('event') + .expect({ + transaction: { + transaction: 'GET /api/api/v1/sub-router/users/:userId/posts/:postId', + transaction_info: { + source: 'route', + }, + }, + }) + .start(); + runner.makeRequest('get', '/api/api/v1/sub-router/users/123/posts/456'); + await runner.completed(); + }); + + test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route and original url has query params', async () => { + const runner = createRunner() + .ignore('event') + .expect({ + transaction: { + transaction: 'GET /api/api/v1/sub-router/users/:userId/posts/:postId', + transaction_info: { + source: 'route', + }, + }, + }) + .start(); + runner.makeRequest('get', '/api/api/v1/sub-router/users/123/posts/456?param=1'); + await runner.completed(); + }); + + test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route and original url ends with trailing slash and has query params', async () => { + const runner = createRunner() + .ignore('event') + .expect({ + transaction: { + transaction: 'GET /api/api/v1/sub-router/users/:userId/posts/:postId', + transaction_info: { + source: 'route', + }, + }, + }) + .start(); + runner.makeRequest('get', '/api/api/v1/sub-router/users/123/posts/456/?param=1'); + await runner.completed(); + }); + }); + }); + + // Before Node 16, parametrization is not working properly here + describe('middle-layer-parameterized', () => { + createCjsTests(__dirname, 'scenario-middle-layer.mjs', 'instrument.mjs', (createRunner, test) => { + test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route', async () => { + const runner = createRunner() + .ignore('event') + .expect({ + transaction: { + transaction: 'GET /api/v1/users/:userId/posts/:postId', + transaction_info: { + source: 'route', + }, + }, + }) + .start(); + runner.makeRequest('get', '/api/v1/users/123/posts/456'); + await runner.completed(); + }); + }); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/express/requestUser/instrument.mjs b/dev-packages/node-integration-tests/suites/express/requestUser/instrument.mjs new file mode 100644 index 000000000000..2bd69014b019 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/requestUser/instrument.mjs @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, + debug: true, +}); diff --git a/dev-packages/node-integration-tests/suites/express/requestUser/server.js b/dev-packages/node-integration-tests/suites/express/requestUser/scenario.mjs similarity index 54% rename from dev-packages/node-integration-tests/suites/express/requestUser/server.js rename to dev-packages/node-integration-tests/suites/express/requestUser/scenario.mjs index d93d22905506..601f73716557 100644 --- a/dev-packages/node-integration-tests/suites/express/requestUser/server.js +++ b/dev-packages/node-integration-tests/suites/express/requestUser/scenario.mjs @@ -1,17 +1,7 @@ -const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node'); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, - debug: true, -}); - -// express must be required after Sentry is initialized -const express = require('express'); -const cors = require('cors'); -const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); +import * as Sentry from '@sentry/node'; +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import cors from 'cors'; +import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/express/requestUser/test.ts b/dev-packages/node-integration-tests/suites/express/requestUser/test.ts index 2605b7ed5127..5a6d0237b96a 100644 --- a/dev-packages/node-integration-tests/suites/express/requestUser/test.ts +++ b/dev-packages/node-integration-tests/suites/express/requestUser/test.ts @@ -1,45 +1,47 @@ -import { afterAll, describe, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../utils/runner'; describe('express user handling', () => { afterAll(() => { cleanupChildProcesses(); }); - test('ignores user from request', async () => { - expect.assertions(2); + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('ignores user from request', async () => { + expect.assertions(2); - const runner = createRunner(__dirname, 'server.js') - .expect({ - event: event => { - expect(event.user).toBeUndefined(); - expect(event.exception?.values?.[0]?.value).toBe('error_1'); - }, - }) - .start(); - runner.makeRequest('get', '/test1', { expectError: true }); - await runner.completed(); - }); - - test('using setUser in middleware works', async () => { - const runner = createRunner(__dirname, 'server.js') - .expect({ - event: { - user: { - id: '2', - email: 'test2@sentry.io', + const runner = createRunner() + .expect({ + event: event => { + expect(event.user).toBeUndefined(); + expect(event.exception?.values?.[0]?.value).toBe('error_1'); }, - exception: { - values: [ - { - value: 'error_2', - }, - ], + }) + .start(); + runner.makeRequest('get', '/test1', { expectError: true }); + await runner.completed(); + }); + + test('using setUser in middleware works', async () => { + const runner = createRunner() + .expect({ + event: { + user: { + id: '2', + email: 'test2@sentry.io', + }, + exception: { + values: [ + { + value: 'error_2', + }, + ], + }, }, - }, - }) - .start(); - runner.makeRequest('get', '/test2', { expectError: true }); - await runner.completed(); + }) + .start(); + runner.makeRequest('get', '/test2', { expectError: true }); + await runner.completed(); + }); }); }); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts deleted file mode 100644 index 20f19dccf3ea..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { parseBaggageHeader } from '@sentry/core'; -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; -import type { TestAPIResponse } from '../server'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('Should overwrite baggage if the incoming request already has Sentry baggage data but no sentry-trace', async () => { - const runner = createRunner(__dirname, '..', 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', - }, - }); - - expect(response).toBeDefined(); - expect(response).not.toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', - }, - }); -}); - -test('Should propagate sentry trace baggage data from an incoming to an outgoing request.', async () => { - const runner = createRunner(__dirname, '..', 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,dogs=great,sentry-sample_rand=0.42', - }, - }); - - expect(response).toBeDefined(); - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,sentry-sample_rand=0.42', - }, - }); -}); - -test('Should not propagate baggage data from an incoming to an outgoing request if sentry-trace is faulty.', async () => { - const runner = createRunner(__dirname, '..', 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - 'sentry-trace': '', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,dogs=great', - }, - }); - - expect(response).toBeDefined(); - expect(response).not.toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', - }, - }); -}); - -test('Should not propagate baggage if sentry-trace header is present in incoming request but no baggage header', async () => { - const runner = createRunner(__dirname, '..', 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - }, - }); - - expect(response).toBeDefined(); - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - }, - }); -}); - -test('Should not propagate baggage and ignore original 3rd party baggage entries if sentry-trace header is present', async () => { - const runner = createRunner(__dirname, '..', 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'foo=bar', - }, - }); - - expect(response).toBeDefined(); - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - }, - }); -}); - -test('Should populate and propagate sentry baggage if sentry-trace header does not exist', async () => { - const runner = createRunner(__dirname, '..', 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express'); - - expect(response).toBeDefined(); - - const parsedBaggage = parseBaggageHeader(response?.test_data.baggage); - - expect(response?.test_data.host).toBe('somewhere.not.sentry'); - expect(parsedBaggage).toStrictEqual({ - 'sentry-environment': 'prod', - 'sentry-release': '1.0', - 'sentry-public_key': 'public', - // TraceId changes, hence we only expect that the string contains the traceid key - 'sentry-trace_id': expect.stringMatching(/\S*/), - 'sentry-sample_rand': expect.stringMatching(/\S*/), - 'sentry-sample_rate': '1', - 'sentry-sampled': 'true', - 'sentry-transaction': 'GET /test/express', - }); -}); - -test('Should populate Sentry and ignore 3rd party content if sentry-trace header does not exist', async () => { - const runner = createRunner(__dirname, '..', 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - baggage: 'foo=bar,bar=baz', - }, - }); - - expect(response).toBeDefined(); - expect(response?.test_data.host).toBe('somewhere.not.sentry'); - - const parsedBaggage = parseBaggageHeader(response?.test_data.baggage); - expect(parsedBaggage).toStrictEqual({ - 'sentry-environment': 'prod', - 'sentry-release': '1.0', - 'sentry-public_key': 'public', - // TraceId changes, hence we only expect that the string contains the traceid key - 'sentry-trace_id': expect.stringMatching(/\S*/), - 'sentry-sample_rand': expect.stringMatching(/\S*/), - 'sentry-sample_rate': '1', - 'sentry-sampled': 'true', - 'sentry-transaction': 'GET /test/express', - }); -}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts deleted file mode 100644 index a66661c50bd2..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - environment: 'prod', - tracePropagationTargets: [/^(?!.*express).*$/], - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -import cors from 'cors'; -import express from 'express'; -import http from 'http'; - -const app = express(); - -Sentry.setUser({ id: 'user123' }); - -app.use(cors()); - -app.get('/test/express', (_req, res) => { - const span = Sentry.getActiveSpan(); - const traceId = span?.spanContext().traceId; - const headers = http.get('http://somewhere.not.sentry/').getHeaders(); - if (traceId) { - headers['baggage'] = (headers['baggage'] as string).replace(traceId, '__SENTRY_TRACE_ID__'); - } - // Responding with the headers outgoing request headers back to the assertions. - res.send({ test_data: headers }); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts deleted file mode 100644 index 4551baa06e23..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; -import type { TestAPIResponse } from './server'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should attach a baggage header to an outgoing request.', async () => { - const runner = createRunner(__dirname, 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express'); - - expect(response).toBeDefined(); - - const baggage = response?.test_data.baggage?.split(','); - - [ - 'sentry-environment=prod', - 'sentry-public_key=public', - 'sentry-release=1.0', - 'sentry-sample_rate=1', - 'sentry-sampled=true', - 'sentry-trace_id=__SENTRY_TRACE_ID__', - 'sentry-transaction=GET%20%2Ftest%2Fexpress', - expect.stringMatching(/sentry-sample_rand=0\.\d+/), - ].forEach(item => { - expect(baggage).toContainEqual(item); - }); - - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - }, - }); -}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts deleted file mode 100644 index 372d413a158e..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - environment: 'prod', - // disable requests to /express - tracePropagationTargets: [/^(?!.*express).*$/], - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -import cors from 'cors'; -import express from 'express'; -import * as http from 'http'; - -const app = express(); - -app.use(cors()); - -app.get('/test/express', (_req, res) => { - // simulate setting a "third party" baggage header which the Sentry SDK should merge with Sentry DSC entries - const headers = http - .get({ - hostname: 'somewhere.not.sentry', - headers: { - baggage: - 'other=vendor,foo=bar,third=party,sentry-release=9.9.9,sentry-environment=staging,sentry-sample_rate=0.54,last=item', - }, - }) - .getHeaders(); - - // Responding with the headers outgoing request headers back to the assertions. - res.send({ test_data: headers }); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts deleted file mode 100644 index eebafa06bfd1..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; -import type { TestAPIResponse } from '../server'; -import { extractTraceparentData } from '@sentry/core'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should ignore sentry-values in `baggage` header of a third party vendor and overwrite them with incoming DSC', async () => { - const runner = createRunner(__dirname, 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-release=2.1.0,sentry-environment=myEnv', - }, - }); - - expect(response).toBeDefined(); - - const baggage = response?.test_data.baggage?.split(',').sort(); - - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - }, - }); - - expect(baggage).toEqual([ - 'foo=bar', - 'last=item', - 'other=vendor', - 'sentry-environment=myEnv', - 'sentry-release=2.1.0', - expect.stringMatching(/sentry-sample_rand=\d+/), - 'third=party', - ]); -}); - -test('should ignore sentry-values in `baggage` header of a third party vendor and overwrite them with new DSC', async () => { - const runner = createRunner(__dirname, 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express'); - - expect(response).toBeDefined(); - - const baggage = response?.test_data.baggage?.split(',').sort(); - const sentryTraceHeader = response?.test_data['sentry-trace']; - - const sentryTrace = extractTraceparentData(sentryTraceHeader); - - expect(sentryTrace?.traceId).toMatch(/^[0-9a-f]{32}$/); - - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - }, - }); - - expect(baggage).toEqual([ - 'foo=bar', - 'last=item', - 'other=vendor', - 'sentry-environment=prod', - 'sentry-public_key=public', - 'sentry-release=1.0', - expect.stringMatching(/sentry-sample_rand=\d+/), - 'sentry-sample_rate=1', - 'sentry-sampled=true', - `sentry-trace_id=${sentryTrace?.traceId}`, - 'sentry-transaction=GET%20%2Ftest%2Fexpress', - 'third=party', - ]); -}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts deleted file mode 100644 index 7aea36941e99..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - environment: 'prod', - // disable requests to /express - tracePropagationTargets: [/^(?!.*express).*$/], - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -import cors from 'cors'; -import express from 'express'; -import http from 'http'; - -const app = express(); - -app.use(cors()); - -app.get('/test/express', (_req, res) => { - // simulate setting a "third party" baggage header which the Sentry SDK should merge with Sentry DSC entries - const headers = http - .get({ hostname: 'somewhere.not.sentry', headers: { baggage: 'other=vendor,foo=bar,third=party' } }) - .getHeaders(); - - // Responding with the headers outgoing request headers back to the assertions. - res.send({ test_data: headers }); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/test.ts deleted file mode 100644 index beb118944408..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; -import type { TestAPIResponse } from './server'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should merge `baggage` header of a third party vendor with the Sentry DSC baggage items', async () => { - const runner = createRunner(__dirname, 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,sentry-sample_rand=0.42', - }, - }); - - expect(response).toBeDefined(); - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - baggage: 'other=vendor,foo=bar,third=party,sentry-release=2.0.0,sentry-environment=myEnv,sentry-sample_rand=0.42', - }, - }); -}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-property-values/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-property-values/server.ts deleted file mode 100644 index da278ce61688..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-property-values/server.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - environment: 'prod', - // disable requests to /express - tracePropagationTargets: [/^(?!.*express).*$/], - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -import cors from 'cors'; -import express from 'express'; -import http from 'http'; - -const app = express(); - -app.use(cors()); - -app.get('/test/express-property-values', (req, res) => { - const incomingBaggage = req.headers.baggage; - - // Forward the incoming baggage (which contains property values) to the outgoing request - // This tests that property values with = signs are preserved during parsing and re-serialization - const headers = http.get({ hostname: 'somewhere.not.sentry', headers: { baggage: incomingBaggage } }).getHeaders(); - - // Responding with the headers outgoing request headers back to the assertions. - res.send({ test_data: headers }); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-property-values/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-property-values/test.ts deleted file mode 100644 index 23848d36a3df..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-property-values/test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; -import type { TestAPIResponse } from './server'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should preserve baggage property values with equal signs (W3C spec compliance)', async () => { - const runner = createRunner(__dirname, 'server.ts').start(); - - // W3C spec example: https://www.w3.org/TR/baggage/#example - const response = await runner.makeRequest('get', '/test/express-property-values', { - headers: { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'key1=value1;property1;property2,key2=value2,key3=value3; propertyKey=propertyValue', - }, - }); - - expect(response).toBeDefined(); - - // The baggage should be parsed and re-serialized, preserving property values with = signs - const baggageItems = response?.test_data.baggage?.split(',').map(item => decodeURIComponent(item.trim())); - - expect(baggageItems).toContain('key1=value1;property1;property2'); - expect(baggageItems).toContain('key2=value2'); - expect(baggageItems).toContain('key3=value3; propertyKey=propertyValue'); -}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts deleted file mode 100644 index 4a735c97d889..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - environment: 'prod', - // disable requests to /express - tracePropagationTargets: [/^(?!.*express).*$/], - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -import cors from 'cors'; -import express from 'express'; -import http from 'http'; - -const app = express(); - -Sentry.setUser({ id: 'user123' }); - -app.use(cors()); - -app.get('/test/express', (_req, res) => { - const headers = http.get('http://somewhere.not.sentry/').getHeaders(); - // Responding with the headers outgoing request headers back to the assertions. - res.send({ test_data: headers }); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/test.ts deleted file mode 100644 index 436a8ea9b4da..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; -import type { TestAPIResponse } from '../server'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('Includes transaction in baggage if the transaction name is parameterized', async () => { - const runner = createRunner(__dirname, 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express'); - - expect(response).toBeDefined(); - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - baggage: expect.stringContaining('sentry-transaction=GET%20%2Ftest%2Fexpress'), - }, - }); -}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/instrument.mjs b/dev-packages/node-integration-tests/suites/express/sentry-trace/instrument.mjs new file mode 100644 index 000000000000..288936c1eeca --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/instrument.mjs @@ -0,0 +1,13 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + environment: 'prod', + // Disable attaching headers to /express endpoints so that only the outgoing + // request to `somewhere.not.sentry` gets trace propagation. + tracePropagationTargets: [/^(?!.*express).*$/], + tracesSampleRate: 1.0, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/scenario.mjs b/dev-packages/node-integration-tests/suites/express/sentry-trace/scenario.mjs new file mode 100644 index 000000000000..77a79b9f71b5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/scenario.mjs @@ -0,0 +1,66 @@ +import * as Sentry from '@sentry/node'; +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import cors from 'cors'; +import express from 'express'; +import http from 'http'; + +const app = express(); + +// Set globally so we can assert user data is not leaked into propagated baggage. +Sentry.setUser({ id: 'user123' }); + +app.use(cors()); + +// Plain outgoing request — used to assert propagated `sentry-trace`/`baggage` headers. +app.get('/test/express', (_req, res) => { + const headers = http.get('http://somewhere.not.sentry/').getHeaders(); + res.send({ test_data: headers }); +}); + +// Replaces the dynamic trace id in the outgoing baggage with a stable placeholder +// so the propagated baggage can be asserted deterministically. +app.get('/test/express-replace-trace-id', (_req, res) => { + const span = Sentry.getActiveSpan(); + const traceId = span?.spanContext().traceId; + const headers = http.get('http://somewhere.not.sentry/').getHeaders(); + if (traceId) { + headers['baggage'] = headers['baggage'].replace(traceId, '__SENTRY_TRACE_ID__'); + } + res.send({ test_data: headers }); +}); + +// Sets a third-party baggage header on the outgoing request, which the SDK should +// merge with the Sentry DSC entries. +app.get('/test/express-third-party-baggage', (_req, res) => { + const headers = http + .get({ hostname: 'somewhere.not.sentry', headers: { baggage: 'other=vendor,foo=bar,third=party' } }) + .getHeaders(); + res.send({ test_data: headers }); +}); + +// Sets a third-party baggage header that also contains sentry-* entries, which the +// SDK should ignore/overwrite with the actual DSC. +app.get('/test/express-third-party-baggage-with-sentry', (_req, res) => { + const headers = http + .get({ + hostname: 'somewhere.not.sentry', + headers: { + baggage: + 'other=vendor,foo=bar,third=party,sentry-release=9.9.9,sentry-environment=staging,sentry-sample_rate=0.54,last=item', + }, + }) + .getHeaders(); + res.send({ test_data: headers }); +}); + +// Forwards the incoming baggage header to the outgoing request, to assert that +// property values with `=` signs are preserved during parsing & re-serialization. +app.get('/test/express-property-values', (req, res) => { + const incomingBaggage = req.headers.baggage; + const headers = http.get({ hostname: 'somewhere.not.sentry', headers: { baggage: incomingBaggage } }).getHeaders(); + res.send({ test_data: headers }); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts deleted file mode 100644 index cc8d5657dc7f..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - environment: 'prod', - tracePropagationTargets: [/^(?!.*express).*$/], - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -import cors from 'cors'; -import express from 'express'; -import http from 'http'; - -const app = express(); - -app.use(cors()); - -app.get('/test/express', (_req, res) => { - const headers = http.get('http://somewhere.not.sentry/').getHeaders(); - - // Responding with the headers outgoing request headers back to the assertions. - res.send({ test_data: headers }); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/test.ts new file mode 100644 index 000000000000..e790bfb7f3ff --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/test.ts @@ -0,0 +1,351 @@ +import { extractTraceparentData, parseBaggageHeader, TRACEPARENT_REGEXP } from '@sentry/core'; +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../utils/runner'; + +type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; + +describe('express sentry-trace', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + describe('baggage header assignment', () => { + test('Should overwrite baggage if the incoming request already has Sentry baggage data but no sentry-trace', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express', { + headers: { + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', + }, + }); + + expect(response).toBeDefined(); + expect(response).not.toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', + }, + }); + }); + + test('Should propagate sentry trace baggage data from an incoming to an outgoing request.', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express', { + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,dogs=great,sentry-sample_rand=0.42', + }, + }); + + expect(response).toBeDefined(); + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,sentry-sample_rand=0.42', + }, + }); + }); + + test('Should not propagate baggage data from an incoming to an outgoing request if sentry-trace is faulty.', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express', { + headers: { + 'sentry-trace': '', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,dogs=great', + }, + }); + + expect(response).toBeDefined(); + expect(response).not.toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', + }, + }); + }); + + test('Should not propagate baggage if sentry-trace header is present in incoming request but no baggage header', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express', { + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + }, + }); + + expect(response).toBeDefined(); + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + }, + }); + }); + + test('Should not propagate baggage and ignore original 3rd party baggage entries if sentry-trace header is present', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express', { + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'foo=bar', + }, + }); + + expect(response).toBeDefined(); + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + }, + }); + }); + + test('Should populate and propagate sentry baggage if sentry-trace header does not exist', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express'); + + expect(response).toBeDefined(); + + const parsedBaggage = parseBaggageHeader(response?.test_data.baggage); + + expect(response?.test_data.host).toBe('somewhere.not.sentry'); + expect(parsedBaggage).toStrictEqual({ + 'sentry-environment': 'prod', + 'sentry-release': '1.0', + 'sentry-public_key': 'public', + // TraceId changes, hence we only expect that the string contains the traceid key + 'sentry-trace_id': expect.stringMatching(/\S*/), + 'sentry-sample_rand': expect.stringMatching(/\S*/), + 'sentry-sample_rate': '1', + 'sentry-sampled': 'true', + 'sentry-transaction': 'GET /test/express', + }); + }); + + test('Should populate Sentry and ignore 3rd party content if sentry-trace header does not exist', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express', { + headers: { + baggage: 'foo=bar,bar=baz', + }, + }); + + expect(response).toBeDefined(); + expect(response?.test_data.host).toBe('somewhere.not.sentry'); + + const parsedBaggage = parseBaggageHeader(response?.test_data.baggage); + expect(parsedBaggage).toStrictEqual({ + 'sentry-environment': 'prod', + 'sentry-release': '1.0', + 'sentry-public_key': 'public', + // TraceId changes, hence we only expect that the string contains the traceid key + 'sentry-trace_id': expect.stringMatching(/\S*/), + 'sentry-sample_rand': expect.stringMatching(/\S*/), + 'sentry-sample_rate': '1', + 'sentry-sampled': 'true', + 'sentry-transaction': 'GET /test/express', + }); + }); + }); + + test('should attach a baggage header to an outgoing request.', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express-replace-trace-id'); + + expect(response).toBeDefined(); + + const baggage = response?.test_data.baggage?.split(','); + + [ + 'sentry-environment=prod', + 'sentry-public_key=public', + 'sentry-release=1.0', + 'sentry-sample_rate=1', + 'sentry-sampled=true', + 'sentry-trace_id=__SENTRY_TRACE_ID__', + 'sentry-transaction=GET%20%2Ftest%2Fexpress-replace-trace-id', + expect.stringMatching(/sentry-sample_rand=0\.\d+/), + ].forEach(item => { + expect(baggage).toContainEqual(item); + }); + + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + }, + }); + }); + + test('should merge `baggage` header of a third party vendor with the Sentry DSC baggage items', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express-third-party-baggage', { + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,sentry-sample_rand=0.42', + }, + }); + + expect(response).toBeDefined(); + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + baggage: + 'other=vendor,foo=bar,third=party,sentry-release=2.0.0,sentry-environment=myEnv,sentry-sample_rand=0.42', + }, + }); + }); + + describe('third party baggage with sentry entries', () => { + test('should ignore sentry-values in `baggage` header of a third party vendor and overwrite them with incoming DSC', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest( + 'get', + '/test/express-third-party-baggage-with-sentry', + { + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'sentry-release=2.1.0,sentry-environment=myEnv', + }, + }, + ); + + expect(response).toBeDefined(); + + const baggage = response?.test_data.baggage?.split(',').sort(); + + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + }, + }); + + expect(baggage).toEqual([ + 'foo=bar', + 'last=item', + 'other=vendor', + 'sentry-environment=myEnv', + 'sentry-release=2.1.0', + expect.stringMatching(/sentry-sample_rand=\d+/), + 'third=party', + ]); + }); + + test('should ignore sentry-values in `baggage` header of a third party vendor and overwrite them with new DSC', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest( + 'get', + '/test/express-third-party-baggage-with-sentry', + ); + + expect(response).toBeDefined(); + + const baggage = response?.test_data.baggage?.split(',').sort(); + const sentryTraceHeader = response?.test_data['sentry-trace']; + + const sentryTrace = extractTraceparentData(sentryTraceHeader); + + expect(sentryTrace?.traceId).toMatch(/^[0-9a-f]{32}$/); + + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + }, + }); + + expect(baggage).toEqual([ + 'foo=bar', + 'last=item', + 'other=vendor', + 'sentry-environment=prod', + 'sentry-public_key=public', + 'sentry-release=1.0', + expect.stringMatching(/sentry-sample_rand=\d+/), + 'sentry-sample_rate=1', + 'sentry-sampled=true', + `sentry-trace_id=${sentryTrace?.traceId}`, + 'sentry-transaction=GET%20%2Ftest%2Fexpress-third-party-baggage-with-sentry', + 'third=party', + ]); + }); + }); + + test('should preserve baggage property values with equal signs (W3C spec compliance)', async () => { + const runner = createRunner().start(); + + // W3C spec example: https://www.w3.org/TR/baggage/#example + const response = await runner.makeRequest('get', '/test/express-property-values', { + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'key1=value1;property1;property2,key2=value2,key3=value3; propertyKey=propertyValue', + }, + }); + + expect(response).toBeDefined(); + + // The baggage should be parsed and re-serialized, preserving property values with = signs + const baggageItems = response?.test_data.baggage?.split(',').map(item => decodeURIComponent(item.trim())); + + expect(baggageItems).toContain('key1=value1;property1;property2'); + expect(baggageItems).toContain('key2=value2'); + expect(baggageItems).toContain('key3=value3; propertyKey=propertyValue'); + }); + + test('Includes transaction in baggage if the transaction name is parameterized', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express'); + + expect(response).toBeDefined(); + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + baggage: expect.stringContaining('sentry-transaction=GET%20%2Ftest%2Fexpress'), + }, + }); + }); + + test('Should assign `sentry-trace` header which sets parent trace id of an outgoing request.', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express', { + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-0', + }, + }); + + expect(response).toBeDefined(); + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + 'sentry-trace': expect.stringContaining('12312012123120121231201212312012-'), + }, + }); + + expect(TRACEPARENT_REGEXP.test(response?.test_data['sentry-trace'] || '')).toBe(true); + }); + + test('should attach a `sentry-trace` header to an outgoing request.', async () => { + const runner = createRunner().start(); + + const response = await runner.makeRequest('get', '/test/express'); + + expect(response).toBeDefined(); + expect(response).toMatchObject({ + test_data: { + host: 'somewhere.not.sentry', + 'sentry-trace': expect.any(String), + }, + }); + + expect(TRACEPARENT_REGEXP.test(response?.test_data['sentry-trace'] || '')).toBe(true); + }); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/server.ts deleted file mode 100644 index 8aa9ed85d3be..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/server.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -export type TestAPIResponse = { test_data: { host: string; 'sentry-trace': string; baggage: string } }; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - environment: 'prod', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -import cors from 'cors'; -import express from 'express'; -import http from 'http'; - -const app = express(); - -app.use(cors()); - -app.get('/test/express', (_req, res) => { - const headers = http.get('http://somewhere.not.sentry/').getHeaders(); - - // Responding with the headers outgoing request headers back to the assertions. - res.send({ test_data: headers }); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/test.ts deleted file mode 100644 index 7d0a729dc4ff..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-assign/test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TRACEPARENT_REGEXP } from '@sentry/core'; -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; -import type { TestAPIResponse } from '../server'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('Should assign `sentry-trace` header which sets parent trace id of an outgoing request.', async () => { - const runner = createRunner(__dirname, 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express', { - headers: { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-0', - }, - }); - - expect(response).toBeDefined(); - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - 'sentry-trace': expect.stringContaining('12312012123120121231201212312012-'), - }, - }); - - expect(TRACEPARENT_REGEXP.test(response?.test_data['sentry-trace'] || '')).toBe(true); -}); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-out/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-out/test.ts deleted file mode 100644 index 8ed4d08bba55..000000000000 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/trace-header-out/test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { TRACEPARENT_REGEXP } from '@sentry/core'; -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; -import type { TestAPIResponse } from '../server'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -test('should attach a `sentry-trace` header to an outgoing request.', async () => { - const runner = createRunner(__dirname, '..', 'server.ts').start(); - - const response = await runner.makeRequest('get', '/test/express'); - - expect(response).toBeDefined(); - expect(response).toMatchObject({ - test_data: { - host: 'somewhere.not.sentry', - 'sentry-trace': expect.any(String), - }, - }); - - expect(TRACEPARENT_REGEXP.test(response?.test_data['sentry-trace'] || '')).toBe(true); -}); diff --git a/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/server.js b/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/server.js deleted file mode 100644 index 0e73923cf88a..000000000000 --- a/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/server.js +++ /dev/null @@ -1,33 +0,0 @@ -const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node'); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, -}); - -// express must be required after Sentry is initialized -const express = require('express'); -const cors = require('cors'); -const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); - -const app = express(); - -app.use(cors()); - -app.get('/test1', (_req, _res) => { - throw new Error('error_1'); -}); - -app.get('/test2', (_req, _res) => { - throw new Error('error_2'); -}); - -Sentry.setupExpressErrorHandler(app, { - shouldHandleError: error => { - return error.message === 'error_2'; - }, -}); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/test.ts b/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/test.ts deleted file mode 100644 index cb76cf3c929b..000000000000 --- a/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { afterAll, describe, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; - -describe('express setupExpressErrorHandler', () => { - afterAll(() => { - cleanupChildProcesses(); - }); - - describe('CJS', () => { - test('allows to pass options to setupExpressErrorHandler', async () => { - const runner = createRunner(__dirname, 'server.js') - .expect({ - event: { - exception: { - values: [ - { - value: 'error_2', - }, - ], - }, - }, - }) - .start(); - - // this error is filtered & ignored - runner.makeRequest('get', '/test1', { expectError: true }); - // this error is actually captured - runner.makeRequest('get', '/test2', { expectError: true }); - - await runner.completed(); - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/express/span-isolationScope/instrument.mjs b/dev-packages/node-integration-tests/suites/express/span-isolationScope/instrument.mjs new file mode 100644 index 000000000000..46a27dd03b74 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/span-isolationScope/instrument.mjs @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/express/span-isolationScope/server.ts b/dev-packages/node-integration-tests/suites/express/span-isolationScope/scenario.mjs similarity index 64% rename from dev-packages/node-integration-tests/suites/express/span-isolationScope/server.ts rename to dev-packages/node-integration-tests/suites/express/span-isolationScope/scenario.mjs index 3e4f9d0de62b..543de8de355b 100644 --- a/dev-packages/node-integration-tests/suites/express/span-isolationScope/server.ts +++ b/dev-packages/node-integration-tests/suites/express/span-isolationScope/scenario.mjs @@ -1,13 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/express/span-isolationScope/test.ts b/dev-packages/node-integration-tests/suites/express/span-isolationScope/test.ts index f8c7c11378d5..b4d03f1cad1e 100644 --- a/dev-packages/node-integration-tests/suites/express/span-isolationScope/test.ts +++ b/dev-packages/node-integration-tests/suites/express/span-isolationScope/test.ts @@ -1,40 +1,44 @@ -import { afterAll, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../utils/runner'; -afterAll(() => { - cleanupChildProcesses(); -}); +describe('express span isolationScope', () => { + afterAll(() => { + cleanupChildProcesses(); + }); -test('correctly applies isolation scope to span', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - transaction: { - transaction: 'GET /test/isolationScope', - breadcrumbs: [ - { - category: 'console', - level: 'log', - message: expect.stringMatching(/\{"port":(\d+)\}/), - timestamp: expect.any(Number), - }, - { - category: 'console', - level: 'log', - message: 'This is a test log.', - timestamp: expect.any(Number), - }, - { - message: 'manual breadcrumb', - timestamp: expect.any(Number), + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('correctly applies isolation scope to span', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /test/isolationScope', + breadcrumbs: [ + { + category: 'console', + level: 'log', + message: expect.stringMatching(/\{"port":(\d+)\}/), + timestamp: expect.any(Number), + }, + { + category: 'console', + level: 'log', + message: 'This is a test log.', + timestamp: expect.any(Number), + }, + { + message: 'manual breadcrumb', + timestamp: expect.any(Number), + }, + ], + tags: { + global: 'tag', + 'isolation-scope': 'tag', + }, }, - ], - tags: { - global: 'tag', - 'isolation-scope': 'tag', - }, - }, - }) - .start(); - runner.makeRequest('get', '/test/isolationScope'); - await runner.completed(); + }) + .start(); + runner.makeRequest('get', '/test/isolationScope'); + await runner.completed(); + }); + }); }); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalizedRequest.js b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/instrument-normalized-request.mjs similarity index 51% rename from dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalizedRequest.js rename to dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/instrument-normalized-request.mjs index da31780f2c5f..72786de129ff 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalizedRequest.js +++ b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/instrument-normalized-request.mjs @@ -1,5 +1,5 @@ -const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node'); +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', @@ -15,20 +15,3 @@ Sentry.init({ ); }, }); - -// express must be required after Sentry is initialized -const express = require('express'); -const cors = require('cors'); -const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); - -const app = express(); - -app.use(cors()); - -app.get('/test-normalized-request', (_req, res) => { - res.send('Success'); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/instrument.mjs b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/instrument.mjs new file mode 100644 index 000000000000..43f87a639195 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/instrument.mjs @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, + tracesSampler: samplingContext => { + // The name we get here is inferred at span creation time + // At this point, we sadly do not have a http.route attribute yet, + // so we infer the name from the unparameterized route instead + return ( + samplingContext.name === 'GET /test/123' && + samplingContext.attributes['sentry.op'] === 'http.server' && + samplingContext.attributes['http.method'] === 'GET' + ); + }, +}); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalized-request.mjs b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalized-request.mjs new file mode 100644 index 000000000000..ec306bb4680f --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario-normalized-request.mjs @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/node'; +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import cors from 'cors'; +import express from 'express'; + +const app = express(); + +app.use(cors()); + +app.get('/test-normalized-request', (_req, res) => { + res.send('Success'); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario.mjs b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario.mjs new file mode 100644 index 000000000000..4edf6a151ede --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/scenario.mjs @@ -0,0 +1,20 @@ +import * as Sentry from '@sentry/node'; +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import cors from 'cors'; +import express from 'express'; + +const app = express(); + +app.use(cors()); + +app.get('/test/:id', (_req, res) => { + res.send('Success'); +}); + +app.get('/test2', (_req, res) => { + res.send('Success'); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/server.js b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/server.js deleted file mode 100644 index b60ea07b636f..000000000000 --- a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/server.js +++ /dev/null @@ -1,39 +0,0 @@ -const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node'); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, - tracesSampler: samplingContext => { - // The name we get here is inferred at span creation time - // At this point, we sadly do not have a http.route attribute yet, - // so we infer the name from the unparameterized route instead - return ( - samplingContext.name === 'GET /test/123' && - samplingContext.attributes['sentry.op'] === 'http.server' && - samplingContext.attributes['http.method'] === 'GET' - ); - }, -}); - -// express must be required after Sentry is initialized -const express = require('express'); -const cors = require('cors'); -const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); - -const app = express(); - -app.use(cors()); - -app.get('/test/:id', (_req, res) => { - res.send('Success'); -}); - -app.get('/test2', (_req, res) => { - res.send('Success'); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/test.ts b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/test.ts index 1b644ada387a..92374c5e8903 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/tracesSampler/test.ts @@ -1,14 +1,14 @@ -import { afterAll, describe, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; +import { afterAll, describe } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../../utils/runner'; describe('express tracesSampler', () => { afterAll(() => { cleanupChildProcesses(); }); - describe('CJS', () => { + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { test('correctly samples & passes data to tracesSampler', async () => { - const runner = createRunner(__dirname, 'server.js') + const runner = createRunner() .expect({ transaction: { transaction: 'GET /test/:id', @@ -23,25 +23,26 @@ describe('express tracesSampler', () => { await runner.completed(); }); }); -}); -describe('express tracesSampler includes normalizedRequest data', () => { - afterAll(() => { - cleanupChildProcesses(); - }); + describe('normalizedRequest data', () => { + createCjsTests( + __dirname, + 'scenario-normalized-request.mjs', + 'instrument-normalized-request.mjs', + (createRunner, test) => { + test('correctly samples & passes normalizedRequest data to tracesSampler', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /test-normalized-request', + }, + }) + .start(); - describe('CJS', () => { - test('correctly samples & passes data to tracesSampler', async () => { - const runner = createRunner(__dirname, 'scenario-normalizedRequest.js') - .expect({ - transaction: { - transaction: 'GET /test-normalized-request', - }, - }) - .start(); - - runner.makeRequest('get', '/test-normalized-request?query=123'); - await runner.completed(); - }); + runner.makeRequest('get', '/test-normalized-request?query=123'); + await runner.completed(); + }); + }, + ); }); }); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/updateName/instrument.mjs b/dev-packages/node-integration-tests/suites/express/tracing/updateName/instrument.mjs new file mode 100644 index 000000000000..5cade6bb7ba1 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/tracing/updateName/instrument.mjs @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + // disable attaching headers to /test/* endpoints + tracePropagationTargets: [/^(?!.*test).*$/], + tracesSampleRate: 1.0, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/updateName/server.js b/dev-packages/node-integration-tests/suites/express/tracing/updateName/scenario.mjs similarity index 66% rename from dev-packages/node-integration-tests/suites/express/tracing/updateName/server.js rename to dev-packages/node-integration-tests/suites/express/tracing/updateName/scenario.mjs index c98e17276d92..7dd863a8abfd 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/updateName/server.js +++ b/dev-packages/node-integration-tests/suites/express/tracing/updateName/scenario.mjs @@ -1,20 +1,8 @@ -const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node'); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - // disable attaching headers to /test/* endpoints - tracePropagationTargets: [/^(?!.*test).*$/], - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -// express must be required after Sentry is initialized -const express = require('express'); -const cors = require('cors'); -const bodyParser = require('body-parser'); -const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); +import * as Sentry from '@sentry/node'; +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/updateName/test.ts b/dev-packages/node-integration-tests/suites/express/tracing/updateName/test.ts index 350ee9714752..a143b939a575 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/updateName/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/updateName/test.ts @@ -1,19 +1,19 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/node'; -import { afterAll, describe, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../../utils/runner'; -describe('express tracing', () => { +describe('express tracing - updateName', () => { afterAll(() => { cleanupChildProcesses(); }); - describe('CJS', () => { + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { // This test documents the unfortunate behaviour of using `span.updateName` on the server-side. // For http.server root spans (which is the root span on the server 99% of the time), Otel's http instrumentation // calls `span.updateName` and overwrites whatever the name was set to before (by us or by users). test("calling just `span.updateName` doesn't update the final name in express (missing source)", async () => { - const runner = createRunner(__dirname, 'server.js') + const runner = createRunner() .expect({ transaction: { transaction: 'GET /test/:id/span-updateName', @@ -30,7 +30,7 @@ describe('express tracing', () => { // Also calling `updateName` AND setting a source doesn't change anything - Otel has no concept of source, this is sentry-internal. // Therefore, only the source is updated but the name is still overwritten by Otel. test('calling `span.updateName` and setting attribute source updates the final name in express', async () => { - const runner = createRunner(__dirname, 'server.js') + const runner = createRunner() .expect({ transaction: { transaction: 'new-name', @@ -46,7 +46,7 @@ describe('express tracing', () => { // This test documents the correct way to update the span name (and implicitly the source) in Node: test('calling `Sentry.updateSpanName` updates the final name and source in express', async () => { - const runner = createRunner(__dirname, 'server.js') + const runner = createRunner() .expect({ transaction: txnEvent => { expect(txnEvent).toMatchObject({ @@ -69,31 +69,31 @@ describe('express tracing', () => { runner.makeRequest('get', '/test/123/updateSpanName'); await runner.completed(); }); - }); - // This test documents the correct way to update the span name (and implicitly the source) in Node: - test('calling `Sentry.updateSpanName` and setting source subsequently updates the final name and sets correct source', async () => { - const runner = createRunner(__dirname, 'server.js') - .expect({ - transaction: txnEvent => { - expect(txnEvent).toMatchObject({ - transaction: 'new-name', - transaction_info: { - source: 'component', - }, - contexts: { - trace: { - op: 'http.server', - data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component' }, + // This test documents the correct way to update the span name (and implicitly the source) in Node: + test('calling `Sentry.updateSpanName` and setting source subsequently updates the final name and sets correct source', async () => { + const runner = createRunner() + .expect({ + transaction: txnEvent => { + expect(txnEvent).toMatchObject({ + transaction: 'new-name', + transaction_info: { + source: 'component', }, - }, - }); - // ensure we delete the internal attribute once we're done with it - expect(txnEvent.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined(); - }, - }) - .start(); - runner.makeRequest('get', '/test/123/updateSpanNameAndSource'); - await runner.completed(); + contexts: { + trace: { + op: 'http.server', + data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component' }, + }, + }, + }); + // ensure we delete the internal attribute once we're done with it + expect(txnEvent.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined(); + }, + }) + .start(); + runner.makeRequest('get', '/test/123/updateSpanNameAndSource'); + await runner.completed(); + }); }); }); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/withError/instrument.mjs b/dev-packages/node-integration-tests/suites/express/tracing/withError/instrument.mjs new file mode 100644 index 000000000000..5cade6bb7ba1 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/tracing/withError/instrument.mjs @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + // disable attaching headers to /test/* endpoints + tracePropagationTargets: [/^(?!.*test).*$/], + tracesSampleRate: 1.0, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/withError/scenario.mjs b/dev-packages/node-integration-tests/suites/express/tracing/withError/scenario.mjs new file mode 100644 index 000000000000..c29ce8662df9 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/tracing/withError/scenario.mjs @@ -0,0 +1,17 @@ +import * as Sentry from '@sentry/node'; +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import cors from 'cors'; +import express from 'express'; + +const app = express(); + +app.use(cors()); + +app.get('/test/:id1/:id2', (_req, res) => { + Sentry.captureException(new Error('error_1')); + res.send('Success'); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/withError/server.js b/dev-packages/node-integration-tests/suites/express/tracing/withError/server.js deleted file mode 100644 index 890d26cda044..000000000000 --- a/dev-packages/node-integration-tests/suites/express/tracing/withError/server.js +++ /dev/null @@ -1,29 +0,0 @@ -const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node'); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - // disable attaching headers to /test/* endpoints - tracePropagationTargets: [/^(?!.*test).*$/], - tracesSampleRate: 1.0, - transport: loggingTransport, -}); - -// express must be required after Sentry is initialized -const express = require('express'); -const cors = require('cors'); -const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); - -const app = express(); - -app.use(cors()); - -app.get('/test/:id1/:id2', (_req, res) => { - Sentry.captureException(new Error('error_1')); - res.send('Success'); -}); - -Sentry.setupExpressErrorHandler(app); - -startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/withError/test.ts b/dev-packages/node-integration-tests/suites/express/tracing/withError/test.ts index e99c5bf44700..08de03b3358e 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/withError/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/withError/test.ts @@ -1,14 +1,14 @@ -import { afterAll, describe, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; +import { afterAll, describe } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../../utils/runner'; -describe('express tracing experimental', () => { +describe('express tracing with error', () => { afterAll(() => { cleanupChildProcesses(); }); - describe('CJS', () => { + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { test('should apply the scope transactionName to error events', async () => { - const runner = createRunner(__dirname, 'server.js') + const runner = createRunner() .ignore('transaction') .expect({ event: { diff --git a/dev-packages/node-integration-tests/suites/express/without-tracing/instrument.mjs b/dev-packages/node-integration-tests/suites/express/without-tracing/instrument.mjs new file mode 100644 index 000000000000..9ffde125d498 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/without-tracing/instrument.mjs @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/express/without-tracing/server.ts b/dev-packages/node-integration-tests/suites/express/without-tracing/scenario.mjs similarity index 76% rename from dev-packages/node-integration-tests/suites/express/without-tracing/server.ts rename to dev-packages/node-integration-tests/suites/express/without-tracing/scenario.mjs index 222566bc945b..b58374f29aff 100644 --- a/dev-packages/node-integration-tests/suites/express/without-tracing/server.ts +++ b/dev-packages/node-integration-tests/suites/express/without-tracing/scenario.mjs @@ -1,12 +1,5 @@ import * as Sentry from '@sentry/node'; -import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, -}); - +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import bodyParser from 'body-parser'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/without-tracing/test.ts b/dev-packages/node-integration-tests/suites/express/without-tracing/test.ts index 5286ab8d2953..ea07c84226e3 100644 --- a/dev-packages/node-integration-tests/suites/express/without-tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/express/without-tracing/test.ts @@ -1,143 +1,148 @@ -import { afterAll, describe, expect, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createCjsTests } from '../../../utils/runner'; describe('express without tracing', () => { - test('correctly applies isolation scope even without tracing', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - transaction: 'GET /test/isolationScope/1', - tags: { - global: 'tag', - 'isolation-scope': 'tag', - 'isolation-scope-1': '1', - }, - // Request is correctly set - request: { - url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test\/isolationScope\/1$/), - method: 'GET', - headers: { - 'user-agent': expect.stringContaining(''), - }, - }, - }, - }) - .start(); - - runner.makeRequest('get', '/test/isolationScope/1'); - await runner.completed(); + afterAll(() => { + cleanupChildProcesses(); }); - describe('request data', () => { - test('correctly captures JSON request data', async () => { - const runner = createRunner(__dirname, 'server.ts') + createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('correctly applies isolation scope even without tracing', async () => { + const runner = createRunner() .expect({ event: { - transaction: 'POST /test-post', + transaction: 'GET /test/isolationScope/1', + tags: { + global: 'tag', + 'isolation-scope': 'tag', + 'isolation-scope-1': '1', + }, + // Request is correctly set request: { - url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), - method: 'POST', + url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test\/isolationScope\/1$/), + method: 'GET', headers: { 'user-agent': expect.stringContaining(''), - 'content-type': 'application/json', }, - data: JSON.stringify({ - foo: 'bar', - other: 1, - }), }, }, }) .start(); - runner.makeRequest('post', '/test-post', { - headers: { - 'Content-Type': 'application/json', - }, - data: JSON.stringify({ foo: 'bar', other: 1 }), - }); + runner.makeRequest('get', '/test/isolationScope/1'); await runner.completed(); }); - test('correctly captures plain text request data', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - transaction: 'POST /test-post', - request: { - url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), - method: 'POST', - headers: { - 'user-agent': expect.stringContaining(''), - 'content-type': 'text/plain', + describe('request data', () => { + test('correctly captures JSON request data', async () => { + const runner = createRunner() + .expect({ + event: { + transaction: 'POST /test-post', + request: { + url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), + method: 'POST', + headers: { + 'user-agent': expect.stringContaining(''), + 'content-type': 'application/json', + }, + data: JSON.stringify({ + foo: 'bar', + other: 1, + }), }, - data: 'some plain text', }, - }, - }) - .start(); + }) + .start(); - runner.makeRequest('post', '/test-post', { - headers: { - 'Content-Type': 'text/plain', - }, - data: 'some plain text', + runner.makeRequest('post', '/test-post', { + headers: { + 'Content-Type': 'application/json', + }, + data: JSON.stringify({ foo: 'bar', other: 1 }), + }); + await runner.completed(); }); - await runner.completed(); - }); - test('correctly captures text buffer request data', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - transaction: 'POST /test-post', - request: { - url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), - method: 'POST', - headers: { - 'user-agent': expect.stringContaining(''), - 'content-type': 'application/octet-stream', + test('correctly captures plain text request data', async () => { + const runner = createRunner() + .expect({ + event: { + transaction: 'POST /test-post', + request: { + url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), + method: 'POST', + headers: { + 'user-agent': expect.stringContaining(''), + 'content-type': 'text/plain', + }, + data: 'some plain text', }, - data: 'some plain text in buffer', }, + }) + .start(); + + runner.makeRequest('post', '/test-post', { + headers: { + 'Content-Type': 'text/plain', }, - }) - .start(); + data: 'some plain text', + }); + await runner.completed(); + }); - runner.makeRequest('post', '/test-post', { - headers: { 'Content-Type': 'application/octet-stream' }, - data: Buffer.from('some plain text in buffer'), + test('correctly captures text buffer request data', async () => { + const runner = createRunner() + .expect({ + event: { + transaction: 'POST /test-post', + request: { + url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), + method: 'POST', + headers: { + 'user-agent': expect.stringContaining(''), + 'content-type': 'application/octet-stream', + }, + data: 'some plain text in buffer', + }, + }, + }) + .start(); + + runner.makeRequest('post', '/test-post', { + headers: { 'Content-Type': 'application/octet-stream' }, + data: Buffer.from('some plain text in buffer'), + }); + await runner.completed(); }); - await runner.completed(); - }); - test('correctly captures non-text buffer request data', async () => { - const runner = createRunner(__dirname, 'server.ts') - .expect({ - event: { - transaction: 'POST /test-post', - request: { - url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), - method: 'POST', - headers: { - 'user-agent': expect.stringContaining(''), - 'content-type': 'application/octet-stream', + test('correctly captures non-text buffer request data', async () => { + const runner = createRunner() + .expect({ + event: { + transaction: 'POST /test-post', + request: { + url: expect.stringMatching(/^http:\/\/localhost:(\d+)\/test-post$/), + method: 'POST', + headers: { + 'user-agent': expect.stringContaining(''), + 'content-type': 'application/octet-stream', + }, + // This is some non-ascii string representation + data: expect.any(String), }, - // This is some non-ascii string representation - data: expect.any(String), }, - }, - }) - .start(); + }) + .start(); - const body = new Uint8Array([1, 2, 3, 4, 5]).buffer; + const body = new Uint8Array([1, 2, 3, 4, 5]).buffer; - runner.makeRequest('post', '/test-post', { headers: { 'Content-Type': 'application/octet-stream' }, data: body }); - await runner.completed(); + runner.makeRequest('post', '/test-post', { + headers: { 'Content-Type': 'application/octet-stream' }, + data: body, + }); + await runner.completed(); + }); }); }); });