From 6518d1232388c0d4937a80dd51dd3a3bb4d7e409 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 11 Aug 2025 13:16:41 +0200 Subject: [PATCH] test(node): Add tests for full http.server span attribute coverage Add tests that definitely show: 1. How get and post spans are captured 2. How URLs are handled with params and fragments --- .../httpIntegration/instrument-options.mjs | 41 +++++ .../tracing/httpIntegration/instrument.mjs | 32 ---- .../suites/tracing/httpIntegration/server.mjs | 4 + .../suites/tracing/httpIntegration/test.ts | 159 ++++++++++++++---- .../node-integration-tests/utils/runner.ts | 4 + yarn.lock | 42 +---- 6 files changed, 173 insertions(+), 109 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs new file mode 100644 index 000000000000..8cf2e8a5248f --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument-options.mjs @@ -0,0 +1,41 @@ +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, + + integrations: [ + Sentry.httpIntegration({ + instrumentation: { + requestHook: (span, req) => { + span.setAttribute('attr1', 'yes'); + Sentry.setExtra('requestHookCalled', { + url: req.url, + method: req.method, + }); + }, + responseHook: (span, res) => { + span.setAttribute('attr2', 'yes'); + Sentry.setExtra('responseHookCalled', { + url: res.req.url, + method: res.req.method, + }); + }, + applyCustomAttributesOnSpan: (span, req, res) => { + span.setAttribute('attr3', 'yes'); + Sentry.setExtra('applyCustomAttributesOnSpanCalled', { + reqUrl: req.url, + reqMethod: req.method, + resUrl: res.req.url, + resMethod: res.req.method, + }); + }, + }, + }), + ], +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument.mjs index 8cf2e8a5248f..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/instrument.mjs @@ -4,38 +4,6 @@ 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, - - integrations: [ - Sentry.httpIntegration({ - instrumentation: { - requestHook: (span, req) => { - span.setAttribute('attr1', 'yes'); - Sentry.setExtra('requestHookCalled', { - url: req.url, - method: req.method, - }); - }, - responseHook: (span, res) => { - span.setAttribute('attr2', 'yes'); - Sentry.setExtra('responseHookCalled', { - url: res.req.url, - method: res.req.method, - }); - }, - applyCustomAttributesOnSpan: (span, req, res) => { - span.setAttribute('attr3', 'yes'); - Sentry.setExtra('applyCustomAttributesOnSpanCalled', { - reqUrl: req.url, - reqMethod: req.method, - resUrl: res.req.url, - resMethod: res.req.method, - }); - }, - }, - }), - ], }); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server.mjs b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server.mjs index 44122f375857..37e629758828 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server.mjs @@ -11,6 +11,10 @@ app.get('/test', (_req, res) => { res.send({ response: 'response 1' }); }); +app.post('/test', (_req, res) => { + res.send({ response: 'response 2' }); +}); + Sentry.setupExpressErrorHandler(app); startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts index 97043c998814..397b6baa7cc7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts @@ -7,47 +7,134 @@ describe('httpIntegration', () => { cleanupChildProcesses(); }); - createEsmAndCjsTests(__dirname, 'server.mjs', 'instrument.mjs', (createRunner, test) => { - test('allows to pass instrumentation options to integration', async () => { - const runner = createRunner() - .expect({ - transaction: { - contexts: { - trace: { - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - data: { + describe('instrumentation options', () => { + createEsmAndCjsTests(__dirname, 'server.mjs', 'instrument-options.mjs', (createRunner, test) => { + test('allows to pass instrumentation options to integration', async () => { + const runner = createRunner() + .expect({ + transaction: { + contexts: { + trace: { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + url: expect.stringMatching(/\/test$/), + 'http.response.status_code': 200, + attr1: 'yes', + attr2: 'yes', + attr3: 'yes', + }, + op: 'http.server', + status: 'ok', + }, + }, + extra: { + requestHookCalled: { url: expect.stringMatching(/\/test$/), - 'http.response.status_code': 200, - attr1: 'yes', - attr2: 'yes', - attr3: 'yes', + method: 'GET', + }, + responseHookCalled: { + url: expect.stringMatching(/\/test$/), + method: 'GET', + }, + applyCustomAttributesOnSpanCalled: { + reqUrl: expect.stringMatching(/\/test$/), + reqMethod: 'GET', + resUrl: expect.stringMatching(/\/test$/), + resMethod: 'GET', }, - op: 'http.server', - status: 'ok', }, }, - extra: { - requestHookCalled: { - url: expect.stringMatching(/\/test$/), - method: 'GET', - }, - responseHookCalled: { - url: expect.stringMatching(/\/test$/), - method: 'GET', - }, - applyCustomAttributesOnSpanCalled: { - reqUrl: expect.stringMatching(/\/test$/), - reqMethod: 'GET', - resUrl: expect.stringMatching(/\/test$/), - resMethod: 'GET', - }, + }) + .start(); + runner.makeRequest('get', '/test'); + await runner.completed(); + }); + }); + }); + + describe('http.server spans', () => { + createEsmAndCjsTests(__dirname, 'server.mjs', 'instrument.mjs', (createRunner, test) => { + test('captures correct attributes for GET requests', async () => { + const runner = createRunner() + .expect({ + transaction: transaction => { + const port = runner.getPort(); + expect(transaction.transaction).toBe('GET /test'); + expect(transaction.contexts?.trace?.data).toEqual({ + 'http.flavor': '1.1', + 'http.host': `localhost:${port}`, + 'http.method': 'GET', + 'http.query': 'a=1&b=2', + 'http.response.status_code': 200, + 'http.route': '/test', + 'http.scheme': 'http', + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.target': '/test?a=1&b=2', + 'http.url': `http://localhost:${port}/test?a=1&b=2`, + 'http.user_agent': 'node', + 'net.host.ip': '::1', + 'net.host.name': 'localhost', + 'net.host.port': port, + 'net.peer.ip': '::1', + 'net.peer.port': expect.any(Number), + 'net.transport': 'ip_tcp', + 'otel.kind': 'SERVER', + 'sentry.op': 'http.server', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + url: `http://localhost:${port}/test`, + }); }, - }, - }) - .start(); - runner.makeRequest('get', '/test'); - await runner.completed(); + }) + .start(); + + runner.makeRequest('get', '/test?a=1&b=2#hash'); + await runner.completed(); + }); + + test('captures correct attributes for POST requests', async () => { + const runner = createRunner() + .expect({ + transaction: transaction => { + const port = runner.getPort(); + expect(transaction.transaction).toBe('POST /test'); + expect(transaction.contexts?.trace?.data).toEqual({ + 'http.flavor': '1.1', + 'http.host': `localhost:${port}`, + 'http.method': 'POST', + 'http.query': 'a=1&b=2', + 'http.request_content_length_uncompressed': 9, + 'http.response.status_code': 200, + 'http.route': '/test', + 'http.scheme': 'http', + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.target': '/test?a=1&b=2', + 'http.url': `http://localhost:${port}/test?a=1&b=2`, + 'http.user_agent': 'node', + 'net.host.ip': '::1', + 'net.host.name': 'localhost', + 'net.host.port': port, + 'net.peer.ip': '::1', + 'net.peer.port': expect.any(Number), + 'net.transport': 'ip_tcp', + 'otel.kind': 'SERVER', + 'sentry.op': 'http.server', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + url: `http://localhost:${port}/test`, + }); + }, + }) + .start(); + + runner.makeRequest('post', '/test?a=1&b=2#hash', { data: 'test body' }); + await runner.completed(); + }); }); }); diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts index f4a176688280..164af83518bc 100644 --- a/dev-packages/node-integration-tests/utils/runner.ts +++ b/dev-packages/node-integration-tests/utils/runner.ts @@ -158,6 +158,7 @@ type StartResult = { completed(): Promise; childHasExited(): boolean; getLogs(): string[]; + getPort(): number | undefined; makeRequest( method: 'get' | 'post', path: string, @@ -617,6 +618,9 @@ export function createRunner(...paths: string[]) { getLogs(): string[] { return logs; }, + getPort(): number | undefined { + return scenarioServerPort; + }, makeRequest: async function ( method: 'get' | 'post', path: string, diff --git a/yarn.lock b/yarn.lock index a79397ae7beb..5e0eafdb61fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6924,7 +6924,7 @@ mitt "^3.0.0" "@sentry-internal/test-utils@link:dev-packages/test-utils": - version "10.2.0" + version "10.3.0" dependencies: express "^4.21.1" @@ -6966,81 +6966,41 @@ magic-string "0.30.8" unplugin "1.0.1" -"@sentry/cli-darwin@2.49.0": - version "2.49.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.49.0.tgz#290657e5840b360cb8ca25c8a78f8c0f15c66b03" - integrity sha512-bgowyDeFuXbjkGq1ZKqcWhmzgfBe7oKIXYWJOOps4+32QfG+YsrdNnottHS01td3bzrJq0QnHj8H12fA81DqrA== - "@sentry/cli-darwin@2.50.2": version "2.50.2" resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.50.2.tgz#fcf924fcc02cfa54748ff07a380334e533635c74" integrity sha512-0Pjpl0vQqKhwuZm19z6AlEF+ds3fJg1KWabv8WzGaSc/fwxMEwjFwOZj+IxWBJPV578cXXNvB39vYjjpCH8j7A== -"@sentry/cli-linux-arm64@2.49.0": - version "2.49.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.49.0.tgz#a732004d7131f7e7b44f6a64abdccc36efb35d52" - integrity sha512-dqxsDUd76aDm03fUwUOs5BR7RHLpSb2EH/B1hlWm0mFvo9uY907XxW9wDFx/qDpCdmpC0aF+lF/lOBOrG9B5Fg== - "@sentry/cli-linux-arm64@2.50.2": version "2.50.2" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.50.2.tgz#ac9e6dba42095832bac8084abab4b86fdd2956f3" integrity sha512-03Cj215M3IdoHAwevCxm5oOm9WICFpuLR05DQnODFCeIUsGvE1pZsc+Gm0Ky/ZArq2PlShBJTpbHvXbCUka+0w== -"@sentry/cli-linux-arm@2.49.0": - version "2.49.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.49.0.tgz#73719561510df3369e05e9a4898b4e43b8753e4c" - integrity sha512-RBDIjIGmNsFw+a6vAt6m3D7ROKsMEB9i3u+UuIRxk0/DyHTcfVWxnK/ScPXGILM6PxQ2XOBfOKad0mmiDHBzZA== - "@sentry/cli-linux-arm@2.50.2": version "2.50.2" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.50.2.tgz#835acd53ca83f6be9fc0d3d85a3cd4c694051bce" integrity sha512-jzFwg9AeeuFAFtoCcyaDEPG05TU02uOy1nAX09c1g7FtsyQlPcbhI94JQGmnPzdRjjDmORtwIUiVZQrVTkDM7w== -"@sentry/cli-linux-i686@2.49.0": - version "2.49.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.49.0.tgz#8d1bb1378251a3aa995cc4b56bd352fa12a84b66" - integrity sha512-gDAd5/vJbEhd4Waud0Cd8ZRqLEagDlOvWwNH3KB694EiHJUwzRSiTA1YUVMYGI8Z9UyEA1sKxARwm2Trv99BxA== - "@sentry/cli-linux-i686@2.50.2": version "2.50.2" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.50.2.tgz#72f0e4bc1c515754aa11225efce711a24fb53524" integrity sha512-J+POvB34uVyHbIYF++Bc/OCLw+gqKW0H/y/mY7rRZCiocgpk266M4NtsOBl6bEaurMx1D+BCIEjr4nc01I/rqA== -"@sentry/cli-linux-x64@2.49.0": - version "2.49.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.49.0.tgz#7bf58fb7005c89fdde4e1262d5ed35e23065aceb" - integrity sha512-mbohGvPNhHjUciYNXzkt9TYUebTmxeAp9v9JfLSb/Soz6fubKwEHhpRJuz1zASxVWIR4PuqkePchqN5zhcLC0A== - "@sentry/cli-linux-x64@2.50.2": version "2.50.2" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.50.2.tgz#d06f8ffd65871b1373a0d2228ab254d9456a615c" integrity sha512-81yQVRLj8rnuHoYcrM7QbOw8ubA3weiMdPtTxTim1s6WExmPgnPTKxLCr9xzxGJxFdYo3xIOhtf5JFpUX/3j4A== -"@sentry/cli-win32-arm64@2.49.0": - version "2.49.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.49.0.tgz#2bf6dd911acbe3ddb02eec0afb4301bb8fb25b53" - integrity sha512-3zwvsp61EPpSuGpGdXY4JelVJmNEjoj4vn5m6EFoOtk7OUI5/VFqqR4wchjy9Hjm3Eh6MB5K+KTKXs4W2p18ng== - "@sentry/cli-win32-arm64@2.50.2": version "2.50.2" resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.50.2.tgz#4bd7a140367c17f77d621903cfe0914232108657" integrity sha512-QjentLGvpibgiZlmlV9ifZyxV73lnGH6pFZWU5wLeRiaYKxWtNrrHpVs+HiWlRhkwQ0mG1/S40PGNgJ20DJ3gA== -"@sentry/cli-win32-i686@2.49.0": - version "2.49.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.49.0.tgz#32e31472ae6c5f69e538a4061d651937fcb8f14a" - integrity sha512-2oWaNl6z0BaOCAjM1Jxequfgjod3XO6wothxow4kA8e9+43JLhgarSdpwJPgQjcVyxjygwQ3/jKPdUFh0qNOmg== - "@sentry/cli-win32-i686@2.50.2": version "2.50.2" resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.50.2.tgz#1eb997cf780c396446cdd8e63c6d4309894465e8" integrity sha512-UkBIIzkQkQ1UkjQX8kHm/+e7IxnEhK6CdgSjFyNlxkwALjDWHJjMztevqAPz3kv4LdM6q1MxpQ/mOqXICNhEGg== -"@sentry/cli-win32-x64@2.49.0": - version "2.49.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.49.0.tgz#86aab38cb41f885914d7c99ceaab7b6ce52c72c6" - integrity sha512-dR4ulyrA6ZT7x7cg4Rwm0tcHf4TZz5QO6t1W1jX6uJ9n/U0bOSqSFZHNf/RryiUzQE1g8LBthOYyKGMkET6T8w== - "@sentry/cli-win32-x64@2.50.2": version "2.50.2" resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.50.2.tgz#1d0c106125b6dc87f3a598ac02519c699f17a6c0"