diff --git a/.gitignore b/.gitignore index 6ec64dbdd..5f1aec2b3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ yarn-error.log # tests /test -/integration /coverage /.nyc_output build/config\.gypi \ No newline at end of file diff --git a/integration/docker-compose.yml b/integration/docker-compose.yml new file mode 100644 index 000000000..d836dce2b --- /dev/null +++ b/integration/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3" + +services: + redis: + container_name: redis_integration + image: redis + ports: + - "6379:6379" + restart: always + nats: + container_name: nats_integration + image: nats + expose: + - "4222" + ports: + - "8222:8222" + - "4222:4222" + restart: always \ No newline at end of file diff --git a/integration/hello-world/.gitignore b/integration/hello-world/.gitignore new file mode 100644 index 000000000..b5e5f9755 --- /dev/null +++ b/integration/hello-world/.gitignore @@ -0,0 +1,21 @@ +# dependencies +/node_modules + +# IDE +/.idea +/.awcache +/.vscode + +# misc +npm-debug.log + +# example +/quick-start + +# tests +/test +/coverage +/.nyc_output + +# dist +/dist \ No newline at end of file diff --git a/integration/hello-world/e2e/fastify-adapter.spec.ts b/integration/hello-world/e2e/fastify-adapter.spec.ts new file mode 100644 index 000000000..35b6dd568 --- /dev/null +++ b/integration/hello-world/e2e/fastify-adapter.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai'; +import * as fastify from 'fastify'; +import * as request from 'supertest'; +import * as express from 'express'; +import { Test } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { ApplicationModule } from './../src/app.module'; +import { FastifyAdapter } from '@nestjs/core/adapters/fastify-adapter'; +import { ExpressAdapter } from '@nestjs/core/adapters/express-adapter'; +import { HelloService } from '../src/hello/hello.service'; + +describe('Hello world (fastify adapter)', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); + + server = fastify(); + app = module.createNestApplication(new FastifyAdapter(server)); + await app.init(); + }); + + it(`/GET`, () => { + return server + .inject({ + method: 'GET', + url: '/hello', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + it(`/GET (Promise/async)`, () => { + return server + .inject({ + method: 'GET', + url: '/hello/async', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + it(`/GET (Observable stream)`, () => { + return server + .inject({ + method: 'GET', + url: '/hello/stream', + }) + .then(({ payload }) => expect(payload).to.be.eql('Hello world!')); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/hello-world/e2e/guards.spec.ts b/integration/hello-world/e2e/guards.spec.ts new file mode 100644 index 000000000..d76bd57a4 --- /dev/null +++ b/integration/hello-world/e2e/guards.spec.ts @@ -0,0 +1,60 @@ +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication, Injectable, UnauthorizedException } from '@nestjs/common'; +import { ApplicationModule } from './../src/app.module'; +import { APP_GUARD } from '@nestjs/core'; + +@Injectable() +export class Guard { + canActivate() { + return false; + } +} + +@Injectable() +export class AuthGuard { + canActivate() { + const x = true; + if (x) { + throw new UnauthorizedException(); + } + } +} + +function createTestModule(guard) { + return Test.createTestingModule({ + imports: [ApplicationModule], + providers: [ + { + provide: APP_GUARD, + useValue: guard, + }, + ], + }).compile(); +} + +describe('Guards', () => { + let app: INestApplication; + + it(`should prevent access (forbidden)`, async () => { + app = (await createTestModule( + new Guard(), + )).createNestApplication(); + + await app.init(); + return request(app.getHttpServer()) + .get('/hello') + .expect(403); + }); + + it(`should prevent access (unauthorized)`, async () => { + app = (await createTestModule( + new AuthGuard(), + )).createNestApplication(); + + await app.init(); + return request(app.getHttpServer()) + .get('/hello') + .expect(401); + }); +}); diff --git a/integration/hello-world/e2e/hello-world.spec.ts b/integration/hello-world/e2e/hello-world.spec.ts new file mode 100644 index 000000000..a5491f321 --- /dev/null +++ b/integration/hello-world/e2e/hello-world.spec.ts @@ -0,0 +1,45 @@ +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { ApplicationModule } from './../src/app.module'; + +describe('Hello world (default adapter)', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ApplicationModule], + }) + .compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + }); + + it(`/GET`, () => { + return request(server) + .get('/hello') + .expect(200) + .expect('Hello world!'); + }); + + it(`/GET (Promise/async)`, () => { + return request(server) + .get('/hello/async') + .expect(200) + .expect('Hello world!'); + }); + + it(`/GET (Observable stream)`, () => { + return request(server) + .get('/hello/stream') + .expect(200) + .expect('Hello world!'); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/hello-world/e2e/interceptors.spec.ts b/integration/hello-world/e2e/interceptors.spec.ts new file mode 100644 index 000000000..11bd15f9c --- /dev/null +++ b/integration/hello-world/e2e/interceptors.spec.ts @@ -0,0 +1,87 @@ +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication, Injectable } from '@nestjs/common'; +import { ApplicationModule } from './../src/app.module'; +import { APP_INTERCEPTOR } from '@nestjs/core'; +import { of } from 'rxjs/observable/of'; +import { map } from 'rxjs/operators'; + +const RETURN_VALUE = 'test'; + +@Injectable() +export class OverrideInterceptor { + intercept(data, context, stream) { + return of(RETURN_VALUE); + } +} + +@Injectable() +export class TransformInterceptor { + intercept(data, context, stream) { + return stream.pipe(map(data => ({ data }))); + } +} + +function createTestModule(interceptor) { + return Test.createTestingModule({ + imports: [ApplicationModule], + providers: [ + { + provide: APP_INTERCEPTOR, + useValue: interceptor, + }, + ], + }).compile(); +} + +describe('Interceptors', () => { + let app: INestApplication; + + it(`should transform response (sync)`, async () => { + app = (await createTestModule( + new OverrideInterceptor(), + )).createNestApplication(); + + await app.init(); + return request(app.getHttpServer()) + .get('/hello') + .expect(200, RETURN_VALUE); + }); + + it(`should map response`, async () => { + app = (await createTestModule( + new TransformInterceptor(), + )).createNestApplication(); + + await app.init(); + return request(app.getHttpServer()) + .get('/hello') + .expect(200, { data: 'Hello world!' }); + }); + + it(`should map response (async)`, async () => { + app = (await createTestModule( + new TransformInterceptor(), + )).createNestApplication(); + + await app.init(); + return request(app.getHttpServer()) + .get('/hello/stream') + .expect(200, { data: 'Hello world!' }); + }); + + it(`should map response (stream)`, async () => { + app = (await createTestModule( + new TransformInterceptor(), + )).createNestApplication(); + + await app.init(); + return request(app.getHttpServer()) + .get('/hello/async') + .expect(200, { data: 'Hello world!' }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/hello-world/package.json b/integration/hello-world/package.json new file mode 100644 index 000000000..00b868cbd --- /dev/null +++ b/integration/hello-world/package.json @@ -0,0 +1,28 @@ +{ + "name": "nest-typescript-starter", + "version": "1.0.0", + "description": "Nest TypeScript starter repository", + "license": "MIT", + "scripts": { + "start": "ts-node src/main" + }, + "dependencies": { + "@nestjs/common": "^4.3.0", + "@nestjs/core": "^4.3.0", + "@nestjs/microservices": "^4.3.0", + "@nestjs/testing": "^4.3.0", + "@nestjs/websockets": "^4.3.0", + "class-transformer": "^0.1.7", + "class-validator": "^0.7.2", + "fastify": "^1.1.1", + "redis": "^2.7.1", + "reflect-metadata": "^0.1.10", + "rxjs": "^5.4.3", + "typescript": "^2.4.2" + }, + "devDependencies": { + "@types/node": "^7.0.41", + "supertest": "^3.0.0", + "ts-node": "^3.3.0" + } +} diff --git a/integration/hello-world/src/app.module.ts b/integration/hello-world/src/app.module.ts new file mode 100644 index 000000000..0fd50e87c --- /dev/null +++ b/integration/hello-world/src/app.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { HelloModule } from './hello/hello.module'; + +@Module({ + imports: [HelloModule], +}) +export class ApplicationModule {} diff --git a/integration/hello-world/src/hello/dto/test.dto.ts b/integration/hello-world/src/hello/dto/test.dto.ts new file mode 100644 index 000000000..b3a30f671 --- /dev/null +++ b/integration/hello-world/src/hello/dto/test.dto.ts @@ -0,0 +1,10 @@ +import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; + +export class TestDto { + @IsString() + @IsNotEmpty() + string: string; + + @IsNumber() + number: number; +} diff --git a/integration/hello-world/src/hello/hello.controller.ts b/integration/hello-world/src/hello/hello.controller.ts new file mode 100644 index 000000000..64b6d6fca --- /dev/null +++ b/integration/hello-world/src/hello/hello.controller.ts @@ -0,0 +1,24 @@ +import { HelloService } from './hello.service'; +import { Controller, Get, Post, Body } from '@nestjs/common'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; + +@Controller('hello') +export class HelloController { + constructor(private readonly helloService: HelloService) {} + + @Get() + greeting(): string { + return this.helloService.greeting(); + } + + @Get('async') + async asyncGreeting(): Promise { + return await this.helloService.greeting(); + } + + @Get('stream') + streamGreeting(): Observable { + return of(this.helloService.greeting()); + } +} diff --git a/integration/hello-world/src/hello/hello.module.ts b/integration/hello-world/src/hello/hello.module.ts new file mode 100644 index 000000000..329c64eec --- /dev/null +++ b/integration/hello-world/src/hello/hello.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { HelloController } from './hello.controller'; +import { HelloService } from './hello.service'; + +@Module({ + controllers: [HelloController], + providers: [HelloService], +}) +export class HelloModule {} diff --git a/integration/hello-world/src/hello/hello.service.ts b/integration/hello-world/src/hello/hello.service.ts new file mode 100644 index 000000000..800117788 --- /dev/null +++ b/integration/hello-world/src/hello/hello.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class HelloService { + greeting(): string { + return 'Hello world!'; + } +} diff --git a/integration/hello-world/tsconfig.json b/integration/hello-world/tsconfig.json new file mode 100644 index 000000000..c6354c564 --- /dev/null +++ b/integration/hello-world/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": true, + "allowJs": true, + "outDir": "./dist" + }, + "include": [ + "src/**/*", + "e2e/**/*" + ], + "exclude": [ + "node_modules", + ] +} \ No newline at end of file diff --git a/integration/hello-world/tslint.json b/integration/hello-world/tslint.json new file mode 100644 index 000000000..fbbb57c94 --- /dev/null +++ b/integration/hello-world/tslint.json @@ -0,0 +1,53 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "eofline": false, + "quotemark": [ + true, + "single" + ], + "ordered-imports": [ + false + ], + "max-line-length": [ + 150 + ], + "member-ordering": [ + false + ], + "curly": false, + "interface-name": [ + false + ], + "array-type": [ + false + ], + "member-access": [ + false + ], + "no-empty-interface": false, + "no-empty": false, + "arrow-parens": false, + "object-literal-sort-keys": false, + "no-unused-expression": false, + "max-classes-per-file": [ + false + ], + "variable-name": [ + false + ], + "one-line": [ + false + ], + "one-variable-per-declaration": [ + false + ] + }, + "rulesDirectory": [] +} \ No newline at end of file diff --git a/integration/injector/.gitignore b/integration/injector/.gitignore new file mode 100644 index 000000000..b5e5f9755 --- /dev/null +++ b/integration/injector/.gitignore @@ -0,0 +1,21 @@ +# dependencies +/node_modules + +# IDE +/.idea +/.awcache +/.vscode + +# misc +npm-debug.log + +# example +/quick-start + +# tests +/test +/coverage +/.nyc_output + +# dist +/dist \ No newline at end of file diff --git a/integration/injector/e2e/injector.spec.ts b/integration/injector/e2e/injector.spec.ts new file mode 100644 index 000000000..d7196106a --- /dev/null +++ b/integration/injector/e2e/injector.spec.ts @@ -0,0 +1,37 @@ +import { expect } from 'chai'; +import { Test, TestingModuleBuilder } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { ExportsModule } from '../src/exports/exports.module'; +import { TestingModule } from '@nestjs/testing/testing-module'; +import { UnknownExportException } from '@nestjs/core/errors/exceptions/unknown-export.exception'; +import { UndefinedDependencyException } from '@nestjs/core/errors/exceptions/undefined-dependency.exception'; +import { InjectModule } from '../src/inject/inject.module'; +import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.exception'; + +describe('Injector', () => { + describe('when "providers" and "exports" properties are inconsistent', () => { + it(`should fail with "UnknownExportException"`, async () => { + try { + const builder = Test.createTestingModule({ + imports: [ExportsModule], + }); + await builder.compile(); + } catch (err) { + expect(err).to.be.instanceof(UnknownExportException); + } + }); + }); + + describe('when Nest cannot resolve dependencies', () => { + it(`should fail with "RuntimeException"`, async () => { + try { + const builder = Test.createTestingModule({ + imports: [InjectModule], + }); + await builder.compile(); + } catch (err) { + expect(err).to.be.instanceof(RuntimeException); + } + }); + }); +}); diff --git a/integration/injector/package.json b/integration/injector/package.json new file mode 100644 index 000000000..2a3dc012e --- /dev/null +++ b/integration/injector/package.json @@ -0,0 +1,27 @@ +{ + "name": "nest-typescript-starter", + "version": "1.0.0", + "description": "Nest TypeScript starter repository", + "license": "MIT", + "scripts": { + "start": "ts-node src/main" + }, + "dependencies": { + "@nestjs/common": "^4.3.0", + "@nestjs/core": "^4.3.0", + "@nestjs/microservices": "^4.3.0", + "@nestjs/testing": "^4.3.0", + "@nestjs/websockets": "^4.3.0", + "class-transformer": "^0.1.7", + "class-validator": "^0.7.2", + "redis": "^2.7.1", + "reflect-metadata": "^0.1.10", + "rxjs": "^5.4.3", + "typescript": "^2.4.2" + }, + "devDependencies": { + "@types/node": "^7.0.41", + "supertest": "^3.0.0", + "ts-node": "^3.3.0" + } +} diff --git a/integration/injector/src/app.module.ts b/integration/injector/src/app.module.ts new file mode 100644 index 000000000..881a3758f --- /dev/null +++ b/integration/injector/src/app.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { ExportsModule } from './exports/exports.module'; + +@Module({ + imports: [ExportsModule], +}) +export class ApplicationModule {} diff --git a/integration/injector/src/exports/exports.module.ts b/integration/injector/src/exports/exports.module.ts new file mode 100644 index 000000000..00190030b --- /dev/null +++ b/integration/injector/src/exports/exports.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { ExportsService } from './exports.service'; + +@Module({ + exports: [ExportsService], +}) +export class ExportsModule {} diff --git a/integration/injector/src/exports/exports.service.ts b/integration/injector/src/exports/exports.service.ts new file mode 100644 index 000000000..8b9c0c8a3 --- /dev/null +++ b/integration/injector/src/exports/exports.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ExportsService {} diff --git a/integration/injector/src/inject/core.service.ts b/integration/injector/src/inject/core.service.ts new file mode 100644 index 000000000..acbb46cc2 --- /dev/null +++ b/integration/injector/src/inject/core.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class CoreService {} diff --git a/integration/injector/src/inject/inject.module.ts b/integration/injector/src/inject/inject.module.ts new file mode 100644 index 000000000..aee4f6c11 --- /dev/null +++ b/integration/injector/src/inject/inject.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { InjectService } from './inject.service'; + +@Module({ + providers: [InjectService], +}) +export class InjectModule {} diff --git a/integration/injector/src/inject/inject.service.ts b/integration/injector/src/inject/inject.service.ts new file mode 100644 index 000000000..36dc8b4ed --- /dev/null +++ b/integration/injector/src/inject/inject.service.ts @@ -0,0 +1,7 @@ +import { Injectable } from '@nestjs/common'; +import { CoreService } from './core.service'; + +@Injectable() +export class InjectService { + constructor(private readonly coreService: CoreService) {} +} diff --git a/integration/injector/tsconfig.json b/integration/injector/tsconfig.json new file mode 100644 index 000000000..c6354c564 --- /dev/null +++ b/integration/injector/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": true, + "allowJs": true, + "outDir": "./dist" + }, + "include": [ + "src/**/*", + "e2e/**/*" + ], + "exclude": [ + "node_modules", + ] +} \ No newline at end of file diff --git a/integration/injector/tslint.json b/integration/injector/tslint.json new file mode 100644 index 000000000..fbbb57c94 --- /dev/null +++ b/integration/injector/tslint.json @@ -0,0 +1,53 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "eofline": false, + "quotemark": [ + true, + "single" + ], + "ordered-imports": [ + false + ], + "max-line-length": [ + 150 + ], + "member-ordering": [ + false + ], + "curly": false, + "interface-name": [ + false + ], + "array-type": [ + false + ], + "member-access": [ + false + ], + "no-empty-interface": false, + "no-empty": false, + "arrow-parens": false, + "object-literal-sort-keys": false, + "no-unused-expression": false, + "max-classes-per-file": [ + false + ], + "variable-name": [ + false + ], + "one-line": [ + false + ], + "one-variable-per-declaration": [ + false + ] + }, + "rulesDirectory": [] +} \ No newline at end of file diff --git a/integration/microservices/.gitignore b/integration/microservices/.gitignore new file mode 100644 index 000000000..b5e5f9755 --- /dev/null +++ b/integration/microservices/.gitignore @@ -0,0 +1,21 @@ +# dependencies +/node_modules + +# IDE +/.idea +/.awcache +/.vscode + +# misc +npm-debug.log + +# example +/quick-start + +# tests +/test +/coverage +/.nyc_output + +# dist +/dist \ No newline at end of file diff --git a/integration/microservices/e2e/multicast-nats.spec.ts b/integration/microservices/e2e/multicast-nats.spec.ts new file mode 100644 index 000000000..a46f8c566 --- /dev/null +++ b/integration/microservices/e2e/multicast-nats.spec.ts @@ -0,0 +1,39 @@ +import * as express from 'express'; +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { Transport } from '@nestjs/microservices'; +import { NatsMulticastController } from '../src/nats/nats-multicast.controller'; + +describe('NATS transport', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + controllers: [NatsMulticastController], + }).compile(); + + server = express(); + app = module.createNestApplication(server); + app.connectMicroservice({ + transport: Transport.NATS, + url: 'nats://localhost:4222' + }); + app.connectMicroservice({ + transport: Transport.NATS, + }); + await app.startAllMicroservicesAsync(); + await app.init(); + }); + + it(`Multicast (2 subscribers)`, () => { + return request(server) + .get('/multicast') + .expect(200, '2'); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/microservices/e2e/multicast-redis.spec.ts b/integration/microservices/e2e/multicast-redis.spec.ts new file mode 100644 index 000000000..5dc371f0d --- /dev/null +++ b/integration/microservices/e2e/multicast-redis.spec.ts @@ -0,0 +1,39 @@ +import * as express from 'express'; +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { Transport } from '@nestjs/microservices'; +import { RedisController } from '../src/redis/redis.controller'; +import { RedisMulticastController } from '../src/redis/redis-multicast.controller'; + +describe('REDIS transport', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + controllers: [RedisMulticastController], + }).compile(); + + server = express(); + app = module.createNestApplication(server); + app.connectMicroservice({ + transport: Transport.REDIS, + }); + app.connectMicroservice({ + transport: Transport.REDIS, + }); + await app.startAllMicroservicesAsync(); + await app.init(); + }); + + it(`Multicast (2 subscribers)`, () => { + return request(server) + .get('/multicast') + .expect(200, '2'); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/microservices/e2e/sum-nats.spec.ts b/integration/microservices/e2e/sum-nats.spec.ts new file mode 100644 index 000000000..4e2844e3c --- /dev/null +++ b/integration/microservices/e2e/sum-nats.spec.ts @@ -0,0 +1,59 @@ +import * as express from 'express'; +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { Transport } from '@nestjs/microservices'; +import { NatsController } from '../src/nats/nats.controller'; + +describe('NATS transport', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + controllers: [NatsController], + }).compile(); + + server = express(); + app = module.createNestApplication(server); + app.connectMicroservice({ + transport: Transport.NATS, + url: 'nats://localhost:4222' + }); + await app.startAllMicroservicesAsync(); + await app.init(); + }); + + it(`/POST`, () => { + return request(server) + .post('/?command=sum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (Promise/async)`, () => { + return request(server) + .post('/?command=asyncSum') + .send([1, 2, 3, 4, 5]) + .expect(200) + .expect(200, '15'); + }); + + it(`/POST (Observable stream)`, () => { + return request(server) + .post('/?command=streamSum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (streaming)`, () => { + return request(server) + .post('/stream') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/microservices/e2e/sum-redis.spec.ts b/integration/microservices/e2e/sum-redis.spec.ts new file mode 100644 index 000000000..f0368ebbb --- /dev/null +++ b/integration/microservices/e2e/sum-redis.spec.ts @@ -0,0 +1,71 @@ +import * as express from 'express'; +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { Transport } from '@nestjs/microservices'; +import { RedisController } from '../src/redis/redis.controller'; + +describe('REDIS transport', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + controllers: [RedisController], + }).compile(); + + server = express(); + app = module.createNestApplication(server); + app.connectMicroservice({ + transport: Transport.REDIS, + }); + await app.startAllMicroservicesAsync(); + await app.init(); + }); + + it(`/POST`, () => { + return request(server) + .post('/?command=sum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (Promise/async)`, () => { + return request(server) + .post('/?command=asyncSum') + .send([1, 2, 3, 4, 5]) + .expect(200) + .expect(200, '15'); + }); + + it(`/POST (Observable stream)`, () => { + return request(server) + .post('/?command=streamSum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (concurrent)`, () => { + return request(server) + .post('/concurrent') + .send([ + [1, 2, 3, 4, 5], + [6, 7, 8, 9, 10], + [11, 12, 13, 14, 15], + [16, 17, 18, 19, 20], + [21, 22, 23, 24, 25], + ]) + .expect(200, 'true'); + }); + + it(`/POST (streaming)`, () => { + return request(server) + .post('/stream') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/microservices/e2e/sum-rpc.spec.ts b/integration/microservices/e2e/sum-rpc.spec.ts new file mode 100644 index 000000000..712988278 --- /dev/null +++ b/integration/microservices/e2e/sum-rpc.spec.ts @@ -0,0 +1,77 @@ +import * as express from 'express'; +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { ApplicationModule } from './../src/app.module'; +import { Transport } from '@nestjs/microservices'; + +describe('RPC transport', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); + + server = express(); + app = module.createNestApplication(server); + app.connectMicroservice({ + transport: Transport.TCP, + }); + await app.startAllMicroservicesAsync(); + await app.init(); + }); + + it(`/POST`, () => { + return request(server) + .post('/?command=sum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (Promise/async)`, () => { + return request(server) + .post('/?command=asyncSum') + .send([1, 2, 3, 4, 5]) + .expect(200) + .expect(200, '15'); + }); + + it(`/POST (Observable stream)`, () => { + return request(server) + .post('/?command=streamSum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (concurrent)`, () => { + return request(server) + .post('/concurrent') + .send([ + [1, 2, 3, 4, 5], + [6, 7, 8, 9, 10], + [11, 12, 13, 14, 15], + [16, 17, 18, 19, 20], + [21, 22, 23, 24, 25], + ]) + .expect(200, 'true'); + }); + + it(`/POST (streaming)`, () => { + return request(server) + .post('/stream') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (pattern not found)`, () => { + return request(server) + .post('/?command=test') + .expect(500); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/microservices/e2e/sum-stan.spec.ts b/integration/microservices/e2e/sum-stan.spec.ts new file mode 100644 index 000000000..8aee4b613 --- /dev/null +++ b/integration/microservices/e2e/sum-stan.spec.ts @@ -0,0 +1,59 @@ +/*import * as express from 'express'; +import * as request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { Transport } from '@nestjs/microservices'; +import { NatsController } from '../src/nats/nats.controller'; + +describe('STAN transport', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + controllers: [NatsController], + }).compile(); + + server = express(); + app = module.createNestApplication(server); + app.connectMicroservice({ + transport: Transport.STAN, + }); + await app.startAllMicroservicesAsync(); + await app.init(); + }); + + it(`/POST`, () => { + return request(server) + .post('/?command=sum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (Promise/async)`, () => { + return request(server) + .post('/?command=asyncSum') + .send([1, 2, 3, 4, 5]) + .expect(200) + .expect(200, '15'); + }); + + it(`/POST (Observable stream)`, () => { + return request(server) + .post('/?command=streamSum') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + it(`/POST (streaming)`, () => { + return request(server) + .post('/stream') + .send([1, 2, 3, 4, 5]) + .expect(200, '15'); + }); + + afterEach(async () => { + await app.close(); + }); +}); +*/ \ No newline at end of file diff --git a/integration/microservices/package.json b/integration/microservices/package.json new file mode 100644 index 000000000..2a3dc012e --- /dev/null +++ b/integration/microservices/package.json @@ -0,0 +1,27 @@ +{ + "name": "nest-typescript-starter", + "version": "1.0.0", + "description": "Nest TypeScript starter repository", + "license": "MIT", + "scripts": { + "start": "ts-node src/main" + }, + "dependencies": { + "@nestjs/common": "^4.3.0", + "@nestjs/core": "^4.3.0", + "@nestjs/microservices": "^4.3.0", + "@nestjs/testing": "^4.3.0", + "@nestjs/websockets": "^4.3.0", + "class-transformer": "^0.1.7", + "class-validator": "^0.7.2", + "redis": "^2.7.1", + "reflect-metadata": "^0.1.10", + "rxjs": "^5.4.3", + "typescript": "^2.4.2" + }, + "devDependencies": { + "@types/node": "^7.0.41", + "supertest": "^3.0.0", + "ts-node": "^3.3.0" + } +} diff --git a/integration/microservices/src/app.controller.ts b/integration/microservices/src/app.controller.ts new file mode 100644 index 000000000..cc97be5dc --- /dev/null +++ b/integration/microservices/src/app.controller.ts @@ -0,0 +1,68 @@ +import { Controller, Get, Post, Body, Query, HttpCode } from '@nestjs/common'; +import { + Client, + MessagePattern, + ClientProxy, + Transport, +} from '@nestjs/microservices'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import { from } from 'rxjs/observable/from'; +import { scan } from 'rxjs/operators'; + +@Controller() +export class AppController { + @Client({ transport: Transport.TCP }) + client: ClientProxy; + + @Post() + @HttpCode(200) + call(@Query('command') cmd, @Body() data: number[]): Observable { + return this.client.send({ cmd }, data); + } + + @Post('stream') + @HttpCode(200) + stream(@Body() data: number[]): Observable { + return this.client.send({ cmd: 'streaming' }, data) + .pipe( + scan((a, b) => a + b), + ); + } + + @Post('concurrent') + @HttpCode(200) + concurrent(@Body() data: number[][]): Promise { + const send = async (tab: number[]) => { + const expected = tab.reduce((a, b) => a + b); + const result = await this.client + .send({ cmd: 'sum' }, tab) + .toPromise(); + + return result === expected; + }; + return data + .map(async tab => await send(tab)) + .reduce(async (a, b) => (await a) && (await b)); + } + + @MessagePattern({ cmd: 'sum' }) + sum(data: number[]): number { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'asyncSum' }) + async asyncSum(data: number[]): Promise { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'streamSum' }) + streamSum(data: number[]): Observable { + return of((data || []).reduce((a, b) => a + b)); + } + + @MessagePattern({ cmd: 'streaming' }) + streaming(data: number[]): Observable { + return from(data); + } +} diff --git a/integration/microservices/src/app.module.ts b/integration/microservices/src/app.module.ts new file mode 100644 index 000000000..4bbb03b10 --- /dev/null +++ b/integration/microservices/src/app.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; + +@Module({ + controllers: [AppController], +}) +export class ApplicationModule {} diff --git a/integration/microservices/src/main.ts b/integration/microservices/src/main.ts new file mode 100644 index 000000000..afb93ba07 --- /dev/null +++ b/integration/microservices/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { ApplicationModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(ApplicationModule); + await app.listen(3000); +} +bootstrap(); diff --git a/integration/microservices/src/nats/nats-multicast.controller.ts b/integration/microservices/src/nats/nats-multicast.controller.ts new file mode 100644 index 000000000..8de08460e --- /dev/null +++ b/integration/microservices/src/nats/nats-multicast.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Get } from '@nestjs/common'; +import { + Client, + MessagePattern, + ClientProxy, + Transport, +} from '@nestjs/microservices'; +import { Observable } from 'rxjs/Observable'; +import { scan, take } from 'rxjs/operators'; + +@Controller() +export class NatsMulticastController { + @Client({ transport: Transport.NATS, url: 'nats://localhost:4222' }) + client: ClientProxy; + + @Get('multicast') + multicats() { + return this.client + .send({ cmd: 'multicast' }, {}) + .pipe(scan((a, b) => a + b), take(2)); + } + + @MessagePattern({ cmd: 'multicast' }) + replyMulticast(): Observable { + return new Observable(observer => observer.next(1)); + } +} diff --git a/integration/microservices/src/nats/nats.controller.ts b/integration/microservices/src/nats/nats.controller.ts new file mode 100644 index 000000000..fab3ab4cd --- /dev/null +++ b/integration/microservices/src/nats/nats.controller.ts @@ -0,0 +1,67 @@ +import { Controller, Get, Post, Body, Query, HttpCode } from '@nestjs/common'; +import { + Client, + MessagePattern, + ClientProxy, + Transport, +} from '@nestjs/microservices'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import { from } from 'rxjs/observable/from'; +import { scan } from 'rxjs/operators'; + +@Controller() +export class NatsController { + @Client({ transport: Transport.NATS, url: 'nats://localhost:4222' }) + client: ClientProxy; + + @Post() + @HttpCode(200) + call(@Query('command') cmd, @Body() data: number[]): Observable { + return this.client.send({ cmd }, data); + } + + @Post('stream') + @HttpCode(200) + stream(@Body() data: number[]): Observable { + return this.client + .send({ cmd: 'streaming' }, data) + .pipe(scan((a, b) => a + b)); + } + + @Post('concurrent') + @HttpCode(200) + concurrent(@Body() data: number[][]): Promise { + const send = async (tab: number[]) => { + const expected = tab.reduce((a, b) => a + b); + const result = await this.client + .send({ cmd: 'sum' }, tab) + .toPromise(); + + return result === expected; + }; + return data + .map(async tab => await send(tab)) + .reduce(async (a, b) => (await a) && (await b)); + } + + @MessagePattern({ cmd: 'sum' }) + sum(data: number[]): number { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'asyncSum' }) + async asyncSum(data: number[]): Promise { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'streamSum' }) + streamSum(data: number[]): Observable { + return of((data || []).reduce((a, b) => a + b)); + } + + @MessagePattern({ cmd: 'streaming' }) + streaming(data: number[]): Observable { + return from(data); + } +} diff --git a/integration/microservices/src/redis/redis-multicast.controller.ts b/integration/microservices/src/redis/redis-multicast.controller.ts new file mode 100644 index 000000000..8d9045d6d --- /dev/null +++ b/integration/microservices/src/redis/redis-multicast.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Get } from '@nestjs/common'; +import { + Client, + MessagePattern, + ClientProxy, + Transport, +} from '@nestjs/microservices'; +import { Observable } from 'rxjs/Observable'; +import { scan, take } from 'rxjs/operators'; + +@Controller() +export class RedisMulticastController { + @Client({ transport: Transport.REDIS }) + client: ClientProxy; + + @Get('multicast') + multicats() { + return this.client.send({ cmd: 'multicast' }, {}) + .pipe( + scan((a, b) => a + b), + take(2), + ); + } + + @MessagePattern({ cmd: 'multicast' }) + replyMulticast(): Observable { + return new Observable((observer) => observer.next(1)); + } +} diff --git a/integration/microservices/src/redis/redis.controller.ts b/integration/microservices/src/redis/redis.controller.ts new file mode 100644 index 000000000..135cef886 --- /dev/null +++ b/integration/microservices/src/redis/redis.controller.ts @@ -0,0 +1,68 @@ +import { Controller, Get, Post, Body, Query, HttpCode } from '@nestjs/common'; +import { + Client, + MessagePattern, + ClientProxy, + Transport, +} from '@nestjs/microservices'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import { from } from 'rxjs/observable/from'; +import { scan } from 'rxjs/operators'; + +@Controller() +export class RedisController { + @Client({ transport: Transport.REDIS }) + client: ClientProxy; + + @Post() + @HttpCode(200) + call(@Query('command') cmd, @Body() data: number[]): Observable { + return this.client.send({ cmd }, data); + } + + @Post('stream') + @HttpCode(200) + stream(@Body() data: number[]): Observable { + return this.client.send({ cmd: 'streaming' }, data) + .pipe( + scan((a, b) => a + b), + ); + } + + @Post('concurrent') + @HttpCode(200) + concurrent(@Body() data: number[][]): Promise { + const send = async (tab: number[]) => { + const expected = tab.reduce((a, b) => a + b); + const result = await this.client + .send({ cmd: 'sum' }, tab) + .toPromise(); + + return result === expected; + }; + return data + .map(async tab => await send(tab)) + .reduce(async (a, b) => (await a) && (await b)); + } + + @MessagePattern({ cmd: 'sum' }) + sum(data: number[]): number { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'asyncSum' }) + async asyncSum(data: number[]): Promise { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'streamSum' }) + streamSum(data: number[]): Observable { + return of((data || []).reduce((a, b) => a + b)); + } + + @MessagePattern({ cmd: 'streaming' }) + streaming(data: number[]): Observable { + return from(data); + } +} diff --git a/integration/microservices/src/stan/stan.controller.ts b/integration/microservices/src/stan/stan.controller.ts new file mode 100644 index 000000000..bb4c2c24c --- /dev/null +++ b/integration/microservices/src/stan/stan.controller.ts @@ -0,0 +1,68 @@ +import { Controller, Get, Post, Body, Query, HttpCode } from '@nestjs/common'; +import { + Client, + MessagePattern, + ClientProxy, + Transport, +} from '@nestjs/microservices'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import { from } from 'rxjs/observable/from'; +import { scan } from 'rxjs/operators'; + +@Controller() +export class NatsController { + @Client({ transport: Transport.STAN }) + client: ClientProxy; + + @Post() + @HttpCode(200) + call(@Query('command') cmd, @Body() data: number[]): Observable { + return this.client.send({ cmd }, data); + } + + @Post('stream') + @HttpCode(200) + stream(@Body() data: number[]): Observable { + return this.client.send({ cmd: 'streaming' }, data) + .pipe( + scan((a, b) => a + b), + ); + } + + @Post('concurrent') + @HttpCode(200) + concurrent(@Body() data: number[][]): Promise { + const send = async (tab: number[]) => { + const expected = tab.reduce((a, b) => a + b); + const result = await this.client + .send({ cmd: 'sum' }, tab) + .toPromise(); + + return result === expected; + }; + return data + .map(async tab => await send(tab)) + .reduce(async (a, b) => (await a) && (await b)); + } + + @MessagePattern({ cmd: 'sum' }) + sum(data: number[]): number { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'asyncSum' }) + async asyncSum(data: number[]): Promise { + return (data || []).reduce((a, b) => a + b); + } + + @MessagePattern({ cmd: 'streamSum' }) + streamSum(data: number[]): Observable { + return of((data || []).reduce((a, b) => a + b)); + } + + @MessagePattern({ cmd: 'streaming' }) + streaming(data: number[]): Observable { + return from(data); + } +} diff --git a/integration/microservices/tsconfig.json b/integration/microservices/tsconfig.json new file mode 100644 index 000000000..c6354c564 --- /dev/null +++ b/integration/microservices/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": true, + "allowJs": true, + "outDir": "./dist" + }, + "include": [ + "src/**/*", + "e2e/**/*" + ], + "exclude": [ + "node_modules", + ] +} \ No newline at end of file diff --git a/integration/microservices/tslint.json b/integration/microservices/tslint.json new file mode 100644 index 000000000..fbbb57c94 --- /dev/null +++ b/integration/microservices/tslint.json @@ -0,0 +1,53 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "eofline": false, + "quotemark": [ + true, + "single" + ], + "ordered-imports": [ + false + ], + "max-line-length": [ + 150 + ], + "member-ordering": [ + false + ], + "curly": false, + "interface-name": [ + false + ], + "array-type": [ + false + ], + "member-access": [ + false + ], + "no-empty-interface": false, + "no-empty": false, + "arrow-parens": false, + "object-literal-sort-keys": false, + "no-unused-expression": false, + "max-classes-per-file": [ + false + ], + "variable-name": [ + false + ], + "one-line": [ + false + ], + "one-variable-per-declaration": [ + false + ] + }, + "rulesDirectory": [] +} \ No newline at end of file