-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4c132e5
commit 7959c33
Showing
6 changed files
with
308 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import * as bcrypt from 'bcrypt'; | ||
|
||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { AuthService } from '~/app/auth/auth.service'; | ||
import { Model } from 'mongoose'; | ||
import { JwtService } from '@nestjs/jwt'; | ||
import { getModelToken } from '@nestjs/mongoose'; | ||
import { HttpException, HttpStatus } from '@nestjs/common'; | ||
|
||
import { User } from '~/schemas/user.schema'; | ||
import { Session } from '~/schemas/session.schema'; | ||
import { SessionService } from '~/app/session/session.service'; | ||
import { SignInDto, SignUpDto } from '~/app/auth/auth.dto'; | ||
import { Entity } from '~/types'; | ||
import { | ||
EMAIL_IS_ALREDY_IN_USE, | ||
INVALID_CREDENTIALS, | ||
USER_NOT_FOUND, | ||
} from '~/errors'; | ||
|
||
describe('AuthService', () => { | ||
let authService: AuthService; | ||
let userModel: Model<User>; | ||
let sessionService: SessionService; | ||
|
||
afterEach(() => jest.clearAllMocks()); | ||
|
||
beforeEach(async () => { | ||
const app: TestingModule = await Test.createTestingModule({ | ||
controllers: [], | ||
providers: [ | ||
AuthService, | ||
JwtService, | ||
SessionService, | ||
{ | ||
provide: getModelToken(User.name), | ||
useValue: Model, | ||
}, | ||
{ | ||
provide: getModelToken(Session.name), | ||
useValue: Model, | ||
}, | ||
], | ||
}).compile(); | ||
|
||
authService = app.get<AuthService>(AuthService); | ||
userModel = app.get<Model<User>>(getModelToken(User.name)); | ||
sessionService = app.get<SessionService>(SessionService); | ||
}); | ||
|
||
describe('signUp', () => { | ||
const payload: SignUpDto = { | ||
email: expect.anything(), | ||
password: expect.anything(), | ||
name: expect.anything(), | ||
}; | ||
const user = {} as unknown as Entity<User>[]; | ||
const session = {} as unknown as Entity<Session>; | ||
|
||
it('must create user and return his created session', async () => { | ||
jest.spyOn(userModel, 'findOne').mockResolvedValue(null); | ||
jest.spyOn(userModel, 'create').mockResolvedValue(user); | ||
jest.spyOn(sessionService, 'create').mockResolvedValue(session); | ||
|
||
const data = await authService.signUp(payload); | ||
|
||
expect(data).toBe(session); | ||
expect(sessionService.create).toHaveBeenCalledTimes(1); | ||
expect(userModel.findOne).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return error if email is already in use', async () => { | ||
jest.spyOn(userModel, 'findOne').mockResolvedValue(user); | ||
jest.spyOn(sessionService, 'create').mockResolvedValue(session); | ||
|
||
await expect(authService.signUp(payload)).rejects.toThrow( | ||
new HttpException(EMAIL_IS_ALREDY_IN_USE, HttpStatus.CONFLICT), | ||
); | ||
expect(sessionService.create).toHaveBeenCalledTimes(0); | ||
expect(userModel.findOne).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
|
||
describe('signIn', () => { | ||
const payload: SignInDto = { | ||
email: expect.anything(), | ||
password: expect.anything(), | ||
}; | ||
const user = {} as unknown as Entity<User>; | ||
const session = {} as unknown as Entity<Session>; | ||
|
||
it('should return the logged in user is session', async () => { | ||
jest.spyOn(userModel, 'findOne').mockResolvedValue(user); | ||
jest.spyOn(sessionService, 'findByUser').mockResolvedValue(session); | ||
jest.spyOn(bcrypt, 'compare').mockImplementation(async () => true); | ||
|
||
const data = await authService.signIn(payload); | ||
|
||
expect(data).toBe(session); | ||
expect(sessionService.findByUser).toHaveBeenCalledTimes(1); | ||
expect(userModel.findOne).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('must log in user and create a new session', async () => { | ||
jest.spyOn(userModel, 'findOne').mockResolvedValue(user); | ||
jest.spyOn(sessionService, 'findByUser').mockResolvedValue(null); | ||
jest.spyOn(sessionService, 'create').mockResolvedValue(session); | ||
jest.spyOn(bcrypt, 'compare').mockImplementation(async () => true); | ||
|
||
const data = await authService.signIn(payload); | ||
|
||
expect(data).toBe(session); | ||
expect(sessionService.findByUser).toHaveBeenCalledTimes(1); | ||
expect(userModel.findOne).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return error when password is invalid', async () => { | ||
jest.spyOn(userModel, 'findOne').mockResolvedValue(user); | ||
jest.spyOn(sessionService, 'findByUser').mockResolvedValue(session); | ||
jest.spyOn(bcrypt, 'compare').mockImplementation(async () => false); | ||
|
||
await expect(authService.signIn(payload)).rejects.toThrow( | ||
new HttpException(INVALID_CREDENTIALS, HttpStatus.UNAUTHORIZED), | ||
); | ||
expect(sessionService.findByUser).toHaveBeenCalledTimes(0); | ||
expect(userModel.findOne).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return error when user is not found', async () => { | ||
jest.spyOn(userModel, 'findOne').mockResolvedValue(null); | ||
jest.spyOn(bcrypt, 'compare').mockImplementation(async () => false); | ||
|
||
await expect(authService.signIn(payload)).rejects.toThrow( | ||
new HttpException(USER_NOT_FOUND, HttpStatus.NOT_FOUND), | ||
); | ||
expect(bcrypt.compare).toHaveBeenCalledTimes(0); | ||
expect(userModel.findOne).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { JwtService } from '@nestjs/jwt'; | ||
import { getModelToken } from '@nestjs/mongoose'; | ||
import { Model } from 'mongoose'; | ||
|
||
import { SessionService } from '~/app/session/session.service'; | ||
import { Session } from '~/schemas/session.schema'; | ||
import { Entity, EntityQuery } from '~/types'; | ||
|
||
describe('AuthService', () => { | ||
let sessionService: SessionService; | ||
let jwtService: JwtService; | ||
let sessionModel: Model<Session>; | ||
|
||
const user = expect.anything(); | ||
|
||
afterEach(() => jest.clearAllMocks()); | ||
|
||
beforeEach(async () => { | ||
const app: TestingModule = await Test.createTestingModule({ | ||
controllers: [], | ||
providers: [ | ||
JwtService, | ||
SessionService, | ||
{ | ||
provide: getModelToken(Session.name), | ||
useValue: Model, | ||
}, | ||
], | ||
}).compile(); | ||
|
||
jwtService = app.get<JwtService>(JwtService); | ||
sessionModel = app.get<Model<Session>>(getModelToken(Session.name)); | ||
sessionService = app.get<SessionService>(SessionService); | ||
}); | ||
|
||
describe('refresh', () => { | ||
it('must generate a new access token and refresh its session', async () => { | ||
const accessToken = expect.anything(); | ||
const session = { | ||
populate: jest.fn(() => session), | ||
} as unknown as EntityQuery<Session, 'findOneAndUpdate'>; | ||
|
||
jest.spyOn(jwtService, 'signAsync').mockResolvedValue(accessToken); | ||
jest.spyOn(sessionModel, 'findOneAndUpdate').mockResolvedValue(session); | ||
|
||
const data = await sessionService.refresh(user); | ||
|
||
expect(data).toBe(session); | ||
expect(session.populate).toHaveBeenCalledTimes(1); | ||
expect(session.populate).toHaveBeenCalledWith('user'); | ||
expect(sessionModel.findOneAndUpdate).toHaveBeenCalledTimes(1); | ||
expect(sessionModel.findOneAndUpdate).toHaveBeenCalledWith( | ||
{ user, isExpired: false }, | ||
{ accessToken }, | ||
{ new: true }, | ||
); | ||
}); | ||
}); | ||
|
||
describe('create', () => { | ||
const session = { | ||
populate: jest.fn(), | ||
}; | ||
|
||
it('should create and return a new session', async () => { | ||
jest.spyOn(jwtService, 'signAsync').mockResolvedValue(expect.anything()); | ||
jest | ||
.spyOn(sessionModel, 'create') | ||
.mockResolvedValue(session as unknown as Entity<Session>[]); | ||
|
||
const data = await sessionService.create(user); | ||
|
||
expect(session.populate).toHaveBeenCalledTimes(1); | ||
expect(session.populate).toHaveBeenCalledWith('user'); | ||
expect(sessionModel.create).toHaveBeenCalledTimes(1); | ||
expect(data).toBe(session); | ||
}); | ||
}); | ||
|
||
describe('expireSession', () => { | ||
it('should expire a session', async () => { | ||
jest | ||
.spyOn(sessionModel, 'updateOne') | ||
.mockResolvedValue(expect.anything()); | ||
|
||
await sessionService.expireSession(user); | ||
|
||
expect(sessionModel.updateOne).toHaveBeenCalledTimes(1); | ||
expect(sessionModel.updateOne).toHaveBeenCalledWith( | ||
{ user }, | ||
{ isExpired: true }, | ||
); | ||
}); | ||
}); | ||
|
||
describe('findByUser', () => { | ||
it('must return session with user data', async () => { | ||
const session = { | ||
populate: jest.fn(() => session), | ||
} as unknown as EntityQuery<Session, 'findOne'>; | ||
jest.spyOn(sessionModel, 'findOne').mockReturnValue(session); | ||
|
||
const data = await sessionService.findByUser(user); | ||
|
||
expect(session.populate).toHaveBeenCalledWith('user'); | ||
expect(session.populate).toHaveBeenCalledTimes(1); | ||
expect(sessionModel.findOne).toHaveBeenCalledWith({ | ||
user, | ||
isExpired: false, | ||
}); | ||
expect(data).toBe(session); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Types, Document, Query } from 'mongoose'; | ||
|
||
export type Entity<T> = Document<unknown, object, T> & | ||
T & { _id: Types.ObjectId }; | ||
export type EntityQuery<T, F> = Query<unknown, unknown, object, T, F, object>; |