diff --git a/README.md b/README.md index 3fac579..0f86a82 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ - [x] add 100 pts - [x] get standings - [x] get user score history - -## additional notes - -- unit tests -- eslint +- [x] controller tests + - [x] user + - [x] rank diff --git a/src/app.module.ts b/src/app.module.ts index a5b2cf6..9717b5c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -24,5 +24,4 @@ import { RankModule } from './rank/rank.module'; RankModule, ], }) - export class AppModule {} diff --git a/src/auth/auth.controller.spec.ts b/src/auth/auth.controller.spec.ts deleted file mode 100644 index 27a31e6..0000000 --- a/src/auth/auth.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthController } from './auth.controller'; - -describe('AuthController', () => { - let controller: AuthController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AuthController], - }).compile(); - - controller = module.get(AuthController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 144ac00..4a915d4 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,6 +1,5 @@ import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import * as bcrypt from 'bcrypt'; -import { Request } from 'express'; import { CreateUserDto } from 'src/user/dtos/CreateUserDto'; import { UserService } from 'src/user/user.service'; import { AuthService } from './auth.service'; @@ -8,10 +7,9 @@ import { LocalAuthGuard } from './local-auth.guard'; @Controller('auth') export class AuthController { - constructor( private userService: UserService, - private authService: AuthService + private authService: AuthService, ) {} @UseGuards(LocalAuthGuard) @@ -25,5 +23,4 @@ export class AuthController { user.password = await bcrypt.hash(user.password, 10); this.userService.create(user); } - } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 9ef70ae..e7e4f80 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -22,14 +22,10 @@ import { JwtStrategy } from './jwt.strategy'; expiresIn: configService.get('JWT_EXPIRY'), }, }; - } + }, }), ], - providers: [ - AuthService, - LocalStrategy, - JwtStrategy, - ], - controllers: [AuthController] + providers: [AuthService, LocalStrategy, JwtStrategy], + controllers: [AuthController], }) export class AuthModule {} diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts deleted file mode 100644 index 800ab66..0000000 --- a/src/auth/auth.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthService } from './auth.service'; - -describe('AuthService', () => { - let service: AuthService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AuthService], - }).compile(); - - service = module.get(AuthService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 928c4fe..5a0b4bb 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -7,13 +7,12 @@ import { JwtService } from '@nestjs/jwt'; export class AuthService { constructor( private userService: UserService, - private jwtService: JwtService + private jwtService: JwtService, ) {} async validate(email: string, password: string): Promise { const user = await this.userService.findByEmail(email); - if (user && - bcrypt.compare(password, user.password)) { + if (user && bcrypt.compare(password, user.password)) { const { password, ...result } = user; return result; } diff --git a/src/auth/jwt-auth.guard.ts b/src/auth/jwt-auth.guard.ts index 5a704e7..2155290 100644 --- a/src/auth/jwt-auth.guard.ts +++ b/src/auth/jwt-auth.guard.ts @@ -1,5 +1,5 @@ -import { Injectable } from "@nestjs/common"; -import { AuthGuard } from "@nestjs/passport"; +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts index c83ad7e..9628721 100644 --- a/src/auth/jwt.strategy.ts +++ b/src/auth/jwt.strategy.ts @@ -1,7 +1,7 @@ -import { Injectable } from "@nestjs/common"; -import { ConfigService } from "@nestjs/config"; -import { PassportStrategy } from "@nestjs/passport"; -import { ExtractJwt, Strategy } from "passport-jwt"; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { diff --git a/src/auth/local-auth.guard.ts b/src/auth/local-auth.guard.ts index 69e12e5..ccf962b 100644 --- a/src/auth/local-auth.guard.ts +++ b/src/auth/local-auth.guard.ts @@ -1,5 +1,5 @@ -import { Injectable } from "@nestjs/common"; -import { AuthGuard } from "@nestjs/passport"; +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; @Injectable() export class LocalAuthGuard extends AuthGuard('local') {} diff --git a/src/auth/local.strategy.ts b/src/auth/local.strategy.ts index a086765..d3dc631 100644 --- a/src/auth/local.strategy.ts +++ b/src/auth/local.strategy.ts @@ -7,7 +7,7 @@ import { AuthService } from './auth.service'; export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super({ - usernameField: 'email' + usernameField: 'email', }); } diff --git a/src/rank/rank.controller.spec.ts b/src/rank/rank.controller.spec.ts index 59c2193..ec129b9 100644 --- a/src/rank/rank.controller.spec.ts +++ b/src/rank/rank.controller.spec.ts @@ -1,13 +1,46 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { RankController } from './rank.controller'; +import { RankService } from './rank.service'; describe('RankController', () => { let controller: RankController; + const mockRankList = []; + + const mockRank = { + id: `${Date.now()}`, + points: 100, + createdAt: Date.now(), + updatedAt: Date.now(), + userId: `${Date.now()}`, + }; + + mockRankList.push(mockRank); + + const mockRankService = { + findRankList: jest.fn(() => mockRankList), + findByUserLatest: jest.fn((_: string) => mockRank), + findByUser: jest.fn((_: string) => mockRankList), + create: jest.fn((pts: number) => ({ + ...mockRank, + points: mockRank.points + pts + })), + }; + + const mockGuard = { + canActivate: jest.fn(() => true) + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [RankController], - }).compile(); + providers: [RankService], + }).overrideProvider(RankService) + .useValue(mockRankService) + .overrideGuard(JwtAuthGuard) + .useValue(mockGuard) + .compile(); controller = module.get(RankController); }); @@ -15,4 +48,46 @@ describe('RankController', () => { it('should be defined', () => { expect(controller).toBeDefined(); }); + + it('should return rank list', () => { + expect(controller.getRankList()).toEqual(mockRankList); + }); + + it('should get latest score of user', () => { + expect(controller.getUserRank(mockRank.userId)).toEqual(mockRank); + }); + + it('should get user rank history', () => { + expect(controller.getUserHistory(mockRank.userId)).toEqual(mockRankList); + }); + + it('should create new rank and add 20 points', () => { + const req = { + user: { id: mockRank.id }, + }; + expect(controller.add20Points(req)).toEqual({ + ...mockRank, + points: 120, + }); + }); + + it('should create new rank and add 60 points', () => { + const req = { + user: { id: mockRank.id }, + }; + expect(controller.add60Points(req)).toEqual({ + ...mockRank, + points: 160, + }); + }); + + it('should create new rank and add 100 points', () => { + const req = { + user: { id: mockRank.id }, + }; + expect(controller.add100Points(req)).toEqual({ + ...mockRank, + points: 200, + }); + }); }); diff --git a/src/rank/rank.controller.ts b/src/rank/rank.controller.ts index 1865bca..970ce65 100644 --- a/src/rank/rank.controller.ts +++ b/src/rank/rank.controller.ts @@ -1,6 +1,5 @@ import { Controller, Get, Param, Post, Req, UseGuards } from '@nestjs/common'; -import { Request } from 'express'; -import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { Rank } from './rank.model'; import { RankService } from './rank.service'; @@ -9,37 +8,37 @@ export class RankController { constructor(private rankService: RankService) {} @Get() - async getRankList(): Promise { + getRankList(): Promise { return this.rankService.findRankList(); } @UseGuards(JwtAuthGuard) @Get('/:id') - async getUserRank(@Param('id') userId: string): Promise { + getUserRank(@Param('id') userId: string): Promise { return this.rankService.findByUserLatest(userId); } @UseGuards(JwtAuthGuard) @Get('/:id/all') - async getUserHistory(@Param('id') userId: string): Promise { + getUserHistory(@Param('id') userId: string): Promise { return this.rankService.findByUser(userId); } @UseGuards(JwtAuthGuard) @Post('/add/20') - async add20Points(@Req() req): Promise { - this.rankService.create(20, req.user.id); + add20Points(@Req() req): Promise { + return this.rankService.create(20, req.user.id); } @UseGuards(JwtAuthGuard) @Post('/add/60') - async add60Points(@Req() req): Promise { - this.rankService.create(60, req.user.id); + add60Points(@Req() req): Promise { + return this.rankService.create(60, req.user.id); } @UseGuards(JwtAuthGuard) @Post('/add/100') - async add100Points(@Req() req): Promise { - this.rankService.create(100, req.user.id); + add100Points(@Req() req): Promise { + return this.rankService.create(100, req.user.id); } } diff --git a/src/rank/rank.model.ts b/src/rank/rank.model.ts index 0ba21c9..1671c5e 100644 --- a/src/rank/rank.model.ts +++ b/src/rank/rank.model.ts @@ -1,5 +1,17 @@ -import { BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, NotEmpty, PrimaryKey, Table, UpdatedAt } from "sequelize-typescript"; -import { User } from "src/user/user.model"; +import { + BelongsTo, + Column, + CreatedAt, + DataType, + Default, + ForeignKey, + Model, + NotEmpty, + PrimaryKey, + Table, + UpdatedAt, +} from 'sequelize-typescript'; +import { User } from '../user/user.model'; @Table({ timestamps: true, diff --git a/src/rank/rank.module.ts b/src/rank/rank.module.ts index a143793..fb932eb 100644 --- a/src/rank/rank.module.ts +++ b/src/rank/rank.module.ts @@ -5,10 +5,8 @@ import { SequelizeModule } from '@nestjs/sequelize'; import { Rank } from './rank.model'; @Module({ - imports: [ - SequelizeModule.forFeature([Rank]), - ], + imports: [SequelizeModule.forFeature([Rank])], providers: [RankService], - controllers: [RankController] + controllers: [RankController], }) export class RankModule {} diff --git a/src/rank/rank.service.spec.ts b/src/rank/rank.service.spec.ts deleted file mode 100644 index ed2c656..0000000 --- a/src/rank/rank.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { RankService } from './rank.service'; - -describe('RankService', () => { - let service: RankService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [RankService], - }).compile(); - - service = module.get(RankService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/rank/rank.service.ts b/src/rank/rank.service.ts index b981e11..433c73d 100644 --- a/src/rank/rank.service.ts +++ b/src/rank/rank.service.ts @@ -2,23 +2,22 @@ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/sequelize'; import { Op } from 'sequelize'; import { col, fn } from 'sequelize'; -import { User } from 'src/user/user.model'; +import { User } from '../user/user.model'; import { Rank } from './rank.model'; @Injectable() export class RankService { - constructor( - @InjectModel(Rank) private rankModel: typeof Rank - ) {} + constructor(@InjectModel(Rank) private rankModel: typeof Rank) {} async create(points, userId): Promise { + const rank = await this.findByUserLatest(userId); return this.rankModel.create({ - points, - userId + points: rank ? rank.points + points : points, + userId, }); } - async findAll(): Promise{ + async findAll(): Promise { return this.rankModel.findAll(); } @@ -40,30 +39,30 @@ export class RankService { } async findRankList(): Promise { - return this.rankModel.findAll({ - attributes: [ - 'id', - [fn('MAX', col('createdAt')), 'mDate'], - 'userId', - ], - group: ['userId'], - }).then((res) => { + return this.rankModel + .findAll({ + attributes: ['id', [fn('MAX', col('createdAt')), 'mDate'], 'userId'], + group: ['userId'], + }) + .then((res) => { const maxIds = []; - res.forEach(r => { + res.forEach((r) => { maxIds.push(r.id); }); return this.rankModel.findAll({ attributes: ['id', 'points'], where: { id: { - [Op.in]: maxIds + [Op.in]: maxIds, }, }, - include: [{ - model: User, - attributes: ['name'], - }], + include: [ + { + model: User, + attributes: ['name'], + }, + ], }); - }); + }); } } diff --git a/src/user/user.controller.spec.ts b/src/user/user.controller.spec.ts index 7057a1a..3d4fbf5 100644 --- a/src/user/user.controller.spec.ts +++ b/src/user/user.controller.spec.ts @@ -1,13 +1,32 @@ import { Test, TestingModule } from '@nestjs/testing'; import { UserController } from './user.controller'; +import { UserService } from './user.service'; describe('UserController', () => { let controller: UserController; + const mockUsers = [ + { + id: `${Date.now()}`, + name: 'User 1', + email: 'email1@email.com', + createdAt: Date.now(), + updatedAt: Date.now(), + }, + ]; + + const mockUserService = { + findAll: jest.fn(() => mockUsers), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UserController], - }).compile(); + providers: [UserService], + }) + .overrideProvider(UserService) + .useValue(mockUserService) + .compile(); controller = module.get(UserController); }); @@ -15,4 +34,8 @@ describe('UserController', () => { it('should be defined', () => { expect(controller).toBeDefined(); }); + + it('should return all users', () => { + expect(controller.findAll()).toEqual(mockUsers); + }); }); diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 9675c6c..4d49742 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -4,7 +4,6 @@ import { UserService } from './user.service'; @Controller('user') export class UserController { - constructor(private userService: UserService) {} @Get() diff --git a/src/user/user.model.ts b/src/user/user.model.ts index d6e220e..cfd637f 100644 --- a/src/user/user.model.ts +++ b/src/user/user.model.ts @@ -1,5 +1,16 @@ -import { Column, Model, Table, CreatedAt, UpdatedAt, PrimaryKey, DataType, NotEmpty, Default, HasMany } from 'sequelize-typescript'; -import { Rank } from 'src/rank/rank.model'; +import { + Column, + Model, + Table, + CreatedAt, + UpdatedAt, + PrimaryKey, + DataType, + NotEmpty, + Default, + HasMany, +} from 'sequelize-typescript'; +import { Rank } from '../rank/rank.model'; @Table({ timestamps: true, diff --git a/src/user/user.service.spec.ts b/src/user/user.service.spec.ts deleted file mode 100644 index 873de8a..0000000 --- a/src/user/user.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UserService } from './user.service'; - -describe('UserService', () => { - let service: UserService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [UserService], - }).compile(); - - service = module.get(UserService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 5b27b8e..048e836 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -7,7 +7,7 @@ import { User } from './user.model'; export class UserService { constructor( @InjectModel(User) - private userModel: typeof User + private userModel: typeof User, ) {} async create(data: CreateUserDto) { @@ -19,7 +19,7 @@ export class UserService { return { id, - name + name, }; } diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts deleted file mode 100644 index 50cda62..0000000 --- a/test/app.e2e-spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); - }); -});