Skip to content

Commit b5fc755

Browse files
cursoragentclaude
andcommitted
test(node): Add integration tests for diagnostics channel HTTP spans
This adds integration tests for the new outgoing request span creation feature using diagnostics channels (Node 22.12+). The tests verify: - Spans are created for outgoing HTTP requests - Trace propagation uses the outgoing span's ID, not the parent - The 'spans' option correctly disables span creation when false - Span attributes include proper sentry.origin values The tests are conditionally run only on Node 22.12+, 23.2+, or 24+ where the http.client.request.created diagnostics channel is available. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 9cb0bcf commit b5fc755

6 files changed

Lines changed: 172 additions & 0 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import*asSentryfrom'@sentry/node';
2+
import{loggingTransport}from'@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
integrations: [Sentry.httpIntegration({spans: false})],
9+
transport: loggingTransport,
10+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import*asSentryfrom'@sentry/node';
2+
import*ashttpfrom'http';
3+
4+
Sentry.startSpan({name: 'test_span'},async()=>{
5+
awaitmakeHttpRequest(`${process.env.SERVER_URL}/api/test`);
6+
});
7+
8+
functionmakeHttpRequest(url){
9+
returnnewPromise((resolve,reject)=>{
10+
http
11+
.request(url,httpRes=>{
12+
httpRes.on('data',()=>{
13+
// we don't care about data
14+
});
15+
httpRes.on('end',()=>{
16+
resolve();
17+
});
18+
httpRes.on('error',reject);
19+
})
20+
.on('error',reject)
21+
.end();
22+
});
23+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import{createTestServer}from'@sentry-internal/test-utils';
2+
import{parseSemver}from'@sentry/core';
3+
import{describe,expect}from'vitest';
4+
import{createEsmAndCjsTests}from'../../../../utils/runner';
5+
6+
constNODE_VERSION=parseSemver(process.versions.node);
7+
8+
constsupportsHttpDiagnosticsChannel=
9+
(NODE_VERSION.major===22&&NODE_VERSION.minor>=12)||
10+
(NODE_VERSION.major===23&&NODE_VERSION.minor>=2)||
11+
NODE_VERSION.major>=24;
12+
13+
consttestIfSupported=supportsHttpDiagnosticsChannel ? describe : describe.skip;
14+
15+
testIfSupported('outgoing http with diagnostics channel spans disabled',()=>{
16+
createEsmAndCjsTests(__dirname,'scenario.mjs','instrument.mjs',(createRunner,test)=>{
17+
test('does not create spans when spans option is false',async()=>{
18+
expect.assertions(3);
19+
20+
const[SERVER_URL,closeTestServer]=awaitcreateTestServer()
21+
.get('/api/test',headers=>{
22+
expect(headers['sentry-trace']).toEqual(expect.any(String));
23+
})
24+
.start();
25+
26+
awaitcreateRunner()
27+
.withEnv({SERVER_URL})
28+
.expect({
29+
transaction: event=>{
30+
expect(event.transaction).toBe('test_span');
31+
32+
consthttpClientSpans=event.spans?.filter(span=>span.op==='http.client');
33+
expect(httpClientSpans).toHaveLength(0);
34+
},
35+
})
36+
.start()
37+
.completed();
38+
closeTestServer();
39+
});
40+
});
41+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import*asSentryfrom'@sentry/node';
2+
import{loggingTransport}from'@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
integrations: [],
9+
transport: loggingTransport,
10+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import*asSentryfrom'@sentry/node';
2+
import*ashttpfrom'http';
3+
4+
Sentry.startSpan({name: 'test_span'},async()=>{
5+
awaitmakeHttpRequest(`${process.env.SERVER_URL}/api/test`);
6+
});
7+
8+
functionmakeHttpRequest(url){
9+
returnnewPromise((resolve,reject)=>{
10+
http
11+
.request(url,httpRes=>{
12+
httpRes.on('data',()=>{
13+
// we don't care about data
14+
});
15+
httpRes.on('end',()=>{
16+
resolve();
17+
});
18+
httpRes.on('error',reject);
19+
})
20+
.on('error',reject)
21+
.end();
22+
});
23+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import{createTestServer}from'@sentry-internal/test-utils';
2+
import{parseSemver}from'@sentry/core';
3+
import{describe,expect}from'vitest';
4+
import{createEsmAndCjsTests}from'../../../../utils/runner';
5+
6+
constNODE_VERSION=parseSemver(process.versions.node);
7+
8+
// The `http.client.request.created` diagnostics channel was added in Node 22.12.0 and 23.2.0
9+
constsupportsHttpDiagnosticsChannel=
10+
(NODE_VERSION.major===22&&NODE_VERSION.minor>=12)||
11+
(NODE_VERSION.major===23&&NODE_VERSION.minor>=2)||
12+
NODE_VERSION.major>=24;
13+
14+
consttestIfSupported=supportsHttpDiagnosticsChannel ? describe : describe.skip;
15+
16+
testIfSupported('outgoing http with diagnostics channel spans',()=>{
17+
createEsmAndCjsTests(__dirname,'scenario.mjs','instrument.mjs',(createRunner,test)=>{
18+
test('creates spans for outgoing requests and propagates trace context within span',async()=>{
19+
expect.assertions(8);
20+
21+
letoutgoingSpanId: string|undefined;
22+
23+
const[SERVER_URL,closeTestServer]=awaitcreateTestServer()
24+
.get('/api/test',headers=>{
25+
// Verify trace propagation headers are present
26+
expect(headers['baggage']).toEqual(expect.any(String));
27+
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f\d]{32})-([a-f\d]{16})-1$/));
28+
29+
// Extract the span ID from the sentry-trace header
30+
constsentryTrace=headers['sentry-trace']asstring;
31+
outgoingSpanId=sentryTrace.split('-')[1];
32+
33+
// Verify we're not propagating all-zero trace IDs
34+
expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-1');
35+
})
36+
.start();
37+
38+
awaitcreateRunner()
39+
.withEnv({SERVER_URL})
40+
.expect({
41+
transaction: event=>{
42+
expect(event.transaction).toBe('test_span');
43+
44+
// Verify that an http.client span was created
45+
consthttpClientSpans=event.spans?.filter(span=>span.op==='http.client');
46+
expect(httpClientSpans).toHaveLength(1);
47+
48+
consthttpSpan=httpClientSpans![0];
49+
expect(httpSpan?.description).toMatch(/^GET/);
50+
51+
// Verify the propagated span ID matches the created span
52+
if(outgoingSpanId){
53+
expect(httpSpan?.span_id).toBe(outgoingSpanId);
54+
}
55+
56+
// Verify span attributes include sentry.origin
57+
expect(httpSpan?.data?.['sentry.origin']).toBe('auto.http.otel.http');
58+
},
59+
})
60+
.start()
61+
.completed();
62+
closeTestServer();
63+
});
64+
});
65+
});

0 commit comments

Comments
 (0)