Skip to content

Commit 9a84484

Browse files
authored
chore(dev-deps): Bump nestjs deps & add nestjs v8 E2E test (#14148)
We support v8 but only test v10. By adding a (very basic) e2e test app for v8 of nestjs we can ensure this continues to work (as long as we want to support it). Also bumps nestjs dev deps to hopefully fix some security warnings for outdates deps (e.g. https://github.com/getsentry/sentry-javascript/security/dependabot/376). Note for the future: There seems to be some problem with the node-integration-tests and module resolution. As soon as the nestjs version there matches the one from the nestjs package - which means it will hoist the dependency to the workspace-level node_modules folder - the tests start failing weirdly. For now, I "fixed" this by using a different version of nestjs in node-integration-tests from nestjs. Since we want to get rid of the node-specific part there anyhow in v9, I figured it's not worth it to put more work into fixing this properly...
1 parent e0282b6 commit 9a84484

34 files changed

+1623
-53
lines changed

.github/workflows/build.yml

+1
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,7 @@ jobs:
928928
'node-nestjs-basic',
929929
'node-nestjs-distributed-tracing',
930930
'nestjs-basic',
931+
'nestjs-8',
931932
'nestjs-distributed-tracing',
932933
'nestjs-with-submodules',
933934
'nestjs-with-submodules-decorator',
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
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
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "nestjs-8",
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": "^8.0.0",
19+
"@nestjs/core": "^8.0.0",
20+
"@nestjs/microservices": "^8.0.0",
21+
"@nestjs/schedule": "^4.1.0",
22+
"@nestjs/platform-express": "^8.0.0",
23+
"@sentry/nestjs": "latest || *",
24+
"@sentry/types": "latest || *",
25+
"reflect-metadata": "^0.2.0",
26+
"rxjs": "^7.8.1"
27+
},
28+
"devDependencies": {
29+
"@playwright/test": "^1.44.1",
30+
"@sentry-internal/test-utils": "link:../../../test-utils",
31+
"@nestjs/cli": "^10.0.0",
32+
"@nestjs/schematics": "^10.0.0",
33+
"@nestjs/testing": "^10.0.0",
34+
"@types/express": "^4.17.17",
35+
"@types/node": "18.15.1",
36+
"@types/supertest": "^6.0.0",
37+
"@typescript-eslint/eslint-plugin": "^6.0.0",
38+
"@typescript-eslint/parser": "^6.0.0",
39+
"eslint": "^8.42.0",
40+
"eslint-config-prettier": "^9.0.0",
41+
"eslint-plugin-prettier": "^5.0.0",
42+
"prettier": "^3.0.0",
43+
"source-map-support": "^0.5.21",
44+
"supertest": "^6.3.3",
45+
"ts-loader": "^9.4.3",
46+
"tsconfig-paths": "^4.2.0",
47+
"typescript": "^4.9.5"
48+
}
49+
}
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;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { Controller, Get, Param, ParseIntPipe, UseFilters, UseGuards, UseInterceptors } from '@nestjs/common';
2+
import { flush } from '@sentry/nestjs';
3+
import { AppService } from './app.service';
4+
import { AsyncInterceptor } from './async-example.interceptor';
5+
import { ExampleInterceptor1 } from './example-1.interceptor';
6+
import { ExampleInterceptor2 } from './example-2.interceptor';
7+
import { ExampleExceptionGlobalFilter } from './example-global-filter.exception';
8+
import { ExampleExceptionLocalFilter } from './example-local-filter.exception';
9+
import { ExampleLocalFilter } from './example-local.filter';
10+
import { ExampleGuard } from './example.guard';
11+
12+
@Controller()
13+
@UseFilters(ExampleLocalFilter)
14+
export class AppController {
15+
constructor(private readonly appService: AppService) {}
16+
17+
@Get('test-transaction')
18+
testTransaction() {
19+
return this.appService.testTransaction();
20+
}
21+
22+
@Get('test-middleware-instrumentation')
23+
testMiddlewareInstrumentation() {
24+
return this.appService.testSpan();
25+
}
26+
27+
@Get('test-guard-instrumentation')
28+
@UseGuards(ExampleGuard)
29+
testGuardInstrumentation() {
30+
return {};
31+
}
32+
33+
@Get('test-interceptor-instrumentation')
34+
@UseInterceptors(ExampleInterceptor1, ExampleInterceptor2)
35+
testInterceptorInstrumentation() {
36+
return this.appService.testSpan();
37+
}
38+
39+
@Get('test-async-interceptor-instrumentation')
40+
@UseInterceptors(AsyncInterceptor)
41+
testAsyncInterceptorInstrumentation() {
42+
return this.appService.testSpan();
43+
}
44+
45+
@Get('test-pipe-instrumentation/:id')
46+
testPipeInstrumentation(@Param('id', ParseIntPipe) id: number) {
47+
return { value: id };
48+
}
49+
50+
@Get('test-exception/:id')
51+
async testException(@Param('id') id: string) {
52+
return this.appService.testException(id);
53+
}
54+
55+
@Get('test-expected-400-exception/:id')
56+
async testExpected400Exception(@Param('id') id: string) {
57+
return this.appService.testExpected400Exception(id);
58+
}
59+
60+
@Get('test-expected-500-exception/:id')
61+
async testExpected500Exception(@Param('id') id: string) {
62+
return this.appService.testExpected500Exception(id);
63+
}
64+
65+
@Get('test-expected-rpc-exception/:id')
66+
async testExpectedRpcException(@Param('id') id: string) {
67+
return this.appService.testExpectedRpcException(id);
68+
}
69+
70+
@Get('test-span-decorator-async')
71+
async testSpanDecoratorAsync() {
72+
return { result: await this.appService.testSpanDecoratorAsync() };
73+
}
74+
75+
@Get('test-span-decorator-sync')
76+
async testSpanDecoratorSync() {
77+
return { result: await this.appService.testSpanDecoratorSync() };
78+
}
79+
80+
@Get('kill-test-cron')
81+
async killTestCron() {
82+
this.appService.killTestCron();
83+
}
84+
85+
@Get('flush')
86+
async flush() {
87+
await flush();
88+
}
89+
90+
@Get('example-exception-global-filter')
91+
async exampleExceptionGlobalFilter() {
92+
throw new ExampleExceptionGlobalFilter();
93+
}
94+
95+
@Get('example-exception-local-filter')
96+
async exampleExceptionLocalFilter() {
97+
throw new ExampleExceptionLocalFilter();
98+
}
99+
100+
@Get('test-service-use')
101+
testServiceWithUseMethod() {
102+
return this.appService.use();
103+
}
104+
105+
@Get('test-service-transform')
106+
testServiceWithTransform() {
107+
return this.appService.transform();
108+
}
109+
110+
@Get('test-service-intercept')
111+
testServiceWithIntercept() {
112+
return this.appService.intercept();
113+
}
114+
115+
@Get('test-service-canActivate')
116+
testServiceWithCanActivate() {
117+
return this.appService.canActivate();
118+
}
119+
120+
@Get('test-function-name')
121+
testFunctionName() {
122+
return this.appService.getFunctionName();
123+
}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { MiddlewareConsumer, Module } from '@nestjs/common';
2+
import { APP_FILTER } from '@nestjs/core';
3+
import { ScheduleModule } from '@nestjs/schedule';
4+
import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup';
5+
import { AppController } from './app.controller';
6+
import { AppService } from './app.service';
7+
import { ExampleGlobalFilter } from './example-global.filter';
8+
import { ExampleMiddleware } from './example.middleware';
9+
10+
@Module({
11+
imports: [SentryModule.forRoot(), ScheduleModule.forRoot()],
12+
controllers: [AppController],
13+
providers: [
14+
AppService,
15+
{
16+
provide: APP_FILTER,
17+
useClass: SentryGlobalFilter,
18+
},
19+
{
20+
provide: APP_FILTER,
21+
useClass: ExampleGlobalFilter,
22+
},
23+
],
24+
})
25+
export class AppModule {
26+
configure(consumer: MiddlewareConsumer): void {
27+
consumer.apply(ExampleMiddleware).forRoutes('test-middleware-instrumentation');
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
2+
import { RpcException } from '@nestjs/microservices';
3+
import { Cron, SchedulerRegistry } from '@nestjs/schedule';
4+
import * as Sentry from '@sentry/nestjs';
5+
import { SentryCron, SentryTraced } from '@sentry/nestjs';
6+
import type { MonitorConfig } from '@sentry/types';
7+
8+
const monitorConfig: MonitorConfig = {
9+
schedule: {
10+
type: 'crontab',
11+
value: '* * * * *',
12+
},
13+
};
14+
15+
@Injectable()
16+
export class AppService {
17+
constructor(private schedulerRegistry: SchedulerRegistry) {}
18+
19+
testTransaction() {
20+
Sentry.startSpan({ name: 'test-span' }, () => {
21+
Sentry.startSpan({ name: 'child-span' }, () => {});
22+
});
23+
}
24+
25+
testSpan() {
26+
// span that should not be a child span of the middleware span
27+
Sentry.startSpan({ name: 'test-controller-span' }, () => {});
28+
}
29+
30+
testException(id: string) {
31+
throw new Error(`This is an exception with id ${id}`);
32+
}
33+
34+
testExpected400Exception(id: string) {
35+
throw new HttpException(`This is an expected 400 exception with id ${id}`, HttpStatus.BAD_REQUEST);
36+
}
37+
38+
testExpected500Exception(id: string) {
39+
throw new HttpException(`This is an expected 500 exception with id ${id}`, HttpStatus.INTERNAL_SERVER_ERROR);
40+
}
41+
42+
testExpectedRpcException(id: string) {
43+
throw new RpcException(`This is an expected RPC exception with id ${id}`);
44+
}
45+
46+
@SentryTraced('wait and return a string')
47+
async wait() {
48+
await new Promise(resolve => setTimeout(resolve, 500));
49+
return 'test';
50+
}
51+
52+
async testSpanDecoratorAsync() {
53+
return await this.wait();
54+
}
55+
56+
@SentryTraced('return a string')
57+
getString(): { result: string } {
58+
return { result: 'test' };
59+
}
60+
61+
@SentryTraced('return the function name')
62+
getFunctionName(): { result: string } {
63+
return { result: this.getFunctionName.name };
64+
}
65+
66+
async testSpanDecoratorSync() {
67+
const returned = this.getString();
68+
// Will fail if getString() is async, because returned will be a Promise<>
69+
return returned.result;
70+
}
71+
72+
/*
73+
Actual cron schedule differs from schedule defined in config because Sentry
74+
only supports minute granularity, but we don't want to wait (worst case) a
75+
full minute for the tests to finish.
76+
*/
77+
@Cron('*/5 * * * * *', { name: 'test-cron-job' })
78+
@SentryCron('test-cron-slug', monitorConfig)
79+
async testCron() {
80+
console.log('Test cron!');
81+
}
82+
83+
async killTestCron() {
84+
this.schedulerRegistry.deleteCronJob('test-cron-job');
85+
}
86+
87+
use() {
88+
console.log('Test use!');
89+
}
90+
91+
transform() {
92+
console.log('Test transform!');
93+
}
94+
95+
intercept() {
96+
console.log('Test intercept!');
97+
}
98+
99+
canActivate() {
100+
console.log('Test canActivate!');
101+
}
102+
}

0 commit comments

Comments
 (0)