├── .gitignore
├── .prettierrc
├── README.md
├── nest-cli.json
├── nodemon-debug.json
├── nodemon.json
├── package-lock.json
├── package.json
├── src
├── api.service.ts
├── app.controller.ts
├── app.module.ts
├── main.ts
├── student.service.ts
└── test
│ ├── app.e2e-spec.ts
│ └── student.service.spec.ts
├── tsconfig.build.json
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # OS
14 | .DS_Store
15 |
16 | # Tests
17 | /coverage
18 | /.nyc_output
19 |
20 | # IDEs and editors
21 | /.idea
22 | .project
23 | .classpath
24 | .c9/
25 | *.launch
26 | .settings/
27 | *.sublime-workspace
28 |
29 | # IDE - VSCode
30 | .vscode/*
31 | !.vscode/settings.json
32 | !.vscode/tasks.json
33 | !.vscode/launch.json
34 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
6 | [travis-url]: https://travis-ci.org/nestjs/nest
7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
8 | [linux-url]: https://travis-ci.org/nestjs/nest
9 |
10 | A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 | ## Description
28 |
29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
30 |
31 | ## Installation
32 |
33 | ```bash
34 | $ npm install
35 | ```
36 |
37 | ## Running the app
38 |
39 | ```bash
40 | # development
41 | $ npm run start
42 |
43 | # watch mode
44 | $ npm run start:dev
45 |
46 | # production mode
47 | $ npm run start:prod
48 | ```
49 |
50 | ## Test
51 |
52 | ```bash
53 | # unit tests
54 | $ npm run test
55 |
56 | # e2e tests
57 | $ npm run test:e2e
58 |
59 | # test coverage
60 | $ npm run test:cov
61 | ```
62 |
63 | ## Support
64 |
65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
66 |
67 | ## Stay in touch
68 |
69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
70 | - Website - [https://nestjs.com](https://nestjs.com/)
71 | - Twitter - [@nestframework](https://twitter.com/nestframework)
72 |
73 | ## License
74 |
75 | Nest is [MIT licensed](LICENSE).
76 |
--------------------------------------------------------------------------------
/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "language": "ts",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "src"
5 | }
6 |
--------------------------------------------------------------------------------
/nodemon-debug.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src"],
3 | "ext": "ts",
4 | "ignore": ["src/**/*.spec.ts"],
5 | "exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts"
6 | }
7 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["dist"],
3 | "ext": "js",
4 | "exec": "node dist/main"
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testing-demo",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "license": "MIT",
7 | "scripts": {
8 | "build": "tsc -p tsconfig.build.json",
9 | "format": "prettier --write \"src/**/*.ts\"",
10 | "start": "ts-node -r tsconfig-paths/register src/main.ts",
11 | "start:dev": "concurrently --handle-input \"wait-on dist/main.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ",
12 | "start:debug": "nodemon --config nodemon-debug.json",
13 | "prestart:prod": "rimraf dist && npm run build",
14 | "start:prod": "node dist/main.js",
15 | "lint": "tslint -p tsconfig.json -c tslint.json",
16 | "test": "jest",
17 | "test:watch": "jest --watch",
18 | "test:cov": "jest --coverage",
19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20 | "test:e2e": "jest --config ./test/jest-e2e.json"
21 | },
22 | "dependencies": {
23 | "@nestjs/common": "^6.0.0",
24 | "@nestjs/core": "^6.0.0",
25 | "@nestjs/platform-express": "^6.0.0",
26 | "reflect-metadata": "^0.1.12",
27 | "rimraf": "^2.6.2",
28 | "rxjs": "^6.3.3"
29 | },
30 | "devDependencies": {
31 | "@nestjs/testing": "^6.0.0",
32 | "@types/express": "^4.16.0",
33 | "@types/jest": "^23.3.13",
34 | "@types/node": "^10.12.18",
35 | "@types/supertest": "^2.0.7",
36 | "concurrently": "^4.1.0",
37 | "jest": "^23.6.0",
38 | "nodemon": "^1.18.9",
39 | "prettier": "^1.15.3",
40 | "supertest": "^3.4.1",
41 | "ts-jest": "24.0.2",
42 | "ts-node": "8.1.0",
43 | "tsconfig-paths": "3.8.0",
44 | "tslint": "5.16.0",
45 | "typescript": "3.4.3",
46 | "wait-on": "^3.2.0"
47 | },
48 | "jest": {
49 | "moduleFileExtensions": [
50 | "js",
51 | "json",
52 | "ts"
53 | ],
54 | "rootDir": "src",
55 | "testRegex": ".spec.ts$",
56 | "transform": {
57 | "^.+\\.(t|j)s$": "ts-jest"
58 | },
59 | "coverageDirectory": "../coverage",
60 | "testEnvironment": "node",
61 | "collectCoverage": true,
62 | "verbose": true
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/api.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, HttpService } from '@nestjs/common';
2 |
3 | interface Student {
4 | name: string;
5 | grades: number[];
6 | }
7 |
8 | @Injectable()
9 | export class ApiService {
10 | constructor(private http: HttpService) {}
11 | async getStudent(firstName: string, lastName: string): Promise {
12 | const url = `....../get-student?firstName=${firstName}&lastName=${lastName}`;
13 | const response = await this.http.get(url).toPromise();
14 | return response.data;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Query, HttpException } from '@nestjs/common';
2 | import { StudentService } from './student.service';
3 |
4 | @Controller('student')
5 | export class AppController {
6 | constructor(private readonly studentService: StudentService) {}
7 |
8 | @Get('/gpa')
9 | async getStudentGpa(
10 | @Query('firstName') firstName: string,
11 | @Query('lastName') lastName: string,
12 | ): Promise {
13 | if (!firstName || !lastName) {
14 | throw new HttpException('Incomplete student information', 400);
15 | }
16 | return await this.studentService.getGpa(firstName, lastName);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, HttpModule } from '@nestjs/common';
2 | import { AppController } from './app.controller';
3 | import { StudentService } from './student.service';
4 | import { ApiService } from './api.service';
5 |
6 | @Module({
7 | imports: [HttpModule],
8 | controllers: [AppController],
9 | providers: [ApiService, StudentService],
10 | })
11 | export class AppModule {}
12 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 |
4 | async function bootstrap() {
5 | const app = await NestFactory.create(AppModule);
6 | await app.listen(3000);
7 | }
8 | bootstrap();
9 |
--------------------------------------------------------------------------------
/src/student.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, HttpException } from '@nestjs/common';
2 | import { ApiService } from './api.service';
3 |
4 | interface Student {
5 | name: string;
6 | grades: number[];
7 | }
8 |
9 | @Injectable()
10 | export class StudentService {
11 | constructor(private apiService: ApiService) {}
12 |
13 | public async getGpa(firstName: string, lastName: string): Promise {
14 | const student: Student = await this.apiService.getStudent(
15 | firstName,
16 | lastName,
17 | );
18 |
19 | if (!student || !student.grades) {
20 | throw new HttpException('Cannot find student or student grades', 404);
21 | }
22 |
23 | let gpa: number = 0;
24 |
25 | for (const grade of student.grades) {
26 | gpa += grade / student.grades.length;
27 | }
28 |
29 | return gpa;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import * as request from 'supertest';
3 | import { AppModule } from '../app.module';
4 | import { INestApplication, HttpService, HttpModule } from '@nestjs/common';
5 | import { ApiService } from '../api.service';
6 | import { StudentService } from '../student.service';
7 | import { AxiosResponse } from 'axios';
8 | import { of } from 'rxjs';
9 |
10 | describe('AppController (e2e)', () => {
11 | let app: INestApplication;
12 | let httpService: HttpService;
13 |
14 | beforeAll(async () => {
15 | const mockAppModule: TestingModule = await Test.createTestingModule({
16 | imports: [AppModule, HttpModule],
17 | providers: [ApiService, StudentService],
18 | }).compile();
19 |
20 | app = mockAppModule.createNestApplication();
21 | httpService = mockAppModule.get(HttpService);
22 | await app.init();
23 | });
24 |
25 | it('GET student GPA if API finds the student', async () => {
26 | const result: AxiosResponse = {
27 | data: {
28 | name: 'Jane Doe',
29 | grades: [3.7, 3.8, 3.9, 4.0, 3.6],
30 | },
31 | status: 200,
32 | statusText: 'OK',
33 | headers: {},
34 | config: {},
35 | };
36 | jest.spyOn(httpService, 'get').mockImplementationOnce(() => of(result));
37 | const expectedGpaString = '3.8';
38 | const response = await request(app.getHttpServer())
39 | .get('/student/gpa?firstName=Jane&lastName=Doe')
40 | .expect(200);
41 | expect(response.text).toEqual(expectedGpaString);
42 | });
43 |
44 | it('throws error if GET request does not include student name', async () => {
45 | return await request(app.getHttpServer())
46 | .get('/student/gpa?firstName=&lastName=')
47 | .expect(400);
48 | });
49 |
50 | it('throws error if API cannot find the student', async () => {
51 | const result: AxiosResponse = {
52 | data: {},
53 | status: 404,
54 | statusText: '',
55 | headers: {},
56 | config: {},
57 | };
58 |
59 | jest.spyOn(httpService, 'get').mockImplementationOnce(() => of(result));
60 |
61 | return await request(app.getHttpServer())
62 | .get('/student/gpa?firstName=Anna&lastName=Julia')
63 | .expect(404);
64 | });
65 |
66 | it('throws error if student does not have grades assigned', async () => {
67 | const result: AxiosResponse = {
68 | data: { name: 'Davy Jones' },
69 | status: 200,
70 | statusText: '',
71 | headers: {},
72 | config: {},
73 | };
74 |
75 | jest.spyOn(httpService, 'get').mockImplementationOnce(() => of(result));
76 |
77 | return await request(app.getHttpServer())
78 | .get('/student/gpa?firstName=Davy&lastName=Jones')
79 | .expect(404);
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/src/test/student.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { ApiService } from '../api.service';
3 | import { StudentService } from '../student.service';
4 |
5 | class ApiServiceMock {
6 | async getStudent(firstName: string, lastName: string) {
7 | return {
8 | name: 'Jane Doe',
9 | grades: [3.7, 3.8, 3.9, 4.0, 3.6],
10 | };
11 | }
12 | }
13 |
14 | describe('StudentService', () => {
15 | let app: TestingModule;
16 | let studentService: StudentService;
17 |
18 | beforeAll(async () => {
19 | const ApiServiceProvider = {
20 | provide: ApiService,
21 | useClass: ApiServiceMock,
22 | };
23 | app = await Test.createTestingModule({
24 | providers: [StudentService, ApiServiceProvider],
25 | }).compile();
26 | studentService = app.get(StudentService);
27 | });
28 |
29 | describe('getGpa', () => {
30 | it('should get student GPA', async () => {
31 | const expectedGpa = 3.8;
32 | const gpa = await studentService.getGpa('Jane', 'Doe');
33 |
34 | expect(gpa).toEqual(expectedGpa);
35 | });
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "target": "es6",
9 | "sourceMap": true,
10 | "outDir": "./dist",
11 | "baseUrl": "./",
12 | "incremental": true
13 | },
14 | "exclude": ["node_modules"]
15 | }
16 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint:recommended"],
4 | "jsRules": {
5 | "no-unused-expression": true
6 | },
7 | "rules": {
8 | "quotemark": [true, "single"],
9 | "member-access": [false],
10 | "ordered-imports": [false],
11 | "max-line-length": [true, 150],
12 | "member-ordering": [false],
13 | "interface-name": [false],
14 | "arrow-parens": false,
15 | "object-literal-sort-keys": false
16 | },
17 | "rulesDirectory": []
18 | }
19 |
--------------------------------------------------------------------------------