diff --git a/dev-packages/node-integration-tests/README.md b/dev-packages/node-integration-tests/README.md index ab1ce5e834de..3f7abd7b5727 100644 --- a/dev-packages/node-integration-tests/README.md +++ b/dev-packages/node-integration-tests/README.md @@ -14,20 +14,11 @@ suites/ |---- scenario_2.ts [optional extra test scenario] |---- server_with_mongo.ts [optional custom server] |---- server_with_postgres.ts [optional custom server] -utils/ -|---- defaults/ - |---- server.ts [default Express server configuration] ``` The tests are grouped by their scopes, such as `public-api` or `tracing`. In every group of tests, there are multiple folders containing test scenarios and assertions. -Tests run on Express servers (a server instance per test). By default, a simple server template inside -`utils/defaults/server.ts` is used. Every server instance runs on a different port. - -A custom server configuration can be used, supplying a script that exports a valid express server instance as default. -`runServer` utility function accepts an optional `serverPath` argument for this purpose. - `scenario.ts` contains the initialization logic and the test subject. By default, `{TEST_DIR}/scenario.ts` is used, but `runServer` also accepts an optional `scenarioPath` argument for non-standard usage. 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 index ecf69671b9f4..58d4a299174c 100644 --- 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 @@ -14,7 +14,7 @@ afterAll(() => { * This test nevertheless covers the behavior so that we're aware. */ test('withScope scope is NOT applied to thrown error caught by global handler', done => { - const runner = createRunner(__dirname, 'server.ts') + createRunner(__dirname, 'server.ts') .expect({ event: { exception: { @@ -42,16 +42,15 @@ test('withScope scope is NOT applied to thrown error caught by global handler', tags: expect.not.objectContaining({ local: expect.anything() }), }, }) - .start(done); - - expect(() => runner.makeRequest('get', '/test/withScope')).rejects.toThrow(); + .start(done) + .makeRequest('get', '/test/withScope', { expectError: true }); }); /** * This test shows that the isolation scope set tags are applied correctly to the error. */ test('isolation scope is applied to thrown error caught by global handler', done => { - const runner = createRunner(__dirname, 'server.ts') + createRunner(__dirname, 'server.ts') .expect({ event: { exception: { @@ -81,7 +80,6 @@ test('isolation scope is applied to thrown error caught by global handler', done }, }, }) - .start(done); - - expect(() => runner.makeRequest('get', '/test/isolationScope')).rejects.toThrow(); + .start(done) + .makeRequest('get', '/test/isolationScope', { expectError: true }); }); 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 index 955d725ae0c5..3ad6a3d2068f 100644 --- 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 @@ -5,7 +5,7 @@ afterAll(() => { }); test('should capture and send Express controller error with txn name if tracesSampleRate is 0', done => { - const runner = createRunner(__dirname, 'server.ts') + createRunner(__dirname, 'server.ts') .ignore('transaction') .expect({ event: { @@ -33,7 +33,6 @@ test('should capture and send Express controller error with txn name if tracesSa transaction: 'GET /test/express/:id', }, }) - .start(done); - - expect(() => runner.makeRequest('get', '/test/express/123')).rejects.toThrow(); + .start(done) + .makeRequest('get', '/test/express/123', { expectError: true }); }); 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 index cb43073fa994..b02d74016ad4 100644 --- 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 @@ -5,7 +5,7 @@ afterAll(() => { }); test('should capture and send Express controller error if tracesSampleRate is not set.', done => { - const runner = createRunner(__dirname, 'server.ts') + createRunner(__dirname, 'server.ts') .ignore('transaction') .expect({ event: { @@ -32,7 +32,6 @@ test('should capture and send Express controller error if tracesSampleRate is no }, }, }) - .start(done); - - expect(() => runner.makeRequest('get', '/test/express/123')).rejects.toThrow(); + .start(done) + .makeRequest('get', '/test/express/123', { expectError: true }); }); 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 index 4ec29414868c..0ee5ca2204f5 100644 --- 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 @@ -9,7 +9,9 @@ test('Should overwrite baggage if the incoming request already has Sentry baggag const runner = createRunner(__dirname, '..', 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', + headers: { + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', + }, }); expect(response).toBeDefined(); @@ -25,8 +27,10 @@ test('Should propagate sentry trace baggage data from an incoming to an outgoing const runner = createRunner(__dirname, '..', 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,dogs=great', + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,dogs=great', + }, }); expect(response).toBeDefined(); @@ -42,8 +46,10 @@ test('Should not propagate baggage data from an incoming to an outgoing request const runner = createRunner(__dirname, '..', 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - 'sentry-trace': '', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,dogs=great', + headers: { + 'sentry-trace': '', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv,dogs=great', + }, }); expect(response).toBeDefined(); @@ -59,7 +65,9 @@ test('Should not propagate baggage if sentry-trace header is present in incoming const runner = createRunner(__dirname, '..', 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + }, }); expect(response).toBeDefined(); @@ -74,8 +82,10 @@ test('Should not propagate baggage and ignore original 3rd party baggage entries const runner = createRunner(__dirname, '..', 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'foo=bar', + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'foo=bar', + }, }); expect(response).toBeDefined(); @@ -107,7 +117,9 @@ test('Should populate Sentry and ignore 3rd party content if sentry-trace header const runner = createRunner(__dirname, '..', 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - baggage: 'foo=bar,bar=baz', + headers: { + baggage: 'foo=bar,bar=baz', + }, }); expect(response).toBeDefined(); 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 index 9af5d4456c89..0e083f5c2dc6 100644 --- 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 @@ -9,8 +9,10 @@ test('should ignore sentry-values in `baggage` header of a third party vendor an const runner = createRunner(__dirname, 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-release=2.1.0,sentry-environment=myEnv', + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'sentry-release=2.1.0,sentry-environment=myEnv', + }, }); expect(response).toBeDefined(); 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 index dd3c0f8cddd7..2403da850d9d 100644 --- 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 @@ -9,8 +9,10 @@ test('should merge `baggage` header of a third party vendor with the Sentry DSC const runner = createRunner(__dirname, 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', + baggage: 'sentry-release=2.0.0,sentry-environment=myEnv', + }, }); expect(response).toBeDefined(); 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 index 071f02f83647..1ef9c11aff70 100644 --- 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 @@ -10,7 +10,9 @@ test('Should assign `sentry-trace` header which sets parent trace id of an outgo const runner = createRunner(__dirname, 'server.ts').start(); const response = await runner.makeRequest('get', '/test/express', { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-0', + headers: { + 'sentry-trace': '12312012123120121231201212312012-1121201211212012-0', + }, }); expect(response).toBeDefined(); diff --git a/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/test.ts b/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/test.ts index 97ff6e3fa769..ffc702d63057 100644 --- a/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/test.ts +++ b/dev-packages/node-integration-tests/suites/express/setupExpressErrorHandler/test.ts @@ -22,9 +22,9 @@ describe('express setupExpressErrorHandler', () => { .start(done); // this error is filtered & ignored - expect(() => runner.makeRequest('get', '/test1')).rejects.toThrow(); + runner.makeRequest('get', '/test1', { expectError: true }); // this error is actually captured - expect(() => runner.makeRequest('get', '/test2')).rejects.toThrow(); + runner.makeRequest('get', '/test2', { expectError: true }); }); }); }); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts index f7a1678e1eb6..5cf4dc8c8c40 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts @@ -1,5 +1,6 @@ import type { SpanJSON } from '@sentry/types'; -import { assertSentryTransaction, cleanupChildProcesses, createRunner } from '../../../../utils/runner'; +import { assertSentryTransaction } from '../../../../utils/assertions'; +import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; afterAll(() => { cleanupChildProcesses(); diff --git a/dev-packages/node-integration-tests/suites/sessions/crashed-session-aggregate/test.ts b/dev-packages/node-integration-tests/suites/sessions/crashed-session-aggregate/test.ts index 6e8a86e627d9..0500c702189a 100644 --- a/dev-packages/node-integration-tests/suites/sessions/crashed-session-aggregate/test.ts +++ b/dev-packages/node-integration-tests/suites/sessions/crashed-session-aggregate/test.ts @@ -4,16 +4,10 @@ afterEach(() => { cleanupChildProcesses(); }); -test('should aggregate successful and crashed sessions', async () => { - let _done: undefined | (() => void); - const promise = new Promise(resolve => { - _done = resolve; - }); - - const runner = createRunner(__dirname, 'server.ts') +test('should aggregate successful and crashed sessions', done => { + const runner = createRunner(__dirname, '..', 'server.ts') .ignore('transaction', 'event') .unignore('sessions') - .expectError() .expect({ sessions: { aggregates: [ @@ -25,11 +19,9 @@ test('should aggregate successful and crashed sessions', async () => { ], }, }) - .start(_done); - - runner.makeRequest('get', '/success'); - runner.makeRequest('get', '/error_unhandled'); - runner.makeRequest('get', '/success_next'); + .start(done); - await promise; + runner.makeRequest('get', '/test/success'); + runner.makeRequest('get', '/test/error_unhandled', { expectError: true }); + runner.makeRequest('get', '/test/success_next'); }); diff --git a/dev-packages/node-integration-tests/suites/sessions/errored-session-aggregate/test.ts b/dev-packages/node-integration-tests/suites/sessions/errored-session-aggregate/test.ts index 383bfca96062..1159d092fdd7 100644 --- a/dev-packages/node-integration-tests/suites/sessions/errored-session-aggregate/test.ts +++ b/dev-packages/node-integration-tests/suites/sessions/errored-session-aggregate/test.ts @@ -4,16 +4,10 @@ afterEach(() => { cleanupChildProcesses(); }); -test('should aggregate successful, crashed and erroneous sessions', async () => { - let _done: undefined | (() => void); - const promise = new Promise(resolve => { - _done = resolve; - }); - - const runner = createRunner(__dirname, 'server.ts') +test('should aggregate successful, crashed and erroneous sessions', done => { + const runner = createRunner(__dirname, '..', 'server.ts') .ignore('transaction', 'event') .unignore('sessions') - .expectError() .expect({ sessions: { aggregates: [ @@ -26,11 +20,9 @@ test('should aggregate successful, crashed and erroneous sessions', async () => ], }, }) - .start(_done); - - runner.makeRequest('get', '/success'); - runner.makeRequest('get', '/error_handled'); - runner.makeRequest('get', '/error_unhandled'); + .start(done); - await promise; + runner.makeRequest('get', '/test/success'); + runner.makeRequest('get', '/test/error_handled'); + runner.makeRequest('get', '/test/error_unhandled', { expectError: true }); }); diff --git a/dev-packages/node-integration-tests/suites/sessions/exited-session-aggregate/test.ts b/dev-packages/node-integration-tests/suites/sessions/exited-session-aggregate/test.ts index 23b80109fa43..465761e76224 100644 --- a/dev-packages/node-integration-tests/suites/sessions/exited-session-aggregate/test.ts +++ b/dev-packages/node-integration-tests/suites/sessions/exited-session-aggregate/test.ts @@ -4,16 +4,10 @@ afterEach(() => { cleanupChildProcesses(); }); -test('should aggregate successful sessions', async () => { - let _done: undefined | (() => void); - const promise = new Promise(resolve => { - _done = resolve; - }); - - const runner = createRunner(__dirname, 'server.ts') +test('should aggregate successful sessions', done => { + const runner = createRunner(__dirname, '..', 'server.ts') .ignore('transaction', 'event') .unignore('sessions') - .expectError() .expect({ sessions: { aggregates: [ @@ -24,11 +18,9 @@ test('should aggregate successful sessions', async () => { ], }, }) - .start(_done); - - runner.makeRequest('get', '/success'); - runner.makeRequest('get', '/success_next'); - runner.makeRequest('get', '/success_slow'); + .start(done); - await promise; + runner.makeRequest('get', '/test/success'); + runner.makeRequest('get', '/test/success_next'); + runner.makeRequest('get', '/test/success_slow'); }); diff --git a/dev-packages/node-integration-tests/suites/sessions/server.ts b/dev-packages/node-integration-tests/suites/sessions/server.ts index e06f00ef486a..2415140b6140 100644 --- a/dev-packages/node-integration-tests/suites/sessions/server.ts +++ b/dev-packages/node-integration-tests/suites/sessions/server.ts @@ -1,26 +1,24 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; import type { SessionFlusher } from '@sentry/core'; import * as Sentry from '@sentry/node'; 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(); -// ### Taken from manual tests ### -// Hack that resets the 60s default flush interval, and replaces it with just a one second interval const flusher = (Sentry.getClient() as Sentry.NodeClient)['_sessionFlusher'] as SessionFlusher; -let flusherIntervalId = flusher && flusher['_intervalId']; - -clearInterval(flusherIntervalId); - -flusherIntervalId = flusher['_intervalId'] = setInterval(() => flusher?.flush(), 2000); - -setTimeout(() => clearInterval(flusherIntervalId), 4000); +// Flush after 2 seconds (to avoid waiting for the default 60s) +setTimeout(() => { + flusher?.flush(); +}, 2000); app.get('/test/success', (_req, res) => { res.send('Success!'); @@ -52,4 +50,4 @@ app.get('/test/error_handled', (_req, res) => { Sentry.setupExpressErrorHandler(app); -export default app; +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/tracing/connect/test.ts b/dev-packages/node-integration-tests/suites/tracing/connect/test.ts index dd14c2277f7b..a416656f6355 100644 --- a/dev-packages/node-integration-tests/suites/tracing/connect/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/connect/test.ts @@ -45,7 +45,6 @@ describe('connect auto-instrumentation', () => { test('CJS - should capture errors in `connect` middleware.', done => { createRunner(__dirname, 'scenario.js') .ignore('transaction') - .expectError() .expect({ event: EXPECTED_EVENT }) .start(done) .makeRequest('get', '/error'); @@ -55,7 +54,6 @@ describe('connect auto-instrumentation', () => { createRunner(__dirname, 'scenario.js') .ignore('event') .expect({ transaction: { transaction: 'GET /error' } }) - .expectError() .start(done) .makeRequest('get', '/error'); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts index 8bb3bfdb0796..4bd995777248 100644 --- a/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/hapi/test.ts @@ -50,9 +50,8 @@ describe('hapi auto-instrumentation', () => { }, }) .expect({ event: EXPECTED_ERROR_EVENT }) - .expectError() .start(done) - .makeRequest('get', '/error'); + .makeRequest('get', '/error', { expectError: true }); }); test('CJS - should assign parameterized transactionName to error.', done => { @@ -64,9 +63,8 @@ describe('hapi auto-instrumentation', () => { }, }) .ignore('transaction') - .expectError() .start(done) - .makeRequest('get', '/error/123'); + .makeRequest('get', '/error/123', { expectError: true }); }); test('CJS - should handle returned Boom errors in routes.', done => { @@ -77,9 +75,8 @@ describe('hapi auto-instrumentation', () => { }, }) .expect({ event: EXPECTED_ERROR_EVENT }) - .expectError() .start(done) - .makeRequest('get', '/boom-error'); + .makeRequest('get', '/boom-error', { expectError: true }); }); test('CJS - should handle promise rejections in routes.', done => { @@ -90,8 +87,7 @@ describe('hapi auto-instrumentation', () => { }, }) .expect({ event: EXPECTED_ERROR_EVENT }) - .expectError() .start(done) - .makeRequest('get', '/promise-error'); + .makeRequest('get', '/promise-error', { expectError: true }); }); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/meta-tags/test.ts b/dev-packages/node-integration-tests/suites/tracing/meta-tags/test.ts index ab63b1c9cb35..7c94d30b686a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/meta-tags/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/meta-tags/test.ts @@ -12,8 +12,10 @@ describe('getTraceMetaTags', () => { const runner = createRunner(__dirname, 'server.js').start(); const response = await runner.makeRequest('get', '/test', { - 'sentry-trace': `${traceId}-${parentSpanId}-1`, - baggage: 'sentry-environment=production', + headers: { + 'sentry-trace': `${traceId}-${parentSpanId}-1`, + baggage: 'sentry-environment=production', + }, }); // @ts-ignore - response is defined, types just don't reflect it @@ -61,8 +63,10 @@ describe('getTraceMetaTags', () => { const runner = createRunner(__dirname, 'server-sdk-disabled.js').start(); const response = await runner.makeRequest('get', '/test', { - 'sentry-trace': `${traceId}-${parentSpanId}-1`, - baggage: 'sentry-environment=production', + headers: { + 'sentry-trace': `${traceId}-${parentSpanId}-1`, + baggage: 'sentry-environment=production', + }, }); // @ts-ignore - response is defined, types just don't reflect it diff --git a/dev-packages/node-integration-tests/utils/assertions.ts b/dev-packages/node-integration-tests/utils/assertions.ts new file mode 100644 index 000000000000..68ce3941ff92 --- /dev/null +++ b/dev-packages/node-integration-tests/utils/assertions.ts @@ -0,0 +1,78 @@ +import type { + ClientReport, + Envelope, + Event, + SerializedCheckIn, + SerializedSession, + SessionAggregates, + TransactionEvent, +} from '@sentry/types'; +import { SDK_VERSION } from '@sentry/utils'; + +/** + * Asserts against a Sentry Event ignoring non-deterministic properties + * + * @param {Record} actual + * @param {Record} expected + */ +export const assertSentryEvent = (actual: Event, expected: Record): void => { + expect(actual).toMatchObject({ + event_id: expect.any(String), + ...expected, + }); +}; + +/** + * Asserts against a Sentry Transaction ignoring non-deterministic properties + * + * @param {Record} actual + * @param {Record} expected + */ +export const assertSentryTransaction = (actual: TransactionEvent, expected: Record): void => { + expect(actual).toMatchObject({ + event_id: expect.any(String), + timestamp: expect.anything(), + start_timestamp: expect.anything(), + spans: expect.any(Array), + type: 'transaction', + ...expected, + }); +}; + +export function assertSentrySession(actual: SerializedSession, expected: Partial): void { + expect(actual).toMatchObject({ + sid: expect.any(String), + ...expected, + }); +} + +export function assertSentrySessions(actual: SessionAggregates, expected: Partial): void { + expect(actual).toMatchObject({ + ...expected, + }); +} + +export function assertSentryCheckIn(actual: SerializedCheckIn, expected: Partial): void { + expect(actual).toMatchObject({ + check_in_id: expect.any(String), + ...expected, + }); +} + +export function assertSentryClientReport(actual: ClientReport, expected: Partial): void { + expect(actual).toMatchObject({ + ...expected, + }); +} + +export function assertEnvelopeHeader(actual: Envelope[0], expected: Partial): void { + expect(actual).toEqual({ + event_id: expect.any(String), + sent_at: expect.any(String), + sdk: { + name: 'sentry.javascript.node', + version: SDK_VERSION, + }, + ...expected, + }); +} diff --git a/dev-packages/node-integration-tests/utils/defaults/server.ts b/dev-packages/node-integration-tests/utils/defaults/server.ts deleted file mode 100644 index 3cf8cadab65a..000000000000 --- a/dev-packages/node-integration-tests/utils/defaults/server.ts +++ /dev/null @@ -1,5 +0,0 @@ -import express from 'express'; - -const app = express(); - -export default app; diff --git a/dev-packages/node-integration-tests/utils/index.ts b/dev-packages/node-integration-tests/utils/index.ts index 8c12ec72e0d2..0beedd250980 100644 --- a/dev-packages/node-integration-tests/utils/index.ts +++ b/dev-packages/node-integration-tests/utils/index.ts @@ -43,37 +43,6 @@ export const conditionalTest = (allowedVersion: { min?: number; max?: number }): : describe; }; -/** - * Asserts against a Sentry Event ignoring non-deterministic properties - * - * @param {Record} actual - * @param {Record} expected - */ -export const assertSentryEvent = (actual: Record, expected: Record): void => { - expect(actual).toMatchObject({ - event_id: expect.any(String), - timestamp: expect.anything(), - ...expected, - }); -}; - -/** - * Asserts against a Sentry Transaction ignoring non-deterministic properties - * - * @param {Record} actual - * @param {Record} expected - */ -export const assertSentryTransaction = (actual: Record, expected: Record): void => { - expect(actual).toMatchObject({ - event_id: expect.any(String), - timestamp: expect.anything(), - start_timestamp: expect.anything(), - spans: expect.any(Array), - type: 'transaction', - ...expected, - }); -}; - /** * Parses response body containing an Envelope * diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts index bde5bd06cd21..1cbd9ade2e67 100644 --- a/dev-packages/node-integration-tests/utils/runner.ts +++ b/dev-packages/node-integration-tests/utils/runner.ts @@ -1,7 +1,7 @@ /* eslint-disable max-lines */ import { spawn, spawnSync } from 'child_process'; +import { existsSync } from 'fs'; import { join } from 'path'; -import { SDK_VERSION } from '@sentry/node'; import type { ClientReport, Envelope, @@ -13,59 +13,19 @@ import type { SessionAggregates, TransactionEvent, } from '@sentry/types'; +import { normalize } from '@sentry/utils'; import axios from 'axios'; +import { + assertEnvelopeHeader, + assertSentryCheckIn, + assertSentryClientReport, + assertSentryEvent, + assertSentrySession, + assertSentrySessions, + assertSentryTransaction, +} from './assertions'; import { createBasicSentryServer } from './server'; -export function assertSentryEvent(actual: Event, expected: Event): void { - expect(actual).toMatchObject({ - event_id: expect.any(String), - ...expected, - }); -} - -export function assertSentrySession(actual: SerializedSession, expected: Partial): void { - expect(actual).toMatchObject({ - sid: expect.any(String), - ...expected, - }); -} - -export function assertSentryTransaction(actual: Event, expected: Partial): void { - expect(actual).toMatchObject({ - event_id: expect.any(String), - timestamp: expect.anything(), - start_timestamp: expect.anything(), - spans: expect.any(Array), - type: 'transaction', - ...expected, - }); -} - -export function assertSentryCheckIn(actual: SerializedCheckIn, expected: Partial): void { - expect(actual).toMatchObject({ - check_in_id: expect.any(String), - ...expected, - }); -} - -export function assertSentryClientReport(actual: ClientReport, expected: Partial): void { - expect(actual).toMatchObject({ - ...expected, - }); -} - -export function assertEnvelopeHeader(actual: Envelope[0], expected: Partial): void { - expect(actual).toEqual({ - event_id: expect.any(String), - sent_at: expect.any(String), - sdk: { - name: 'sentry.javascript.node', - version: SDK_VERSION, - }, - ...expected, - }); -} - const CLEANUP_STEPS = new Set(); export function cleanupChildProcesses(): void { @@ -130,8 +90,7 @@ async function runDockerCompose(options: DockerOptions): Promise { function newData(data: Buffer): void { const text = data.toString('utf8'); - // eslint-disable-next-line no-console - if (process.env.DEBUG) console.log(text); + if (process.env.DEBUG) log(text); for (const match of options.readyMatches) { if (text.includes(match)) { @@ -147,24 +106,31 @@ async function runDockerCompose(options: DockerOptions): Promise { }); } +type ExpectedEvent = Partial | ((event: Event) => void); +type ExpectedTransaction = Partial | ((event: TransactionEvent) => void); +type ExpectedSession = Partial | ((event: SerializedSession) => void); +type ExpectedSessions = Partial | ((event: SessionAggregates) => void); +type ExpectedCheckIn = Partial | ((event: SerializedCheckIn) => void); +type ExpectedClientReport = Partial | ((event: ClientReport) => void); + type Expected = | { - event: Partial | ((event: Event) => void); + event: ExpectedEvent; } | { - transaction: Partial | ((event: TransactionEvent) => void); + transaction: ExpectedTransaction; } | { - session: Partial | ((event: SerializedSession) => void); + session: ExpectedSession; } | { - sessions: Partial | ((event: SessionAggregates) => void); + sessions: ExpectedSessions; } | { - check_in: Partial | ((event: SerializedCheckIn) => void); + check_in: ExpectedCheckIn; } | { - client_report: Partial | ((event: ClientReport) => void); + client_report: ExpectedClientReport; }; type ExpectedEnvelopeHeader = @@ -178,16 +144,19 @@ type ExpectedEnvelopeHeader = export function createRunner(...paths: string[]) { const testPath = join(...paths); + if (!existsSync(testPath)) { + throw new Error(`Test scenario not found: ${testPath}`); + } + const expectedEnvelopes: Expected[] = []; let expectedEnvelopeHeaders: ExpectedEnvelopeHeader[] | undefined = undefined; const flags: string[] = []; // By default, we ignore session & sessions - const ignored: EnvelopeItemType[] = ['session', 'sessions']; + const ignored: Set = new Set(['session', 'sessions']); let withEnv: Record = {}; let withSentryServer = false; let dockerOptions: DockerOptions | undefined; let ensureNoErrorOutput = false; - let expectError = false; const logs: string[] = []; if (testPath.endsWith('.ts')) { @@ -207,10 +176,6 @@ export function createRunner(...paths: string[]) { expectedEnvelopeHeaders.push(expected); return this; }, - expectError: function () { - expectError = true; - return this; - }, withEnv: function (env: Record) { withEnv = env; return this; @@ -224,15 +189,12 @@ export function createRunner(...paths: string[]) { return this; }, ignore: function (...types: EnvelopeItemType[]) { - ignored.push(...types); + types.forEach(t => ignored.add(t)); return this; }, unignore: function (...types: EnvelopeItemType[]) { for (const t of types) { - const pos = ignored.indexOf(t); - if (pos > -1) { - ignored.splice(pos, 1); - } + ignored.delete(t); } return this; }, @@ -254,7 +216,7 @@ export function createRunner(...paths: string[]) { function complete(error?: Error): void { child?.kill(); - done?.(error); + done?.(normalize(error)); } /** Called after each expect callback to check if we're complete */ @@ -269,7 +231,7 @@ export function createRunner(...paths: string[]) { for (const item of envelope[1]) { const envelopeItemType = item[0].type; - if (ignored.includes(envelopeItemType)) { + if (ignored.has(envelopeItemType)) { continue; } @@ -307,58 +269,25 @@ export function createRunner(...paths: string[]) { } if ('event' in expected) { - const event = item[1] as Event; - if (typeof expected.event === 'function') { - expected.event(event); - } else { - assertSentryEvent(event, expected.event); - } - + expectErrorEvent(item[1] as Event, expected.event); expectCallbackCalled(); - } - - if ('transaction' in expected) { - const event = item[1] as TransactionEvent; - if (typeof expected.transaction === 'function') { - expected.transaction(event); - } else { - assertSentryTransaction(event, expected.transaction); - } - + } else if ('transaction' in expected) { + expectTransactionEvent(item[1] as TransactionEvent, expected.transaction); expectCallbackCalled(); - } - - if ('session' in expected) { - const session = item[1] as SerializedSession; - if (typeof expected.session === 'function') { - expected.session(session); - } else { - assertSentrySession(session, expected.session); - } - + } else if ('session' in expected) { + expectSessionEvent(item[1] as SerializedSession, expected.session); expectCallbackCalled(); - } - - if ('check_in' in expected) { - const checkIn = item[1] as SerializedCheckIn; - if (typeof expected.check_in === 'function') { - expected.check_in(checkIn); - } else { - assertSentryCheckIn(checkIn, expected.check_in); - } - + } else if ('sessions' in expected) { + expectSessionsEvent(item[1] as SessionAggregates, expected.sessions); expectCallbackCalled(); - } - - if ('client_report' in expected) { - const clientReport = item[1] as ClientReport; - if (typeof expected.client_report === 'function') { - expected.client_report(clientReport); - } else { - assertSentryClientReport(clientReport, expected.client_report); - } - + } else if ('check_in' in expected) { + expectCheckInEvent(item[1] as SerializedCheckIn, expected.check_in); + expectCallbackCalled(); + } else if ('client_report' in expected) { + expectClientReport(item[1] as ClientReport, expected.client_report); expectCallbackCalled(); + } else { + throw new Error(`Unhandled expected envelope item type: ${JSON.stringify(expected)}`); } } catch (e) { complete(e as Error); @@ -397,8 +326,7 @@ export function createRunner(...paths: string[]) { ? { ...process.env, ...withEnv, SENTRY_DSN: `http://public@localhost:${mockServerPort}/1337` } : { ...process.env, ...withEnv }; - // eslint-disable-next-line no-console - if (process.env.DEBUG) console.log('starting scenario', testPath, flags, env.SENTRY_DSN); + if (process.env.DEBUG) log('starting scenario', testPath, flags, env.SENTRY_DSN); child = spawn('node', [...flags, testPath], { env }); @@ -425,8 +353,7 @@ export function createRunner(...paths: string[]) { // Pass error to done to end the test quickly child.on('error', e => { - // eslint-disable-next-line no-console - if (process.env.DEBUG) console.log('scenario error', e); + if (process.env.DEBUG) log('scenario error', e); complete(e); }); @@ -465,8 +392,7 @@ export function createRunner(...paths: string[]) { logs.push(line.trim()); buffer = Buffer.from(buffer.subarray(splitIndex + 1)); - // eslint-disable-next-line no-console - if (process.env.DEBUG) console.log('line', line); + if (process.env.DEBUG) log('line', line); tryParseEnvelopeFromStdoutLine(line); } }); @@ -483,35 +409,95 @@ export function createRunner(...paths: string[]) { makeRequest: async function ( method: 'get' | 'post', path: string, - headers: Record = {}, - data?: any, // axios accept any as data + options: { headers?: Record; data?: unknown; expectError?: boolean } = {}, ): Promise { try { await waitFor(() => scenarioServerPort !== undefined); } catch (e) { complete(e as Error); - return undefined; + return; } const url = `http://localhost:${scenarioServerPort}${path}`; - if (expectError) { - try { - if (method === 'get') { - await axios.get(url, { headers }); - } else { - await axios.post(url, data, { headers }); - } - } catch (e) { + const data = options.data; + const headers = options.headers || {}; + const expectError = options.expectError || false; + + if (process.env.DEBUG) log('making request', method, url, headers, data); + + try { + const res = + method === 'post' ? await axios.post(url, data, { headers }) : await axios.get(url, { headers }); + + if (expectError) { + complete(new Error(`Expected request to "${path}" to fail, but got a ${res.status} response`)); + return; + } + + return res.data; + } catch (e) { + if (expectError) { return; } + + complete(e as Error); return; - } else if (method === 'get') { - return (await axios.get(url, { headers })).data; - } else { - return (await axios.post(url, data, { headers })).data; } }, }; }, }; } + +function log(...args: unknown[]): void { + // eslint-disable-next-line no-console + console.log(...args.map(arg => normalize(arg))); +} + +function expectErrorEvent(item: Event, expected: ExpectedEvent): void { + if (typeof expected === 'function') { + expected(item); + } else { + assertSentryEvent(item, expected); + } +} + +function expectTransactionEvent(item: TransactionEvent, expected: ExpectedTransaction): void { + if (typeof expected === 'function') { + expected(item); + } else { + assertSentryTransaction(item, expected); + } +} + +function expectSessionEvent(item: SerializedSession, expected: ExpectedSession): void { + if (typeof expected === 'function') { + expected(item); + } else { + assertSentrySession(item, expected); + } +} + +function expectSessionsEvent(item: SessionAggregates, expected: ExpectedSessions): void { + if (typeof expected === 'function') { + expected(item); + } else { + assertSentrySessions(item, expected); + } +} + +function expectCheckInEvent(item: SerializedCheckIn, expected: ExpectedCheckIn): void { + if (typeof expected === 'function') { + expected(item); + } else { + assertSentryCheckIn(item, expected); + } +} + +function expectClientReport(item: ClientReport, expected: ExpectedClientReport): void { + if (typeof expected === 'function') { + expected(item); + } else { + assertSentryClientReport(item, expected); + } +}