From 0abb355dd27605a2bffe29b5e3adc0b21ac2b65d Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 11:31:29 +0200 Subject: [PATCH 1/8] Add separate nest sample application for distributed tracing tests --- .../nestjs-distributed-tracing/.gitignore | 56 +++ .../nestjs-distributed-tracing/.npmrc | 2 + .../nestjs-distributed-tracing/nest-cli.json | 8 + .../nestjs-distributed-tracing/package.json | 47 +++ .../playwright.config.mjs | 7 + .../src/app.controller.ts | 57 +++ .../src/app.module.ts | 17 + .../src/app.service.ts | 64 ++++ .../src/instrument.ts | 9 + .../nestjs-distributed-tracing/src/main.ts | 24 ++ .../nestjs-distributed-tracing/src/utils.ts | 26 ++ .../start-event-proxy.mjs | 6 + .../tests/propagation.test.ts | 356 ++++++++++++++++++ .../tsconfig.build.json | 4 + .../nestjs-distributed-tracing/tsconfig.json | 21 ++ 15 files changed, 704 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/nest-cli.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.service.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/instrument.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/utils.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.build.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/nest-cli.json new file mode 100644 index 000000000000..f9aa683b1ad5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json new file mode 100644 index 000000000000..b4d0ead875f9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json @@ -0,0 +1,47 @@ +{ + "name": "nestjs-distributed-tracing", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test": "playwright test", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@sentry/nestjs": "latest || *", + "@sentry/types": "latest || *", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "18.15.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", + "tsconfig-paths": "^4.2.0", + "typescript": "^4.9.5" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.controller.ts new file mode 100644 index 000000000000..0810728747e4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.controller.ts @@ -0,0 +1,57 @@ +import { Controller, Get, Headers, Param } from '@nestjs/common'; +import { AppService1, AppService2 } from './app.service'; + +@Controller() +export class AppController1 { + constructor(private readonly appService: AppService1) {} + + @Get('test-inbound-headers/:id') + testInboundHeaders(@Headers() headers, @Param('id') id: string) { + return this.appService.testInboundHeaders(headers, id); + } + + @Get('test-outgoing-http/:id') + async testOutgoingHttp(@Param('id') id: string) { + return this.appService.testOutgoingHttp(id); + } + + @Get('test-outgoing-fetch/:id') + async testOutgoingFetch(@Param('id') id: string) { + return this.appService.testOutgoingFetch(id); + } + + @Get('test-outgoing-fetch-external-allowed') + async testOutgoingFetchExternalAllowed() { + return this.appService.testOutgoingFetchExternalAllowed(); + } + + @Get('test-outgoing-fetch-external-disallowed') + async testOutgoingFetchExternalDisallowed() { + return this.appService.testOutgoingFetchExternalDisallowed(); + } + + @Get('test-outgoing-http-external-allowed') + async testOutgoingHttpExternalAllowed() { + return this.appService.testOutgoingHttpExternalAllowed(); + } + + @Get('test-outgoing-http-external-disallowed') + async testOutgoingHttpExternalDisallowed() { + return this.appService.testOutgoingHttpExternalDisallowed(); + } +} + +@Controller() +export class AppController2 { + constructor(private readonly appService: AppService2) {} + + @Get('external-allowed') + externalAllowed(@Headers() headers) { + return this.appService.externalAllowed(headers); + } + + @Get('external-disallowed') + externalDisallowed(@Headers() headers) { + return this.appService.externalDisallowed(headers); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.module.ts new file mode 100644 index 000000000000..5fda2f1e209f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { AppController1, AppController2 } from './app.controller'; +import { AppService1, AppService2 } from './app.service'; + +@Module({ + imports: [], + controllers: [AppController1], + providers: [AppService1], +}) +export class AppModule1 {} + +@Module({ + imports: [], + controllers: [AppController2], + providers: [AppService2], +}) +export class AppModule2 {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.service.ts new file mode 100644 index 000000000000..2fd4e85efcba --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.service.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@nestjs/common'; +import { makeHttpRequest } from './utils'; + +@Injectable() +export class AppService1 { + constructor() {} + + testInboundHeaders(headers: Record, id: string) { + return { + headers, + id, + }; + } + + async testOutgoingHttp(id: string) { + const data = await makeHttpRequest(`http://localhost:3030/test-inbound-headers/${id}`); + + return data; + } + + async testOutgoingFetch(id: string) { + const response = await fetch(`http://localhost:3030/test-inbound-headers/${id}`); + const data = await response.json(); + + return data; + } + + async testOutgoingFetchExternalAllowed() { + const fetchResponse = await fetch('http://localhost:3040/external-allowed'); + + return fetchResponse.json(); + } + + async testOutgoingFetchExternalDisallowed() { + const fetchResponse = await fetch('http://localhost:3040/external-disallowed'); + + return fetchResponse.json(); + } + + async testOutgoingHttpExternalAllowed() { + return makeHttpRequest('http://localhost:3040/external-allowed'); + } + + async testOutgoingHttpExternalDisallowed() { + return makeHttpRequest('http://localhost:3040/external-disallowed'); + } +} + +@Injectable() +export class AppService2 { + externalAllowed(headers: Record) { + return { + headers, + route: 'external-allowed', + }; + } + + externalDisallowed(headers: Record) { + return { + headers, + route: 'external-disallowed', + }; + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/instrument.ts new file mode 100644 index 000000000000..b5ca047e497c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/instrument.ts @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/nestjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, + tracePropagationTargets: ['http://localhost:3030', '/external-allowed'], +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts new file mode 100644 index 000000000000..c2682662154d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts @@ -0,0 +1,24 @@ +// Import this first +import './instrument'; + +// Import other modules +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import * as Sentry from '@sentry/nestjs'; +import { AppModule1, AppModule2 } from './app.module'; + +const app1Port = 3030; +const app2Port = 3040; + +async function bootstrap() { + const app1 = await NestFactory.create(AppModule1); + + const { httpAdapter } = app1.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); + + await app1.listen(app1Port); + + const app2 = await NestFactory.create(AppModule2); + await app2.listen(app2Port); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/utils.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/utils.ts new file mode 100644 index 000000000000..27639ef26349 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/utils.ts @@ -0,0 +1,26 @@ +import * as http from 'http'; + +export function makeHttpRequest(url) { + return new Promise(resolve => { + const data = []; + + http + .request(url, httpRes => { + httpRes.on('data', chunk => { + data.push(chunk); + }); + httpRes.on('error', error => { + resolve({ error: error.message, url }); + }); + httpRes.on('end', () => { + try { + const json = JSON.parse(Buffer.concat(data).toString()); + resolve(json); + } catch { + resolve({ data: Buffer.concat(data).toString(), url }); + } + }); + }) + .end(); + }); +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/start-event-proxy.mjs new file mode 100644 index 000000000000..e9917b9273da --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nestjs', +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts new file mode 100644 index 000000000000..2922435c542b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts @@ -0,0 +1,356 @@ +import crypto from 'crypto'; +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; +import { SpanJSON } from '@sentry/types'; + +test('Propagates trace for outgoing http requests', async ({ baseURL }) => { + const id = crypto.randomUUID(); + + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` + ); + }); + + const outboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http/${id}` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-http/${id}`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + const outboundTransaction = await outboundTransactionPromise; + + const traceId = outboundTransaction?.contexts?.trace?.trace_id; + const outgoingHttpSpan = outboundTransaction?.spans?.find(span => span.op === 'http.client') as SpanJSON | undefined; + + expect(outgoingHttpSpan).toBeDefined(); + + const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + + expect(traceId).toEqual(expect.any(String)); + + // data is passed through from the inbound request, to verify we have the correct headers set + const inboundHeaderSentryTrace = data.headers?.['sentry-trace']; + const inboundHeaderBaggage = data.headers?.['baggage']; + + expect(inboundHeaderSentryTrace).toEqual(`${traceId}-${outgoingHttpSpanId}-1`); + expect(inboundHeaderBaggage).toBeDefined(); + + const baggage = (inboundHeaderBaggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); + + expect(outboundTransaction.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: `http://localhost:3030/test-outgoing-http/${id}`, + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': `http://localhost:3030/test-outgoing-http/${id}`, + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': `/test-outgoing-http/${id}`, + 'http.user_agent': 'node', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-outgoing-http/:id', + }, + op: 'http.server', + span_id: expect.any(String), + status: 'ok', + trace_id: traceId, + origin: 'auto.http.otel.http', + }); + + expect(inboundTransaction.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: `http://localhost:3030/test-inbound-headers/${id}`, + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': `http://localhost:3030/test-inbound-headers/${id}`, + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': `/test-inbound-headers/${id}`, + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-inbound-headers/:id', + }, + op: 'http.server', + parent_span_id: outgoingHttpSpanId, + span_id: expect.any(String), + status: 'ok', + trace_id: traceId, + origin: 'auto.http.otel.http', + }); +}); + +test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { + const id = crypto.randomUUID(); + + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` + ); + }); + + const outboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch/${id}` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch/${id}`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + const outboundTransaction = await outboundTransactionPromise; + + const traceId = outboundTransaction?.contexts?.trace?.trace_id; + const outgoingHttpSpan = outboundTransaction?.spans?.find(span => span.op === 'http.client') as SpanJSON | undefined; + + expect(outgoingHttpSpan).toBeDefined(); + + const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + + expect(traceId).toEqual(expect.any(String)); + + // data is passed through from the inbound request, to verify we have the correct headers set + const inboundHeaderSentryTrace = data.headers?.['sentry-trace']; + const inboundHeaderBaggage = data.headers?.['baggage']; + + expect(inboundHeaderSentryTrace).toEqual(`${traceId}-${outgoingHttpSpanId}-1`); + expect(inboundHeaderBaggage).toBeDefined(); + + const baggage = (inboundHeaderBaggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); + + expect(outboundTransaction.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: `http://localhost:3030/test-outgoing-fetch/${id}`, + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': `http://localhost:3030/test-outgoing-fetch/${id}`, + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': `/test-outgoing-fetch/${id}`, + 'http.user_agent': 'node', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-outgoing-fetch/:id', + }, + op: 'http.server', + span_id: expect.any(String), + status: 'ok', + trace_id: traceId, + origin: 'auto.http.otel.http', + }); + + expect(inboundTransaction.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: `http://localhost:3030/test-inbound-headers/${id}`, + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': `http://localhost:3030/test-inbound-headers/${id}`, + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': `/test-inbound-headers/${id}`, + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-inbound-headers/:id', + }), + op: 'http.server', + parent_span_id: outgoingHttpSpanId, + span_id: expect.any(String), + status: 'ok', + trace_id: traceId, + origin: 'auto.http.otel.http', + }); +}); + +test('Propagates trace for outgoing external http requests', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-allowed` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-http-external-allowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; + + expect(traceId).toEqual(expect.any(String)); + expect(spanId).toEqual(expect.any(String)); + + expect(data).toEqual({ + headers: expect.objectContaining({ + 'sentry-trace': `${traceId}-${spanId}-1`, + baggage: expect.any(String), + }), + route: 'external-allowed', + }); + + const baggage = (data.headers.baggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); +}); + +test('Does not propagate outgoing http requests not covered by tracePropagationTargets', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-disallowed` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-http-external-disallowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; + + expect(traceId).toEqual(expect.any(String)); + expect(spanId).toEqual(expect.any(String)); + + expect(data.route).toBe('external-disallowed'); + expect(data.headers?.['sentry-trace']).toBeUndefined(); + expect(data.headers?.baggage).toBeUndefined(); +}); + +test('Propagates trace for outgoing external fetch requests', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-allowed` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch-external-allowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; + + expect(traceId).toEqual(expect.any(String)); + expect(spanId).toEqual(expect.any(String)); + + expect(data).toEqual({ + headers: expect.objectContaining({ + 'sentry-trace': `${traceId}-${spanId}-1`, + baggage: expect.any(String), + }), + route: 'external-allowed', + }); + + const baggage = (data.headers.baggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); +}); + +test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-disallowed` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch-external-disallowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; + + expect(traceId).toEqual(expect.any(String)); + expect(spanId).toEqual(expect.any(String)); + + expect(data.route).toBe('external-disallowed'); + expect(data.headers?.['sentry-trace']).toBeUndefined(); + expect(data.headers?.baggage).toBeUndefined(); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.build.json new file mode 100644 index 000000000000..26c30d4eddf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist"] +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.json new file mode 100644 index 000000000000..95f5641cf7f3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} From 19a2af05d15efa207b703ac9854b3d7be3a4a16e Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 12:02:51 +0200 Subject: [PATCH 2/8] Add nestjs-distributed-tracing test application to build script --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91e40645e0fc..8e967e42f7df 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1031,6 +1031,7 @@ jobs: 'generic-ts3.8', 'node-fastify', 'node-hapi', + 'nestjs-distributed-tracing', 'nestjs', 'node-exports-test-app', 'node-koa', From c8371aa5286f262db446d84203cffdec59b4bede Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 12:31:34 +0200 Subject: [PATCH 3/8] Add nest sample application for testing with submodules --- .github/workflows/build.yml | 3 +- .../nestjs-with-submodules/.gitignore | 56 +++++++++++++++++++ .../nestjs-with-submodules/.npmrc | 2 + .../nestjs-with-submodules/nest-cli.json | 8 +++ .../nestjs-with-submodules/package.json | 47 ++++++++++++++++ .../playwright.config.mjs | 7 +++ .../src/app.controller.ts | 17 ++++++ .../nestjs-with-submodules/src/app.module.ts | 11 ++++ .../nestjs-with-submodules/src/app.service.ts | 14 +++++ .../nestjs-with-submodules/src/instrument.ts | 9 +++ .../nestjs-with-submodules/src/main.ts | 20 +++++++ .../src/test-module/test.controller.ts | 12 ++++ .../src/test-module/test.exception.ts | 5 ++ .../src/test-module/test.filter.ts | 13 +++++ .../src/test-module/test.module.ts | 16 ++++++ .../start-event-proxy.mjs | 6 ++ .../tests/errors.test.ts | 31 ++++++++++ .../tsconfig.build.json | 4 ++ .../nestjs-with-submodules/tsconfig.json | 21 +++++++ 19 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/nest-cli.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.service.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.exception.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.filter.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.build.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e967e42f7df..9e8abb392fc8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1031,8 +1031,9 @@ jobs: 'generic-ts3.8', 'node-fastify', 'node-hapi', - 'nestjs-distributed-tracing', 'nestjs', + 'nestjs-distributed-tracing', + 'nestjs-with-submodules', 'node-exports-test-app', 'node-koa', 'node-connect', diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/nest-cli.json new file mode 100644 index 000000000000..f9aa683b1ad5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json new file mode 100644 index 000000000000..dfbe5e83e640 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json @@ -0,0 +1,47 @@ +{ + "name": "nestjs-with-submodules", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test": "playwright test", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@sentry/nestjs": "latest || *", + "@sentry/types": "latest || *", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "18.15.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", + "tsconfig-paths": "^4.2.0", + "typescript": "^4.9.5" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts new file mode 100644 index 000000000000..71a410e8d0a8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('test-exception/:id') + async testException(@Param('id') id: string) { + return this.appService.testException(id); + } + + @Get('test-expected-exception/:id') + async testExpectedException(@Param('id') id: string) { + return this.appService.testExpectedException(id); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts new file mode 100644 index 000000000000..b4664edab418 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { TestModule } from './test-module/test.module'; + +@Module({ + imports: [TestModule], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.service.ts new file mode 100644 index 000000000000..242408023586 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.service.ts @@ -0,0 +1,14 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + constructor() {} + + testException(id: string) { + throw new Error(`This is an exception with id ${id}`); + } + + testExpectedException(id: string) { + throw new HttpException(`This is an expected exception with id ${id}`, HttpStatus.FORBIDDEN); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts new file mode 100644 index 000000000000..b5ca047e497c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/nestjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, + tracePropagationTargets: ['http://localhost:3030', '/external-allowed'], +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts new file mode 100644 index 000000000000..325e316b9f51 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts @@ -0,0 +1,20 @@ +// Import this first +import './instrument'; + +// Import other modules +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import * as Sentry from '@sentry/nestjs'; +import { AppModule } from './app.module'; + +const app1Port = 3030; + +async function bootstrap() { + const app1 = await NestFactory.create(AppModule); + + const { httpAdapter } = app1.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); + + await app1.listen(app1Port); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.controller.ts new file mode 100644 index 000000000000..150fb0e07546 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { TestException } from './test.exception'; + +@Controller('test-module') +export class TestController { + constructor() {} + + @Get() + getTest(): string { + throw new TestException(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.exception.ts new file mode 100644 index 000000000000..b736596b6717 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.exception.ts @@ -0,0 +1,5 @@ +export class TestException extends Error { + constructor() { + super('Something went wrong in the test module!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.filter.ts new file mode 100644 index 000000000000..87a4ca0920e5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.filter.ts @@ -0,0 +1,13 @@ +import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { TestException } from './test.exception'; + +@Catch(TestException) +export class TestExceptionFilter extends BaseExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + if (exception instanceof TestException) { + return super.catch(new BadRequestException(exception.message), host); + } + return super.catch(exception, host); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.module.ts new file mode 100644 index 000000000000..37b6dbe7e819 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { TestController } from './test.controller'; +import { TestExceptionFilter } from './test.filter'; + +@Module({ + imports: [], + controllers: [TestController], + providers: [ + { + provide: APP_FILTER, + useClass: TestExceptionFilter, + }, + ], +}) +export class TestModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/start-event-proxy.mjs new file mode 100644 index 000000000000..e9917b9273da --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nestjs', +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts new file mode 100644 index 000000000000..344218b58ef6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; + }); + + const response = await fetch(`${baseURL}/test-module`); + expect(response.status).toBe(500); // should be 400 + + // should never arrive, but does because the exception is not handled properly + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/test-module', + }); + + expect(errorEvent.transaction).toEqual('GET /test-module'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.build.json new file mode 100644 index 000000000000..26c30d4eddf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist"] +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.json new file mode 100644 index 000000000000..95f5641cf7f3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} From 6b9fb3408e1fba867d3a26bf98c1d212792b6869 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 12:34:48 +0200 Subject: [PATCH 4/8] Update --- .../test-applications/nestjs-with-submodules/src/instrument.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts index b5ca047e497c..f1f4de865435 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts @@ -5,5 +5,4 @@ Sentry.init({ dsn: process.env.E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, - tracePropagationTargets: ['http://localhost:3030', '/external-allowed'], }); From cf083a41c978851b1c538e064678e2a088d5bbef Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 14:13:23 +0200 Subject: [PATCH 5/8] Update basic nestjs test application --- .github/workflows/build.yml | 2 +- .../{nestjs => nestjs-basic}/.gitignore | 0 .../{nestjs => nestjs-basic}/.npmrc | 0 .../{nestjs => nestjs-basic}/nest-cli.json | 0 .../{nestjs => nestjs-basic}/package.json | 2 +- .../playwright.config.mjs | 0 .../nestjs-basic/src/app.controller.ts | 37 ++ .../nestjs-basic/src/app.module.ts | 11 + .../src/app.service.ts | 78 +--- .../src/instrument.ts | 1 - .../nestjs-basic/src/main.ts | 20 + .../start-event-proxy.mjs | 0 .../tests/cron-decorator.test.ts | 0 .../tests/errors.test.ts | 29 -- .../tests/span-decorator.test.ts | 0 .../tests/transactions.test.ts | 0 .../tsconfig.build.json | 0 .../{nestjs => nestjs-basic}/tsconfig.json | 0 .../nestjs/src/app.controller.ts | 102 ----- .../nestjs/src/app.module.ts | 19 - .../test-applications/nestjs/src/main.ts | 24 -- .../nestjs/src/test-module/test.controller.ts | 12 - .../nestjs/src/test-module/test.exception.ts | 5 - .../nestjs/src/test-module/test.filter.ts | 13 - .../nestjs/src/test-module/test.module.ts | 16 - .../test-applications/nestjs/src/utils.ts | 26 -- .../nestjs/tests/propagation.test.ts | 356 ------------------ 27 files changed, 71 insertions(+), 682 deletions(-) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/.gitignore (100%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/nest-cli.json (100%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/package.json (98%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/playwright.config.mjs (100%) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/src/app.service.ts (51%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/src/instrument.ts (75%) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/start-event-proxy.mjs (100%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/tests/cron-decorator.test.ts (100%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/tests/errors.test.ts (63%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/tests/span-decorator.test.ts (100%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/tests/transactions.test.ts (100%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/tsconfig.build.json (100%) rename dev-packages/e2e-tests/test-applications/{nestjs => nestjs-basic}/tsconfig.json (100%) delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/app.controller.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/main.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/utils.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/tests/propagation.test.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9e8abb392fc8..cdfa579c7797 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1031,7 +1031,7 @@ jobs: 'generic-ts3.8', 'node-fastify', 'node-hapi', - 'nestjs', + 'nestjs-basic', 'nestjs-distributed-tracing', 'nestjs-with-submodules', 'node-exports-test-app', diff --git a/dev-packages/e2e-tests/test-applications/nestjs/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-basic/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/.gitignore rename to dev-packages/e2e-tests/test-applications/nestjs-basic/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/nestjs/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-basic/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/.npmrc rename to dev-packages/e2e-tests/test-applications/nestjs-basic/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/nestjs/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/nest-cli.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/nest-cli.json rename to dev-packages/e2e-tests/test-applications/nestjs-basic/nest-cli.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs/package.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json similarity index 98% rename from dev-packages/e2e-tests/test-applications/nestjs/package.json rename to dev-packages/e2e-tests/test-applications/nestjs-basic/package.json index 94c4e445bfe0..f4c44ff7cef3 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json @@ -1,5 +1,5 @@ { - "name": "nestjs", + "name": "nestjs-basic", "version": "0.0.1", "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/nestjs/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-basic/playwright.config.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/playwright.config.mjs rename to dev-packages/e2e-tests/test-applications/nestjs-basic/playwright.config.mjs diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts new file mode 100644 index 000000000000..b54604d999cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts @@ -0,0 +1,37 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('test-transaction') + testTransaction() { + return this.appService.testTransaction(); + } + + @Get('test-exception/:id') + async testException(@Param('id') id: string) { + return this.appService.testException(id); + } + + @Get('test-expected-exception/:id') + async testExpectedException(@Param('id') id: string) { + return this.appService.testExpectedException(id); + } + + @Get('test-span-decorator-async') + async testSpanDecoratorAsync() { + return { result: await this.appService.testSpanDecoratorAsync() }; + } + + @Get('test-span-decorator-sync') + async testSpanDecoratorSync() { + return { result: await this.appService.testSpanDecoratorSync() }; + } + + @Get('kill-test-cron') + async killTestCron() { + this.appService.killTestCron(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts new file mode 100644 index 000000000000..ceb7199a99cf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [ScheduleModule.forRoot()], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts similarity index 51% rename from dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts index f5666bffeb46..3afb7b5147bd 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts @@ -3,7 +3,6 @@ import { Cron, SchedulerRegistry } from '@nestjs/schedule'; import * as Sentry from '@sentry/nestjs'; import { SentryCron, SentryTraced } from '@sentry/nestjs'; import type { MonitorConfig } from '@sentry/types'; -import { makeHttpRequest } from './utils'; const monitorConfig: MonitorConfig = { schedule: { @@ -13,53 +12,15 @@ const monitorConfig: MonitorConfig = { }; @Injectable() -export class AppService1 { +export class AppService { constructor(private schedulerRegistry: SchedulerRegistry) {} - testSuccess() { - return { version: 'v1' }; - } - - testParam(id: string) { - return { - paramWas: id, - }; - } - - testInboundHeaders(headers: Record, id: string) { - return { - headers, - id, - }; - } - - async testOutgoingHttp(id: string) { - const data = await makeHttpRequest(`http://localhost:3030/test-inbound-headers/${id}`); - - return data; - } - - async testOutgoingFetch(id: string) { - const response = await fetch(`http://localhost:3030/test-inbound-headers/${id}`); - const data = await response.json(); - - return data; - } - testTransaction() { Sentry.startSpan({ name: 'test-span' }, () => { Sentry.startSpan({ name: 'child-span' }, () => {}); }); } - async testError() { - const exceptionId = Sentry.captureException(new Error('This is an error')); - - await Sentry.flush(2000); - - return { exceptionId }; - } - testException(id: string) { throw new Error(`This is an exception with id ${id}`); } @@ -68,26 +29,6 @@ export class AppService1 { throw new HttpException(`This is an expected exception with id ${id}`, HttpStatus.FORBIDDEN); } - async testOutgoingFetchExternalAllowed() { - const fetchResponse = await fetch('http://localhost:3040/external-allowed'); - - return fetchResponse.json(); - } - - async testOutgoingFetchExternalDisallowed() { - const fetchResponse = await fetch('http://localhost:3040/external-disallowed'); - - return fetchResponse.json(); - } - - async testOutgoingHttpExternalAllowed() { - return makeHttpRequest('http://localhost:3040/external-allowed'); - } - - async testOutgoingHttpExternalDisallowed() { - return makeHttpRequest('http://localhost:3040/external-disallowed'); - } - @SentryTraced('wait and return a string') async wait() { await new Promise(resolve => setTimeout(resolve, 500)); @@ -124,20 +65,3 @@ export class AppService1 { this.schedulerRegistry.deleteCronJob('test-cron-job'); } } - -@Injectable() -export class AppService2 { - externalAllowed(headers: Record) { - return { - headers, - route: 'external-allowed', - }; - } - - externalDisallowed(headers: Record) { - return { - headers, - route: 'external-disallowed', - }; - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts similarity index 75% rename from dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts index b5ca047e497c..f1f4de865435 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts @@ -5,5 +5,4 @@ Sentry.init({ dsn: process.env.E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, - tracePropagationTargets: ['http://localhost:3030', '/external-allowed'], }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts new file mode 100644 index 000000000000..b00fb961847a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts @@ -0,0 +1,20 @@ +// Import this first +import './instrument'; + +// Import other modules +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import * as Sentry from '@sentry/nestjs'; +import { AppModule } from './app.module'; + +const app1Port = 3030; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + const { httpAdapter } = app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); + + await app.listen(app1Port); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-basic/start-event-proxy.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/start-event-proxy.mjs rename to dev-packages/e2e-tests/test-applications/nestjs-basic/start-event-proxy.mjs diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/cron-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/cron-decorator.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts similarity index 63% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts index ffb48f4e5e70..349b25b0eee9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts @@ -53,32 +53,3 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { expect(errorEventOccurred).toBe(false); }); - -test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { - const errorEventPromise = waitForError('nestjs', event => { - return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; - }); - - const response = await fetch(`${baseURL}/test-module`); - expect(response.status).toBe(500); // should be 400 - - // should never arrive, but does because the exception is not handled properly - const errorEvent = await errorEventPromise; - - expect(errorEvent.exception?.values).toHaveLength(1); - expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!'); - - expect(errorEvent.request).toEqual({ - method: 'GET', - cookies: {}, - headers: expect.any(Object), - url: 'http://localhost:3030/test-module', - }); - - expect(errorEvent.transaction).toEqual('GET /test-module'); - - expect(errorEvent.contexts?.trace).toEqual({ - trace_id: expect.any(String), - span_id: expect.any(String), - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/span-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/span-decorator.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/span-decorator.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/span-decorator.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.build.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tsconfig.build.json rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.build.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tsconfig.json rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.controller.ts deleted file mode 100644 index 7fda9eef768e..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.controller.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Controller, Get, Headers, Param } from '@nestjs/common'; -import { AppService1, AppService2 } from './app.service'; - -@Controller() -export class AppController1 { - constructor(private readonly appService: AppService1) {} - - @Get('test-success') - testSuccess() { - return this.appService.testSuccess(); - } - - @Get('test-param/:param') - testParam(@Param() params) { - return this.appService.testParam(params.param); - } - - @Get('test-inbound-headers/:id') - testInboundHeaders(@Headers() headers, @Param('id') id: string) { - return this.appService.testInboundHeaders(headers, id); - } - - @Get('test-outgoing-http/:id') - async testOutgoingHttp(@Param('id') id: string) { - return this.appService.testOutgoingHttp(id); - } - - @Get('test-outgoing-fetch/:id') - async testOutgoingFetch(@Param('id') id: string) { - return this.appService.testOutgoingFetch(id); - } - - @Get('test-transaction') - testTransaction() { - return this.appService.testTransaction(); - } - - @Get('test-error') - async testError() { - return this.appService.testError(); - } - - @Get('test-exception/:id') - async testException(@Param('id') id: string) { - return this.appService.testException(id); - } - - @Get('test-expected-exception/:id') - async testExpectedException(@Param('id') id: string) { - return this.appService.testExpectedException(id); - } - - @Get('test-outgoing-fetch-external-allowed') - async testOutgoingFetchExternalAllowed() { - return this.appService.testOutgoingFetchExternalAllowed(); - } - - @Get('test-outgoing-fetch-external-disallowed') - async testOutgoingFetchExternalDisallowed() { - return this.appService.testOutgoingFetchExternalDisallowed(); - } - - @Get('test-outgoing-http-external-allowed') - async testOutgoingHttpExternalAllowed() { - return this.appService.testOutgoingHttpExternalAllowed(); - } - - @Get('test-outgoing-http-external-disallowed') - async testOutgoingHttpExternalDisallowed() { - return this.appService.testOutgoingHttpExternalDisallowed(); - } - - @Get('test-span-decorator-async') - async testSpanDecoratorAsync() { - return { result: await this.appService.testSpanDecoratorAsync() }; - } - - @Get('test-span-decorator-sync') - async testSpanDecoratorSync() { - return { result: await this.appService.testSpanDecoratorSync() }; - } - - @Get('kill-test-cron') - async killTestCron() { - this.appService.killTestCron(); - } -} - -@Controller() -export class AppController2 { - constructor(private readonly appService: AppService2) {} - - @Get('external-allowed') - externalAllowed(@Headers() headers) { - return this.appService.externalAllowed(headers); - } - - @Get('external-disallowed') - externalDisallowed(@Headers() headers) { - return this.appService.externalDisallowed(headers); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts deleted file mode 100644 index 932d1af99611..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Module } from '@nestjs/common'; -import { ScheduleModule } from '@nestjs/schedule'; -import { AppController1, AppController2 } from './app.controller'; -import { AppService1, AppService2 } from './app.service'; -import { TestModule } from './test-module/test.module'; - -@Module({ - imports: [ScheduleModule.forRoot(), TestModule], - controllers: [AppController1], - providers: [AppService1], -}) -export class AppModule1 {} - -@Module({ - imports: [], - controllers: [AppController2], - providers: [AppService2], -}) -export class AppModule2 {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts deleted file mode 100644 index c2682662154d..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Import this first -import './instrument'; - -// Import other modules -import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; -import * as Sentry from '@sentry/nestjs'; -import { AppModule1, AppModule2 } from './app.module'; - -const app1Port = 3030; -const app2Port = 3040; - -async function bootstrap() { - const app1 = await NestFactory.create(AppModule1); - - const { httpAdapter } = app1.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); - - await app1.listen(app1Port); - - const app2 = await NestFactory.create(AppModule2); - await app2.listen(app2Port); -} - -bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts deleted file mode 100644 index 150fb0e07546..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { TestException } from './test.exception'; - -@Controller('test-module') -export class TestController { - constructor() {} - - @Get() - getTest(): string { - throw new TestException(); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts deleted file mode 100644 index b736596b6717..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class TestException extends Error { - constructor() { - super('Something went wrong in the test module!'); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts deleted file mode 100644 index 87a4ca0920e5..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; -import { BaseExceptionFilter } from '@nestjs/core'; -import { TestException } from './test.exception'; - -@Catch(TestException) -export class TestExceptionFilter extends BaseExceptionFilter { - catch(exception: unknown, host: ArgumentsHost) { - if (exception instanceof TestException) { - return super.catch(new BadRequestException(exception.message), host); - } - return super.catch(exception, host); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts deleted file mode 100644 index 37b6dbe7e819..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; -import { TestController } from './test.controller'; -import { TestExceptionFilter } from './test.filter'; - -@Module({ - imports: [], - controllers: [TestController], - providers: [ - { - provide: APP_FILTER, - useClass: TestExceptionFilter, - }, - ], -}) -export class TestModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/utils.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/utils.ts deleted file mode 100644 index 27639ef26349..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as http from 'http'; - -export function makeHttpRequest(url) { - return new Promise(resolve => { - const data = []; - - http - .request(url, httpRes => { - httpRes.on('data', chunk => { - data.push(chunk); - }); - httpRes.on('error', error => { - resolve({ error: error.message, url }); - }); - httpRes.on('end', () => { - try { - const json = JSON.parse(Buffer.concat(data).toString()); - resolve(json); - } catch { - resolve({ data: Buffer.concat(data).toString(), url }); - } - }); - }) - .end(); - }); -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/propagation.test.ts deleted file mode 100644 index 2922435c542b..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/propagation.test.ts +++ /dev/null @@ -1,356 +0,0 @@ -import crypto from 'crypto'; -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; -import { SpanJSON } from '@sentry/types'; - -test('Propagates trace for outgoing http requests', async ({ baseURL }) => { - const id = crypto.randomUUID(); - - const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { - return ( - transactionEvent.contexts?.trace?.op === 'http.server' && - transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` - ); - }); - - const outboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { - return ( - transactionEvent.contexts?.trace?.op === 'http.server' && - transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http/${id}` - ); - }); - - const response = await fetch(`${baseURL}/test-outgoing-http/${id}`); - const data = await response.json(); - - const inboundTransaction = await inboundTransactionPromise; - const outboundTransaction = await outboundTransactionPromise; - - const traceId = outboundTransaction?.contexts?.trace?.trace_id; - const outgoingHttpSpan = outboundTransaction?.spans?.find(span => span.op === 'http.client') as SpanJSON | undefined; - - expect(outgoingHttpSpan).toBeDefined(); - - const outgoingHttpSpanId = outgoingHttpSpan?.span_id; - - expect(traceId).toEqual(expect.any(String)); - - // data is passed through from the inbound request, to verify we have the correct headers set - const inboundHeaderSentryTrace = data.headers?.['sentry-trace']; - const inboundHeaderBaggage = data.headers?.['baggage']; - - expect(inboundHeaderSentryTrace).toEqual(`${traceId}-${outgoingHttpSpanId}-1`); - expect(inboundHeaderBaggage).toBeDefined(); - - const baggage = (inboundHeaderBaggage || '').split(','); - expect(baggage).toEqual( - expect.arrayContaining([ - 'sentry-environment=qa', - `sentry-trace_id=${traceId}`, - expect.stringMatching(/sentry-public_key=/), - ]), - ); - - expect(outboundTransaction.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'route', - 'sentry.origin': 'auto.http.otel.http', - 'sentry.op': 'http.server', - 'sentry.sample_rate': 1, - url: `http://localhost:3030/test-outgoing-http/${id}`, - 'otel.kind': 'SERVER', - 'http.response.status_code': 200, - 'http.url': `http://localhost:3030/test-outgoing-http/${id}`, - 'http.host': 'localhost:3030', - 'net.host.name': 'localhost', - 'http.method': 'GET', - 'http.scheme': 'http', - 'http.target': `/test-outgoing-http/${id}`, - 'http.user_agent': 'node', - 'http.flavor': '1.1', - 'net.transport': 'ip_tcp', - 'net.host.ip': expect.any(String), - 'net.host.port': expect.any(Number), - 'net.peer.ip': expect.any(String), - 'net.peer.port': expect.any(Number), - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.route': '/test-outgoing-http/:id', - }, - op: 'http.server', - span_id: expect.any(String), - status: 'ok', - trace_id: traceId, - origin: 'auto.http.otel.http', - }); - - expect(inboundTransaction.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'route', - 'sentry.origin': 'auto.http.otel.http', - 'sentry.op': 'http.server', - 'sentry.sample_rate': 1, - url: `http://localhost:3030/test-inbound-headers/${id}`, - 'otel.kind': 'SERVER', - 'http.response.status_code': 200, - 'http.url': `http://localhost:3030/test-inbound-headers/${id}`, - 'http.host': 'localhost:3030', - 'net.host.name': 'localhost', - 'http.method': 'GET', - 'http.scheme': 'http', - 'http.target': `/test-inbound-headers/${id}`, - 'http.flavor': '1.1', - 'net.transport': 'ip_tcp', - 'net.host.ip': expect.any(String), - 'net.host.port': expect.any(Number), - 'net.peer.ip': expect.any(String), - 'net.peer.port': expect.any(Number), - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.route': '/test-inbound-headers/:id', - }, - op: 'http.server', - parent_span_id: outgoingHttpSpanId, - span_id: expect.any(String), - status: 'ok', - trace_id: traceId, - origin: 'auto.http.otel.http', - }); -}); - -test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { - const id = crypto.randomUUID(); - - const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` - ); - }); - - const outboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch/${id}` - ); - }); - - const response = await fetch(`${baseURL}/test-outgoing-fetch/${id}`); - const data = await response.json(); - - const inboundTransaction = await inboundTransactionPromise; - const outboundTransaction = await outboundTransactionPromise; - - const traceId = outboundTransaction?.contexts?.trace?.trace_id; - const outgoingHttpSpan = outboundTransaction?.spans?.find(span => span.op === 'http.client') as SpanJSON | undefined; - - expect(outgoingHttpSpan).toBeDefined(); - - const outgoingHttpSpanId = outgoingHttpSpan?.span_id; - - expect(traceId).toEqual(expect.any(String)); - - // data is passed through from the inbound request, to verify we have the correct headers set - const inboundHeaderSentryTrace = data.headers?.['sentry-trace']; - const inboundHeaderBaggage = data.headers?.['baggage']; - - expect(inboundHeaderSentryTrace).toEqual(`${traceId}-${outgoingHttpSpanId}-1`); - expect(inboundHeaderBaggage).toBeDefined(); - - const baggage = (inboundHeaderBaggage || '').split(','); - expect(baggage).toEqual( - expect.arrayContaining([ - 'sentry-environment=qa', - `sentry-trace_id=${traceId}`, - expect.stringMatching(/sentry-public_key=/), - ]), - ); - - expect(outboundTransaction.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'route', - 'sentry.origin': 'auto.http.otel.http', - 'sentry.op': 'http.server', - 'sentry.sample_rate': 1, - url: `http://localhost:3030/test-outgoing-fetch/${id}`, - 'otel.kind': 'SERVER', - 'http.response.status_code': 200, - 'http.url': `http://localhost:3030/test-outgoing-fetch/${id}`, - 'http.host': 'localhost:3030', - 'net.host.name': 'localhost', - 'http.method': 'GET', - 'http.scheme': 'http', - 'http.target': `/test-outgoing-fetch/${id}`, - 'http.user_agent': 'node', - 'http.flavor': '1.1', - 'net.transport': 'ip_tcp', - 'net.host.ip': expect.any(String), - 'net.host.port': expect.any(Number), - 'net.peer.ip': expect.any(String), - 'net.peer.port': expect.any(Number), - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.route': '/test-outgoing-fetch/:id', - }, - op: 'http.server', - span_id: expect.any(String), - status: 'ok', - trace_id: traceId, - origin: 'auto.http.otel.http', - }); - - expect(inboundTransaction.contexts?.trace).toEqual({ - data: expect.objectContaining({ - 'sentry.source': 'route', - 'sentry.origin': 'auto.http.otel.http', - 'sentry.op': 'http.server', - 'sentry.sample_rate': 1, - url: `http://localhost:3030/test-inbound-headers/${id}`, - 'otel.kind': 'SERVER', - 'http.response.status_code': 200, - 'http.url': `http://localhost:3030/test-inbound-headers/${id}`, - 'http.host': 'localhost:3030', - 'net.host.name': 'localhost', - 'http.method': 'GET', - 'http.scheme': 'http', - 'http.target': `/test-inbound-headers/${id}`, - 'http.flavor': '1.1', - 'net.transport': 'ip_tcp', - 'net.host.ip': expect.any(String), - 'net.host.port': expect.any(Number), - 'net.peer.ip': expect.any(String), - 'net.peer.port': expect.any(Number), - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.route': '/test-inbound-headers/:id', - }), - op: 'http.server', - parent_span_id: outgoingHttpSpanId, - span_id: expect.any(String), - status: 'ok', - trace_id: traceId, - origin: 'auto.http.otel.http', - }); -}); - -test('Propagates trace for outgoing external http requests', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-allowed` - ); - }); - - const response = await fetch(`${baseURL}/test-outgoing-http-external-allowed`); - const data = await response.json(); - - const inboundTransaction = await inboundTransactionPromise; - - const traceId = inboundTransaction?.contexts?.trace?.trace_id; - const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; - - expect(traceId).toEqual(expect.any(String)); - expect(spanId).toEqual(expect.any(String)); - - expect(data).toEqual({ - headers: expect.objectContaining({ - 'sentry-trace': `${traceId}-${spanId}-1`, - baggage: expect.any(String), - }), - route: 'external-allowed', - }); - - const baggage = (data.headers.baggage || '').split(','); - expect(baggage).toEqual( - expect.arrayContaining([ - 'sentry-environment=qa', - `sentry-trace_id=${traceId}`, - expect.stringMatching(/sentry-public_key=/), - ]), - ); -}); - -test('Does not propagate outgoing http requests not covered by tracePropagationTargets', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-disallowed` - ); - }); - - const response = await fetch(`${baseURL}/test-outgoing-http-external-disallowed`); - const data = await response.json(); - - const inboundTransaction = await inboundTransactionPromise; - - const traceId = inboundTransaction?.contexts?.trace?.trace_id; - const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; - - expect(traceId).toEqual(expect.any(String)); - expect(spanId).toEqual(expect.any(String)); - - expect(data.route).toBe('external-disallowed'); - expect(data.headers?.['sentry-trace']).toBeUndefined(); - expect(data.headers?.baggage).toBeUndefined(); -}); - -test('Propagates trace for outgoing external fetch requests', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-allowed` - ); - }); - - const response = await fetch(`${baseURL}/test-outgoing-fetch-external-allowed`); - const data = await response.json(); - - const inboundTransaction = await inboundTransactionPromise; - - const traceId = inboundTransaction?.contexts?.trace?.trace_id; - const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; - - expect(traceId).toEqual(expect.any(String)); - expect(spanId).toEqual(expect.any(String)); - - expect(data).toEqual({ - headers: expect.objectContaining({ - 'sentry-trace': `${traceId}-${spanId}-1`, - baggage: expect.any(String), - }), - route: 'external-allowed', - }); - - const baggage = (data.headers.baggage || '').split(','); - expect(baggage).toEqual( - expect.arrayContaining([ - 'sentry-environment=qa', - `sentry-trace_id=${traceId}`, - expect.stringMatching(/sentry-public_key=/), - ]), - ); -}); - -test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => { - const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-disallowed` - ); - }); - - const response = await fetch(`${baseURL}/test-outgoing-fetch-external-disallowed`); - const data = await response.json(); - - const inboundTransaction = await inboundTransactionPromise; - - const traceId = inboundTransaction?.contexts?.trace?.trace_id; - const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; - - expect(traceId).toEqual(expect.any(String)); - expect(spanId).toEqual(expect.any(String)); - - expect(data.route).toBe('external-disallowed'); - expect(data.headers?.['sentry-trace']).toBeUndefined(); - expect(data.headers?.baggage).toBeUndefined(); -}); From a752fa4a4176da19c4a9aa4d90d9302ea343c44b Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 14:26:18 +0200 Subject: [PATCH 6/8] Rename test module to example module --- .../nestjs-with-submodules/src/app.module.ts | 4 ++-- .../src/example-module/example.controller.ts | 12 ++++++++++++ .../src/example-module/example.exception.ts | 5 +++++ .../example.filter.ts} | 8 ++++---- .../src/example-module/example.module.ts | 16 ++++++++++++++++ .../src/test-module/test.controller.ts | 12 ------------ .../src/test-module/test.exception.ts | 5 ----- .../src/test-module/test.module.ts | 16 ---------------- .../nestjs-with-submodules/tests/errors.test.ts | 10 +++++----- 9 files changed, 44 insertions(+), 44 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts rename dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/{test-module/test.filter.ts => example-module/example.filter.ts} (61%) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.controller.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.exception.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.module.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index b4664edab418..e078591979c6 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { TestModule } from './test-module/test.module'; +import { ExampleModule } from "./example-module/example.module"; @Module({ - imports: [TestModule], + imports: [ExampleModule], controllers: [AppController], providers: [AppService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts new file mode 100644 index 000000000000..b71179c195cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { ExampleException } from './example.exception'; + +@Controller('example-module') +export class ExampleController { + constructor() {} + + @Get() + getTest(): string { + throw new ExampleException(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts new file mode 100644 index 000000000000..ac43dddfa8dc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts @@ -0,0 +1,5 @@ +export class ExampleException extends Error { + constructor() { + super('Something went wrong in the example module!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.filter.ts similarity index 61% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.filter.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.filter.ts index 87a4ca0920e5..848441caf855 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.filter.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.filter.ts @@ -1,11 +1,11 @@ import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; -import { TestException } from './test.exception'; +import { ExampleException } from './example.exception'; -@Catch(TestException) -export class TestExceptionFilter extends BaseExceptionFilter { +@Catch(ExampleException) +export class ExampleExceptionFilter extends BaseExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { - if (exception instanceof TestException) { + if (exception instanceof ExampleException) { return super.catch(new BadRequestException(exception.message), host); } return super.catch(exception, host); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts new file mode 100644 index 000000000000..fabd71c4df90 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { ExampleController } from './example.controller'; +import { ExampleExceptionFilter } from './example.filter'; + +@Module({ + imports: [], + controllers: [ExampleController], + providers: [ + { + provide: APP_FILTER, + useClass: ExampleExceptionFilter, + }, + ], +}) +export class ExampleModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.controller.ts deleted file mode 100644 index 150fb0e07546..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { TestException } from './test.exception'; - -@Controller('test-module') -export class TestController { - constructor() {} - - @Get() - getTest(): string { - throw new TestException(); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.exception.ts deleted file mode 100644 index b736596b6717..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.exception.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class TestException extends Error { - constructor() { - super('Something went wrong in the test module!'); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.module.ts deleted file mode 100644 index 37b6dbe7e819..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/test-module/test.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; -import { TestController } from './test.controller'; -import { TestExceptionFilter } from './test.filter'; - -@Module({ - imports: [], - controllers: [TestController], - providers: [ - { - provide: APP_FILTER, - useClass: TestExceptionFilter, - }, - ], -}) -export class TestModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index 344218b58ef6..3711cbe8fd0f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -3,26 +3,26 @@ import { waitForError } from '@sentry-internal/test-utils'; test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { const errorEventPromise = waitForError('nestjs', event => { - return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; + return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the example module!'; }); - const response = await fetch(`${baseURL}/test-module`); + const response = await fetch(`${baseURL}/example-module`); expect(response.status).toBe(500); // should be 400 // should never arrive, but does because the exception is not handled properly const errorEvent = await errorEventPromise; expect(errorEvent.exception?.values).toHaveLength(1); - expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!'); + expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the example module!'); expect(errorEvent.request).toEqual({ method: 'GET', cookies: {}, headers: expect.any(Object), - url: 'http://localhost:3030/test-module', + url: 'http://localhost:3030/example-module', }); - expect(errorEvent.transaction).toEqual('GET /test-module'); + expect(errorEvent.transaction).toEqual('GET /example-module'); expect(errorEvent.contexts?.trace).toEqual({ trace_id: expect.any(String), From b32759e3d340ec2204e35661efc0f7f51ba1f039 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 15:32:46 +0200 Subject: [PATCH 7/8] Lint --- .../test-applications/nestjs-with-submodules/src/app.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index e078591979c6..944b84e66d27 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { ExampleModule } from "./example-module/example.module"; +import { ExampleModule } from './example-module/example.module'; @Module({ imports: [ExampleModule], From 4165d78a8803d3743f165a2b9fc0a9fedb400c1f Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 18 Jul 2024 11:30:54 +0200 Subject: [PATCH 8/8] Address PR comments --- .../nestjs-basic/src/main.ts | 4 +- .../src/app.controller.ts | 57 ------------------- .../src/app.module.ts | 17 ------ .../nestjs-distributed-tracing/src/main.ts | 19 ++++--- .../src/trace-initiator.controller.ts | 42 ++++++++++++++ .../src/trace-initiator.module.ts | 10 ++++ ....service.ts => trace-initiator.service.ts} | 19 +------ .../src/trace-receiver.controller.ts | 17 ++++++ .../src/trace-receiver.module.ts | 10 ++++ .../src/trace-receiver.service.ts | 18 ++++++ .../nestjs-with-submodules/src/main.ts | 10 ++-- 11 files changed, 115 insertions(+), 108 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.controller.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts rename dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/{app.service.ts => trace-initiator.service.ts} (78%) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.service.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts index b00fb961847a..3a7b5ded8645 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts @@ -6,7 +6,7 @@ import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core' import * as Sentry from '@sentry/nestjs'; import { AppModule } from './app.module'; -const app1Port = 3030; +const PORT = 3030; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -14,7 +14,7 @@ async function bootstrap() { const { httpAdapter } = app.get(HttpAdapterHost); Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); - await app.listen(app1Port); + await app.listen(PORT); } bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.controller.ts deleted file mode 100644 index 0810728747e4..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.controller.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Controller, Get, Headers, Param } from '@nestjs/common'; -import { AppService1, AppService2 } from './app.service'; - -@Controller() -export class AppController1 { - constructor(private readonly appService: AppService1) {} - - @Get('test-inbound-headers/:id') - testInboundHeaders(@Headers() headers, @Param('id') id: string) { - return this.appService.testInboundHeaders(headers, id); - } - - @Get('test-outgoing-http/:id') - async testOutgoingHttp(@Param('id') id: string) { - return this.appService.testOutgoingHttp(id); - } - - @Get('test-outgoing-fetch/:id') - async testOutgoingFetch(@Param('id') id: string) { - return this.appService.testOutgoingFetch(id); - } - - @Get('test-outgoing-fetch-external-allowed') - async testOutgoingFetchExternalAllowed() { - return this.appService.testOutgoingFetchExternalAllowed(); - } - - @Get('test-outgoing-fetch-external-disallowed') - async testOutgoingFetchExternalDisallowed() { - return this.appService.testOutgoingFetchExternalDisallowed(); - } - - @Get('test-outgoing-http-external-allowed') - async testOutgoingHttpExternalAllowed() { - return this.appService.testOutgoingHttpExternalAllowed(); - } - - @Get('test-outgoing-http-external-disallowed') - async testOutgoingHttpExternalDisallowed() { - return this.appService.testOutgoingHttpExternalDisallowed(); - } -} - -@Controller() -export class AppController2 { - constructor(private readonly appService: AppService2) {} - - @Get('external-allowed') - externalAllowed(@Headers() headers) { - return this.appService.externalAllowed(headers); - } - - @Get('external-disallowed') - externalDisallowed(@Headers() headers) { - return this.appService.externalDisallowed(headers); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.module.ts deleted file mode 100644 index 5fda2f1e209f..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from '@nestjs/common'; -import { AppController1, AppController2 } from './app.controller'; -import { AppService1, AppService2 } from './app.service'; - -@Module({ - imports: [], - controllers: [AppController1], - providers: [AppService1], -}) -export class AppModule1 {} - -@Module({ - imports: [], - controllers: [AppController2], - providers: [AppService2], -}) -export class AppModule2 {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts index c2682662154d..83d0b33d687d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts @@ -4,21 +4,22 @@ import './instrument'; // Import other modules import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; import * as Sentry from '@sentry/nestjs'; -import { AppModule1, AppModule2 } from './app.module'; +import { TraceInitiatorModule } from './trace-initiator.module'; +import { TraceReceiverModule } from './trace-receiver.module'; -const app1Port = 3030; -const app2Port = 3040; +const TRACE_INITIATOR_PORT = 3030; +const TRACE_RECEIVER_PORT = 3040; async function bootstrap() { - const app1 = await NestFactory.create(AppModule1); + const trace_initiator_app = await NestFactory.create(TraceInitiatorModule); - const { httpAdapter } = app1.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); + const { httpAdapter } = trace_initiator_app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(trace_initiator_app, new BaseExceptionFilter(httpAdapter)); - await app1.listen(app1Port); + await trace_initiator_app.listen(TRACE_INITIATOR_PORT); - const app2 = await NestFactory.create(AppModule2); - await app2.listen(app2Port); + const trace_receiver_app = await NestFactory.create(TraceReceiverModule); + await trace_receiver_app.listen(TRACE_RECEIVER_PORT); } bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.controller.ts new file mode 100644 index 000000000000..62e0c299a239 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.controller.ts @@ -0,0 +1,42 @@ +import { Controller, Get, Headers, Param } from '@nestjs/common'; +import { TraceInitiatorService } from './trace-initiator.service'; + +@Controller() +export class TraceInitiatorController { + constructor(private readonly traceInitiatorService: TraceInitiatorService) {} + + @Get('test-inbound-headers/:id') + testInboundHeaders(@Headers() headers, @Param('id') id: string) { + return this.traceInitiatorService.testInboundHeaders(headers, id); + } + + @Get('test-outgoing-http/:id') + async testOutgoingHttp(@Param('id') id: string) { + return this.traceInitiatorService.testOutgoingHttp(id); + } + + @Get('test-outgoing-fetch/:id') + async testOutgoingFetch(@Param('id') id: string) { + return this.traceInitiatorService.testOutgoingFetch(id); + } + + @Get('test-outgoing-fetch-external-allowed') + async testOutgoingFetchExternalAllowed() { + return this.traceInitiatorService.testOutgoingFetchExternalAllowed(); + } + + @Get('test-outgoing-fetch-external-disallowed') + async testOutgoingFetchExternalDisallowed() { + return this.traceInitiatorService.testOutgoingFetchExternalDisallowed(); + } + + @Get('test-outgoing-http-external-allowed') + async testOutgoingHttpExternalAllowed() { + return this.traceInitiatorService.testOutgoingHttpExternalAllowed(); + } + + @Get('test-outgoing-http-external-disallowed') + async testOutgoingHttpExternalDisallowed() { + return this.traceInitiatorService.testOutgoingHttpExternalDisallowed(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts new file mode 100644 index 000000000000..9256f29928ab --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TraceInitiatorController } from './trace-initiator.controller'; +import { TraceInitiatorService } from './trace-initiator.service'; + +@Module({ + imports: [], + controllers: [TraceInitiatorController], + providers: [TraceInitiatorService], +}) +export class TraceInitiatorModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.service.ts similarity index 78% rename from dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.service.ts rename to dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.service.ts index 2fd4e85efcba..67c5333cedaf 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/app.service.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { makeHttpRequest } from './utils'; @Injectable() -export class AppService1 { +export class TraceInitiatorService { constructor() {} testInboundHeaders(headers: Record, id: string) { @@ -45,20 +45,3 @@ export class AppService1 { return makeHttpRequest('http://localhost:3040/external-disallowed'); } } - -@Injectable() -export class AppService2 { - externalAllowed(headers: Record) { - return { - headers, - route: 'external-allowed', - }; - } - - externalDisallowed(headers: Record) { - return { - headers, - route: 'external-disallowed', - }; - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.controller.ts new file mode 100644 index 000000000000..2a1899f1097d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Headers } from '@nestjs/common'; +import { TraceReceiverService } from './trace-receiver.service'; + +@Controller() +export class TraceReceiverController { + constructor(private readonly traceReceiverService: TraceReceiverService) {} + + @Get('external-allowed') + externalAllowed(@Headers() headers) { + return this.traceReceiverService.externalAllowed(headers); + } + + @Get('external-disallowed') + externalDisallowed(@Headers() headers) { + return this.traceReceiverService.externalDisallowed(headers); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.module.ts new file mode 100644 index 000000000000..2680b3071fb7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TraceReceiverController } from './trace-receiver.controller'; +import { TraceReceiverService } from './trace-receiver.service'; + +@Module({ + imports: [], + controllers: [TraceReceiverController], + providers: [TraceReceiverService], +}) +export class TraceReceiverModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.service.ts new file mode 100644 index 000000000000..a40b28ad0778 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class TraceReceiverService { + externalAllowed(headers: Record) { + return { + headers, + route: 'external-allowed', + }; + } + + externalDisallowed(headers: Record) { + return { + headers, + route: 'external-disallowed', + }; + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts index 325e316b9f51..3a7b5ded8645 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts @@ -6,15 +6,15 @@ import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core' import * as Sentry from '@sentry/nestjs'; import { AppModule } from './app.module'; -const app1Port = 3030; +const PORT = 3030; async function bootstrap() { - const app1 = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule); - const { httpAdapter } = app1.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); + const { httpAdapter } = app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); - await app1.listen(app1Port); + await app.listen(PORT); } bootstrap();