Skip to content

Commit ae84d1a

Browse files
authored
test(node): Node HTTP instrumentation integration tests (#11335)
This PR adds some utilities to aid in testing Node HTTP instrumentation and then updates the tests to use the new runner
1 parent fc23402 commit ae84d1a

File tree

6 files changed

+132
-83
lines changed

6 files changed

+132
-83
lines changed

dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
12
import * as Sentry from '@sentry/node';
23

34
Sentry.init({
45
dsn: 'https://[email protected]/1337',
56
release: '1.0',
67
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
79
});
810

911
import * as http from 'http';
1012

1113
// eslint-disable-next-line @typescript-eslint/no-floating-promises
1214
Sentry.startSpan({ name: 'test_transaction' }, async () => {
13-
http.get('http://match-this-url.com/api/v0');
14-
http.get('http://match-this-url.com/api/v1');
15+
http.get(`${process.env.SERVER_URL}/api/v0`);
16+
http.get(`${process.env.SERVER_URL}/api/v1`);
1517

1618
// Give it a tick to resolve...
1719
await new Promise(resolve => setTimeout(resolve, 100));
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,48 @@
1-
import nock from 'nock';
1+
import { createRunner } from '../../../utils/runner';
2+
import { createTestServer } from '../../../utils/server';
23

3-
import { TestEnv, assertSentryTransaction } from '../../../utils';
4+
test('should capture spans for outgoing http requests', done => {
5+
expect.assertions(3);
46

5-
// TODO: Convert this test to the new test runner
6-
// eslint-disable-next-line jest/no-disabled-tests
7-
test.skip('should capture spans for outgoing http requests', async () => {
8-
const match1 = nock('http://match-this-url.com').get('/api/v0').reply(200);
9-
const match2 = nock('http://match-this-url.com').get('/api/v1').reply(200);
10-
11-
const env = await TestEnv.init(__dirname);
12-
const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' });
13-
14-
expect(match1.isDone()).toBe(true);
15-
expect(match2.isDone()).toBe(true);
16-
17-
expect(envelope).toHaveLength(3);
18-
19-
assertSentryTransaction(envelope[2], {
20-
transaction: 'test_transaction',
21-
spans: [
22-
{
23-
description: 'GET http://match-this-url.com/api/v0',
24-
op: 'http.client',
25-
origin: 'auto.http.node.http',
26-
status: 'ok',
27-
},
28-
{
29-
description: 'GET http://match-this-url.com/api/v1',
30-
op: 'http.client',
31-
origin: 'auto.http.node.http',
32-
status: 'ok',
7+
createTestServer(done)
8+
.get('/api/v0', () => {
9+
// Just ensure we're called
10+
expect(true).toBe(true);
11+
})
12+
.get(
13+
'/api/v1',
14+
() => {
15+
// Just ensure we're called
16+
expect(true).toBe(true);
3317
},
34-
],
35-
});
18+
404,
19+
)
20+
.start()
21+
.then(SERVER_URL => {
22+
createRunner(__dirname, 'scenario.ts')
23+
.withEnv({ SERVER_URL })
24+
.expect({
25+
transaction: {
26+
transaction: 'test_transaction',
27+
spans: expect.arrayContaining([
28+
expect.objectContaining({
29+
description: expect.stringMatching(/GET .*\/api\/v0/),
30+
op: 'http.client',
31+
origin: 'auto.http.otel.http',
32+
status: 'ok',
33+
}),
34+
expect.objectContaining({
35+
description: expect.stringMatching(/GET .*\/api\/v1/),
36+
op: 'http.client',
37+
origin: 'auto.http.otel.http',
38+
status: 'unknown_error',
39+
data: expect.objectContaining({
40+
'http.response.status_code': 404,
41+
}),
42+
}),
43+
]),
44+
},
45+
})
46+
.start(done);
47+
});
3648
});
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
12
import * as Sentry from '@sentry/node';
23

34
Sentry.init({
@@ -6,13 +7,14 @@ Sentry.init({
67
tracesSampleRate: 1.0,
78
tracePropagationTargets: [/\/v0/, 'v1'],
89
integrations: [],
10+
transport: loggingTransport,
911
});
1012

1113
import * as http from 'http';
1214

1315
Sentry.startSpan({ name: 'test_span' }, () => {
14-
http.get('http://match-this-url.com/api/v0');
15-
http.get('http://match-this-url.com/api/v1');
16-
http.get('http://dont-match-this-url.com/api/v2');
17-
http.get('http://dont-match-this-url.com/api/v3');
16+
http.get(`${process.env.SERVER_URL}/api/v0`);
17+
http.get(`${process.env.SERVER_URL}/api/v1`);
18+
http.get(`${process.env.SERVER_URL}/api/v2`);
19+
http.get(`${process.env.SERVER_URL}/api/v3`);
1820
});
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,35 @@
1-
import nock from 'nock';
2-
3-
import { TestEnv, runScenario } from '../../../utils';
4-
5-
// TODO: Convert this test to the new test runner
6-
// eslint-disable-next-line jest/no-disabled-tests
7-
test.skip('HttpIntegration should instrument correct requests when tracePropagationTargets option is provided', async () => {
8-
const match1 = nock('http://match-this-url.com')
9-
.get('/api/v0')
10-
.matchHeader('baggage', val => typeof val === 'string')
11-
.matchHeader('sentry-trace', val => typeof val === 'string')
12-
.reply(200);
13-
14-
const match2 = nock('http://match-this-url.com')
15-
.get('/api/v1')
16-
.matchHeader('baggage', val => typeof val === 'string')
17-
.matchHeader('sentry-trace', val => typeof val === 'string')
18-
.reply(200);
19-
20-
const match3 = nock('http://dont-match-this-url.com')
21-
.get('/api/v2')
22-
.matchHeader('baggage', val => val === undefined)
23-
.matchHeader('sentry-trace', val => val === undefined)
24-
.reply(200);
25-
26-
const match4 = nock('http://dont-match-this-url.com')
27-
.get('/api/v3')
28-
.matchHeader('baggage', val => val === undefined)
29-
.matchHeader('sentry-trace', val => val === undefined)
30-
.reply(200);
31-
32-
const env = await TestEnv.init(__dirname);
33-
await runScenario(env.url);
34-
35-
env.server.close();
36-
nock.cleanAll();
37-
38-
await new Promise(resolve => env.server.close(resolve));
39-
40-
expect(match1.isDone()).toBe(true);
41-
expect(match2.isDone()).toBe(true);
42-
expect(match3.isDone()).toBe(true);
43-
expect(match4.isDone()).toBe(true);
1+
import { createRunner } from '../../../utils/runner';
2+
import { createTestServer } from '../../../utils/server';
3+
4+
test('HttpIntegration should instrument correct requests when tracePropagationTargets option is provided', done => {
5+
expect.assertions(9);
6+
7+
createTestServer(done)
8+
.get('/api/v0', headers => {
9+
expect(typeof headers['baggage']).toBe('string');
10+
expect(typeof headers['sentry-trace']).toBe('string');
11+
})
12+
.get('/api/v1', headers => {
13+
expect(typeof headers['baggage']).toBe('string');
14+
expect(typeof headers['sentry-trace']).toBe('string');
15+
})
16+
.get('/api/v2', headers => {
17+
expect(headers['baggage']).toBeUndefined();
18+
expect(headers['sentry-trace']).toBeUndefined();
19+
})
20+
.get('/api/v3', headers => {
21+
expect(headers['baggage']).toBeUndefined();
22+
expect(headers['sentry-trace']).toBeUndefined();
23+
})
24+
.start()
25+
.then(SERVER_URL => {
26+
createRunner(__dirname, 'scenario.ts')
27+
.withEnv({ SERVER_URL })
28+
.expect({
29+
transaction: {
30+
// we're not too concerned with the actual transaction here since this is tested elsewhere
31+
},
32+
})
33+
.start(done);
34+
});
4435
});

dev-packages/node-integration-tests/utils/runner.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export function createRunner(...paths: string[]) {
126126
const expectedEnvelopes: Expected[] = [];
127127
const flags: string[] = [];
128128
const ignored: EnvelopeItemType[] = [];
129+
let withEnv: Record<string, string> = {};
129130
let withSentryServer = false;
130131
let dockerOptions: DockerOptions | undefined;
131132
let ensureNoErrorOutput = false;
@@ -144,6 +145,10 @@ export function createRunner(...paths: string[]) {
144145
expectError = true;
145146
return this;
146147
},
148+
withEnv: function (env: Record<string, string>) {
149+
withEnv = env;
150+
return this;
151+
},
147152
withFlags: function (...args: string[]) {
148153
flags.push(...args);
149154
return this;
@@ -263,8 +268,8 @@ export function createRunner(...paths: string[]) {
263268
}
264269

265270
const env = mockServerPort
266-
? { ...process.env, SENTRY_DSN: `http://public@localhost:${mockServerPort}/1337` }
267-
: process.env;
271+
? { ...process.env, ...withEnv, SENTRY_DSN: `http://public@localhost:${mockServerPort}/1337` }
272+
: { ...process.env, ...withEnv };
268273

269274
// eslint-disable-next-line no-console
270275
if (process.env.DEBUG) console.log('starting scenario', testPath, flags, env.SENTRY_DSN);

dev-packages/node-integration-tests/utils/server.ts

+37
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,40 @@ export function createBasicSentryServer(onEnvelope: (env: Envelope) => void): Pr
3131
});
3232
});
3333
}
34+
35+
type HeaderAssertCallback = (headers: Record<string, string | string[] | undefined>) => void;
36+
37+
/** Creates a test server that can be used to check headers */
38+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
39+
export function createTestServer(done: (error: unknown) => void) {
40+
const gets: Array<[string, HeaderAssertCallback, number]> = [];
41+
42+
return {
43+
get: function (path: string, callback: HeaderAssertCallback, result = 200) {
44+
gets.push([path, callback, result]);
45+
return this;
46+
},
47+
start: async (): Promise<string> => {
48+
const app = express();
49+
50+
for (const [path, callback, result] of gets) {
51+
app.get(path, (req, res) => {
52+
try {
53+
callback(req.headers);
54+
} catch (e) {
55+
done(e);
56+
}
57+
58+
res.status(result).send();
59+
});
60+
}
61+
62+
return new Promise(resolve => {
63+
const server = app.listen(0, () => {
64+
const address = server.address() as AddressInfo;
65+
resolve(`http://localhost:${address.port}`);
66+
});
67+
});
68+
},
69+
};
70+
}

0 commit comments

Comments
 (0)