8000 test(node): Add basic integration tests for Express. (#4817) · michax/sentry-javascript@2934a73 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2934a73

Browse files
authored
test(node): Add basic integration tests for Express. (getsentry#4817)
1 parent db661b4 commit 2934a73

File tree

10 files changed

+241
-3
lines changed

10 files changed

+241
-3
lines changed

packages/node-integration-tests/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@types/mongodb": "^3.6.20",
1919
"@types/mysql": "^2.15.21",
2020
"@types/pg": "^8.6.5",
21+
"cors": "^2.8.5",
2122
"express": "^4.17.3",
2223
"mongodb": "^3.7.3",
2324
"mongodb-memory-server": "^7.6.3",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as Sentry from '@sentry/node';
2+
import express from 'express';
3+
4+
const app = express();
5+
6+
Sentry.init({
7+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
8+
release: '1.0',
9+
});
10+
11+
app.use(Sentry.Handlers.requestHandler());
12+
13+
app.get('/test/express', () => {
14+
throw new Error('test_error');
15+
});
16+
17+
app.use(Sentry.Handlers. 67E6 errorHandler());
18+
19+
export default app;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { assertSentryEvent, getEventRequest, runServer } from '../../../utils/index';
2+
3+
test('should capture and send Express controller error.', async () => {
4+
const url = await runServer(__dirname, `${__dirname}/server.ts`);
5+
const event = await getEventRequest(`${url}/express`);
6+
7+
expect((event as any).exception.values[0].stacktrace.frames.length).toBeGreaterThan(0);
8+
9+
assertSentryEvent(event, {
10+
exception: {
11+
values: [
12+
{
13+
mechanism: {
14+
type: 'generic',
15+
handled: true,
16+
},
17+
type: 'Error',
18+
value: 'test_error',
19+
},
20+
],
21+
},
22+
});
23+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as Sentry from '@sentry/node';
2+
import * as Tracing from '@sentry/tracing';
3+
import cors from 'cors';
4+
import express from 'express';
5+
import http from 'http';
6+
7+
const app = express();
8+
9+
Sentry.init({
10+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
11+
release: '1.0',
12+
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
13+
tracesSampleRate: 1.0,
14+
});
15+
16+
app.use(Sentry.Handlers.requestHandler());
17+
app.use(Sentry.Handlers.tracingHandler());
18+
19+
app.use(cors());
20+
21+
app.get('/test/express', (_req, res) => {
22+
const headers = http.get('http://somewhere.not.sentry/').getHeaders();
23+
24+
// Responding with the headers outgoing request headers back to the assertions.
25+
res.send({ test_data: headers });
26+
});
27+
28+
app.use(Sentry.Handlers.errorHandler());
29+
30+
export default app;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { TRACEPARENT_REGEXP } from '@sentry/utils';
2+
3+
import { getAPIResponse, runServer } from '../../../../utils/index';
4+
import path = require('path');
5+
6+
test('Should assign `sentry-trace` header which sets parent trace id of an outgoing request.', async () => {
7+
const url = await runServer(__dirname, `${path.resolve(__dirname, '..')}/server.ts`);
8+
9+
const response = await getAPIResponse(new URL(`${url}/express`), {
10+
'sentry-trace': '12312012123120121231201212312012-1121201211212012-0',
11+
});
12+
13+
expect(response).toBeDefined();
14+
expect(response).toMatchObject({
15+
test_data: {
16+
host: 'somewhere.not.sentry',
17+
'sentry-trace': expect.stringContaining('12312012123120121231201212312012-'),
18+
},
19+
});
20+
21+
expect(TRACEPARENT_REGEXP.test(response.test_data['sentry-trace'])).toBe(true);
22+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { TRACEPARENT_REGEXP } from '@sentry/utils';
2+
3+
import { getAPIResponse, runServer } from '../../../../utils/index';
4+
import path = require('path');
5+
6+
test('should attach a `sentry-trace` header to an outgoing request.', async () => {
7+
const url = await runServer(__dirname, `${path.resolve(__dirname, '..')}/server.ts`);
8+
9+
const response = await getAPIResponse(new URL(`${url}/express`));
10+
11+
expect(response).toBeDefined();
12+
expect(response).toMatchObject({
13+
test_data: {
14+
host: 'somewhere.not.sentry',
15+
'sentry-trace': expect.any(String),
16+
},
17+
});
18+
19+
expect(TRACEPARENT_REGEXP.test(response.test_data['sentry-trace'])).toBe(true);
20+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as Sentry from '@sentry/node';
2+
import * as Tracing from '@sentry/tracing';
3+
import cors from 'cors';
4+
import express from 'express';
5+
6+
const app = express();
7+
8+
Sentry.init({
9+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
10+
release: '1.0',
11+
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
12+
tracesSampleRate: 1.0,
13+
});
14+
15+
app.use(Sentry.Handlers.requestHandler());
16+
app.use(Sentry.Handlers.tracingHandler());
17+
18+
app.use(cors());
19+
20+
app.get('/test/express', (_req, res) => {
21+
res.send({ response: 'response 1' });
22+
});
23+
24+
app.use(Sentry.Handlers.errorHandler());
25+
26+
export default app;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { assertSentryTransaction, getEnvelopeRequest, runServer } from '../../../utils/index';
2+
3+
test('should create and send transactions for Express routes and spans for middlewares.', async () => {
4+
const url = await runServer(__dirname, `${__dirname}/server.ts`);
5+
const envelope = await getEnvelopeRequest(`${url}/express`);
6+
7+
expect(envelope).toHaveLength(3);
8+
9+
assertSentryTransaction(envelope[2], {
10+
contexts: {
11+
trace: {
12+
data: {
13+
url: '/test/express',
14+
},
15+
op: 'http.server',
16+
status: 'ok',
17+
tags: {
18+
'http.status_code': '200',
19+
},
20+
},
21+
},
22+
spans: [
23+
{
24+
description: 'corsMiddleware',
25+
op: 'express.middleware.use',
26+
},
27+
],
28+
});
29+
});

packages/node-integration-tests/utils/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
12
import { parseSemver } from '@sentry/utils';
23
import { Express } from 'express';
34
import * as http from 'http';
5+
import { RequestOptions } from 'https';
46
import nock from 'nock';
57
import * as path from 'path';
68
import { getPortPromise } from 'portfinder';
@@ -130,6 +132,39 @@ export const getMultipleEnvelopeRequest = async (url: string, count: number): Pr
130132
});
131133
};
132134

135+
/**
136+
* Sends a get request to given URL, with optional headers
137+
*
138+
* @param {URL} url
139+
* @param {Record<string, string>} [headers]
140+
* @return {*} {Promise<any>}
141+
*/
142+
export const getAPIResponse = async (url: URL, headers?: Record<string, string>): Promise<any> => {
143+
return await new Promise(resolve => {
144+
http.get(
145+
headers
146+
? ({
147+
protocol: url.protocol,
148+
host: url.hostname,
149+
path: url.pathname,
150+
port: url.port,
151+
headers,
152+
} as RequestOptions)
153+
: url,
154+
response => {
155+
let body = '';
156+
157+
response.on('data', function (chunk: string) {
158+
body += chunk;
159+
});
160+
response.on('end', function () {
161+
resolve(JSON.parse(body));
162+
});
163+
},
164+
);
165+
});
166+
};
167+
133168
/**
134169
* Intercepts and extracts a single request containing a Sentry envelope
135170
*

yarn.lock

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4573,11 +4573,32 @@ after@0.8.2:
45734573
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
45744574
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
45754575

4576-
agent-base@4, agent-base@5, agent-base@6, agent-base@^4.3.0, agent-base@^6.0.2, agent-base@~4.2.1:
4576+
agent-base@4, agent-base@^4.3.0:
4577+
version "4.3.0"
4578+
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
4579+
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
4580+
dependencies:
4581+
es6-promisify "^5.0.0"
4582+
4583+
agent-base@5:
45774584
version "5.1.1"
45784585
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
45794586
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
45804587

4588+
agent-base@6, agent-base@^6.0.2:
4589+
version "6.0.2"
4590+
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
4591+
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
4592+
dependencies:
4593+
debug "4"
4594+
4595+
agent-base@~4.2.1:
4596+
version "4.2.1"
4597+
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
4598+
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
4599+
dependencies:
4600+
es6-promisify "^5.0.0"
4601+
45814602
agentkeepalive@^3.4.1:
45824603
version "3.5.2"
45834604
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67"
@@ -8032,7 +8053,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
80328053
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
80338054
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
80348055

8035-
cors@~2.8.5:
8056+
cors@^2.8.5, cors@~2.8.5:
80368057
version "2.8.5"
80378058
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
80388059
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
@@ -10080,6 +10101,18 @@ es6-object-assign@^1.1.0:
1008010101
resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
1008110102
integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=
1008210103

10104+
es6-promise@^4.0.3:
10105+
version "4.2.8"
10106+
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
10107+
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
10108+
10109+
es6-promisify@^5.0.0:
10110+
version "5.0.0"
10111+
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
10112+
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
10113+
dependencies:
10114+
es6-promise "^4.0.3"
10115+
1008310116
escalade@^3.1.1:
1008410117
version "3.1.1"
1008510118
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -13594,7 +13627,7 @@ jest-environment-jsdom@^24.9.0:
1359413627
jest-util "^24.9.0"
1359513628
jsdom "^11.5.1"
1359613629

13597-
jest-environment-node@24, "jest-environment-node@>=24 <=26", jest-environment-node@^24.9.0:
13630+
"jest-environment-node@>=24 <=26", jest-environment-node@^24.9.0:
1359813631
version "24.9.0"
1359913632
resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3"
1360013633
integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==

0 commit comments

Comments
 (0)
0