Skip to content

Commit d629991

Browse files
authored
tests(e2e): Refactor nestjs e2e applications into multiple smaller test applications (#12948)
Refactor of the existing nestjs test applications. Before we had one sample application testing everything nest-related. This PR splits them up into three applications to make it more readable and easier to understand what is being tested. It also allows for iterating a bit quicker in local development. No new functionality was added. Will add more tests in a follow-up. The three new services are: - nestjs-basic: Simple nestjs application with no submodules and tests for basic functionality of the SDK like error monitoring and span instrumentation. - nestjs-with-submodules: NestJS application that is bit more complex including a submodule (and potentially multiple in the future) to have a more realistic setup for more advanced testing. - nestjs-distributed-tracing: Includes tests for trace propagation with multiple services.
1 parent 935bb61 commit d629991

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+690
-290
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,9 @@ jobs:
10371037
'generic-ts3.8',
10381038
'node-fastify',
10391039
'node-hapi',
1040-
'nestjs',
1040+
'nestjs-basic',
1041+
'nestjs-distributed-tracing',
1042+
'nestjs-with-submodules',
10411043
'node-exports-test-app',
10421044
'node-koa',
10431045
'node-connect',

dev-packages/e2e-tests/test-applications/nestjs/package.json renamed to dev-packages/e2e-tests/test-applications/nestjs-basic/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "nestjs",
2+
"name": "nestjs-basic",
33
"version": "0.0.1",
44
"private": true,
55
"scripts": {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Controller, Get, Param } from '@nestjs/common';
2+
import { AppService } from './app.service';
3+
4+
@Controller()
5+
export class AppController {
6+
constructor(private readonly appService: AppService) {}
7+
8+
@Get('test-transaction')
9+
testTransaction() {
10+
return this.appService.testTransaction();
11+
}
12+
13+
@Get('test-exception/:id')
14+
async testException(@Param('id') id: string) {
15+
return this.appService.testException(id);
16+
}
17+
18+
@Get('test-expected-exception/:id')
19+
async testExpectedException(@Param('id') id: string) {
20+
return this.appService.testExpectedException(id);
21+
}
22+
23+
@Get('test-span-decorator-async')
24+
async testSpanDecoratorAsync() {
25+
return { result: await this.appService.testSpanDecoratorAsync() };
26+
}
27+
28+
@Get('test-span-decorator-sync')
29+
async testSpanDecoratorSync() {
30+
return { result: await this.appService.testSpanDecoratorSync() };
31+
}
32+
33+
@Get('kill-test-cron')
34+
async killTestCron() {
35+
this.appService.killTestCron();
36+
}
37+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Module } from '@nestjs/common';
2+
import { ScheduleModule } from '@nestjs/schedule';
3+
import { AppController } from './app.controller';
4+
import { AppService } from './app.service';
5+
6+
@Module({
7+
imports: [ScheduleModule.forRoot()],
8+
controllers: [AppController],
9+
providers: [AppService],
10+
})
11+
export class AppModule {}

dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts renamed to dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Cron, SchedulerRegistry } from '@nestjs/schedule';
33
import * as Sentry from '@sentry/nestjs';
44
import { SentryCron, SentryTraced } from '@sentry/nestjs';
55
import type { MonitorConfig } from '@sentry/types';
6-
import { makeHttpRequest } from './utils';
76

87
const monitorConfig: MonitorConfig = {
98
schedule: {
@@ -13,53 +12,15 @@ const monitorConfig: MonitorConfig = {
1312
};
1413

1514
@Injectable()
16-
export class AppService1 {
15+
export class AppService {
1716
constructor(private schedulerRegistry: SchedulerRegistry) {}
1817

19-
testSuccess() {
20-
return { version: 'v1' };
21-
}
22-
23-
testParam(id: string) {
24-
return {
25-
paramWas: id,
26-
};
27-
}
28-
29-
testInboundHeaders(headers: Record<string, string>, id: string) {
30-
return {
31-
headers,
32-
id,
33-
};
34-
}
35-
36-
async testOutgoingHttp(id: string) {
37-
const data = await makeHttpRequest(`http://localhost:3030/test-inbound-headers/${id}`);
38-
39-
return data;
40-
}
41-
42-
async testOutgoingFetch(id: string) {
43-
const response = await fetch(`http://localhost:3030/test-inbound-headers/${id}`);
44-
const data = await response.json();
45-
46-
return data;
47-
}
48-
4918
testTransaction() {
5019
Sentry.startSpan({ name: 'test-span' }, () => {
5120
Sentry.startSpan({ name: 'child-span' }, () => {});
5221
});
5322
}
5423

55-
async testError() {
56-
const exceptionId = Sentry.captureException(new Error('This is an error'));
57-
58-
await Sentry.flush(2000);
59-
60-
return { exceptionId };
61-
}
62-
6324
testException(id: string) {
6425
throw new Error(`This is an exception with id ${id}`);
6526
}
@@ -68,26 +29,6 @@ export class AppService1 {
6829
throw new HttpException(`This is an expected exception with id ${id}`, HttpStatus.FORBIDDEN);
6930
}
7031

71-
async testOutgoingFetchExternalAllowed() {
72-
const fetchResponse = await fetch('http://localhost:3040/external-allowed');
73-
74-
return fetchResponse.json();
75-
}
76-
77-
async testOutgoingFetchExternalDisallowed() {
78-
const fetchResponse = await fetch('http://localhost:3040/external-disallowed');
79-
80-
return fetchResponse.json();
81-
}
82-
83-
async testOutgoingHttpExternalAllowed() {
84-
return makeHttpRequest('http://localhost:3040/external-allowed');
85-
}
86-
87-
async testOutgoingHttpExternalDisallowed() {
88-
return makeHttpRequest('http://localhost:3040/external-disallowed');
89-
}
90-
9132
@SentryTraced('wait and return a string')
9233
async wait() {
9334
await new Promise(resolve => setTimeout(resolve, 500));
@@ -124,20 +65,3 @@ export class AppService1 {
12465
this.schedulerRegistry.deleteCronJob('test-cron-job');
12566
}
12667
}
127-
128-
@Injectable()
129-
export class AppService2 {
130-
externalAllowed(headers: Record<string, string>) {
131-
return {
132-
headers,
133-
route: 'external-allowed',
134-
};
135-
}
136-
137-
externalDisallowed(headers: Record<string, string>) {
138-
return {
139-
headers,
140-
route: 'external-disallowed',
141-
};
142-
}
143-
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/nestjs';
2+
3+
Sentry.init({
4+
environment: 'qa', // dynamic sampling bias to keep transactions
5+
dsn: process.env.E2E_TEST_DSN,
6+
tunnel: `http://localhost:3031/`, // proxy server
7+
tracesSampleRate: 1,
8+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Import this first
2+
import './instrument';
3+
4+
// Import other modules
5+
import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core';
6+
import * as Sentry from '@sentry/nestjs';
7+
import { AppModule } from './app.module';
8+
9+
const PORT = 3030;
10+
11+
async function bootstrap() {
12+
const app = await NestFactory.create(AppModule);
13+
14+
const { httpAdapter } = app.get(HttpAdapterHost);
15+
Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter));
16+
17+
await app.listen(PORT);
18+
}
19+
20+
bootstrap();

dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts renamed to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,32 +53,3 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => {
5353

5454
expect(errorEventOccurred).toBe(false);
5555
});
56-
57-
test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => {
58-
const errorEventPromise = waitForError('nestjs', event => {
59-
return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!';
60-
});
61-
62-
const response = await fetch(`${baseURL}/test-module`);
63-
expect(response.status).toBe(500); // should be 400
64-
65-
// should never arrive, but does because the exception is not handled properly
66-
const errorEvent = await errorEventPromise;
67-
68-
expect(errorEvent.exception?.values).toHaveLength(1);
69-
expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!');
70-
71-
expect(errorEvent.request).toEqual({
72-
method: 'GET',
73-
cookies: {},
74-
headers: expect.any(Object),
75-
url: 'http://localhost:3030/test-module',
76-
});
77-
78-
expect(errorEvent.transaction).toEqual('GET /test-module');
79-
80-
expect(errorEvent.contexts?.trace).toEqual({
81-
trace_id: expect.any(String),
82-
span_id: expect.any(String),
83-
});
84-
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# compiled output
2+
/dist
3+
/node_modules
4+
/build
5+
6+
# Logs
7+
logs
8+
*.log
9+
npm-debug.log*
10+
pnpm-debug.log*
11+
yarn-debug.log*
12+
yarn-error.log*
13+
lerna-debug.log*
14+
15+
# OS
16+
.DS_Store
17+
18+
# Tests
19+
/coverage
20+
/.nyc_output
21+
22+
# IDEs and editors
23+
/.idea
24+
.project
25+
.classpath
26+
.c9/
27+
*.launch
28+
.settings/
29+
*.sublime-workspace
30+
31+
# IDE - VSCode
32+
.vscode/*
33+
!.vscode/settings.json
34+
!.vscode/tasks.json
35+
!.vscode/launch.json
36+
!.vscode/extensions.json
37+
38+
# dotenv environment variable files
39+
.env
40+
.env.development.local
41+
.env.test.local
42+
.env.production.local
43+
.env.local
44+
45+
# temp directory
46+
.temp
47+
.tmp
48+
49+
# Runtime data
50+
pids
51+
*.pid
52+
*.seed
53+
*.pid.lock
54+
55+
# Diagnostic reports (https://nodejs.org/api/report.html)
56+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "https://json.schemastore.org/nest-cli",
3+
"collection": "@nestjs/schematics",
4+
"sourceRoot": "src",
5+
"compilerOptions": {
6+
"deleteOutDir": true
7+
}
8+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "nestjs-distributed-tracing",
3+
"version": "0.0.1",
4+
"private": true,
5+
"scripts": {
6+
"build": "nest build",
7+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
8+
"start": "nest start",
9+
"start:dev": "nest start --watch",
10+
"start:debug": "nest start --debug --watch",
11+
"start:prod": "node dist/main",
12+
"clean": "npx rimraf node_modules pnpm-lock.yaml",
13+
"test": "playwright test",
14+
"test:build": "pnpm install",
15+
"test:assert": "pnpm test"
16+
},
17+
"dependencies": {
18+
"@nestjs/common": "^10.0.0",
19+
"@nestjs/core": "^10.0.0",
20+
"@nestjs/platform-express": "^10.0.0",
21+
"@sentry/nestjs": "latest || *",
22+
"@sentry/types": "latest || *",
23+
"reflect-metadata": "^0.2.0",
24+
"rxjs": "^7.8.1"
25+
},
26+
"devDependencies": {
27+
"@playwright/test": "^1.44.1",
28+
"@sentry-internal/test-utils": "link:../../../test-utils",
29+
"@nestjs/cli": "^10.0.0",
30+
"@nestjs/schematics": "^10.0.0",
31+
"@nestjs/testing": "^10.0.0",
32+
"@types/express": "^4.17.17",
33+
"@types/node": "18.15.1",
34+
"@types/supertest": "^6.0.0",
35+
"@typescript-eslint/eslint-plugin": "^6.0.0",
36+
"@typescript-eslint/parser": "^6.0.0",
37+
"eslint": "^8.42.0",
38+
"eslint-config-prettier": "^9.0.0",
39+
"eslint-plugin-prettier": "^5.0.0",
40+
"prettier": "^3.0.0",
41+
"source-map-support": "^0.5.21",
42+
"supertest": "^6.3.3",
43+
"ts-loader": "^9.4.3",
44+
"tsconfig-paths": "^4.2.0",
45+
"typescript": "^4.9.5"
46+
}
47+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
2+
3+
const config = getPlaywrightConfig({
4+
startCommand: `pnpm start`,
5+
});
6+
7+
export default config;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Import this first
2+
import './instrument';
3+
4+
// Import other modules
5+
import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core';
6+
import * as Sentry from '@sentry/nestjs';
7+
import { TraceInitiatorModule } from './trace-initiator.module';
8+
import { TraceReceiverModule } from './trace-receiver.module';
9+
10+
const TRACE_INITIATOR_PORT = 3030;
11+
const TRACE_RECEIVER_PORT = 3040;
12+
13+
async function bootstrap() {
14+
const trace_initiator_app = await NestFactory.create(TraceInitiatorModule);
15+
16+
const { httpAdapter } = trace_initiator_app.get(HttpAdapterHost);
17+
Sentry.setupNestErrorHandler(trace_initiator_app, new BaseExceptionFilter(httpAdapter));
18+
19+
await trace_initiator_app.listen(TRACE_INITIATOR_PORT);
20+
21+
const trace_receiver_app = await NestFactory.create(TraceReceiverModule);
22+
await trace_receiver_app.listen(TRACE_RECEIVER_PORT);
23+
}
24+
25+
bootstrap();

0 commit comments

Comments
 (0)