mirror of
https://github.com/hantsy/nestjs-rest-sample.git
synced 2025-12-08 20:36:27 +00:00
test(e2e): add endpoints testing
This commit is contained in:
parent
202f2b0e8e
commit
450537dab7
46
.github/workflows/e2e.yml
vendored
Normal file
46
.github/workflows/e2e.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
name: e2e
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
branches:
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v2-beta
|
||||
with:
|
||||
node-version: "14"
|
||||
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-node-
|
||||
${{ runner.OS }}-
|
||||
|
||||
- name: Running mongodb service
|
||||
run: |
|
||||
docker-compose up -d mongodb
|
||||
docker ps -a
|
||||
|
||||
# install dependencies and build the project
|
||||
- name: Running e2e testing
|
||||
run: |
|
||||
npm install
|
||||
npm run test:e2e -- --runInBand --forceExit
|
||||
61
package-lock.json
generated
61
package-lock.json
generated
@ -1189,10 +1189,10 @@
|
||||
"integrity": "sha512-wo2rHprtDzTHf4tiSxavktJ52ntiwmg7eHNGFLH38G1of8OfGVwOc1sVbpM4jN/HK/rCMhYOi6xzoPqsv0537A==",
|
||||
"dev": true
|
||||
},
|
||||
"@golevelup/nestjs-testing": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npm.taobao.org/@golevelup/nestjs-testing/download/@golevelup/nestjs-testing-0.1.2.tgz",
|
||||
"integrity": "sha1-MU6H5jTVgXl9CGyfX0Xc6AKtkZo=",
|
||||
"@golevelup/ts-jest": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@golevelup/ts-jest/download/@golevelup/ts-jest-0.3.0.tgz",
|
||||
"integrity": "sha1-coMoYwTPl7FzYOeOOtHWbnGcQy8=",
|
||||
"dev": true
|
||||
},
|
||||
"@istanbuljs/load-nyc-config": {
|
||||
@ -2208,6 +2208,13 @@
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.set": "4.3.2",
|
||||
"uuid": "8.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
|
||||
"integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nestjs/core": {
|
||||
@ -2768,6 +2775,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/validator": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@types/validator/download/@types/validator-13.0.0.tgz",
|
||||
"integrity": "sha1-Nl8b+Taurd0IVvxBqh1vgtiO5bM="
|
||||
},
|
||||
"@types/webpack": {
|
||||
"version": "4.41.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.17.tgz",
|
||||
@ -4226,6 +4238,11 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"class-transformer": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npm.taobao.org/class-transformer/download/class-transformer-0.2.3.tgz",
|
||||
"integrity": "sha1-WYySynHcynP5HMuHXXSjhHzPoy0="
|
||||
},
|
||||
"class-utils": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npm.taobao.org/class-utils/download/class-utils-0.3.6.tgz",
|
||||
@ -4249,6 +4266,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"class-validator": {
|
||||
"version": "0.12.2",
|
||||
"resolved": "https://registry.npm.taobao.org/class-validator/download/class-validator-0.12.2.tgz",
|
||||
"integrity": "sha1-LOty+Ihz6ccUz1+cJ4y8cfb2yO8=",
|
||||
"requires": {
|
||||
"@types/validator": "13.0.0",
|
||||
"google-libphonenumber": "^3.2.8",
|
||||
"tslib": ">=1.9.0",
|
||||
"validator": "13.0.0"
|
||||
}
|
||||
},
|
||||
"cli-color": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz",
|
||||
@ -6954,6 +6982,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"google-libphonenumber": {
|
||||
"version": "3.2.10",
|
||||
"resolved": "https://registry.npm.taobao.org/google-libphonenumber/download/google-libphonenumber-3.2.10.tgz",
|
||||
"integrity": "sha1-AhoxRlJ0fXNqOeLmDcZw8EMUJa0="
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.4.tgz",
|
||||
@ -9231,6 +9264,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-mock-extended": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npm.taobao.org/jest-mock-extended/download/jest-mock-extended-1.0.9.tgz",
|
||||
"integrity": "sha1-NIk4eOLOi4Yjzx8Wq+KGHeH+JbQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ts-essentials": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"jest-pnp-resolver": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
|
||||
@ -13904,6 +13946,12 @@
|
||||
"integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=",
|
||||
"dev": true
|
||||
},
|
||||
"ts-essentials": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npm.taobao.org/ts-essentials/download/ts-essentials-4.0.0.tgz",
|
||||
"integrity": "sha1-UGxCsnC70EZVdLkEFlMxdbCSBas=",
|
||||
"dev": true
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "26.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.1.tgz",
|
||||
@ -14313,6 +14361,11 @@
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"validator": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npm.taobao.org/validator/download/validator-13.0.0.tgz",
|
||||
"integrity": "sha1-D7bGu1IY6iPTaKg0fm0PWnDjvKs="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
13
package.json
13
package.json
@ -21,12 +21,14 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^7.2.0",
|
||||
"@nestjs/common": "^7.0.0",
|
||||
"@nestjs/config": "^0.5.0",
|
||||
"@nestjs/core": "^7.2.0",
|
||||
"@nestjs/core": "^7.0.0",
|
||||
"@nestjs/jwt": "^7.0.0",
|
||||
"@nestjs/passport": "^7.1.0",
|
||||
"@nestjs/platform-express": "^7.2.0",
|
||||
"@nestjs/platform-express": "^7.0.0",
|
||||
"class-transformer": "^0.2.3",
|
||||
"class-validator": "^0.12.2",
|
||||
"mongoose": "^5.9.20",
|
||||
"passport": "^0.4.1",
|
||||
"passport-jwt": "^4.0.0",
|
||||
@ -38,10 +40,10 @@
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^9.0.1",
|
||||
"@commitlint/config-conventional": "^9.0.1",
|
||||
"@golevelup/nestjs-testing": "^0.1.2",
|
||||
"@golevelup/ts-jest": "^0.3.0",
|
||||
"@nestjs/cli": "^7.4.1",
|
||||
"@nestjs/schematics": "^7.0.0",
|
||||
"@nestjs/testing": "^7.2.0",
|
||||
"@nestjs/testing": "^7.0.0",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "26.0.3",
|
||||
"@types/mongoose": "^5.7.29",
|
||||
@ -56,6 +58,7 @@
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "26.1.0",
|
||||
"jest-mock-extended": "^1.0.9",
|
||||
"prettier": "^2.0.5",
|
||||
"supertest": "^4.0.2",
|
||||
"ts-jest": "26.1.1",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createMock } from '@golevelup/nestjs-testing';
|
||||
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
import { createMock } from '@golevelup/ts-jest';
|
||||
import { ExecutionContext } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||
|
||||
@ -44,7 +44,7 @@ describe('LocalAuthGuard', () => {
|
||||
try {
|
||||
guard.handleRequest(undefined, undefined, undefined);
|
||||
} catch (e) {
|
||||
// console.log(e);
|
||||
// console.log(e);
|
||||
expect(e).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,12 +1,24 @@
|
||||
import { RolesGuard } from './roles.guard';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { TestingModule, Test } from '@nestjs/testing';
|
||||
import { createMock } from '@golevelup/ts-jest';
|
||||
import { ExecutionContext } from '@nestjs/common';
|
||||
import { createMock } from '@golevelup/nestjs-testing';
|
||||
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import {
|
||||
anyFunction,
|
||||
anyString,
|
||||
instance,
|
||||
mock,
|
||||
verify,
|
||||
when,
|
||||
reset,
|
||||
} from 'ts-mockito';
|
||||
import { mock as jestMock, any, mockClear, mockDeep } from 'jest-mock-extended';
|
||||
import { RoleType } from '../database/role-type.enum';
|
||||
import { HAS_ROLES_KEY } from './auth.constants';
|
||||
import { AuthenticatedRequest } from './authenticated-request.interface';
|
||||
import { RolesGuard } from './roles.guard';
|
||||
|
||||
describe('RolesGuard', () => {
|
||||
xdescribe('RolesGuard', () => {
|
||||
let guard: RolesGuard;
|
||||
let reflector: Reflector;
|
||||
beforeEach(async () => {
|
||||
@ -27,59 +39,208 @@ describe('RolesGuard', () => {
|
||||
reflector = module.get<Reflector>(Reflector);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(guard).toBeDefined();
|
||||
});
|
||||
|
||||
it('should skip(return true) if the `HasRoles` decorator is not set', async () => {
|
||||
jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => {
|
||||
return undefined;
|
||||
});
|
||||
expect(
|
||||
guard.canActivate(
|
||||
createMock<ExecutionContext>({
|
||||
getHandler: jest.fn(),
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => []);
|
||||
const context = createMock<ExecutionContext>();
|
||||
const result = await guard.canActivate(context);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(reflector.get).toBeCalled();
|
||||
});
|
||||
|
||||
it('should return true if the `HasRoles` decorator is set', async () => {
|
||||
jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => {
|
||||
return [RoleType.USER];
|
||||
jest
|
||||
.spyOn(reflector, 'get')
|
||||
.mockImplementation((a: any, b: any) => [RoleType.USER]);
|
||||
const context = createMock<ExecutionContext>({
|
||||
getHandler: jest.fn(),
|
||||
switchToHttp: jest.fn().mockReturnValue({
|
||||
getRequest: jest.fn().mockReturnValue({
|
||||
user: { roles: [RoleType.USER] },
|
||||
} as AuthenticatedRequest),
|
||||
}),
|
||||
});
|
||||
expect(
|
||||
guard.canActivate(
|
||||
createMock<ExecutionContext>({
|
||||
getHandler: jest.fn(),
|
||||
switchToHttp: jest.fn().mockReturnValue({
|
||||
getRequest: jest.fn().mockReturnValue({
|
||||
user: { roles: [RoleType.USER]},
|
||||
} as AuthenticatedRequest),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
const result = await guard.canActivate(context);
|
||||
expect(result).toBeTruthy();
|
||||
expect(reflector.get).toBeCalled();
|
||||
});
|
||||
|
||||
it('should return false if the `HasRoles` decorator is set but role is not allowed', async () => {
|
||||
jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => {
|
||||
return [RoleType.ADMIN];
|
||||
jest.spyOn(reflector, 'get').mockReturnValue([RoleType.ADMIN]);
|
||||
const request = {
|
||||
user: { roles: [RoleType.USER] },
|
||||
} as AuthenticatedRequest;
|
||||
const context = createMock<ExecutionContext>();
|
||||
const httpArgsHost = createMock<HttpArgumentsHost>({
|
||||
getRequest: () => request,
|
||||
});
|
||||
expect(
|
||||
guard.canActivate(
|
||||
createMock<ExecutionContext>({
|
||||
getHandler: jest.fn(),
|
||||
switchToHttp: jest.fn().mockReturnValue({
|
||||
getRequest: jest.fn().mockReturnValue({
|
||||
user: { roles: [RoleType.USER]},
|
||||
} as AuthenticatedRequest),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
).toBe(false);
|
||||
context.switchToHttp.mockImplementation(() => httpArgsHost);
|
||||
|
||||
const result = await guard.canActivate(context);
|
||||
expect(result).toBeFalsy();
|
||||
expect(reflector.get).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RolesGuard(ts-mockito)', () => {
|
||||
let guard: RolesGuard;
|
||||
const reflecter = mock(Reflector);
|
||||
beforeEach(() => {
|
||||
guard = new RolesGuard(instance(reflecter));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
reset();
|
||||
});
|
||||
|
||||
it('should skip(return true) if the `HasRoles` decorator is not set', async () => {
|
||||
const context = mock<ExecutionContext>();
|
||||
when(context.getHandler()).thenReturn({} as any);
|
||||
|
||||
const contextInstacne = instance(context);
|
||||
when(
|
||||
reflecter.get<RoleType[]>(HAS_ROLES_KEY, contextInstacne.getHandler()),
|
||||
).thenReturn([] as RoleType[]);
|
||||
const result = await guard.canActivate(contextInstacne);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
verify(
|
||||
reflecter.get<RoleType[]>(HAS_ROLES_KEY, contextInstacne.getHandler()),
|
||||
).once();
|
||||
});
|
||||
|
||||
it('should return true if the `HasRoles` decorator is set', async () => {
|
||||
const context = mock<ExecutionContext>();
|
||||
|
||||
when(context.getHandler()).thenReturn({} as any);
|
||||
|
||||
const arguHost = mock<HttpArgumentsHost>();
|
||||
when(arguHost.getRequest()).thenReturn({
|
||||
user: { roles: [RoleType.USER] },
|
||||
} as any);
|
||||
|
||||
when(context.switchToHttp()).thenReturn(instance(arguHost));
|
||||
const contextInstacne = instance(context);
|
||||
|
||||
when(
|
||||
reflecter.get<RoleType[]>(HAS_ROLES_KEY, contextInstacne.getHandler()),
|
||||
).thenReturn([RoleType.USER] as RoleType[]);
|
||||
|
||||
const result = await guard.canActivate(contextInstacne);
|
||||
console.log(result);
|
||||
expect(result).toBeTruthy();
|
||||
verify(
|
||||
reflecter.get<RoleType[]>(HAS_ROLES_KEY, contextInstacne.getHandler()),
|
||||
).once();
|
||||
});
|
||||
|
||||
it('should return false if the `HasRoles` decorator is set but role is not allowed', async () => {
|
||||
const context = mock<ExecutionContext>();
|
||||
|
||||
when(context.getHandler()).thenReturn({} as any);
|
||||
|
||||
// logged in as USER
|
||||
const arguHost = mock<HttpArgumentsHost>();
|
||||
when(arguHost.getRequest()).thenReturn({
|
||||
user: { roles: [RoleType.USER] },
|
||||
} as any);
|
||||
|
||||
when(context.switchToHttp()).thenReturn(instance(arguHost));
|
||||
const contextInstacne = instance(context);
|
||||
|
||||
// but requires ADMIN
|
||||
when(
|
||||
reflecter.get<RoleType[]>(HAS_ROLES_KEY, contextInstacne.getHandler()),
|
||||
).thenReturn([RoleType.ADMIN] as RoleType[]);
|
||||
|
||||
const result = await guard.canActivate(contextInstacne);
|
||||
console.log(result);
|
||||
expect(result).toBeFalsy();
|
||||
verify(
|
||||
reflecter.get<RoleType[]>(HAS_ROLES_KEY, contextInstacne.getHandler()),
|
||||
).once();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RoelsGuard(jest-mock-extended)', () => {
|
||||
let guard: RolesGuard;
|
||||
const reflecter = jestMock<Reflector>();
|
||||
|
||||
beforeEach(() => {
|
||||
guard = new RolesGuard(reflecter);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockClear(reflecter);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(guard).toBeDefined();
|
||||
});
|
||||
|
||||
it('should skip(return true) if the `HasRoles` decorator is not set', async () => {
|
||||
const context = jestMock<ExecutionContext>();
|
||||
context.getHandler.mockReturnValue({} as any);
|
||||
reflecter.get
|
||||
.mockReturnValue([])
|
||||
.calledWith(HAS_ROLES_KEY, context.getHandler());
|
||||
|
||||
const result = await guard.canActivate(context);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(reflecter.get).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return true if the `HasRoles` decorator is set', async () => {
|
||||
const context = jestMock<ExecutionContext>();
|
||||
context.getHandler.mockReturnValue({} as any);
|
||||
|
||||
const arguHost = jestMock<HttpArgumentsHost>();
|
||||
arguHost.getRequest.mockReturnValue({
|
||||
user: { roles: [RoleType.USER] },
|
||||
} as any);
|
||||
|
||||
context.switchToHttp.mockReturnValue(arguHost);
|
||||
|
||||
reflecter.get
|
||||
.mockReturnValue([RoleType.USER])
|
||||
.calledWith(HAS_ROLES_KEY, context.getHandler());
|
||||
|
||||
const result = await guard.canActivate(context);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(reflecter.get).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return false if the `HasRoles` decorator is set but role is not allowed', async () => {
|
||||
// logged in as USER
|
||||
const context = jestMock<ExecutionContext>();
|
||||
context.getHandler.mockReturnValue({} as any);
|
||||
|
||||
const arguHost = jestMock<HttpArgumentsHost>();
|
||||
arguHost.getRequest.mockReturnValue({
|
||||
user: { roles: [RoleType.USER] },
|
||||
} as any);
|
||||
|
||||
context.switchToHttp.mockReturnValue(arguHost);
|
||||
|
||||
//but requires ADMIN
|
||||
reflecter.get
|
||||
.mockReturnValue([RoleType.ADMIN])
|
||||
.calledWith(HAS_ROLES_KEY, context.getHandler());
|
||||
|
||||
const result = await guard.canActivate(context);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
expect(reflecter.get).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { RoleType } from '../database/role-type.enum';
|
||||
import { HAS_ROLES_KEY } from './auth.constants';
|
||||
import { AuthenticatedRequest } from './authenticated-request.interface';
|
||||
@ -15,11 +15,13 @@ export class RolesGuard implements CanActivate {
|
||||
HAS_ROLES_KEY,
|
||||
context.getHandler(),
|
||||
);
|
||||
if (!roles) {
|
||||
if (!roles || roles.length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { user }= context.switchToHttp().getRequest() as AuthenticatedRequest;
|
||||
return user.roles && user.roles.some(r => roles.includes(r));
|
||||
const {
|
||||
user,
|
||||
} = context.switchToHttp().getRequest() as AuthenticatedRequest;
|
||||
return user.roles && user.roles.some((r) => roles.includes(r));
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,8 @@ export const databaseProviders = [
|
||||
connect(dbConfig.uri, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
//see: https://mongoosejs.com/docs/deprecations.html#findandmodify
|
||||
useFindAndModify: false
|
||||
}),
|
||||
inject: [mongodbConfig.KEY],
|
||||
},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
@ -7,6 +8,7 @@ async function bootstrap() {
|
||||
// enable shutdown hooks explicitly.
|
||||
app.enableShutdownHooks();
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
app.enableCors();
|
||||
//app.useLogger();
|
||||
await app.listen(3000);
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
export class CreateCommentDto {
|
||||
|
||||
@IsNotEmpty()
|
||||
readonly content: string;
|
||||
}
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
export class CreatePostDto {
|
||||
|
||||
@IsNotEmpty()
|
||||
readonly title: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
readonly content: string;
|
||||
}
|
||||
|
||||
28
src/post/parse-object-id.pipe.spec.ts
Normal file
28
src/post/parse-object-id.pipe.spec.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import * as mongoose from 'mongoose';
|
||||
import { ParseObjectIdPipe } from './parse-object-id.pipe';
|
||||
|
||||
describe('ParseObjectIdPipe', () => {
|
||||
let isObjectId;
|
||||
|
||||
beforeEach(() => {
|
||||
isObjectId = new ParseObjectIdPipe();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(isObjectId).toBeDefined();
|
||||
});
|
||||
|
||||
it('if valid', () => {
|
||||
const validId = new mongoose.Types.ObjectId().toHexString();
|
||||
const result = isObjectId.transform(validId, {} as any);
|
||||
expect(result).toEqual(validId);
|
||||
});
|
||||
|
||||
it('if invalid', () => {
|
||||
try {
|
||||
const result = isObjectId.transform('anerror', {} as any);
|
||||
} catch (e) {
|
||||
expect(e).not.toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
17
src/post/parse-object-id.pipe.ts
Normal file
17
src/post/parse-object-id.pipe.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {
|
||||
ArgumentMetadata,
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
PipeTransform,
|
||||
} from '@nestjs/common';
|
||||
import * as mongoose from 'mongoose';
|
||||
|
||||
@Injectable()
|
||||
export class ParseObjectIdPipe implements PipeTransform<string, string> {
|
||||
transform(value: string, metadata: ArgumentMetadata) {
|
||||
if (!mongoose.isValidObjectId(value)) {
|
||||
throw new BadRequestException(`$value is not a valid mongoose object id`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ import {
|
||||
Inject,
|
||||
Injectable,
|
||||
OnModuleDestroy,
|
||||
OnModuleInit
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { Model } from 'mongoose';
|
||||
import { Comment } from '../database/comment.model';
|
||||
@ -32,33 +32,21 @@ export class PostDataInitializerService
|
||||
@Inject(POST_MODEL) private postModel: Model<Post>,
|
||||
@Inject(COMMENT_MODEL) private commentModel: Model<Comment>,
|
||||
) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
console.log('(PostModule) is initialized...');
|
||||
this.data.forEach(d => {
|
||||
this.postModel.create(d).then(saved => console.log(saved));
|
||||
});
|
||||
|
||||
this.postModel
|
||||
.create({
|
||||
title: 'Model relations in Mongoose',
|
||||
content: 'content of Model relations in Mongoose',
|
||||
})
|
||||
.then(post =>
|
||||
this.commentModel.create({
|
||||
post: { _id: post._id },
|
||||
content: 'comment of Model relations in Mongoose',
|
||||
}),
|
||||
)
|
||||
.then(saved => console.log(saved));
|
||||
this.postModel.insertMany(this.data).then((r) => console.log(r));
|
||||
// Promise.all(this.data.map((d) => this.postModel.create(d))).then((saved) =>
|
||||
// console.log(saved),
|
||||
// );
|
||||
}
|
||||
|
||||
onModuleDestroy(): void {
|
||||
console.log('(PostModule) is being destroyed...');
|
||||
this.postModel
|
||||
.deleteMany({})
|
||||
.then(del => console.log(`deleted ${del.deletedCount} rows of posts`));
|
||||
this.commentModel
|
||||
.deleteMany({})
|
||||
.then(del => console.log(`deleted ${del.deletedCount} rows of comments`));
|
||||
Promise.all([
|
||||
this.postModel.deleteMany({}),
|
||||
this.commentModel.deleteMany({}),
|
||||
]).then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import { PostController } from './post.controller';
|
||||
import { PostService } from './post.service';
|
||||
import { PostServiceStub } from './post.service.stub';
|
||||
import { UpdatePostDto } from './update-post.dto';
|
||||
import { createMock } from '@golevelup/nestjs-testing';
|
||||
import { createMock } from '@golevelup/ts-jest';
|
||||
import { Response } from 'express';
|
||||
|
||||
describe('Post Controller', () => {
|
||||
|
||||
@ -26,6 +26,7 @@ import { CreatePostDto } from './create-post.dto';
|
||||
import { PostService } from './post.service';
|
||||
import { UpdatePostDto } from './update-post.dto';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ParseObjectIdPipe } from './parse-object-id.pipe';
|
||||
|
||||
@Controller({ path: 'posts', scope: Scope.REQUEST })
|
||||
export class PostController {
|
||||
@ -41,7 +42,7 @@ export class PostController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getPostById(@Param('id') id: string): Observable<BlogPost> {
|
||||
getPostById(@Param('id', ParseObjectIdPipe) id: string): Observable<BlogPost> {
|
||||
return this.postService.findById(id);
|
||||
}
|
||||
|
||||
@ -66,7 +67,7 @@ export class PostController {
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@HasRoles(RoleType.USER, RoleType.ADMIN)
|
||||
updatePost(
|
||||
@Param('id') id: string,
|
||||
@Param('id', ParseObjectIdPipe) id: string,
|
||||
@Body() post: UpdatePostDto,
|
||||
@Res() res: Response,
|
||||
): Observable<Response> {
|
||||
@ -81,7 +82,7 @@ export class PostController {
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@HasRoles(RoleType.ADMIN)
|
||||
deletePostById(
|
||||
@Param('id') id: string,
|
||||
@Param('id', ParseObjectIdPipe) id: string,
|
||||
@Res() res: Response,
|
||||
): Observable<Response> {
|
||||
return this.postService.deleteById(id).pipe(
|
||||
@ -95,7 +96,7 @@ export class PostController {
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@HasRoles(RoleType.USER)
|
||||
createCommentForPost(
|
||||
@Param('id') id: string,
|
||||
@Param('id', ParseObjectIdPipe) id: string,
|
||||
@Body() data: CreateCommentDto,
|
||||
@Res() res: Response,
|
||||
): Observable<Response> {
|
||||
@ -110,7 +111,7 @@ export class PostController {
|
||||
}
|
||||
|
||||
@Get(':id/comments')
|
||||
getAllCommentsOfPost(@Param('id') id: string): Observable<Comment[]> {
|
||||
getAllCommentsOfPost(@Param('id', ParseObjectIdPipe) id: string): Observable<Comment[]> {
|
||||
return this.postService.commentsOf(id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { FilterQuery, Model } from 'mongoose';
|
||||
import { Comment } from '../database/comment.model';
|
||||
import { COMMENT_MODEL, POST_MODEL } from '../database/database.constants';
|
||||
import { Post } from '../database/post.model';
|
||||
import { Comment } from '../database/comment.model';
|
||||
import { PostService } from './post.service';
|
||||
|
||||
describe('PostService', () => {
|
||||
@ -28,6 +28,7 @@ describe('PostService', () => {
|
||||
exec: jest.fn(),
|
||||
deleteMany: jest.fn(),
|
||||
deleteOne: jest.fn(),
|
||||
updateOne: jest.fn(),
|
||||
findOneAndUpdate: jest.fn(),
|
||||
findOneAndDelete: jest.fn(),
|
||||
},
|
||||
@ -39,6 +40,8 @@ describe('PostService', () => {
|
||||
constructor: jest.fn(),
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
updateOne: jest.fn(),
|
||||
deleteOne: jest.fn(),
|
||||
update: jest.fn(),
|
||||
create: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
@ -119,24 +122,42 @@ describe('PostService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('findById with an existing id should return one post', (done) => {
|
||||
const found = {
|
||||
_id: '5ee49c3115a4e75254bb732e',
|
||||
title: 'Generate a NestJS project',
|
||||
content: 'content',
|
||||
};
|
||||
describe('findByid', () => {
|
||||
it('if exists return one post', (done) => {
|
||||
const found = {
|
||||
_id: '5ee49c3115a4e75254bb732e',
|
||||
title: 'Generate a NestJS project',
|
||||
content: 'content',
|
||||
};
|
||||
|
||||
jest.spyOn(model, 'findOne').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValueOnce(found) as any,
|
||||
} as any);
|
||||
jest.spyOn(model, 'findOne').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValueOnce(found) as any,
|
||||
} as any);
|
||||
|
||||
service.findById('1').subscribe({
|
||||
next: (data) => {
|
||||
expect(data._id).toBe('5ee49c3115a4e75254bb732e');
|
||||
expect(data.title).toEqual('Generate a NestJS project');
|
||||
},
|
||||
error: (error) => console.log(error),
|
||||
complete: done(),
|
||||
service.findById('1').subscribe({
|
||||
next: (data) => {
|
||||
expect(data._id).toBe('5ee49c3115a4e75254bb732e');
|
||||
expect(data.title).toEqual('Generate a NestJS project');
|
||||
},
|
||||
error: (error) => console.log(error),
|
||||
complete: done(),
|
||||
});
|
||||
});
|
||||
|
||||
it('if not found throw an NotFoundException', (done) => {
|
||||
jest.spyOn(model, 'findOne').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValueOnce(null) as any,
|
||||
} as any);
|
||||
|
||||
service.findById('1').subscribe({
|
||||
next: (data) => {
|
||||
console.log(data);
|
||||
},
|
||||
error: (error) => {
|
||||
expect(error).toBeDefined();
|
||||
},
|
||||
complete: done(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -164,40 +185,80 @@ describe('PostService', () => {
|
||||
expect(model.create).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should update post', (done) => {
|
||||
const toUpdated = {
|
||||
_id: '5ee49c3115a4e75254bb732e',
|
||||
title: 'test title',
|
||||
content: 'test content',
|
||||
};
|
||||
describe('update', () => {
|
||||
it('perform update if post exists', (done) => {
|
||||
const toUpdated = {
|
||||
_id: '5ee49c3115a4e75254bb732e',
|
||||
title: 'test title',
|
||||
content: 'test content',
|
||||
};
|
||||
|
||||
jest.spyOn(model, 'findOneAndUpdate').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValueOnce(toUpdated) as any,
|
||||
} as any);
|
||||
jest.spyOn(model, 'findOneAndUpdate').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValue(toUpdated) as any,
|
||||
} as any);
|
||||
|
||||
service.update('5ee49c3115a4e75254bb732e', toUpdated).subscribe({
|
||||
next: (data) => {
|
||||
expect(data._id).toBe('5ee49c3115a4e75254bb732e');
|
||||
},
|
||||
error: (error) => console.log(error),
|
||||
complete: done(),
|
||||
service.update('5ee49c3115a4e75254bb732e', toUpdated).subscribe({
|
||||
next: (data) => {
|
||||
expect(data).toBeTruthy();
|
||||
expect(model.findOneAndUpdate).toBeCalled();
|
||||
},
|
||||
error: (error) => console.log(error),
|
||||
complete: done(),
|
||||
});
|
||||
});
|
||||
|
||||
it('throw an NotFoundException if post not exists', (done) => {
|
||||
const toUpdated = {
|
||||
_id: '5ee49c3115a4e75254bb732e',
|
||||
title: 'test title',
|
||||
content: 'test content',
|
||||
};
|
||||
jest.spyOn(model, 'findOneAndUpdate').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValue(null) as any,
|
||||
} as any);
|
||||
|
||||
service.update('5ee49c3115a4e75254bb732e', toUpdated).subscribe({
|
||||
error: (error) => {
|
||||
expect(error).toBeDefined();
|
||||
expect(model.findOneAndUpdate).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
complete: done(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete post', (done) => {
|
||||
const toDeleted = {
|
||||
_id: '5ee49c3115a4e75254bb732e',
|
||||
title: 'test title',
|
||||
content: 'test content',
|
||||
};
|
||||
jest.spyOn(model, 'findOneAndDelete').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValueOnce(toDeleted),
|
||||
} as any);
|
||||
describe('delete', () => {
|
||||
it('perform delete if post exists', (done) => {
|
||||
const toDeleted = {
|
||||
_id: '5ee49c3115a4e75254bb732e',
|
||||
title: 'test title',
|
||||
content: 'test content',
|
||||
};
|
||||
jest.spyOn(model, 'findOneAndDelete').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValueOnce(toDeleted),
|
||||
} as any);
|
||||
|
||||
service.deleteById('anystring').subscribe({
|
||||
next: (data) => expect(data._id).toEqual('5ee49c3115a4e75254bb732e'),
|
||||
error: (error) => console.log(error),
|
||||
complete: done(),
|
||||
service.deleteById('anystring').subscribe({
|
||||
next: (data) => {
|
||||
expect(data).toBeTruthy();
|
||||
expect(model.findOneAndDelete).toBeCalled();
|
||||
},
|
||||
error: (error) => console.log(error),
|
||||
complete: done(),
|
||||
});
|
||||
});
|
||||
|
||||
it('throw an NotFoundException if post not exists', (done) => {
|
||||
jest.spyOn(model, 'findOneAndDelete').mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValue(null),
|
||||
} as any);
|
||||
service.deleteById('anystring').subscribe({
|
||||
error: (error) => {
|
||||
expect(error).toBeDefined();
|
||||
expect(model.findOneAndDelete).toBeCalledTimes(1);
|
||||
},
|
||||
complete: done(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -240,19 +301,15 @@ describe('PostService', () => {
|
||||
callback?: (err: any, res: Comment[]) => void,
|
||||
) => {
|
||||
return {
|
||||
select: jest
|
||||
.fn()
|
||||
.mockReturnValue({
|
||||
exec: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
{
|
||||
_id: 'test',
|
||||
content: 'content',
|
||||
post: { _id: '_test_id' },
|
||||
}
|
||||
] as any),
|
||||
}),
|
||||
select: jest.fn().mockReturnValue({
|
||||
exec: jest.fn().mockResolvedValue([
|
||||
{
|
||||
_id: 'test',
|
||||
content: 'content',
|
||||
post: { _id: '_test_id' },
|
||||
},
|
||||
] as any),
|
||||
}),
|
||||
} as any;
|
||||
},
|
||||
);
|
||||
|
||||
@ -54,7 +54,7 @@ export class PostServiceStub implements Pick<PostService, keyof PostService> {
|
||||
}
|
||||
|
||||
deleteById(id: string): Observable<Post> {
|
||||
return of({ ...this.posts[0], _id: id } as Post);
|
||||
return of({ _id: id, title:'test title', content:'content' } as Post);
|
||||
}
|
||||
|
||||
deleteAll(): Observable<any> {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { Inject, Injectable, Scope, NotFoundException } from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { Model } from 'mongoose';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { from, Observable, EMPTY, of } from 'rxjs';
|
||||
import { AuthenticatedRequest } from '../auth/authenticated-request.interface';
|
||||
import { Comment } from '../database/comment.model';
|
||||
import { COMMENT_MODEL, POST_MODEL } from '../database/database.constants';
|
||||
@ -9,7 +9,7 @@ import { Post } from '../database/post.model';
|
||||
import { CreateCommentDto } from './create-comment.dto';
|
||||
import { CreatePostDto } from './create-post.dto';
|
||||
import { UpdatePostDto } from './update-post.dto';
|
||||
|
||||
import { throwIfEmpty, flatMap, filter, map, switchMap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class PostService {
|
||||
@ -29,18 +29,15 @@ export class PostService {
|
||||
.exec(),
|
||||
);
|
||||
} else {
|
||||
return from(
|
||||
this.postModel
|
||||
.find({})
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.exec(),
|
||||
);
|
||||
return from(this.postModel.find({}).skip(skip).limit(limit).exec());
|
||||
}
|
||||
}
|
||||
|
||||
findById(id: string): Observable<Post> {
|
||||
return from(this.postModel.findOne({ _id: id }).exec());
|
||||
return from(this.postModel.findOne({ _id: id }).exec()).pipe(
|
||||
flatMap((p) => (p ? of(p) : EMPTY)),
|
||||
throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
|
||||
);
|
||||
}
|
||||
|
||||
save(data: CreatePostDto): Observable<Post> {
|
||||
@ -58,13 +55,39 @@ export class PostService {
|
||||
.findOneAndUpdate(
|
||||
{ _id: id },
|
||||
{ ...data, updatedBy: { _id: this.req.user.id } },
|
||||
{ new: true },
|
||||
)
|
||||
.exec(),
|
||||
).pipe(
|
||||
flatMap((p) => (p ? of(p) : EMPTY)),
|
||||
throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
|
||||
);
|
||||
// const filter = { _id: id };
|
||||
// const update = { ...data, updatedBy: { _id: this.req.user.id } };
|
||||
// return from(this.postModel.findOne(filter).exec()).pipe(
|
||||
// flatMap((post) => (post ? of(post) : EMPTY)),
|
||||
// throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
|
||||
// switchMap((p, i) => {
|
||||
// return from(this.postModel.updateOne(filter, update).exec());
|
||||
// }),
|
||||
// map((res) => res.nModified),
|
||||
// );
|
||||
}
|
||||
|
||||
deleteById(id: string): Observable<Post> {
|
||||
return from(this.postModel.findOneAndDelete({ _id: id }).exec());
|
||||
return from(this.postModel.findOneAndDelete({ _id: id }).exec()).pipe(
|
||||
flatMap((p) => (p ? of(p) : EMPTY)),
|
||||
throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
|
||||
);
|
||||
// const filter = { _id: id };
|
||||
// return from(this.postModel.findOne(filter).exec()).pipe(
|
||||
// flatMap((post) => (post ? of(post) : EMPTY)),
|
||||
// throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
|
||||
// switchMap((p, i) => {
|
||||
// return from(this.postModel.deleteOne(filter).exec());
|
||||
// }),
|
||||
// map((res) => res.deletedCount),
|
||||
// );
|
||||
}
|
||||
|
||||
deleteAll(): Observable<any> {
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import { IsNotEmpty } from "class-validator";
|
||||
|
||||
export class UpdatePostDto {
|
||||
|
||||
@IsNotEmpty()
|
||||
readonly title: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
readonly content: string;
|
||||
}
|
||||
|
||||
@ -5,31 +5,39 @@ import {
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { Model } from 'mongoose';
|
||||
import { RoleType } from '../database/role-type.enum';
|
||||
import { USER_MODEL } from '../database/database.constants';
|
||||
import { RoleType } from '../database/role-type.enum';
|
||||
import { User } from '../database/user.model';
|
||||
|
||||
@Injectable()
|
||||
export class UserDataInitializerService
|
||||
implements OnModuleInit, OnModuleDestroy {
|
||||
constructor(@Inject(USER_MODEL) private userModel: Model<User>) {
|
||||
//console.log(`userModel in UserDataInitializerService:${userModel}`);
|
||||
}
|
||||
constructor(@Inject(USER_MODEL) private userModel: Model<User>) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
console.log('(UserModule) is initialized...');
|
||||
this.userModel
|
||||
.create({
|
||||
username: 'hantsy',
|
||||
password: 'password',
|
||||
email: 'hantsy@example.com',
|
||||
roles: [RoleType.USER],
|
||||
})
|
||||
.then(data => console.log(data));
|
||||
const user = {
|
||||
username: 'hantsy',
|
||||
password: 'password',
|
||||
email: 'hantsy@example.com',
|
||||
roles: [RoleType.USER],
|
||||
};
|
||||
|
||||
const admin = {
|
||||
username: 'admin',
|
||||
password: 'password',
|
||||
email: 'admin@example.com',
|
||||
roles: [RoleType.ADMIN],
|
||||
};
|
||||
|
||||
[user, admin].map((u) =>
|
||||
this.userModel.create(u).then((data) => console.log(data)),
|
||||
);
|
||||
}
|
||||
|
||||
onModuleDestroy(): void {
|
||||
console.log('(UserModule) is being destroyed...');
|
||||
this.userModel
|
||||
.deleteMany({})
|
||||
.then(del => console.log(`deleted ${del.deletedCount} rows`));
|
||||
this.userModel.deleteMany({}).then((del) => {
|
||||
console.log(`deleted ${del.deletedCount} rows`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +1,186 @@
|
||||
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from './../src/app.module';
|
||||
import * as mongoose from 'mongoose';
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
describe('API endpoints testing (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
app.enableShutdownHooks();
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('if user is not logged in', () => {
|
||||
it('/posts (GET)', async () => {
|
||||
const res = await request(app.getHttpServer()).get('/posts').send();
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.length).toEqual(3);
|
||||
});
|
||||
|
||||
it('/posts (GET) if none existing should return 404', async () => {
|
||||
const id = mongoose.Types.ObjectId();
|
||||
const res = await request(app.getHttpServer()).get('/posts/' + id);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it('/posts (GET) if invalid id should return 400', async () => {
|
||||
const id = "invalidid";
|
||||
const res = await request(app.getHttpServer()).get('/posts/' + id);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('/posts (POST) should return 401', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/posts')
|
||||
.send({ title: 'test title', content: 'test content' });
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('/posts (PUT) should return 401', async () => {
|
||||
const id = mongoose.Types.ObjectId();
|
||||
const res = await request(app.getHttpServer())
|
||||
.put('/posts/' + id)
|
||||
.send({ title: 'test title', content: 'test content' });
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('/posts (DELETE) should return 401', async () => {
|
||||
const id = mongoose.Types.ObjectId();
|
||||
const res = await request(app.getHttpServer())
|
||||
.delete('/posts/' + id)
|
||||
.send();
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if user is logged in as (USER)', () => {
|
||||
let jwttoken: any;
|
||||
beforeEach(async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ username: 'hantsy', password: 'password' });
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
jwttoken = res.body.access_token;
|
||||
//console.log(jwttoken);
|
||||
});
|
||||
|
||||
it('/posts (GET)', async () => {
|
||||
const res = await request(app.getHttpServer()).get('/posts');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.length).toEqual(3);
|
||||
});
|
||||
|
||||
it('/posts (POST) with empty body should return 400', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/posts')
|
||||
.set('Authorization', 'Bearer ' + jwttoken)
|
||||
.send({});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('/posts (PUT) if none existing should return 404', async () => {
|
||||
const id = mongoose.Types.ObjectId();
|
||||
const res = await request(app.getHttpServer())
|
||||
.put('/posts/' + id)
|
||||
.set('Authorization', 'Bearer ' + jwttoken)
|
||||
.send({ title: 'test title', content: 'test content' });
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it('/posts (DELETE) if none existing should return 403', async () => {
|
||||
const id = mongoose.Types.ObjectId();
|
||||
const res = await request(app.getHttpServer())
|
||||
.delete('/posts/' + id)
|
||||
.set('Authorization', 'Bearer ' + jwttoken)
|
||||
.send();
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it('/posts crud flow', async () => {
|
||||
//create a post
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/posts')
|
||||
.set('Authorization', 'Bearer ' + jwttoken)
|
||||
.send({ title: 'test title', content: 'test content' });
|
||||
expect(res.status).toBe(201);
|
||||
const saveduri = res.get('Location');
|
||||
//console.log(saveduri);
|
||||
|
||||
// get the saved post
|
||||
const resget = await request(app.getHttpServer()).get(saveduri);
|
||||
expect(resget.status).toBe(200);
|
||||
expect(resget.body.title).toBe('test title');
|
||||
expect(resget.body.content).toBe('test content');
|
||||
expect(resget.body.createdAt).toBeDefined();
|
||||
|
||||
// update the post
|
||||
const updateres = await request(app.getHttpServer())
|
||||
.put(saveduri)
|
||||
.set('Authorization', 'Bearer ' + jwttoken)
|
||||
.send({ title: 'updated title', content: 'updated content' });
|
||||
expect(updateres.status).toBe(204);
|
||||
|
||||
// verify the updated post
|
||||
const updatedres = await request(app.getHttpServer()).get(saveduri);
|
||||
expect(updatedres.status).toBe(200);
|
||||
expect(updatedres.body.title).toBe('updated title');
|
||||
expect(updatedres.body.content).toBe('updated content');
|
||||
expect(updatedres.body.updatedAt).toBeDefined();
|
||||
|
||||
// creat a comment
|
||||
const commentres = await request(app.getHttpServer())
|
||||
.post(saveduri + '/comments')
|
||||
.set('Authorization', 'Bearer ' + jwttoken)
|
||||
.send({ content: 'test content' });
|
||||
expect(commentres.status).toBe(201);
|
||||
expect(commentres.get('Location')).toBeTruthy();
|
||||
|
||||
// get the comments of post
|
||||
const getCommentsRes = await request(app.getHttpServer()).get(
|
||||
saveduri + '/comments',
|
||||
);
|
||||
expect(getCommentsRes.status).toBe(200);
|
||||
expect(getCommentsRes.body.length).toEqual(1);
|
||||
|
||||
// delete the posts
|
||||
const deleteRes = await request(app.getHttpServer())
|
||||
.delete(saveduri)
|
||||
.set('Authorization', 'Bearer ' + jwttoken)
|
||||
.send();
|
||||
expect(deleteRes.status).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if user is logged in as (ADMIN)', () => {
|
||||
let jwttoken: any;
|
||||
beforeEach(async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/auth/login')
|
||||
.send({ username: 'admin', password: 'password' });
|
||||
jwttoken = res.body.access_token;
|
||||
// console.log(jwttoken);
|
||||
});
|
||||
|
||||
it('/posts (DELETE) if none existing should return 404', async () => {
|
||||
const id = mongoose.Types.ObjectId();
|
||||
const res = await request(app.getHttpServer())
|
||||
.delete('/posts/' + id)
|
||||
.set('Authorization', 'Bearer ' + jwttoken)
|
||||
.send();
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user