Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finished the unit tests of service layer across all the application #86

Merged
merged 11 commits into from
Dec 2, 2023
34 changes: 34 additions & 0 deletions src/app.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Test, TestingModule } from '@nestjs/testing'
import { AppService, WelcomeResponse } from './app.service'

describe('🏠AppService | Service Layer', () => {
let appService: AppService

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AppService],
}).compile()

appService = module.get<AppService>(AppService)
})

afterEach(() => {
jest.clearAllMocks()
})

describe('getHello', () => {
it('should return a welcome message with author information and links', () => {
const result: WelcomeResponse = appService.getHello()

expect(typeof result).toBe('object')
})
})

describe('ping', () => {
it('should return a successful ping message', () => {
const result = appService.ping()

expect(typeof result.message).toBe('string')
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ describe('🏠PrivateAuthController | Controllers Layer', () => {
privateAuthController = module.get<PrivateAuthController>(PrivateAuthController)
})

afterEach(() => {
jest.clearAllMocks()
})

it('should be defined', () => {
expect(privateAuthController).toBeDefined()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ describe('🏠PublicAuthController | Controllers layer', () => {
publicAuthController = module.get<PublicAuthController>(PublicAuthController)
})

afterEach(() => {
jest.clearAllMocks()
})

it('should be defined', () => {
expect(publicAuthController).toBeDefined()
})
Expand Down
126 changes: 126 additions & 0 deletions src/modules/auth/services/tests/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { SessionService } from '@src/modules/session/services'
import { LoginAttemptService } from '../login-attempt.service'
import { UserService } from '@src/modules/user/services'
import { HashUtil, TokenUtil } from '@src/shared/utils'
import { UnauthorizedException } from '@nestjs/common'
import { Test, TestingModule } from '@nestjs/testing'
import { AuthService } from '../auth.service'

describe('🏠AuthService | Service Layer', () => {
let authService: Partial<AuthService>
let userService: Partial<UserService>
let sessionService: Partial<SessionService>
let loginAttemptService: Partial<LoginAttemptService>

beforeEach(async () => {
userService = {
findUserByEmail: jest.fn(),
}

sessionService = {
createSession: jest.fn(),
getSession: jest.fn(),
deleteSession: jest.fn(),
}

loginAttemptService = {
isFailedLoginAttemptsExceeded: jest.fn(),
incrementFailedLoginAttemptsCount: jest.fn(),
}

const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
{ provide: UserService, useValue: userService },
{ provide: SessionService, useValue: sessionService },
{ provide: LoginAttemptService, useValue: loginAttemptService },
HashUtil,
TokenUtil,
],
}).compile()

authService = module.get<AuthService>(AuthService)
})

afterEach(() => {
jest.clearAllMocks()
})

describe('login method', () => {
it('should throw UnauthorizedException for invalid email', async () => {
userService.findUserByEmail = jest.fn().mockResolvedValueOnce(null)

await expect(
authService.login({ email: 'email@example.com', password: 'password123' }, '127.0.0.1', {}),
).rejects.toThrow(UnauthorizedException)

expect(loginAttemptService.incrementFailedLoginAttemptsCount).toHaveBeenCalled()
expect(loginAttemptService.isFailedLoginAttemptsExceeded).toHaveBeenCalled()
})

it('should throw UnauthorizedException for invalid password', async () => {
userService.findUserByEmail = jest.fn().mockResolvedValueOnce({
_id: 'user_id',
email: 'existent@example.com',
password: 'hashedPassword',
})

HashUtil.verifyHash = jest.fn().mockResolvedValueOnce(false)

await expect(
authService.login({ email: 'existent@example.com', password: 'incorrectPassword' }, '127.0.0.1', {}),
).rejects.toThrow(UnauthorizedException)

expect(loginAttemptService.incrementFailedLoginAttemptsCount).toHaveBeenCalled()
expect(loginAttemptService.isFailedLoginAttemptsExceeded).toHaveBeenCalled()
})

it('should generate tokens and create session for valid credentials', async () => {
userService.findUserByEmail = jest.fn().mockResolvedValueOnce({
_id: 'user_id',
email: 'existent@example.com',
password: 'hashedPassword',
})

HashUtil.verifyHash = jest.fn().mockResolvedValueOnce(true)

TokenUtil.generateAccessToken = jest.fn().mockResolvedValueOnce('accessToken')
TokenUtil.generateRefreshToken = jest.fn().mockResolvedValueOnce('refreshToken')

const createSessionSpy = jest.spyOn(sessionService, 'createSession')

const result = await authService.login(
{ email: 'existent@example.com', password: 'correctPassword' },
'127.0.0.1',
{},
)

expect(result).toEqual({
accessToken: 'accessToken',
refreshToken: 'refreshToken',
})
expect(createSessionSpy).toHaveBeenCalledWith({
accessToken: 'accessToken',
refreshToken: 'refreshToken',
ipAddress: '127.0.0.1',
device: {},
})
expect(loginAttemptService.isFailedLoginAttemptsExceeded).toHaveBeenCalled()
expect(loginAttemptService.incrementFailedLoginAttemptsCount).not.toHaveBeenCalled()
})
})

describe('logOut method', () => {
it('should delete session for valid accessToken', async () => {
sessionService.getSession = jest.fn().mockResolvedValueOnce({
_id: 'session_id',
})

const deleteSessionSpy = jest.spyOn(sessionService, 'deleteSession')

await authService.logOut('validAccessToken')

expect(deleteSessionSpy).toHaveBeenCalledWith('session_id')
})
})
})
97 changes: 97 additions & 0 deletions src/modules/auth/services/tests/login-attempt.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { LoginAttemptService } from '../login-attempt.service'
import { LoginAttemptRepository } from '../../repositories'
import { HttpException, HttpStatus } from '@nestjs/common'
import { Test, TestingModule } from '@nestjs/testing'
import { LoginAttempt } from '../../schemas'
import { MESSAGES } from '../../constants'

describe('LoginAttemptService', () => {
let loginAttemptService: LoginAttemptService
let loginAttemptRepository: Partial<LoginAttemptRepository>

loginAttemptRepository = {
createOne: jest.fn(),
findOne: jest.fn(),
updateOne: jest.fn(),
}

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
LoginAttemptService,
{
provide: LoginAttemptRepository,
useValue: loginAttemptRepository,
},
],
}).compile()

loginAttemptService = module.get<LoginAttemptService>(LoginAttemptService)
loginAttemptRepository = module.get<LoginAttemptRepository>(LoginAttemptRepository)
})

describe('createLoginAttempt', () => {
it('should create a login attempt', async () => {
const mockLoginAttempt: LoginAttempt = { _id: 'mockId', attemptsCount: 1, createdAt: new Date() }

loginAttemptRepository.createOne = jest.fn().mockResolvedValueOnce(mockLoginAttempt)

const result = await loginAttemptService.createLoginAttempt()

expect(result).toEqual(mockLoginAttempt)
expect(loginAttemptRepository.createOne).toHaveBeenCalled()
})
})

describe('incrementFailedLoginAttemptsCount', () => {
it('should increment login attempts count when attempt exists', async () => {
const mockExistingAttempt: LoginAttempt = { _id: 'mockId', attemptsCount: 2, createdAt: new Date() }

loginAttemptRepository.findOne = jest.fn().mockResolvedValueOnce(mockExistingAttempt)
loginAttemptRepository.updateOne = jest.fn().mockResolvedValueOnce(mockExistingAttempt)

const result = await loginAttemptService.incrementFailedLoginAttemptsCount()

expect(result).toEqual(mockExistingAttempt)
expect(loginAttemptRepository.findOne).toHaveBeenCalled()
expect(loginAttemptRepository.updateOne).toHaveBeenCalledWith(mockExistingAttempt._id)
})

it('should create a new login attempt when attempt does not exist', async () => {
const mockNewAttempt: LoginAttempt = { _id: 'newMockId', attemptsCount: 1, createdAt: new Date() }

loginAttemptRepository.findOne = jest.fn().mockResolvedValueOnce(null)
loginAttemptRepository.createOne = jest.fn().mockResolvedValueOnce(mockNewAttempt)

const result = await loginAttemptService.incrementFailedLoginAttemptsCount()

expect(result).toEqual(mockNewAttempt)
expect(loginAttemptRepository.findOne).toHaveBeenCalled()
expect(loginAttemptRepository.createOne).toHaveBeenCalled()
})
})

describe('isFailedLoginAttemptsExceeded', () => {
it('should throw TOO_MANY_REQUESTS exception when attempts count exceeds limit', async () => {
const mockExceededAttempt: LoginAttempt = { _id: 'mockId', attemptsCount: 5, createdAt: new Date() }

loginAttemptRepository.findOne = jest.fn().mockResolvedValueOnce(mockExceededAttempt)

await expect(loginAttemptService.isFailedLoginAttemptsExceeded()).rejects.toThrow(
new HttpException(MESSAGES.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS),
)

expect(loginAttemptRepository.findOne).toHaveBeenCalled()
})

it('should return false when attempts count does not exceed limit', async () => {
const mockNotExceededAttempt: LoginAttempt = { _id: 'mockId', attemptsCount: 3, createdAt: new Date() }
loginAttemptRepository.findOne = jest.fn().mockResolvedValueOnce(mockNotExceededAttempt)

const result = await loginAttemptService.isFailedLoginAttemptsExceeded()

expect(result).toBe(false)
expect(loginAttemptRepository.findOne).toHaveBeenCalled()
})
})
})
101 changes: 0 additions & 101 deletions src/modules/auth/tests/services/auth.service.spec.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ describe('🏠PrivateKeywordController | Controller Layer', () => {
privateKeywordController = module.get<PrivateKeywordController>(PrivateKeywordController)
})

afterEach(() => {
jest.clearAllMocks()
})


describe('createKeyword method', () => {
it('should create a keyword successfully', async () => {
const createKeywordDto: CreateKeywordDto = { name: 'Test Keyword' }
Expand Down
Loading
Loading