Skip to content

Commit 7a93b0d

Browse files
fix(microservices): fix request scoped enhancers nestjs#2489
1 parent 791b1ce commit 7a93b0d

18 files changed

Lines changed: 299 additions & 13 deletions
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { INestApplication } from '@nestjs/common';
2+
import { Transport } from '@nestjs/microservices';
3+
import { Test } from '@nestjs/testing';
4+
import { expect } from 'chai';
5+
import * as request from 'supertest';
6+
import { Guard } from '../src/msvc/guards/request-scoped.guard';
7+
import { HelloController } from '../src/msvc/hello.controller';
8+
import { HelloModule } from '../src/msvc/hello.module';
9+
import { Interceptor } from '../src/msvc/interceptors/logging.interceptor';
10+
import { UsersService } from '../src/msvc/users/users.service';
11+
12+
class Meta {
13+
static COUNTER = 0;
14+
constructor() {
15+
Meta.COUNTER++;
16+
}
17+
}
18+
19+
describe('Request scope (microservices)', () => {
20+
let server;
21+
let app: INestApplication;
22+
23+
before(async () => {
24+
const module = await Test.createTestingModule({
25+
imports: [
26+
HelloModule.forRoot({
27+
provide: 'META',
28+
useClass: Meta,
29+
}),
30+
],
31+
}).compile();
32+
33+
app = module.createNestApplication();
34+
app.connectMicroservice({ transport: Transport.TCP });
35+
36+
server = app.getHttpServer();
37+
await app.init();
38+
await app.startAllMicroservicesAsync();
39+
});
40+
41+
describe('when one service is request scoped', () => {
42+
before(async () => {
43+
const performHttpCall = end =>
44+
request(server)
45+
.get('/hello')
46+
.end((err, res) => {
47+
if (err) return end(err);
48+
end();
49+
});
50+
await new Promise(resolve => performHttpCall(resolve));
51+
await new Promise(resolve => performHttpCall(resolve));
52+
await new Promise(resolve => performHttpCall(resolve));
53+
});
54+
55+
it(`should create controller for each request`, async () => {
56+
expect(HelloController.COUNTER).to.be.eql(3);
57+
});
58+
59+
it(`should create service for each request`, async () => {
60+
expect(UsersService.COUNTER).to.be.eql(3);
61+
});
62+
63+
it(`should share static provider across requests`, async () => {
64+
expect(Meta.COUNTER).to.be.eql(1);
65+
});
66+
67+
it(`should create request scoped interceptor for each request`, async () => {
68+
expect(Interceptor.COUNTER).to.be.eql(3);
69+
expect(Interceptor.REQUEST_SCOPED_DATA).to.deep.equal([1, 1, 1]);
70+
});
71+
72+
it(`should create request scoped guard for each request`, async () => {
73+
expect(Guard.COUNTER).to.be.eql(3);
74+
expect(Guard.REQUEST_SCOPED_DATA).to.deep.equal([1, 1, 1]);
75+
});
76+
});
77+
78+
after(async () => {
79+
await app.close();
80+
});
81+
});

integration/scopes/e2e/request-scope.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,17 @@ describe('Request scope', () => {
6363

6464
it(`should create request scoped pipe for each request`, async () => {
6565
expect(UserByIdPipe.COUNTER).to.be.eql(3);
66+
expect(UserByIdPipe.REQUEST_SCOPED_DATA).to.deep.equal([1, 1, 1]);
6667
});
6768

6869
it(`should create request scoped interceptor for each request`, async () => {
6970
expect(Interceptor.COUNTER).to.be.eql(3);
71+
expect(Interceptor.REQUEST_SCOPED_DATA).to.deep.equal([1, 1, 1]);
7072
});
7173

7274
it(`should create request scoped guard for each request`, async () => {
7375
expect(Guard.COUNTER).to.be.eql(3);
76+
expect(Guard.REQUEST_SCOPED_DATA).to.deep.equal([1, 1, 1]);
7477
});
7578
});
7679

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
CanActivate,
33
ExecutionContext,
4+
Inject,
45
Injectable,
56
Scope,
67
} from '@nestjs/common';
@@ -9,13 +10,16 @@ import { Observable } from 'rxjs';
910
@Injectable({ scope: Scope.REQUEST })
1011
export class Guard implements CanActivate {
1112
static COUNTER = 0;
12-
constructor() {
13+
static REQUEST_SCOPED_DATA = [];
14+
15+
constructor(@Inject('REQUEST_ID') private requestId: number) {
1316
Guard.COUNTER++;
1417
}
1518

1619
canActivate(
1720
context: ExecutionContext,
1821
): boolean | Promise<boolean> | Observable<boolean> {
22+
Guard.REQUEST_SCOPED_DATA.push(this.requestId);
1923
return true;
2024
}
2125
}

integration/scopes/src/hello/hello.module.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
import { DynamicModule, Inject, Module, Provider } from '@nestjs/common';
1+
import { DynamicModule, Inject, Module, Provider, Scope } from '@nestjs/common';
22
import { HelloController } from './hello.controller';
33
import { HelloService } from './hello.service';
44
import { UsersService } from './users/users.service';
55

66
@Module({
77
controllers: [HelloController],
8-
providers: [HelloService, UsersService],
8+
providers: [
9+
HelloService,
10+
UsersService,
11+
{
12+
provide: 'REQUEST_ID',
13+
useFactory: () => 1,
14+
scope: Scope.REQUEST,
15+
},
16+
],
917
})
1018
export class HelloModule {
1119
constructor(@Inject('META') private readonly meta) {}

integration/scopes/src/hello/interceptors/logging.interceptor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
CallHandler,
33
ExecutionContext,
4+
Inject,
45
Injectable,
56
NestInterceptor,
67
Scope,
@@ -10,10 +11,14 @@ import { Observable } from 'rxjs';
1011
@Injectable({ scope: Scope.REQUEST })
1112
export class Interceptor implements NestInterceptor {
1213
static COUNTER = 0;
13-
constructor() {
14+
static REQUEST_SCOPED_DATA = [];
15+
16+
constructor(@Inject('REQUEST_ID') private requestId: number) {
1417
Interceptor.COUNTER++;
1518
}
19+
1620
intercept(context: ExecutionContext, call: CallHandler): Observable<any> {
21+
Interceptor.REQUEST_SCOPED_DATA.push(this.requestId);
1722
return call.handle();
1823
}
1924
}
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1-
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
1+
import {
2+
ArgumentMetadata,
3+
Inject,
4+
Injectable,
5+
PipeTransform,
6+
} from '@nestjs/common';
27
import { UsersService } from './users.service';
38

49
@Injectable()
510
export class UserByIdPipe implements PipeTransform<string> {
611
static COUNTER = 0;
7-
constructor(private readonly usersService: UsersService) {
12+
static REQUEST_SCOPED_DATA = [];
13+
14+
constructor(
15+
@Inject('REQUEST_ID') private requestId: number,
16+
private readonly usersService: UsersService,
17+
) {
818
UserByIdPipe.COUNTER++;
919
}
1020

1121
transform(value: string, metadata: ArgumentMetadata) {
22+
UserByIdPipe.REQUEST_SCOPED_DATA.push(this.requestId);
1223
return this.usersService.findById(value);
1324
}
1425
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
2+
3+
export class TestDto {
4+
@IsString()
5+
@IsNotEmpty()
6+
string: string;
7+
8+
@IsNumber()
9+
number: number;
10+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {
2+
CanActivate,
3+
ExecutionContext,
4+
Inject,
5+
Injectable,
6+
Scope,
7+
} from '@nestjs/common';
8+
import { Observable } from 'rxjs';
9+
10+
@Injectable({ scope: Scope.REQUEST })
11+
export class Guard implements CanActivate {
12+
static COUNTER = 0;
13+
static REQUEST_SCOPED_DATA = [];
14+
15+
constructor(@Inject('REQUEST_ID') private requestId: number) {
16+
Guard.COUNTER++;
17+
}
18+
19+
canActivate(
20+
context: ExecutionContext,
21+
): boolean | Promise<boolean> | Observable<boolean> {
22+
Guard.REQUEST_SCOPED_DATA.push(this.requestId);
23+
return true;
24+
}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Controller, UseGuards, UseInterceptors } from '@nestjs/common';
2+
import { MessagePattern } from '@nestjs/microservices';
3+
import { Guard } from './guards/request-scoped.guard';
4+
import { HelloService } from './hello.service';
5+
import { Interceptor } from './interceptors/logging.interceptor';
6+
import { UsersService } from './users/users.service';
7+
8+
@Controller()
9+
export class HelloController {
10+
static COUNTER = 0;
11+
constructor(
12+
private readonly helloService: HelloService,
13+
private readonly usersService: UsersService,
14+
) {
15+
HelloController.COUNTER++;
16+
}
17+
18+
@UseGuards(Guard)
19+
@UseInterceptors(Interceptor)
20+
@MessagePattern('test')
21+
greeting(): string {
22+
return this.helloService.greeting();
23+
}
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { DynamicModule, Inject, Module, Provider, Scope } from '@nestjs/common';
2+
import { HelloController } from './hello.controller';
3+
import { HelloService } from './hello.service';
4+
import { HttpController } from './http.controller';
5+
import { UsersService } from './users/users.service';
6+
7+
@Module({
8+
controllers: [HelloController, HttpController],
9+
providers: [
10+
HelloService,
11+
UsersService,
12+
{
13+
provide: 'REQUEST_ID',
14+
useFactory: () => 1,
15+
scope: Scope.REQUEST,
16+
},
17+
],
18+
})
19+
export class HelloModule {
20+
constructor(@Inject('META') private readonly meta) {}
21+
22+
static forRoot(meta: Provider): DynamicModule {
23+
return {
24+
module: HelloModule,
25+
providers: [meta],
26+
};
27+
}
28+
}

0 commit comments

Comments
 (0)