mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
Merge pull request #11517 from nestjs/10.0.0
chore: laying the grounds for 10.0.0
This commit is contained in:
@@ -3,19 +3,19 @@ version: 2
|
||||
aliases:
|
||||
- &restore-cache
|
||||
restore_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
- &install-deps
|
||||
run:
|
||||
name: Install dependencies
|
||||
command: npm ci
|
||||
name: Install dependencies
|
||||
command: npm ci --legacy-peer-deps
|
||||
- &build-packages
|
||||
run:
|
||||
name: Build
|
||||
command: npm run build
|
||||
name: Build
|
||||
command: npm run build
|
||||
- &run-unit-tests
|
||||
run:
|
||||
name: Test
|
||||
command: npm run test
|
||||
name: Test
|
||||
command: npm run test
|
||||
|
||||
unit-tests-template: &unit-tests-template
|
||||
working_directory: ~/nest
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
build:
|
||||
working_directory: ~/nest
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
- image: cimg/node:16.19
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: npm ci
|
||||
command: npm ci --legacy-peer-deps
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
paths:
|
||||
@@ -48,21 +48,11 @@ jobs:
|
||||
- run:
|
||||
name: Build
|
||||
command: npm run build
|
||||
|
||||
test_node_12:
|
||||
<<: *unit-tests-template
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
|
||||
test_node_14:
|
||||
<<: *unit-tests-template
|
||||
docker:
|
||||
- image: circleci/node:14
|
||||
|
||||
test_node_16:
|
||||
working_directory: ~/nest
|
||||
docker:
|
||||
- image: circleci/node:16
|
||||
- image: cimg/node:16.19
|
||||
steps:
|
||||
- checkout
|
||||
- *restore-cache
|
||||
@@ -77,6 +67,16 @@ jobs:
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
|
||||
test_node_18:
|
||||
<<: *unit-tests-template
|
||||
docker:
|
||||
- image: cimg/node:18.14
|
||||
|
||||
test_node_19:
|
||||
<<: *unit-tests-template
|
||||
docker:
|
||||
- image: cimg/node:19.8
|
||||
|
||||
lint:
|
||||
working_directory: ~/nest
|
||||
docker:
|
||||
@@ -97,17 +97,17 @@ jobs:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
- run:
|
||||
name: Prepare nvm
|
||||
command: |
|
||||
echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
|
||||
echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
|
||||
- run:
|
||||
- run:
|
||||
name: Upgrade Node.js
|
||||
command: |
|
||||
nvm install v12
|
||||
nvm install v16
|
||||
node -v
|
||||
nvm alias default v12
|
||||
nvm alias default v16
|
||||
- run:
|
||||
name: Install Docker Compose
|
||||
command: |
|
||||
@@ -126,11 +126,11 @@ jobs:
|
||||
- run:
|
||||
name: Integration tests
|
||||
command: npm run test:integration
|
||||
|
||||
|
||||
codechecks_benchmarks:
|
||||
working_directory: ~/nest
|
||||
docker:
|
||||
- image: circleci/node:16
|
||||
- image: cimg/node:16.19
|
||||
steps:
|
||||
- checkout
|
||||
- *restore-cache
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
samples:
|
||||
working_directory: ~/nest
|
||||
docker:
|
||||
- image: circleci/node:16
|
||||
- image: cimg/node:16.19
|
||||
environment:
|
||||
- DISABLE_OPENCOLLECTIVE: true
|
||||
steps:
|
||||
@@ -165,10 +165,7 @@ workflows:
|
||||
- test_node_16:
|
||||
requires:
|
||||
- build
|
||||
- test_node_14:
|
||||
requires:
|
||||
- build
|
||||
- test_node_12:
|
||||
- test_node_18:
|
||||
requires:
|
||||
- build
|
||||
- lint:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { CACHE_MANAGER, INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { AsyncRegisterExtraModule } from '../src/async-register-extra-providers/async-register-extra.module';
|
||||
import { Cache } from 'cache-manager';
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('Async Register Extra Providers', () => {
|
||||
let server;
|
||||
let app: INestApplication;
|
||||
let cacheManager: Cache;
|
||||
|
||||
before(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AsyncRegisterExtraModule],
|
||||
}).compile();
|
||||
|
||||
cacheManager = module.get<Cache>(CACHE_MANAGER);
|
||||
app = module.createNestApplication();
|
||||
server = app.getHttpServer();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should be defined', async () => {
|
||||
assert.isDefined(cacheManager);
|
||||
});
|
||||
|
||||
it(`should return empty`, async () => {
|
||||
return request(server).get('/').expect(200, 'Not found');
|
||||
});
|
||||
|
||||
it(`should return data`, async () => {
|
||||
return request(server).get('/').expect(200, 'value');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
39
integration/cache/e2e/async-register.spec.ts
vendored
39
integration/cache/e2e/async-register.spec.ts
vendored
@@ -1,39 +0,0 @@
|
||||
import { CACHE_MANAGER, INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { AsyncRegisterModule } from '../src/async-register/async-register.module';
|
||||
import { Cache } from 'cache-manager';
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('Async Register', () => {
|
||||
let server;
|
||||
let app: INestApplication;
|
||||
let cacheManager: Cache;
|
||||
|
||||
before(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AsyncRegisterModule],
|
||||
}).compile();
|
||||
|
||||
cacheManager = module.get<Cache>(CACHE_MANAGER);
|
||||
app = module.createNestApplication();
|
||||
server = app.getHttpServer();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should be defined', async () => {
|
||||
assert.isDefined(cacheManager);
|
||||
});
|
||||
|
||||
it(`should return empty`, async () => {
|
||||
return request(server).get('/').expect(200, 'Not found');
|
||||
});
|
||||
|
||||
it(`should return data`, async () => {
|
||||
return request(server).get('/').expect(200, 'value');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
35
integration/cache/e2e/custom-ttl.spec.ts
vendored
35
integration/cache/e2e/custom-ttl.spec.ts
vendored
@@ -1,35 +0,0 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { CustomTtlModule } from '../src/custom-ttl/custom-ttl.module';
|
||||
|
||||
describe('Caching Custom TTL', () => {
|
||||
let server;
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [CustomTtlModule],
|
||||
}).compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
server = app.getHttpServer();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('should return a differnt value after the TTL is elapsed', async () => {
|
||||
await request(server).get('/').expect(200, '0');
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
await request(server).get('/').expect(200, '1');
|
||||
});
|
||||
|
||||
it('should return the cached value within the TTL', async () => {
|
||||
await request(server).get('/').expect(200, '0');
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await request(server).get('/').expect(200, '0');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
31
integration/cache/e2e/multi-store.spec.ts
vendored
31
integration/cache/e2e/multi-store.spec.ts
vendored
@@ -1,31 +0,0 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { MultiStoreModule } from '../src/multi-store/multi-store.module';
|
||||
|
||||
describe('Caching Multi Store', () => {
|
||||
let server;
|
||||
let app: INestApplication;
|
||||
|
||||
before(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [MultiStoreModule],
|
||||
}).compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
server = app.getHttpServer();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it(`should return empty`, async () => {
|
||||
return request(server).get('/').expect(200, '');
|
||||
});
|
||||
|
||||
it(`should return data`, async () => {
|
||||
return request(server).get('/').expect(200, 'multi-store-value');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Controller, Get, Inject, CACHE_MANAGER } from '@nestjs/common';
|
||||
import { Cache } from 'cache-manager';
|
||||
|
||||
@Controller()
|
||||
export class AsyncRegisterExtraController {
|
||||
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
|
||||
|
||||
@Get()
|
||||
async getFromStore(): Promise<string> {
|
||||
const value: string | undefined = await this.cacheManager.get('key');
|
||||
if (!value) {
|
||||
await this.cacheManager.set('key', 'value');
|
||||
}
|
||||
return value ?? 'Not found';
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { CacheModule, Module } from '@nestjs/common';
|
||||
import { AsyncRegisterExtraController } from './async-register-extra.controller';
|
||||
import { CacheConfig } from './config/cache.config';
|
||||
import { ConfigModule } from './config/config.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CacheModule.registerAsync({
|
||||
extraProviders: [ConfigModule],
|
||||
isGlobal: true,
|
||||
useClass: CacheConfig,
|
||||
}),
|
||||
],
|
||||
controllers: [AsyncRegisterExtraController],
|
||||
})
|
||||
export class AsyncRegisterExtraModule {}
|
||||
@@ -1,17 +0,0 @@
|
||||
import {
|
||||
CacheModuleOptions,
|
||||
CacheOptionsFactory,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
@Injectable()
|
||||
export class CacheConfig implements CacheOptionsFactory {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
createCacheOptions(): CacheModuleOptions {
|
||||
const ttl = this.configService.getTtl();
|
||||
|
||||
return { ttl };
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
@Module({
|
||||
providers: [ConfigService],
|
||||
exports: [ConfigService],
|
||||
})
|
||||
export class ConfigModule {}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
public getTtl(): number {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@nestjs/common": ["../../packages/common"],
|
||||
"@nestjs/common/*": ["../../packages/common/*"],
|
||||
"@nestjs/core": ["../../packages/core"],
|
||||
"@nestjs/core/*": ["../../packages/core/*"],
|
||||
"@nestjs/microservices": ["../../packages/microservices"],
|
||||
"@nestjs/microservices/*": ["../../packages/microservices/*"],
|
||||
"@nestjs/websockets": ["../../packages/websockets"],
|
||||
"@nestjs/websockets/*": ["../../packages/websockets/*"],
|
||||
"@nestjs/testing": ["../../packages/testing"],
|
||||
"@nestjs/testing/*": ["../../packages/testing/*"],
|
||||
"@nestjs/platform-express": ["../../packages/platform-express"],
|
||||
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
|
||||
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
|
||||
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
|
||||
"@nestjs/platform-ws": ["../../packages/platform-ws"],
|
||||
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"e2e/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Controller, Get, Inject, CACHE_MANAGER } from '@nestjs/common';
|
||||
import { Cache } from 'cache-manager';
|
||||
|
||||
@Controller()
|
||||
export class AsyncRegisterController {
|
||||
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
|
||||
|
||||
@Get()
|
||||
async getFromStore(): Promise<string> {
|
||||
const value: string | undefined = await this.cacheManager.get('key');
|
||||
if (!value) {
|
||||
await this.cacheManager.set('key', 'value');
|
||||
}
|
||||
return value ?? 'Not found';
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { CacheModule, Module } from '@nestjs/common';
|
||||
import { AsyncRegisterController } from './async-register.controller';
|
||||
import { CacheConfig } from './config/cache.config';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CacheModule.registerAsync({
|
||||
isGlobal: true,
|
||||
useClass: CacheConfig,
|
||||
}),
|
||||
],
|
||||
controllers: [AsyncRegisterController],
|
||||
})
|
||||
export class AsyncRegisterModule {}
|
||||
@@ -1,14 +0,0 @@
|
||||
import {
|
||||
CacheModuleOptions,
|
||||
CacheOptionsFactory,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class CacheConfig implements CacheOptionsFactory {
|
||||
createCacheOptions(): CacheModuleOptions {
|
||||
const ttl = 100;
|
||||
|
||||
return { ttl };
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@nestjs/common": ["../../packages/common"],
|
||||
"@nestjs/common/*": ["../../packages/common/*"],
|
||||
"@nestjs/core": ["../../packages/core"],
|
||||
"@nestjs/core/*": ["../../packages/core/*"],
|
||||
"@nestjs/microservices": ["../../packages/microservices"],
|
||||
"@nestjs/microservices/*": ["../../packages/microservices/*"],
|
||||
"@nestjs/websockets": ["../../packages/websockets"],
|
||||
"@nestjs/websockets/*": ["../../packages/websockets/*"],
|
||||
"@nestjs/testing": ["../../packages/testing"],
|
||||
"@nestjs/testing/*": ["../../packages/testing/*"],
|
||||
"@nestjs/platform-express": ["../../packages/platform-express"],
|
||||
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
|
||||
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
|
||||
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
|
||||
"@nestjs/platform-ws": ["../../packages/platform-ws"],
|
||||
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"e2e/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import {
|
||||
CacheInterceptor,
|
||||
CacheTTL,
|
||||
Controller,
|
||||
Get,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
|
||||
@Controller()
|
||||
export class CustomTtlController {
|
||||
counter = 0;
|
||||
constructor() {}
|
||||
|
||||
@Get()
|
||||
@CacheTTL(500)
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
getNumber() {
|
||||
return this.counter++;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { CacheModule, Module } from '@nestjs/common';
|
||||
import { CustomTtlController } from './custom-ttl.controller';
|
||||
|
||||
@Module({
|
||||
imports: [CacheModule.register()],
|
||||
controllers: [CustomTtlController],
|
||||
})
|
||||
export class CustomTtlModule {}
|
||||
40
integration/cache/src/custom-ttl/tsconfig.json
vendored
40
integration/cache/src/custom-ttl/tsconfig.json
vendored
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@nestjs/common": ["../../packages/common"],
|
||||
"@nestjs/common/*": ["../../packages/common/*"],
|
||||
"@nestjs/core": ["../../packages/core"],
|
||||
"@nestjs/core/*": ["../../packages/core/*"],
|
||||
"@nestjs/microservices": ["../../packages/microservices"],
|
||||
"@nestjs/microservices/*": ["../../packages/microservices/*"],
|
||||
"@nestjs/websockets": ["../../packages/websockets"],
|
||||
"@nestjs/websockets/*": ["../../packages/websockets/*"],
|
||||
"@nestjs/testing": ["../../packages/testing"],
|
||||
"@nestjs/testing/*": ["../../packages/testing/*"],
|
||||
"@nestjs/platform-express": ["../../packages/platform-express"],
|
||||
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
|
||||
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
|
||||
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
|
||||
"@nestjs/platform-ws": ["../../packages/platform-ws"],
|
||||
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"e2e/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Controller, Get, Inject, CACHE_MANAGER } from '@nestjs/common';
|
||||
import { Cache } from 'cache-manager';
|
||||
|
||||
@Controller()
|
||||
export class MultiStoreController {
|
||||
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
|
||||
|
||||
@Get()
|
||||
async getFromMultiStore(): Promise<unknown> {
|
||||
const value = await this.cacheManager.get('multi-store-key');
|
||||
if (!value) {
|
||||
await this.cacheManager.set('multi-store-key', 'multi-store-value');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { CacheModule, Module } from '@nestjs/common';
|
||||
import { redisStore } from 'cache-manager-redis-store';
|
||||
import { MultiStoreController } from './multi-store.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CacheModule.register([
|
||||
{
|
||||
store: 'memory',
|
||||
max: 100,
|
||||
ttl: 10,
|
||||
},
|
||||
{
|
||||
store: redisStore,
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
db: 0,
|
||||
ttl: 600,
|
||||
},
|
||||
]),
|
||||
],
|
||||
controllers: [MultiStoreController],
|
||||
})
|
||||
export class MultiStoreModule {}
|
||||
40
integration/cache/src/multi-store/tsconfig.json
vendored
40
integration/cache/src/multi-store/tsconfig.json
vendored
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@nestjs/common": ["../../packages/common"],
|
||||
"@nestjs/common/*": ["../../packages/common/*"],
|
||||
"@nestjs/core": ["../../packages/core"],
|
||||
"@nestjs/core/*": ["../../packages/core/*"],
|
||||
"@nestjs/microservices": ["../../packages/microservices"],
|
||||
"@nestjs/microservices/*": ["../../packages/microservices/*"],
|
||||
"@nestjs/websockets": ["../../packages/websockets"],
|
||||
"@nestjs/websockets/*": ["../../packages/websockets/*"],
|
||||
"@nestjs/testing": ["../../packages/testing"],
|
||||
"@nestjs/testing/*": ["../../packages/testing/*"],
|
||||
"@nestjs/platform-express": ["../../packages/platform-express"],
|
||||
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
|
||||
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
|
||||
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
|
||||
"@nestjs/platform-ws": ["../../packages/platform-ws"],
|
||||
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"e2e/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { HttpServer, HttpStatus, INestApplication } from '@nestjs/common';
|
||||
import { HttpStatus, INestApplication } from '@nestjs/common';
|
||||
import {
|
||||
FastifyAdapter,
|
||||
NestFastifyApplication,
|
||||
} from '@nestjs/platform-fastify';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
import { RawServerDefault } from 'fastify';
|
||||
import * as request from 'supertest';
|
||||
import { ErrorsController } from '../src/errors/errors.controller';
|
||||
|
||||
describe('Error messages', () => {
|
||||
let server: HttpServer;
|
||||
let server: RawServerDefault;
|
||||
|
||||
describe('Express', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
@@ -3,7 +3,10 @@ import {
|
||||
Get,
|
||||
MiddlewareConsumer,
|
||||
Module,
|
||||
NestMiddleware,
|
||||
NestModule,
|
||||
Query,
|
||||
Req,
|
||||
RequestMethod,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
@@ -14,178 +17,385 @@ import { Test } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
import { AppModule } from '../src/app.module';
|
||||
|
||||
const INCLUDED_VALUE = 'test_included';
|
||||
const QUERY_VALUE = 'test_query';
|
||||
const REQ_URL_VALUE = 'test_req_url';
|
||||
const RETURN_VALUE = 'test';
|
||||
const SCOPED_VALUE = 'test_scoped';
|
||||
const WILDCARD_VALUE = 'test_wildcard';
|
||||
|
||||
@Controller()
|
||||
class TestController {
|
||||
@Get('express_style_wildcard/wildcard_nested')
|
||||
express_style_wildcard() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
|
||||
@Get('test')
|
||||
test() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
|
||||
@Get('query')
|
||||
query() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
|
||||
@Get('tests/wildcard_nested')
|
||||
wildcard_nested() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
|
||||
@Get('tests/included')
|
||||
included() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Controller(QUERY_VALUE)
|
||||
class TestQueryController {
|
||||
@Get()
|
||||
[QUERY_VALUE](@Query('test') test: string) {
|
||||
return test;
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
imports: [AppModule],
|
||||
controllers: [TestController, TestQueryController],
|
||||
})
|
||||
class TestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply((req, res, next) => res.end(INCLUDED_VALUE))
|
||||
.forRoutes({ path: 'tests/included', method: RequestMethod.POST })
|
||||
.apply((req, res, next) => res.end(`${REQ_URL_VALUE}${req.url}`))
|
||||
.forRoutes('req/url/')
|
||||
.apply((req, res, next) => res.end(WILDCARD_VALUE))
|
||||
.forRoutes('express_style_wildcard/*', 'tests/(.*)')
|
||||
.apply((req, res, next) => res.end(QUERY_VALUE))
|
||||
.forRoutes('query')
|
||||
.apply((req, res, next) => next())
|
||||
.forRoutes(TestQueryController)
|
||||
.apply((req, res, next) => res.end(SCOPED_VALUE))
|
||||
.forRoutes(TestController)
|
||||
.apply((req, res, next) => res.end(RETURN_VALUE))
|
||||
.exclude({ path: QUERY_VALUE, method: -1 as any })
|
||||
.forRoutes('(.*)');
|
||||
}
|
||||
}
|
||||
|
||||
describe('Middleware (FastifyAdapter)', () => {
|
||||
let app: NestFastifyApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = (
|
||||
await Test.createTestingModule({
|
||||
imports: [TestModule],
|
||||
}).compile()
|
||||
).createNestApplication<NestFastifyApplication>(new FastifyAdapter());
|
||||
describe('should return expected values depending on the route', () => {
|
||||
const INCLUDED_VALUE = 'test_included';
|
||||
const QUERY_VALUE = 'test_query';
|
||||
const REQ_URL_VALUE = 'test_req_url';
|
||||
const RETURN_VALUE = 'test';
|
||||
const SCOPED_VALUE = 'test_scoped';
|
||||
const WILDCARD_VALUE = 'test_wildcard';
|
||||
|
||||
await app.init();
|
||||
@Controller()
|
||||
class TestController {
|
||||
@Get('express_style_wildcard/wildcard_nested')
|
||||
express_style_wildcard() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
|
||||
@Get('test')
|
||||
test() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
|
||||
@Get('query')
|
||||
query() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
|
||||
@Get('tests/wildcard_nested')
|
||||
wildcard_nested() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
|
||||
@Get('tests/included')
|
||||
included() {
|
||||
return RETURN_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Controller(QUERY_VALUE)
|
||||
class TestQueryController {
|
||||
@Get()
|
||||
[QUERY_VALUE](@Query('test') test: string) {
|
||||
return test;
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
imports: [AppModule],
|
||||
controllers: [TestController, TestQueryController],
|
||||
})
|
||||
class TestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply((req, res, next) => res.end(INCLUDED_VALUE))
|
||||
.forRoutes({ path: 'tests/included', method: RequestMethod.POST })
|
||||
.apply((req, res, next) => res.end(REQ_URL_VALUE))
|
||||
.forRoutes('req/url/(.*)')
|
||||
.apply((req, res, next) => res.end(WILDCARD_VALUE))
|
||||
.forRoutes('express_style_wildcard/*', 'tests/(.*)')
|
||||
.apply((req, res, next) => res.end(QUERY_VALUE))
|
||||
.forRoutes('query')
|
||||
.apply((req, res, next) => next())
|
||||
.forRoutes(TestQueryController)
|
||||
.apply((req, res, next) => res.end(SCOPED_VALUE))
|
||||
.forRoutes(TestController)
|
||||
.apply((req, res, next) => res.end(RETURN_VALUE))
|
||||
.exclude({ path: QUERY_VALUE, method: -1 as any })
|
||||
.forRoutes('(.*)');
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
app = (
|
||||
await Test.createTestingModule({
|
||||
imports: [TestModule],
|
||||
}).compile()
|
||||
).createNestApplication<NestFastifyApplication>(new FastifyAdapter());
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it(`forRoutes((.*))`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/hello',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(RETURN_VALUE));
|
||||
});
|
||||
|
||||
it(`forRoutes(TestController)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/test',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(SCOPED_VALUE));
|
||||
});
|
||||
|
||||
it(`query?test=${QUERY_VALUE} forRoutes(query)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/query',
|
||||
query: {
|
||||
test: QUERY_VALUE,
|
||||
},
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
|
||||
});
|
||||
|
||||
it(`${QUERY_VALUE}?test=${QUERY_VALUE} forRoutes(${QUERY_VALUE})`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: QUERY_VALUE,
|
||||
query: {
|
||||
test: QUERY_VALUE,
|
||||
},
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
|
||||
});
|
||||
|
||||
it(`forRoutes(tests/(.*))`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/tests/wildcard_nested',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
|
||||
});
|
||||
|
||||
it(`forRoutes(express_style_wildcard/*)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/express_style_wildcard/wildcard_nested',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
|
||||
});
|
||||
|
||||
it(`forRoutes(req/url/)`, () => {
|
||||
const reqUrl = '/test';
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: `/req/url${reqUrl}`,
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(REQ_URL_VALUE));
|
||||
});
|
||||
|
||||
it(`GET forRoutes(POST tests/included)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/tests/included',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
|
||||
});
|
||||
|
||||
it(`POST forRoutes(POST tests/included)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'POST',
|
||||
url: '/tests/included',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(INCLUDED_VALUE));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
it(`forRoutes((.*))`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/hello',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(RETURN_VALUE));
|
||||
});
|
||||
describe.only('should execute middleware only once for given routes', () => {
|
||||
class Middleware implements NestMiddleware {
|
||||
use(request: any, reply: any, next: () => void) {
|
||||
if (request.middlewareExecutionCount === undefined) {
|
||||
request.middlewareExecutionCount = 1;
|
||||
} else {
|
||||
request.middlewareExecutionCount++;
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
it(`forRoutes(TestController)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/test',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(SCOPED_VALUE));
|
||||
});
|
||||
@Controller()
|
||||
class AbcController {
|
||||
@Get('/a')
|
||||
async a(@Req() request: any) {
|
||||
return this.validateExecutionCount({
|
||||
request,
|
||||
expected: 1,
|
||||
});
|
||||
}
|
||||
|
||||
it(`query?test=${QUERY_VALUE} forRoutes(query)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/query',
|
||||
query: {
|
||||
test: QUERY_VALUE,
|
||||
},
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
|
||||
});
|
||||
@Get('/a/b')
|
||||
async ab(@Req() request: any) {
|
||||
return this.validateExecutionCount({
|
||||
request,
|
||||
expected: 1,
|
||||
});
|
||||
}
|
||||
|
||||
it(`${QUERY_VALUE}?test=${QUERY_VALUE} forRoutes(${QUERY_VALUE})`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: QUERY_VALUE,
|
||||
query: {
|
||||
test: QUERY_VALUE,
|
||||
},
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(QUERY_VALUE));
|
||||
});
|
||||
@Get('/a/b/c')
|
||||
async abc(@Req() request: any) {
|
||||
return this.validateExecutionCount({
|
||||
request,
|
||||
expected: 1,
|
||||
});
|
||||
}
|
||||
|
||||
it(`forRoutes(tests/(.*))`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/tests/wildcard_nested',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
|
||||
});
|
||||
@Get('/similar')
|
||||
async withSimilar(@Req() request: any) {
|
||||
return this.validateExecutionCount({
|
||||
request,
|
||||
expected: 1,
|
||||
});
|
||||
}
|
||||
|
||||
it(`forRoutes(express_style_wildcard/*)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/express_style_wildcard/wildcard_nested',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
|
||||
});
|
||||
@Get('/similar/test')
|
||||
async withSimilarTest(@Req() request: any) {
|
||||
return this.validateExecutionCount({
|
||||
request,
|
||||
expected: 1,
|
||||
});
|
||||
}
|
||||
|
||||
it(`forRoutes(req/url/)`, () => {
|
||||
const reqUrl = '/test';
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: `/req/url${reqUrl}`,
|
||||
})
|
||||
.then(({ payload }) =>
|
||||
expect(payload).to.be.eql(`${REQ_URL_VALUE}${reqUrl}`),
|
||||
);
|
||||
});
|
||||
@Get('/similar/:id')
|
||||
async withSimilarId(@Req() request: any) {
|
||||
return this.validateExecutionCount({
|
||||
request,
|
||||
expected: 1,
|
||||
});
|
||||
}
|
||||
|
||||
it(`GET forRoutes(POST tests/included)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/tests/included',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(WILDCARD_VALUE));
|
||||
});
|
||||
private validateExecutionCount({
|
||||
request,
|
||||
expected,
|
||||
}: {
|
||||
request: any;
|
||||
expected: number;
|
||||
}) {
|
||||
let actual: number | undefined;
|
||||
actual = request.raw.middlewareExecutionCount;
|
||||
actual ??= 0;
|
||||
|
||||
it(`POST forRoutes(POST tests/included)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'POST',
|
||||
url: '/tests/included',
|
||||
})
|
||||
.then(({ payload }) => expect(payload).to.be.eql(INCLUDED_VALUE));
|
||||
});
|
||||
return {
|
||||
success: actual === expected,
|
||||
actual,
|
||||
expected,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
@Module({
|
||||
controllers: [AbcController],
|
||||
})
|
||||
class TestModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(Middleware).forRoutes(AbcController);
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
app = (
|
||||
await Test.createTestingModule({
|
||||
imports: [TestModule],
|
||||
}).compile()
|
||||
).createNestApplication<NestFastifyApplication>(new FastifyAdapter());
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it(`GET forRoutes(/a/b/c)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/a/b/c',
|
||||
})
|
||||
.then(({ payload }) => {
|
||||
expect(payload).to.be.eql(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
actual: 1,
|
||||
expected: 1,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`GET forRoutes(/a/b)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/a/b',
|
||||
})
|
||||
.then(({ payload }) =>
|
||||
expect(payload).to.be.eql(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
actual: 1,
|
||||
expected: 1,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it(`GET forRoutes(/a)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/a',
|
||||
})
|
||||
.then(({ payload }) =>
|
||||
expect(payload).to.be.eql(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
actual: 1,
|
||||
expected: 1,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it(`GET forRoutes(/similar)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/similar',
|
||||
})
|
||||
.then(({ payload }) =>
|
||||
expect(payload).to.be.eql(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
actual: 1,
|
||||
expected: 1,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it(`GET forRoutes(/similar/test)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/similar/test',
|
||||
})
|
||||
.then(({ payload }) =>
|
||||
expect(payload).to.be.eql(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
actual: 1,
|
||||
expected: 1,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it(`GET forRoutes(/similar/arbitrary)`, () => {
|
||||
return app
|
||||
.inject({
|
||||
method: 'GET',
|
||||
url: '/similar/arbitrary',
|
||||
})
|
||||
.then(({ payload }) =>
|
||||
expect(payload).to.be.eql(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
actual: 1,
|
||||
expected: 1,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
103
integration/hello-world/e2e/middleware-run-match-route.ts
Normal file
103
integration/hello-world/e2e/middleware-run-match-route.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
INestApplication,
|
||||
Injectable,
|
||||
MiddlewareConsumer,
|
||||
NestMiddleware,
|
||||
Module,
|
||||
} from '@nestjs/common';
|
||||
import { Test } from '../../../packages/testing';
|
||||
import * as request from 'supertest';
|
||||
import { expect } from 'chai';
|
||||
|
||||
/**
|
||||
* Number of times that the middleware was executed.
|
||||
*/
|
||||
let triggerCounter = 0;
|
||||
@Injectable()
|
||||
class Middleware implements NestMiddleware {
|
||||
use(req, res, next) {
|
||||
triggerCounter++;
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
@Controller()
|
||||
class TestController {
|
||||
@Get('/test')
|
||||
testA() {}
|
||||
|
||||
@Get('/:id')
|
||||
testB() {}
|
||||
|
||||
@Get('/static/route')
|
||||
testC() {}
|
||||
|
||||
@Get('/:id/:nested')
|
||||
testD() {}
|
||||
}
|
||||
|
||||
@Module({
|
||||
controllers: [TestController],
|
||||
})
|
||||
class TestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(Middleware).forRoutes(TestController);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Middleware (run on route match)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
triggerCounter = 0;
|
||||
app = (
|
||||
await Test.createTestingModule({
|
||||
imports: [TestModule],
|
||||
}).compile()
|
||||
).createNestApplication();
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it(`forRoutes(TestController) should execute middleware once when request url is equal match`, () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/test')
|
||||
.expect(200)
|
||||
.then(() => {
|
||||
expect(triggerCounter).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
it(`forRoutes(TestController) should execute middleware once when request url is not equal match`, () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/1')
|
||||
.expect(200)
|
||||
.then(() => {
|
||||
expect(triggerCounter).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
it(`forRoutes(TestController) should execute middleware once when request url is not of nested params`, () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/static/route')
|
||||
.expect(200)
|
||||
.then(() => {
|
||||
expect(triggerCounter).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
it(`forRoutes(TestController) should execute middleware once when request url is of nested params`, () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/1/abc')
|
||||
.expect(200)
|
||||
.then(() => {
|
||||
expect(triggerCounter).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -822,23 +822,6 @@
|
||||
"token": "HttpAdapterHost"
|
||||
}
|
||||
},
|
||||
"-553129559": {
|
||||
"id": "-553129559",
|
||||
"label": "HttpAdapterHost",
|
||||
"parent": "555176277",
|
||||
"metadata": {
|
||||
"type": "provider",
|
||||
"internal": true,
|
||||
"sourceModuleName": "InternalCoreModule",
|
||||
"durable": false,
|
||||
"static": true,
|
||||
"scope": 0,
|
||||
"transient": false,
|
||||
"exported": true,
|
||||
"token": "HttpAdapterHost",
|
||||
"initTime": 0
|
||||
}
|
||||
},
|
||||
"-702581189": {
|
||||
"id": "-702581189",
|
||||
"label": "LazyModuleLoader",
|
||||
@@ -2214,23 +2197,6 @@
|
||||
},
|
||||
"id": "1976848738"
|
||||
},
|
||||
"-1759625292": {
|
||||
"source": "-553129559",
|
||||
"target": "-326832201",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "InternalCoreModule",
|
||||
"sourceClassName": "HttpAdapterHost",
|
||||
"targetClassName": "HttpAdapterHost",
|
||||
"sourceClassToken": "HttpAdapterHost",
|
||||
"targetClassToken": "HttpAdapterHost",
|
||||
"targetModuleName": "InternalCoreModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor",
|
||||
"internal": true
|
||||
},
|
||||
"id": "-1759625292"
|
||||
},
|
||||
"-2105726668": {
|
||||
"source": "-1803759743",
|
||||
"target": "1010833816",
|
||||
@@ -2756,7 +2722,8 @@
|
||||
"patterns": [
|
||||
"createExternalSvc"
|
||||
],
|
||||
"isEventHandler": false
|
||||
"isEventHandler": false,
|
||||
"extras": {}
|
||||
},
|
||||
"id": "-349899408_create"
|
||||
},
|
||||
@@ -2771,7 +2738,8 @@
|
||||
"patterns": [
|
||||
"findAllExternalSvc"
|
||||
],
|
||||
"isEventHandler": false
|
||||
"isEventHandler": false,
|
||||
"extras": {}
|
||||
},
|
||||
"id": "-349899408_findAll"
|
||||
},
|
||||
@@ -2786,7 +2754,8 @@
|
||||
"patterns": [
|
||||
"findOneExternalSvc"
|
||||
],
|
||||
"isEventHandler": false
|
||||
"isEventHandler": false,
|
||||
"extras": {}
|
||||
},
|
||||
"id": "-349899408_findOne"
|
||||
},
|
||||
@@ -2801,7 +2770,8 @@
|
||||
"patterns": [
|
||||
"updateExternalSvc"
|
||||
],
|
||||
"isEventHandler": false
|
||||
"isEventHandler": false,
|
||||
"extras": {}
|
||||
},
|
||||
"id": "-349899408_update"
|
||||
},
|
||||
@@ -2816,7 +2786,8 @@
|
||||
"patterns": [
|
||||
"removeExternalSvc"
|
||||
],
|
||||
"isEventHandler": false
|
||||
"isEventHandler": false,
|
||||
"extras": {}
|
||||
},
|
||||
"id": "-349899408_remove"
|
||||
}
|
||||
|
||||
@@ -806,23 +806,6 @@
|
||||
"token": "HttpAdapterHost"
|
||||
}
|
||||
},
|
||||
"-553129559": {
|
||||
"id": "-553129559",
|
||||
"label": "HttpAdapterHost",
|
||||
"parent": "555176277",
|
||||
"metadata": {
|
||||
"type": "provider",
|
||||
"internal": true,
|
||||
"sourceModuleName": "InternalCoreModule",
|
||||
"durable": false,
|
||||
"static": true,
|
||||
"scope": 0,
|
||||
"transient": false,
|
||||
"exported": true,
|
||||
"token": "HttpAdapterHost",
|
||||
"initTime": 0
|
||||
}
|
||||
},
|
||||
"-702581189": {
|
||||
"id": "-702581189",
|
||||
"label": "LazyModuleLoader",
|
||||
@@ -2198,23 +2181,6 @@
|
||||
},
|
||||
"id": "1976848738"
|
||||
},
|
||||
"-1759625292": {
|
||||
"source": "-553129559",
|
||||
"target": "-326832201",
|
||||
"metadata": {
|
||||
"type": "class-to-class",
|
||||
"sourceModuleName": "InternalCoreModule",
|
||||
"sourceClassName": "HttpAdapterHost",
|
||||
"targetClassName": "HttpAdapterHost",
|
||||
"sourceClassToken": "HttpAdapterHost",
|
||||
"targetClassToken": "HttpAdapterHost",
|
||||
"targetModuleName": "InternalCoreModule",
|
||||
"keyOrIndex": 0,
|
||||
"injectionType": "constructor",
|
||||
"internal": true
|
||||
},
|
||||
"id": "-1759625292"
|
||||
},
|
||||
"-2105726668": {
|
||||
"source": "-1803759743",
|
||||
"target": "1010833816",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SerializedGraph } from '@nestjs/core/inspector/serialized-graph';
|
||||
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import * as sinon from 'sinon';
|
||||
import { AppModule } from '../src/app.module';
|
||||
@@ -26,10 +26,10 @@ describe('Graph inspector', () => {
|
||||
const graph = testingModule.get(SerializedGraph);
|
||||
|
||||
// Update snapshot:
|
||||
writeFileSync(
|
||||
join(__dirname, 'fixtures', 'pre-init-graph.json'),
|
||||
graph.toString(),
|
||||
);
|
||||
// writeFileSync(
|
||||
// join(__dirname, 'fixtures', 'pre-init-graph.json'),
|
||||
// graph.toString(),
|
||||
// );
|
||||
|
||||
const snapshot = readFileSync(
|
||||
join(__dirname, 'fixtures', 'pre-init-graph.json'),
|
||||
@@ -54,10 +54,10 @@ describe('Graph inspector', () => {
|
||||
const graph = testingModule.get(SerializedGraph);
|
||||
|
||||
// Update snapshot:
|
||||
writeFileSync(
|
||||
join(__dirname, 'fixtures', 'post-init-graph.json'),
|
||||
graph.toString(),
|
||||
);
|
||||
// writeFileSync(
|
||||
// join(__dirname, 'fixtures', 'post-init-graph.json'),
|
||||
// graph.toString(),
|
||||
// );
|
||||
|
||||
const snapshot = readFileSync(
|
||||
join(__dirname, 'fixtures', 'post-init-graph.json'),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"lib": ["dom"],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Injectable, Module, forwardRef } from '@nestjs/common';
|
||||
import { BModule, BProvider } from './b.module';
|
||||
|
||||
@Injectable()
|
||||
export class AProvider {}
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => BModule)],
|
||||
providers: [AProvider],
|
||||
exports: [AProvider],
|
||||
})
|
||||
export class AModule {}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Injectable, Module, forwardRef } from '@nestjs/common';
|
||||
import { AModule, AProvider } from './a.module';
|
||||
|
||||
@Injectable()
|
||||
export class BProvider {}
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => AModule)],
|
||||
providers: [BProvider],
|
||||
exports: [BProvider],
|
||||
})
|
||||
export class BModule {}
|
||||
309
integration/testing-module-override/e2e/modules-override.spec.ts
Normal file
309
integration/testing-module-override/e2e/modules-override.spec.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
import {
|
||||
Controller,
|
||||
DynamicModule,
|
||||
forwardRef,
|
||||
Global,
|
||||
Injectable,
|
||||
Module,
|
||||
} from '@nestjs/common';
|
||||
import { LazyModuleLoader } from '@nestjs/core';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { AModule, AProvider } from './circular-dependency/a.module';
|
||||
import { BModule, BProvider } from './circular-dependency/b.module';
|
||||
|
||||
describe('Modules overriding', () => {
|
||||
describe('Top-level module', () => {
|
||||
@Controller()
|
||||
class ControllerOverwritten {}
|
||||
|
||||
@Module({
|
||||
controllers: [ControllerOverwritten],
|
||||
})
|
||||
class ModuleToBeOverwritten {}
|
||||
|
||||
@Controller()
|
||||
class ControllerOverride {}
|
||||
|
||||
@Module({
|
||||
controllers: [ControllerOverride],
|
||||
})
|
||||
class ModuleOverride {}
|
||||
|
||||
let testingModule: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [ModuleToBeOverwritten],
|
||||
})
|
||||
.overrideModule(ModuleToBeOverwritten)
|
||||
.useModule(ModuleOverride)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override top-level modules using testing module builder', () => {
|
||||
expect(() =>
|
||||
testingModule.get<ControllerOverwritten>(ControllerOverwritten),
|
||||
).to.throw();
|
||||
expect(
|
||||
testingModule.get<ControllerOverride>(ControllerOverride),
|
||||
).to.be.an.instanceof(ControllerOverride);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dynamic module', () => {
|
||||
@Controller()
|
||||
class ControllerOverwritten {}
|
||||
|
||||
@Module({})
|
||||
class DynamicModuleToBeOverwritten {}
|
||||
|
||||
const dynamicModuleOverwritten: DynamicModule = {
|
||||
module: DynamicModuleToBeOverwritten,
|
||||
controllers: [ControllerOverwritten],
|
||||
};
|
||||
|
||||
@Controller()
|
||||
class ControllerOverride {}
|
||||
|
||||
@Module({})
|
||||
class DynamicModuleOverride {}
|
||||
|
||||
const dynamicModuleOverride: DynamicModule = {
|
||||
module: DynamicModuleOverride,
|
||||
controllers: [ControllerOverride],
|
||||
};
|
||||
|
||||
let testingModule: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [dynamicModuleOverwritten],
|
||||
})
|
||||
.overrideModule(dynamicModuleOverwritten)
|
||||
.useModule(dynamicModuleOverride)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override dynamic modules using testing module builder', () => {
|
||||
expect(() =>
|
||||
testingModule.get<ControllerOverwritten>(ControllerOverwritten),
|
||||
).to.throw();
|
||||
expect(
|
||||
testingModule.get<ControllerOverride>(ControllerOverride),
|
||||
).to.be.an.instanceof(ControllerOverride);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Circular dependency module', () => {
|
||||
let testingModule: TestingModule;
|
||||
|
||||
@Injectable()
|
||||
class CProvider {}
|
||||
|
||||
@Module({
|
||||
providers: [CProvider],
|
||||
})
|
||||
class CModule {}
|
||||
|
||||
@Injectable()
|
||||
class BProviderOverride {}
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => AModule), forwardRef(() => CModule)],
|
||||
providers: [BProviderOverride],
|
||||
exports: [BProviderOverride],
|
||||
})
|
||||
class BModuleOverride {}
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [AModule],
|
||||
})
|
||||
.overrideModule(BModule)
|
||||
.useModule(BModuleOverride)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override top-level modules using testing module builder', () => {
|
||||
expect(testingModule.get<AProvider>(AProvider)).to.be.an.instanceof(
|
||||
AProvider,
|
||||
);
|
||||
expect(() => testingModule.get<BProvider>(BProvider)).to.throw();
|
||||
expect(testingModule.get<CProvider>(CProvider)).to.be.an.instanceof(
|
||||
CProvider,
|
||||
);
|
||||
expect(
|
||||
testingModule.get<BProviderOverride>(BProviderOverride),
|
||||
).to.be.an.instanceof(BProviderOverride);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Nested module', () => {
|
||||
let testingModule: TestingModule;
|
||||
|
||||
@Controller()
|
||||
class OverwrittenNestedModuleController {}
|
||||
|
||||
@Module({
|
||||
controllers: [OverwrittenNestedModuleController],
|
||||
})
|
||||
class OverwrittenNestedModule {}
|
||||
|
||||
@Controller()
|
||||
class OverrideNestedModuleController {}
|
||||
|
||||
@Module({
|
||||
controllers: [OverrideNestedModuleController],
|
||||
})
|
||||
class OverrideNestedModule {}
|
||||
|
||||
@Module({
|
||||
imports: [OverwrittenNestedModule],
|
||||
})
|
||||
class AppModule {}
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
})
|
||||
.overrideModule(OverwrittenNestedModule)
|
||||
.useModule(OverrideNestedModule)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override nested modules using testing module builder', () => {
|
||||
expect(
|
||||
testingModule.get<OverrideNestedModuleController>(
|
||||
OverrideNestedModuleController,
|
||||
),
|
||||
).to.be.an.instanceof(OverrideNestedModuleController);
|
||||
expect(() =>
|
||||
testingModule.get<OverwrittenNestedModuleController>(
|
||||
OverwrittenNestedModuleController,
|
||||
),
|
||||
).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Lazy-loaded module', () => {
|
||||
let testingModule: TestingModule;
|
||||
|
||||
@Injectable()
|
||||
class OverwrittenLazyProvider {
|
||||
value() {
|
||||
return 'overwritten lazy';
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: 'LAZY_PROVIDER',
|
||||
useClass: OverwrittenLazyProvider,
|
||||
},
|
||||
],
|
||||
})
|
||||
class OverwrittenLazyModule {}
|
||||
|
||||
@Injectable()
|
||||
class OverrideLazyProvider {
|
||||
value() {
|
||||
return 'override lazy';
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: 'LAZY_PROVIDER',
|
||||
useClass: OverrideLazyProvider,
|
||||
},
|
||||
],
|
||||
})
|
||||
class OverrideLazyModule {}
|
||||
|
||||
@Injectable()
|
||||
class AppService {
|
||||
constructor(private lazyModuleLoader: LazyModuleLoader) {}
|
||||
|
||||
async value() {
|
||||
const moduleRef = await this.lazyModuleLoader.load(
|
||||
() => OverwrittenLazyModule,
|
||||
);
|
||||
return moduleRef.get('LAZY_PROVIDER').value();
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [AppService],
|
||||
})
|
||||
class AppModule {}
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
})
|
||||
.overrideModule(OverwrittenLazyModule)
|
||||
.useModule(OverrideLazyModule)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override lazy loaded modules using testing module builder', async () => {
|
||||
const result = await testingModule.get<AppService>(AppService).value();
|
||||
expect(result).to.be.equal('override lazy');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Global module', () => {
|
||||
let testingModule: TestingModule;
|
||||
|
||||
@Injectable()
|
||||
class OverwrittenProvider {
|
||||
value() {
|
||||
return 'overwritten lazy';
|
||||
}
|
||||
}
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [OverwrittenProvider],
|
||||
exports: [OverwrittenProvider],
|
||||
})
|
||||
class OverwrittenModule {}
|
||||
|
||||
@Injectable()
|
||||
class OverrideProvider {
|
||||
value() {
|
||||
return 'override lazy';
|
||||
}
|
||||
}
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [OverrideProvider],
|
||||
exports: [OverrideProvider],
|
||||
})
|
||||
class OverrideModule {}
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [OverwrittenModule],
|
||||
})
|
||||
.overrideModule(OverwrittenModule)
|
||||
.useModule(OverrideModule)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override global modules using testing module builder', () => {
|
||||
expect(
|
||||
testingModule.get<OverrideProvider>(OverrideProvider),
|
||||
).to.be.an.instanceof(OverrideProvider);
|
||||
expect(() =>
|
||||
testingModule.get<OverwrittenProvider>(OverwrittenProvider),
|
||||
).to.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
17
integration/testing-module-override/tsconfig.json
Normal file
17
integration/testing-module-override/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -352,4 +352,70 @@ describe('URI Versioning', () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================================== //
|
||||
describe('with the global prefix enabled and an excluded route', () => {
|
||||
before(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
app.setGlobalPrefix('api', { exclude: ['/foo/bar'] });
|
||||
app.enableVersioning({
|
||||
type: VersioningType.URI,
|
||||
defaultVersion: '1',
|
||||
});
|
||||
await app.init();
|
||||
});
|
||||
|
||||
describe('GET /', () => {
|
||||
it('V1', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/api/v1')
|
||||
.expect(200)
|
||||
.expect('Hello World V1!');
|
||||
});
|
||||
|
||||
it('V2', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/api/v2')
|
||||
.expect(200)
|
||||
.expect('Hello World V2!');
|
||||
});
|
||||
|
||||
it('V3', () => {
|
||||
return request(app.getHttpServer()).get('/api/v3').expect(404);
|
||||
});
|
||||
|
||||
it('No Version', () => {
|
||||
return request(app.getHttpServer()).get('/api').expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /foo/bar (excluded from the API prefix)', () => {
|
||||
it('V1', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/v1/foo/bar')
|
||||
.expect(200)
|
||||
.expect('Hello FooBar!');
|
||||
});
|
||||
|
||||
it('V2', () => {
|
||||
return request(app.getHttpServer()).get('/v2/foo/bar').expect(404);
|
||||
});
|
||||
|
||||
it('V3', () => {
|
||||
return request(app.getHttpServer()).get('/v3/foo/bar').expect(404);
|
||||
});
|
||||
|
||||
it('No Version', () => {
|
||||
return request(app.getHttpServer()).get('/foo/bar').expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
@@ -145,7 +145,7 @@ describe('WebSocketGateway (WsAdapter)', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
}).timeout(5000);
|
||||
|
||||
it(`should support 2 different gateways running on the same path (but different ports)`, async function () {
|
||||
this.retries(10);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
|
||||
39395
package-lock.json
generated
39395
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -176,7 +176,7 @@
|
||||
"ws": "8.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.9.0"
|
||||
"node": ">= 16"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
|
||||
16
packages/common/cache/cache.constants.ts
vendored
16
packages/common/cache/cache.constants.ts
vendored
@@ -1,16 +0,0 @@
|
||||
import { MODULE_OPTIONS_TOKEN } from './cache.module-definition';
|
||||
|
||||
/**
|
||||
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
|
||||
* @publicApi
|
||||
*/
|
||||
export const CACHE_MANAGER = 'CACHE_MANAGER';
|
||||
|
||||
export const CACHE_KEY_METADATA = 'cache_module:cache_key';
|
||||
export const CACHE_TTL_METADATA = 'cache_module:cache_ttl';
|
||||
|
||||
/**
|
||||
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
|
||||
* @publicApi
|
||||
*/
|
||||
export const CACHE_MODULE_OPTIONS = MODULE_OPTIONS_TOKEN;
|
||||
12
packages/common/cache/cache.module-definition.ts
vendored
12
packages/common/cache/cache.module-definition.ts
vendored
@@ -1,12 +0,0 @@
|
||||
import { ConfigurableModuleBuilder } from '../module-utils';
|
||||
import {
|
||||
CacheModuleOptions,
|
||||
CacheOptionsFactory,
|
||||
} from './interfaces/cache-module.interface';
|
||||
|
||||
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
|
||||
new ConfigurableModuleBuilder<CacheModuleOptions>({
|
||||
moduleName: 'Cache',
|
||||
})
|
||||
.setFactoryMethodName('createCacheOptions' as keyof CacheOptionsFactory)
|
||||
.build();
|
||||
60
packages/common/cache/cache.module.ts
vendored
60
packages/common/cache/cache.module.ts
vendored
@@ -1,60 +0,0 @@
|
||||
import { Module } from '../decorators';
|
||||
import { DynamicModule } from '../interfaces';
|
||||
import { CACHE_MANAGER } from './cache.constants';
|
||||
import { ConfigurableModuleClass } from './cache.module-definition';
|
||||
import { createCacheManager } from './cache.providers';
|
||||
import {
|
||||
CacheModuleAsyncOptions,
|
||||
CacheModuleOptions,
|
||||
} from './interfaces/cache-module.interface';
|
||||
|
||||
/**
|
||||
* Module that provides Nest cache-manager.
|
||||
*
|
||||
* @see [Caching](https://docs.nestjs.com/techniques/caching)
|
||||
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
|
||||
* @publicApi
|
||||
*/
|
||||
@Module({
|
||||
providers: [createCacheManager()],
|
||||
exports: [CACHE_MANAGER],
|
||||
})
|
||||
export class CacheModule extends ConfigurableModuleClass {
|
||||
/**
|
||||
* Configure the cache manager statically.
|
||||
*
|
||||
* @param options options to configure the cache manager
|
||||
*
|
||||
* @see [Customize caching](https://docs.nestjs.com/techniques/caching#customize-caching)
|
||||
*/
|
||||
static register<StoreConfig extends Record<any, any> = Record<string, any>>(
|
||||
options: CacheModuleOptions<StoreConfig> = {} as any,
|
||||
): DynamicModule {
|
||||
return {
|
||||
global: options.isGlobal,
|
||||
...super.register(options),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the cache manager dynamically.
|
||||
*
|
||||
* @param options method for dynamically supplying cache manager configuration
|
||||
* options
|
||||
*
|
||||
* @see [Async configuration](https://docs.nestjs.com/techniques/caching#async-configuration)
|
||||
*/
|
||||
static registerAsync<
|
||||
StoreConfig extends Record<any, any> = Record<string, any>,
|
||||
>(options: CacheModuleAsyncOptions<StoreConfig>): DynamicModule {
|
||||
const moduleDefinition = super.registerAsync(options);
|
||||
|
||||
return {
|
||||
global: options.isGlobal,
|
||||
...moduleDefinition,
|
||||
providers: options.extraProviders
|
||||
? moduleDefinition.providers.concat(options.extraProviders)
|
||||
: moduleDefinition.providers,
|
||||
};
|
||||
}
|
||||
}
|
||||
61
packages/common/cache/cache.providers.ts
vendored
61
packages/common/cache/cache.providers.ts
vendored
@@ -1,61 +0,0 @@
|
||||
import { Provider } from '../interfaces';
|
||||
import { loadPackage } from '../utils/load-package.util';
|
||||
import { CACHE_MANAGER } from './cache.constants';
|
||||
import { MODULE_OPTIONS_TOKEN } from './cache.module-definition';
|
||||
import { defaultCacheOptions } from './default-options';
|
||||
import {
|
||||
CacheManagerOptions,
|
||||
CacheStore,
|
||||
} from './interfaces/cache-manager.interface';
|
||||
|
||||
/**
|
||||
* Creates a CacheManager Provider.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function createCacheManager(): Provider {
|
||||
return {
|
||||
provide: CACHE_MANAGER,
|
||||
useFactory: async (options: CacheManagerOptions) => {
|
||||
const cacheManager = loadPackage('cache-manager', 'CacheModule', () =>
|
||||
require('cache-manager'),
|
||||
);
|
||||
const cacheManagerIsv5OrGreater = 'memoryStore' in cacheManager;
|
||||
const cachingFactory = async (
|
||||
store: CacheManagerOptions['store'],
|
||||
options: Omit<CacheManagerOptions, 'store'>,
|
||||
): Promise<Record<string, any>> => {
|
||||
if (!cacheManagerIsv5OrGreater) {
|
||||
return cacheManager.caching({
|
||||
...defaultCacheOptions,
|
||||
...{ ...options, store },
|
||||
});
|
||||
}
|
||||
let cache: string | Function | CacheStore = 'memory';
|
||||
defaultCacheOptions.ttl *= 1000;
|
||||
if (typeof store === 'object') {
|
||||
if ('create' in store) {
|
||||
cache = store.create;
|
||||
} else {
|
||||
cache = store;
|
||||
}
|
||||
} else if (typeof store === 'function') {
|
||||
cache = store;
|
||||
}
|
||||
return cacheManager.caching(cache, {
|
||||
...defaultCacheOptions,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
return Array.isArray(options)
|
||||
? cacheManager.multiCaching(
|
||||
await Promise.all(
|
||||
options.map(option => cachingFactory(option.store, option)),
|
||||
),
|
||||
)
|
||||
: cachingFactory(options.store, options);
|
||||
},
|
||||
inject: [MODULE_OPTIONS_TOKEN],
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { SetMetadata } from '../../decorators';
|
||||
import { CACHE_KEY_METADATA } from '../cache.constants';
|
||||
|
||||
/**
|
||||
* Decorator that sets the caching key used to store/retrieve cached items for
|
||||
* Web sockets or Microservice based apps.
|
||||
*
|
||||
* For example:
|
||||
* `@CacheKey('events')`
|
||||
*
|
||||
* @param key string naming the field to be used as a cache key
|
||||
*
|
||||
* @see [Caching](https://docs.nestjs.com/techniques/caching)
|
||||
*
|
||||
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
|
||||
* @publicApi
|
||||
*/
|
||||
export const CacheKey = (key: string) => SetMetadata(CACHE_KEY_METADATA, key);
|
||||
@@ -1,19 +0,0 @@
|
||||
import { SetMetadata } from '../../decorators';
|
||||
import { ExecutionContext } from '../../interfaces/features/execution-context.interface';
|
||||
import { CACHE_TTL_METADATA } from '../cache.constants';
|
||||
|
||||
/**
|
||||
* Decorator that sets the cache ttl setting the duration for cache expiration.
|
||||
*
|
||||
* For example: `@CacheTTL(5)`
|
||||
*
|
||||
* @param ttl number set the cache expiration time
|
||||
*
|
||||
* @see [Caching](https://docs.nestjs.com/techniques/caching)
|
||||
*
|
||||
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
|
||||
* @publicApi
|
||||
*/
|
||||
type CacheTTLFactory = (ctx: ExecutionContext) => Promise<number> | number;
|
||||
export const CacheTTL = (ttl: number | CacheTTLFactory) =>
|
||||
SetMetadata(CACHE_TTL_METADATA, ttl);
|
||||
2
packages/common/cache/decorators/index.ts
vendored
2
packages/common/cache/decorators/index.ts
vendored
@@ -1,2 +0,0 @@
|
||||
export * from './cache-key.decorator';
|
||||
export * from './cache-ttl.decorator';
|
||||
5
packages/common/cache/default-options.ts
vendored
5
packages/common/cache/default-options.ts
vendored
@@ -1,5 +0,0 @@
|
||||
export const defaultCacheOptions = {
|
||||
ttl: 5,
|
||||
max: 100,
|
||||
store: 'memory',
|
||||
};
|
||||
5
packages/common/cache/index.ts
vendored
5
packages/common/cache/index.ts
vendored
@@ -1,5 +0,0 @@
|
||||
export * from './cache.constants';
|
||||
export * from './cache.module';
|
||||
export * from './decorators';
|
||||
export * from './interceptors';
|
||||
export * from './interfaces';
|
||||
@@ -1,134 +0,0 @@
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { Inject, Injectable, Optional } from '../../decorators';
|
||||
import { StreamableFile } from '../../file-stream';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
HttpServer,
|
||||
NestInterceptor,
|
||||
} from '../../interfaces';
|
||||
import { Logger } from '../../services/logger.service';
|
||||
import { loadPackage } from '../../utils/load-package.util';
|
||||
import { isFunction, isNil } from '../../utils/shared.utils';
|
||||
import {
|
||||
CACHE_KEY_METADATA,
|
||||
CACHE_MANAGER,
|
||||
CACHE_TTL_METADATA,
|
||||
} from '../cache.constants';
|
||||
|
||||
/** @deprecated */
|
||||
const HTTP_ADAPTER_HOST = 'HttpAdapterHost';
|
||||
|
||||
/** @deprecated */
|
||||
const REFLECTOR = 'Reflector';
|
||||
|
||||
/** @deprecated Import from the "@nestjs/core" instead. */
|
||||
export interface HttpAdapterHost<T extends HttpServer = any> {
|
||||
httpAdapter: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [Caching](https://docs.nestjs.com/techniques/caching)
|
||||
*
|
||||
* @deprecated `CacheModule` (from the `@nestjs/common` package) is deprecated and will be removed in the next major release. Please, use the `@nestjs/cache-manager` package instead
|
||||
* @publicApi
|
||||
*/
|
||||
@Injectable()
|
||||
export class CacheInterceptor implements NestInterceptor {
|
||||
@Optional()
|
||||
@Inject(HTTP_ADAPTER_HOST)
|
||||
protected readonly httpAdapterHost: HttpAdapterHost;
|
||||
|
||||
protected allowedMethods = ['GET'];
|
||||
|
||||
private cacheManagerIsv5OrGreater: boolean;
|
||||
|
||||
constructor(
|
||||
@Inject(CACHE_MANAGER) protected readonly cacheManager: any,
|
||||
@Inject(REFLECTOR) protected readonly reflector: any,
|
||||
) {
|
||||
// We need to check if the cache-manager package is v5 or greater
|
||||
// because the set method signature changed in v5
|
||||
const cacheManagerPackage = loadPackage(
|
||||
'cache-manager',
|
||||
'CacheModule',
|
||||
() => require('cache-manager'),
|
||||
);
|
||||
this.cacheManagerIsv5OrGreater = 'memoryStore' in cacheManagerPackage;
|
||||
|
||||
Logger.warn(
|
||||
'DEPRECATED! "CacheModule" (from the "@nestjs/common" package) is deprecated and will be removed in the next major release. Please, use the "@nestjs/cache-manager" package instead.',
|
||||
);
|
||||
}
|
||||
|
||||
async intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Promise<Observable<any>> {
|
||||
const key = this.trackBy(context);
|
||||
const ttlValueOrFactory =
|
||||
this.reflector.get(CACHE_TTL_METADATA, context.getHandler()) ?? null;
|
||||
|
||||
if (!key) {
|
||||
return next.handle();
|
||||
}
|
||||
try {
|
||||
const value = await this.cacheManager.get(key);
|
||||
if (!isNil(value)) {
|
||||
return of(value);
|
||||
}
|
||||
const ttl = isFunction(ttlValueOrFactory)
|
||||
? await ttlValueOrFactory(context)
|
||||
: ttlValueOrFactory;
|
||||
|
||||
return next.handle().pipe(
|
||||
tap(async response => {
|
||||
if (response instanceof StreamableFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const args = [key, response];
|
||||
if (!isNil(ttl)) {
|
||||
args.push(this.cacheManagerIsv5OrGreater ? ttl : { ttl });
|
||||
}
|
||||
|
||||
try {
|
||||
await this.cacheManager.set(...args);
|
||||
} catch (err) {
|
||||
Logger.error(
|
||||
`An error has occurred when inserting "key: ${key}", "value: ${response}"`,
|
||||
'CacheInterceptor',
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
|
||||
protected trackBy(context: ExecutionContext): string | undefined {
|
||||
const httpAdapter = this.httpAdapterHost.httpAdapter;
|
||||
const isHttpApp = httpAdapter && !!httpAdapter.getRequestMethod;
|
||||
const cacheMetadata = this.reflector.get(
|
||||
CACHE_KEY_METADATA,
|
||||
context.getHandler(),
|
||||
);
|
||||
|
||||
if (!isHttpApp || cacheMetadata) {
|
||||
return cacheMetadata;
|
||||
}
|
||||
|
||||
const request = context.getArgByIndex(0);
|
||||
if (!this.isRequestCacheable(context)) {
|
||||
return undefined;
|
||||
}
|
||||
return httpAdapter.getRequestUrl(request);
|
||||
}
|
||||
|
||||
protected isRequestCacheable(context: ExecutionContext): boolean {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
return this.allowedMethods.includes(req.method);
|
||||
}
|
||||
}
|
||||
1
packages/common/cache/interceptors/index.ts
vendored
1
packages/common/cache/interceptors/index.ts
vendored
@@ -1 +0,0 @@
|
||||
export * from './cache.interceptor';
|
||||
@@ -1,86 +0,0 @@
|
||||
export interface LiteralObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface defining a cache store. Implement this interface to create a custom
|
||||
* cache store.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface CacheStore {
|
||||
/**
|
||||
* Create a key/value pair in the cache.
|
||||
*
|
||||
* @param key cache key
|
||||
* @param value cache value
|
||||
*/
|
||||
set<T>(
|
||||
key: string,
|
||||
value: T,
|
||||
options?: CacheStoreSetOptions<T> | number,
|
||||
): Promise<void> | void;
|
||||
/**
|
||||
* Retrieve a key/value pair from the cache.
|
||||
*
|
||||
* @param key cache key
|
||||
*/
|
||||
get<T>(key: string): Promise<T | undefined> | T | undefined;
|
||||
/**
|
||||
* Destroy a key/value pair from the cache.
|
||||
*
|
||||
* @param key cache key
|
||||
*/
|
||||
del?(key: string): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface CacheStoreSetOptions<T> {
|
||||
/**
|
||||
* Time to live - amount of time in seconds that a response is cached before it
|
||||
* is deleted. Defaults based on your cache manager settings.
|
||||
*/
|
||||
ttl?: ((value: T) => number) | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface defining a factory to create a cache store.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type CacheStoreFactory =
|
||||
| {
|
||||
/**
|
||||
* Return a configured cache store.
|
||||
*
|
||||
* @param args Cache manager options received from `CacheModule.register()`
|
||||
* or `CacheModule.registerAsync()`
|
||||
*/
|
||||
create(args: LiteralObject): CacheStore;
|
||||
}
|
||||
| ((args: LiteralObject) => CacheStore | Promise<CacheStore>);
|
||||
|
||||
/**
|
||||
* Interface defining Cache Manager configuration options.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface CacheManagerOptions {
|
||||
/**
|
||||
* Cache storage manager. Default is `'memory'` (in-memory store). See
|
||||
* [Different stores](https://docs.nestjs.com/techniques/caching#different-stores)
|
||||
* for more info.
|
||||
*/
|
||||
store?: string | CacheStoreFactory | CacheStore;
|
||||
/**
|
||||
* Time to live - amount of time that a response is cached before it
|
||||
* is deleted. Subsequent request will call through the route handler and refresh
|
||||
* the cache. Defaults to 5 seconds. In `cache-manager@^4` this value is in seconds.
|
||||
* In `cache-manager@^5` this value is in milliseconds.
|
||||
*/
|
||||
ttl?: number;
|
||||
/**
|
||||
* Maximum number of responses to store in the cache. Defaults to 100.
|
||||
*/
|
||||
max?: number;
|
||||
isCacheableValue?: (value: any) => boolean;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Provider, Type } from '../../interfaces';
|
||||
import { ConfigurableModuleAsyncOptions } from '../../module-utils';
|
||||
import { CacheManagerOptions } from './cache-manager.interface';
|
||||
|
||||
export type CacheModuleOptions<
|
||||
StoreConfig extends Record<any, any> = Record<string, any>,
|
||||
> =
|
||||
// Store-specific configuration takes precedence over cache module options due
|
||||
// to how `createCacheManager` is implemented.
|
||||
CacheManagerOptions &
|
||||
StoreConfig & {
|
||||
/**
|
||||
* If "true', register `CacheModule` as a global module.
|
||||
*/
|
||||
isGlobal?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface describing a `CacheOptionsFactory`. Providers supplying configuration
|
||||
* options for the Cache module must implement this interface.
|
||||
*
|
||||
* @see [Async configuration](https://docs.nestjs.com/techniques/caching#async-configuration)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface CacheOptionsFactory<
|
||||
StoreConfig extends Record<any, any> = Record<string, any>,
|
||||
> {
|
||||
createCacheOptions():
|
||||
| Promise<CacheModuleOptions<StoreConfig>>
|
||||
| CacheModuleOptions<StoreConfig>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for dynamically configuring the Cache module.
|
||||
*
|
||||
* @see [Async configuration](https://docs.nestjs.com/techniques/caching#async-configuration)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface CacheModuleAsyncOptions<
|
||||
StoreConfig extends Record<any, any> = Record<string, any>,
|
||||
> extends ConfigurableModuleAsyncOptions<
|
||||
CacheModuleOptions<StoreConfig>,
|
||||
keyof CacheOptionsFactory
|
||||
> {
|
||||
/**
|
||||
* Injection token resolving to an existing provider. The provider must implement
|
||||
* the `CacheOptionsFactory` interface.
|
||||
*/
|
||||
useExisting?: Type<CacheOptionsFactory<StoreConfig>>;
|
||||
/**
|
||||
* Injection token resolving to a class that will be instantiated as a provider.
|
||||
* The class must implement the `CacheOptionsFactory` interface.
|
||||
*/
|
||||
useClass?: Type<CacheOptionsFactory<StoreConfig>>;
|
||||
/**
|
||||
* Function returning options (or a Promise resolving to options) to configure the
|
||||
* cache module.
|
||||
*/
|
||||
useFactory?: (
|
||||
...args: any[]
|
||||
) =>
|
||||
| Promise<CacheModuleOptions<StoreConfig>>
|
||||
| CacheModuleOptions<StoreConfig>;
|
||||
/**
|
||||
* Dependencies that a Factory may inject.
|
||||
*/
|
||||
inject?: any[];
|
||||
/**
|
||||
* Extra providers to be registered within a scope of this module.
|
||||
*/
|
||||
extraProviders?: Provider[];
|
||||
/**
|
||||
* If "true', register `CacheModule` as a global module.
|
||||
*/
|
||||
isGlobal?: boolean;
|
||||
}
|
||||
2
packages/common/cache/interfaces/index.ts
vendored
2
packages/common/cache/interfaces/index.ts
vendored
@@ -1,2 +0,0 @@
|
||||
export * from './cache-manager.interface';
|
||||
export * from './cache-module.interface';
|
||||
@@ -17,10 +17,6 @@ export const SCOPE_OPTIONS_METADATA = 'scope:options';
|
||||
export const METHOD_METADATA = 'method';
|
||||
export const ROUTE_ARGS_METADATA = '__routeArguments__';
|
||||
export const CUSTOM_ROUTE_ARGS_METADATA = '__customRouteArgs__';
|
||||
/**
|
||||
* @deprecated Use `CUSTOM_ROUTE_ARGS_METADATA` instead
|
||||
*/
|
||||
export const CUSTOM_ROUTE_AGRS_METADATA = CUSTOM_ROUTE_ARGS_METADATA;
|
||||
export const FILTER_CATCH_EXCEPTIONS = '__filterCatchExceptions__';
|
||||
|
||||
export const PIPES_METADATA = '__pipes__';
|
||||
|
||||
@@ -33,7 +33,9 @@ import { isUndefined } from '../../utils/shared.utils';
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function Inject<T = any>(token?: T) {
|
||||
export function Inject<T = any>(
|
||||
token?: T,
|
||||
): PropertyDecorator & ParameterDecorator {
|
||||
return (target: object, key: string | symbol | undefined, index?: number) => {
|
||||
const type = token || Reflect.getMetadata('design:type', target, key);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { isUndefined } from '../../utils/shared.utils';
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function Optional() {
|
||||
export function Optional(): PropertyDecorator & ParameterDecorator {
|
||||
return (target: object, key: string | symbol | undefined, index?: number) => {
|
||||
if (!isUndefined(index)) {
|
||||
const args = Reflect.getMetadata(OPTIONAL_DEPS_METADATA, target) || [];
|
||||
|
||||
@@ -109,3 +109,12 @@ export const Head = createMappingDecorator(RequestMethod.HEAD);
|
||||
* @publicApi
|
||||
*/
|
||||
export const All = createMappingDecorator(RequestMethod.ALL);
|
||||
|
||||
/**
|
||||
* Route handler (method) Decorator. Routes all HTTP requests to the specified path.
|
||||
*
|
||||
* @see [Routing](https://docs.nestjs.com/controllers#routing)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export const Search = createMappingDecorator(RequestMethod.SEARCH);
|
||||
|
||||
@@ -7,4 +7,5 @@ export enum RequestMethod {
|
||||
ALL,
|
||||
OPTIONS,
|
||||
HEAD,
|
||||
SEARCH,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Logger } from '../services';
|
||||
import {
|
||||
HttpExceptionBody,
|
||||
HttpExceptionBodyMessage,
|
||||
} from '../interfaces/http/http-exception-body.interface';
|
||||
import { isObject, isString } from '../utils/shared.utils';
|
||||
|
||||
export interface HttpExceptionOptions {
|
||||
@@ -26,8 +29,6 @@ export class HttpException extends Error {
|
||||
* @example
|
||||
* throw new HttpException()
|
||||
* throw new HttpException('message', HttpStatus.BAD_REQUEST)
|
||||
* throw new HttpException({ reason: 'this can be a human readable reason' }, HttpStatus.BAD_REQUEST)
|
||||
* throw new HttpException(new Error('Cause Error'), HttpStatus.BAD_REQUEST)
|
||||
* throw new HttpException('custom message', HttpStatus.BAD_REQUEST, {
|
||||
* cause: new Error('Cause Error'),
|
||||
* })
|
||||
@@ -81,13 +82,6 @@ export class HttpException extends Error {
|
||||
this.cause = this.options.cause;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.response instanceof Error) {
|
||||
Logger.warn(
|
||||
'DEPRECATED! Passing the error cause as the first argument to HttpException constructor is deprecated. You should use the "options" parameter instead: new HttpException("message", 400, { cause: new Error("Some Error") }) ',
|
||||
);
|
||||
this.cause = this.response;
|
||||
}
|
||||
}
|
||||
|
||||
public initMessage() {
|
||||
@@ -118,17 +112,42 @@ export class HttpException extends Error {
|
||||
}
|
||||
|
||||
public static createBody(
|
||||
objectOrErrorMessage: object | string,
|
||||
description?: string,
|
||||
nil: null | '',
|
||||
message: HttpExceptionBodyMessage,
|
||||
statusCode: number,
|
||||
): HttpExceptionBody;
|
||||
|
||||
public static createBody(
|
||||
message: HttpExceptionBodyMessage,
|
||||
error: string,
|
||||
statusCode: number,
|
||||
): HttpExceptionBody;
|
||||
|
||||
public static createBody<Body extends Record<string, unknown>>(
|
||||
custom: Body,
|
||||
): Body;
|
||||
|
||||
public static createBody<Body extends Record<string, unknown>>(
|
||||
arg0: null | HttpExceptionBodyMessage | Body,
|
||||
arg1?: HttpExceptionBodyMessage | string,
|
||||
statusCode?: number,
|
||||
) {
|
||||
if (!objectOrErrorMessage) {
|
||||
return { statusCode, message: description };
|
||||
): HttpExceptionBody | Body {
|
||||
if (!arg0) {
|
||||
return {
|
||||
message: arg1,
|
||||
statusCode: statusCode,
|
||||
};
|
||||
}
|
||||
return isObject(objectOrErrorMessage) &&
|
||||
!Array.isArray(objectOrErrorMessage)
|
||||
? objectOrErrorMessage
|
||||
: { statusCode, message: objectOrErrorMessage, error: description };
|
||||
|
||||
if (isString(arg0) || Array.isArray(arg0)) {
|
||||
return {
|
||||
message: arg0,
|
||||
error: arg1 as string,
|
||||
statusCode: statusCode,
|
||||
};
|
||||
}
|
||||
|
||||
return arg0;
|
||||
}
|
||||
|
||||
public static getDescriptionFrom(
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
import 'reflect-metadata';
|
||||
|
||||
export * from './cache';
|
||||
export * from './decorators';
|
||||
export * from './enums';
|
||||
export * from './exceptions';
|
||||
@@ -27,6 +26,8 @@ export {
|
||||
FactoryProvider,
|
||||
ForwardReference,
|
||||
HttpServer,
|
||||
HttpExceptionBody,
|
||||
HttpExceptionBodyMessage,
|
||||
INestApplication,
|
||||
INestApplicationContext,
|
||||
INestMicroservice,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export type HttpExceptionBodyMessage = string | string[];
|
||||
|
||||
export interface HttpExceptionBody {
|
||||
message: HttpExceptionBodyMessage;
|
||||
error?: string;
|
||||
statusCode: number;
|
||||
}
|
||||
@@ -18,7 +18,11 @@ export type RequestHandler<TRequest = any, TResponse = any> = (
|
||||
next?: Function,
|
||||
) => any;
|
||||
|
||||
export interface HttpServer<TRequest = any, TResponse = any> {
|
||||
export interface HttpServer<
|
||||
TRequest = any,
|
||||
TResponse = any,
|
||||
ServerInstance = any,
|
||||
> {
|
||||
use(
|
||||
handler:
|
||||
| RequestHandler<TRequest, TResponse>
|
||||
@@ -69,7 +73,7 @@ export interface HttpServer<TRequest = any, TResponse = any> {
|
||||
getRequestHostname?(request: TRequest): string;
|
||||
getRequestMethod?(request: TRequest): string;
|
||||
getRequestUrl?(request: TRequest): string;
|
||||
getInstance(): any;
|
||||
getInstance(): ServerInstance;
|
||||
registerParserMiddleware(...args: any[]): any;
|
||||
enableCors(options: CorsOptions | CorsOptionsDelegate<TRequest>): any;
|
||||
getHttpServer(): any;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './http-exception-body.interface';
|
||||
export * from './http-server.interface';
|
||||
export * from './message-event.interface';
|
||||
export * from './raw-body-request.interface';
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Type } from '../type.interface';
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
export type InjectionToken =
|
||||
export type InjectionToken<T = any> =
|
||||
| string
|
||||
| symbol
|
||||
| Type<any>
|
||||
| Abstract<any>
|
||||
| Type<T>
|
||||
| Abstract<T>
|
||||
| Function;
|
||||
|
||||
@@ -21,7 +21,8 @@ import { WebSocketAdapter } from './websockets/web-socket-adapter.interface';
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface INestApplication extends INestApplicationContext {
|
||||
export interface INestApplication<TServer = any>
|
||||
extends INestApplicationContext {
|
||||
/**
|
||||
* A wrapper function around HTTP adapter method: `adapter.use()`.
|
||||
* Example `app.use(cors())`
|
||||
@@ -110,9 +111,9 @@ export interface INestApplication extends INestApplicationContext {
|
||||
/**
|
||||
* Returns the underlying native HTTP server.
|
||||
*
|
||||
* @returns {*}
|
||||
* @returns {TServer}
|
||||
*/
|
||||
getHttpServer(): any;
|
||||
getHttpServer(): TServer;
|
||||
|
||||
/**
|
||||
* Returns the underlying HTTP adapter.
|
||||
|
||||
@@ -23,16 +23,12 @@
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cache-manager": "<=5",
|
||||
"class-transformer": "*",
|
||||
"class-validator": "*",
|
||||
"reflect-metadata": "^0.1.12",
|
||||
"rxjs": "^7.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"cache-manager": {
|
||||
"optional": true
|
||||
},
|
||||
"class-validator": {
|
||||
"optional": true
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FileValidator } from './file-validator.interface';
|
||||
import { IFile } from './interfaces';
|
||||
|
||||
export type FileTypeValidatorOptions = {
|
||||
fileType: string | RegExp;
|
||||
@@ -20,17 +21,14 @@ export class FileTypeValidator extends FileValidator<FileTypeValidatorOptions> {
|
||||
return `Validation failed (expected type is ${this.validationOptions.fileType})`;
|
||||
}
|
||||
|
||||
isValid(file: any): boolean {
|
||||
isValid<TFile extends IFile = any>(file: TFile): boolean {
|
||||
if (!this.validationOptions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!file.mimetype) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(
|
||||
(file.mimetype as string).match(this.validationOptions.fileType),
|
||||
return (
|
||||
'mimetype' in file &&
|
||||
!!file.mimetype.match(this.validationOptions.fileType)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { IFile } from './interfaces';
|
||||
|
||||
/**
|
||||
* Interface describing FileValidators, which can be added to a ParseFilePipe
|
||||
*
|
||||
@@ -11,7 +13,9 @@ export abstract class FileValidator<TValidationOptions = Record<string, any>> {
|
||||
* Indicates if this file should be considered valid, according to the options passed in the constructor.
|
||||
* @param file the file from the request object
|
||||
*/
|
||||
abstract isValid(file?: any): boolean | Promise<boolean>;
|
||||
abstract isValid<TFile extends IFile = any>(
|
||||
file?: TFile,
|
||||
): boolean | Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Builds an error message in case the validation fails.
|
||||
|
||||
4
packages/common/pipes/file/interfaces/file.interface.ts
Normal file
4
packages/common/pipes/file/interfaces/file.interface.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IFile {
|
||||
mimetype: string;
|
||||
size: number;
|
||||
}
|
||||
1
packages/common/pipes/file/interfaces/index.ts
Normal file
1
packages/common/pipes/file/interfaces/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './file.interface';
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FileValidator } from './file-validator.interface';
|
||||
import { IFile } from './interfaces';
|
||||
|
||||
export type MaxFileSizeValidatorOptions = {
|
||||
maxSize: number;
|
||||
@@ -25,11 +26,11 @@ export class MaxFileSizeValidator extends FileValidator<MaxFileSizeValidatorOpti
|
||||
return `Validation failed (expected size is less than ${this.validationOptions.maxSize})`;
|
||||
}
|
||||
|
||||
public isValid(file: any): boolean {
|
||||
public isValid<TFile extends IFile = any>(file: TFile): boolean {
|
||||
if (!this.validationOptions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return file.size < this.validationOptions.maxSize;
|
||||
return 'size' in file && file.size < this.validationOptions.maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
HttpErrorByCode,
|
||||
} from '../utils/http-error-by-code.util';
|
||||
import { loadPackage } from '../utils/load-package.util';
|
||||
import { isNil } from '../utils/shared.utils';
|
||||
import { isNil, isUndefined } from '../utils/shared.utils';
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
@@ -193,6 +193,13 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
return value;
|
||||
}
|
||||
if (metatype === Boolean) {
|
||||
if (isUndefined(value)) {
|
||||
// This is an workaround to deal with optional boolean values since
|
||||
// optional booleans shouldn't be parsed to a valid boolean when
|
||||
// they were not defined
|
||||
return undefined;
|
||||
}
|
||||
// Any fasly value but `undefined` will be parsed to `false`
|
||||
return value === true || value === 'true';
|
||||
}
|
||||
if (metatype === Number) {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { expect } from 'chai';
|
||||
import { CacheKey, CacheInterceptor, CacheTTL } from '../../cache';
|
||||
import {
|
||||
CACHE_KEY_METADATA,
|
||||
CACHE_TTL_METADATA,
|
||||
} from '../../cache/cache.constants';
|
||||
import { UseInterceptors } from '../../decorators';
|
||||
import { Controller } from '../../decorators/core/controller.decorator';
|
||||
|
||||
describe('@Cache', () => {
|
||||
@Controller('test')
|
||||
@CacheKey('/a_different_cache_key')
|
||||
@CacheTTL(99999)
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
class TestAll {}
|
||||
|
||||
@Controller()
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheKey('/a_different_cache_key')
|
||||
class TestKey {}
|
||||
|
||||
@Controller()
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheTTL(99999)
|
||||
class TestTTL {}
|
||||
|
||||
it('should override global defaults for CacheKey and CacheTTL', () => {
|
||||
expect(Reflect.getMetadata(CACHE_KEY_METADATA, TestAll)).to.be.eql(
|
||||
'/a_different_cache_key',
|
||||
);
|
||||
expect(Reflect.getMetadata(CACHE_TTL_METADATA, TestAll)).to.be.greaterThan(
|
||||
9999,
|
||||
);
|
||||
});
|
||||
|
||||
it('should override only the TTL', () => {
|
||||
expect(Reflect.getMetadata(CACHE_TTL_METADATA, TestTTL)).to.be.greaterThan(
|
||||
9999,
|
||||
);
|
||||
});
|
||||
|
||||
it('should override only the Key', () => {
|
||||
expect(Reflect.getMetadata(CACHE_KEY_METADATA, TestKey)).to.be.eql(
|
||||
'/a_different_cache_key',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai';
|
||||
import { Body, HostParam, Param, Query } from '../../decorators';
|
||||
import { Body, HostParam, Param, Query, Search } from '../../decorators';
|
||||
import { RequestMethod } from '../../enums/request-method.enum';
|
||||
import { All, Delete, Get, Patch, Post, Put } from '../../index';
|
||||
|
||||
@@ -311,6 +311,63 @@ describe('@Patch', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('@Search', () => {
|
||||
const requestPath = 'test';
|
||||
const requestProps = {
|
||||
path: requestPath,
|
||||
method: RequestMethod.SEARCH,
|
||||
};
|
||||
|
||||
const requestPathUsingArray = ['foo', 'bar'];
|
||||
const requestPropsUsingArray = {
|
||||
path: requestPathUsingArray,
|
||||
method: RequestMethod.SEARCH,
|
||||
};
|
||||
|
||||
it('should enhance class with expected request metadata', () => {
|
||||
class Test {
|
||||
@Search(requestPath)
|
||||
public static test() {}
|
||||
|
||||
@Search(requestPathUsingArray)
|
||||
public static testUsingArray() {}
|
||||
}
|
||||
|
||||
const path = Reflect.getMetadata('path', Test.test);
|
||||
const method = Reflect.getMetadata('method', Test.test);
|
||||
const pathUsingArray = Reflect.getMetadata('path', Test.testUsingArray);
|
||||
const methodUsingArray = Reflect.getMetadata('method', Test.testUsingArray);
|
||||
|
||||
expect(path).to.be.eql(requestPath);
|
||||
expect(method).to.be.eql(requestProps.method);
|
||||
expect(pathUsingArray).to.be.eql(requestPathUsingArray);
|
||||
expect(methodUsingArray).to.be.eql(requestPropsUsingArray.method);
|
||||
});
|
||||
|
||||
it('should set path on "/" by default', () => {
|
||||
class Test {
|
||||
@Search()
|
||||
public static test(
|
||||
@Query() query,
|
||||
@Param() params,
|
||||
@HostParam() hostParams,
|
||||
) {}
|
||||
|
||||
@Search([])
|
||||
public static testUsingArray(
|
||||
@Query() query,
|
||||
@Param() params,
|
||||
@HostParam() hostParams,
|
||||
) {}
|
||||
}
|
||||
|
||||
const path = Reflect.getMetadata('path', Test.test);
|
||||
const pathUsingArray = Reflect.getMetadata('path', Test.testUsingArray);
|
||||
expect(path).to.be.eql('/');
|
||||
expect(pathUsingArray).to.be.eql('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Inheritance', () => {
|
||||
const requestPath = 'test';
|
||||
const requestProps = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Type } from '../../../common';
|
||||
import { expect } from 'chai';
|
||||
import { Type } from '../../../common';
|
||||
import {
|
||||
BadGatewayException,
|
||||
BadRequestException,
|
||||
@@ -220,15 +220,6 @@ describe('HttpException', () => {
|
||||
const errorCause = new Error('An internal error cause');
|
||||
const customDescription = 'custom description';
|
||||
|
||||
it('configures a cause when message is an instance of error', () => {
|
||||
const message = new Error('Some Error');
|
||||
const error = new HttpException(message, 400);
|
||||
expect(`${error}`).to.be.eql(`HttpException: ${message.message}`);
|
||||
const { cause } = error;
|
||||
|
||||
expect(cause).to.be.eql(message);
|
||||
});
|
||||
|
||||
it('configures a cause when message is a string and the options object is passed', () => {
|
||||
const error = new HttpException(customDescription, 400, {
|
||||
cause: errorCause,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FileTypeValidator } from '../../../pipes';
|
||||
import { expect } from 'chai';
|
||||
import { FileTypeValidator } from '../../../pipes';
|
||||
|
||||
describe('FileTypeValidator', () => {
|
||||
describe('isValid', () => {
|
||||
@@ -10,7 +10,7 @@ describe('FileTypeValidator', () => {
|
||||
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
};
|
||||
} as any;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
@@ -22,7 +22,7 @@ describe('FileTypeValidator', () => {
|
||||
|
||||
const requestFile = {
|
||||
mimetype: 'image/jpeg',
|
||||
};
|
||||
} as any;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
@@ -34,7 +34,7 @@ describe('FileTypeValidator', () => {
|
||||
|
||||
const requestFile = {
|
||||
mimetype: 'application/msword',
|
||||
};
|
||||
} as any;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
@@ -46,7 +46,7 @@ describe('FileTypeValidator', () => {
|
||||
|
||||
const requestFile = {
|
||||
mimetype: 'image/png',
|
||||
};
|
||||
} as any;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
@@ -58,7 +58,7 @@ describe('FileTypeValidator', () => {
|
||||
|
||||
const requestFile = {
|
||||
mimetype: 'image/png',
|
||||
};
|
||||
} as any;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
@@ -68,7 +68,7 @@ describe('FileTypeValidator', () => {
|
||||
fileType: 'image/jpeg',
|
||||
});
|
||||
|
||||
const requestFile = {};
|
||||
const requestFile = {} as any;
|
||||
|
||||
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('MaxFileSizeValidator', () => {
|
||||
|
||||
const requestFile = {
|
||||
size: 100,
|
||||
};
|
||||
} as any;
|
||||
|
||||
expect(maxFileSizeValidator.isValid(requestFile)).to.equal(true);
|
||||
});
|
||||
@@ -24,7 +24,7 @@ describe('MaxFileSizeValidator', () => {
|
||||
|
||||
const requestFile = {
|
||||
size: oneKb + 1,
|
||||
};
|
||||
} as any;
|
||||
|
||||
expect(maxFileSizeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
@@ -36,7 +36,7 @@ describe('MaxFileSizeValidator', () => {
|
||||
|
||||
const requestFile = {
|
||||
size: oneKb,
|
||||
};
|
||||
} as any;
|
||||
|
||||
expect(maxFileSizeValidator.isValid(requestFile)).to.equal(false);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
FileTypeValidator,
|
||||
FileTypeValidatorOptions,
|
||||
FileValidator,
|
||||
MaxFileSizeValidator,
|
||||
ParseFilePipeBuilder,
|
||||
|
||||
@@ -221,7 +221,7 @@ describe('ValidationPipe', () => {
|
||||
});
|
||||
});
|
||||
describe('when input is a query parameter (boolean)', () => {
|
||||
it('should parse to boolean', async () => {
|
||||
it('should parse the string "true" to the boolean true', async () => {
|
||||
target = new ValidationPipe({ transform: true });
|
||||
const value = 'true';
|
||||
|
||||
@@ -233,9 +233,45 @@ describe('ValidationPipe', () => {
|
||||
}),
|
||||
).to.be.true;
|
||||
});
|
||||
it('should parse the string "false" to the boolean false', async () => {
|
||||
target = new ValidationPipe({ transform: true });
|
||||
const value = 'false';
|
||||
|
||||
expect(
|
||||
await target.transform(value, {
|
||||
metatype: Boolean,
|
||||
data: 'test',
|
||||
type: 'query',
|
||||
}),
|
||||
).to.be.false;
|
||||
});
|
||||
it('should parse an empty string to false', async () => {
|
||||
target = new ValidationPipe({ transform: true });
|
||||
const value = '';
|
||||
|
||||
expect(
|
||||
await target.transform(value, {
|
||||
metatype: Boolean,
|
||||
data: 'test',
|
||||
type: 'query',
|
||||
}),
|
||||
).to.be.false;
|
||||
});
|
||||
it('should parse undefined to undefined', async () => {
|
||||
target = new ValidationPipe({ transform: true });
|
||||
const value = undefined;
|
||||
|
||||
expect(
|
||||
await target.transform(value, {
|
||||
metatype: Boolean,
|
||||
data: 'test',
|
||||
type: 'query',
|
||||
}),
|
||||
).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('when input is a path parameter (boolean)', () => {
|
||||
it('should parse to boolean', async () => {
|
||||
it('should parse the string "true" to boolean true', async () => {
|
||||
target = new ValidationPipe({ transform: true });
|
||||
const value = 'true';
|
||||
|
||||
@@ -247,6 +283,42 @@ describe('ValidationPipe', () => {
|
||||
}),
|
||||
).to.be.true;
|
||||
});
|
||||
it('should parse the string "false" to boolean false', async () => {
|
||||
target = new ValidationPipe({ transform: true });
|
||||
const value = 'false';
|
||||
|
||||
expect(
|
||||
await target.transform(value, {
|
||||
metatype: Boolean,
|
||||
data: 'test',
|
||||
type: 'param',
|
||||
}),
|
||||
).to.be.false;
|
||||
});
|
||||
it('should parse an empty string to false', async () => {
|
||||
target = new ValidationPipe({ transform: true });
|
||||
const value = '';
|
||||
|
||||
expect(
|
||||
await target.transform(value, {
|
||||
metatype: Boolean,
|
||||
data: 'test',
|
||||
type: 'param',
|
||||
}),
|
||||
).to.be.false;
|
||||
});
|
||||
it('should parse undefined to undefined', async () => {
|
||||
target = new ValidationPipe({ transform: true });
|
||||
const value = undefined;
|
||||
|
||||
expect(
|
||||
await target.transform(value, {
|
||||
metatype: Boolean,
|
||||
data: 'test',
|
||||
type: 'param',
|
||||
}),
|
||||
).to.be.undefined;
|
||||
});
|
||||
});
|
||||
describe('when validation strips', () => {
|
||||
it('should return a TestModel without extra properties', async () => {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { expect } from 'chai';
|
||||
import { selectExceptionFilterMetadata } from '../../utils/select-exception-filter-metadata.util';
|
||||
|
||||
class FirstError {}
|
||||
|
||||
class SecondError {}
|
||||
|
||||
class ThirdError {}
|
||||
|
||||
class FourthError {}
|
||||
|
||||
describe('selectExceptionFilterMetadata', () => {
|
||||
it('should pass error handling to first suitable error handler', () => {
|
||||
const metadataList = [
|
||||
{
|
||||
exceptionMetatypes: [FirstError, SecondError],
|
||||
func: () => {},
|
||||
},
|
||||
{
|
||||
exceptionMetatypes: [ThirdError, FourthError],
|
||||
func: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
expect(
|
||||
selectExceptionFilterMetadata(metadataList, new FourthError()),
|
||||
).to.be.equal(metadataList[1]);
|
||||
});
|
||||
|
||||
describe('when multiple exception handlers are accepting error handling', () => {
|
||||
it('should pass exception handling to the first one', () => {
|
||||
const metadataList = [
|
||||
{
|
||||
exceptionMetatypes: [FirstError, SecondError],
|
||||
func: () => {},
|
||||
},
|
||||
{
|
||||
exceptionMetatypes: [FirstError, FourthError],
|
||||
func: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
expect(
|
||||
selectExceptionFilterMetadata(metadataList, new FirstError()),
|
||||
).to.be.equal(metadataList[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when no exception handler is accepting error handling', () => {
|
||||
it('should return undefined', () => {
|
||||
const metadataList = [
|
||||
{
|
||||
exceptionMetatypes: [FirstError, SecondError],
|
||||
func: () => {},
|
||||
},
|
||||
{
|
||||
exceptionMetatypes: [FirstError, FourthError],
|
||||
func: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
expect(selectExceptionFilterMetadata(metadataList, new ThirdError())).to
|
||||
.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when exception handler has empty list of meta types', () => {
|
||||
it('should pass any remaining error handling to it', () => {
|
||||
const metadataList = [
|
||||
{
|
||||
exceptionMetatypes: [FirstError, SecondError],
|
||||
func: () => {},
|
||||
},
|
||||
{
|
||||
exceptionMetatypes: [],
|
||||
func: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
expect(
|
||||
selectExceptionFilterMetadata(metadataList, new ThirdError()),
|
||||
).to.be.equal(metadataList[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ExceptionFilterMetadata } from '../interfaces/exceptions';
|
||||
|
||||
export const selectExceptionFilterMetadata = <T = any>(
|
||||
filters: ExceptionFilterMetadata[],
|
||||
exception: T,
|
||||
): ExceptionFilterMetadata | undefined =>
|
||||
filters.find(
|
||||
({ exceptionMetatypes }) =>
|
||||
!exceptionMetatypes.length ||
|
||||
exceptionMetatypes.some(
|
||||
ExceptionMetaType => exception instanceof ExceptionMetaType,
|
||||
),
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user