Skip to content

Commit

Permalink
test(api): ✅ add unit tests for auth
Browse files Browse the repository at this point in the history
  • Loading branch information
rasouza committed Apr 27, 2024
1 parent 3238e7e commit 33cb851
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 19 deletions.
1 change: 1 addition & 0 deletions apps/api/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default {
}
]
},
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/api',
};
37 changes: 32 additions & 5 deletions apps/api/src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import { Test, TestingModule } from '@nestjs/testing'
/* eslint-disable @typescript-eslint/unbound-method */
import { TestBed } from '@automock/jest'
import { createMock } from '@golevelup/ts-jest'
import { Request, Response } from 'express'

import { AuthController } from './auth.controller'
import { AuthService } from './auth.service'

describe('AuthController', () => {
let controller: AuthController
let authService: jest.Mocked<AuthService>

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController]
}).compile()
const { unit, unitRef } = TestBed.create(AuthController).compile()

controller = module.get<AuthController>(AuthController)
controller = unit
authService = unitRef.get(AuthService)
})

it('should be defined', () => {
expect(controller).toBeDefined()
})

it('should set cookies on google login callback', async () => {
const req = createMock<Request>({
user: {
email: 'test-email',
name: 'test-name',
picture: 'test-picture'
}
})

const res = createMock<Response>()
jest.spyOn(authService, 'signIn').mockResolvedValue('token')

await controller.googleLoginCallback(req, res)

expect(authService.signIn).toHaveBeenCalledWith({
email: 'test-email',
name: 'test-name',
picture: 'test-picture'
})

expect(res.cookie).toHaveBeenCalledWith('access_token', 'token')
})
})
51 changes: 46 additions & 5 deletions apps/api/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,60 @@
import { Test, TestingModule } from '@nestjs/testing'
import { TestBed } from '@automock/jest'
import { JwtService } from '@nestjs/jwt'

import { UsersService } from '../users/users.service'

import { AuthService } from './auth.service'

describe('AuthService', () => {
let service: AuthService

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService]
}).compile()
const { unit } = TestBed.create(AuthService)
.mock(UsersService)
.using({
findOrCreate: jest.fn().mockResolvedValue({ id: 1, email: '[email protected]' })
})
.mock(JwtService)
.using({
sign: jest.fn().mockReturnValue('jwt')
})
.compile()

service = module.get<AuthService>(AuthService)
service = unit
})

it('should be defined', () => {
expect(service).toBeDefined()
})

it('should return a user and token', async () => {
const user = await service.signIn({
email: 'test',
name: 'test',
picture: 'test'
})
expect(user).toEqual('jwt')
})

it('should throw an error if no profile is provided', async () => {
try {
// @ts-expect-error: Testing OAuth provider does not return profile
await service.signIn(null)
} catch (e) {
expect(e.message).toEqual('Unauthenticated')
}
})

it('should throw an error if no email is provided', async () => {
try {
await service.signIn({
// @ts-expect-error: Testing when OAuth provider does not return email
email: null,
name: 'test',
picture: 'test'
})
} catch (e) {
expect(e.message).toEqual('Email not found')
}
})
})
5 changes: 5 additions & 0 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ export class AuthService {
return this.jwtService.sign(payload)
}

// TODO: Implement ProfileDTO for improved validation
async signIn (profile: Profile) {
if (!profile) {
throw new BadRequestException('Unauthenticated')
}

if (!profile.email) {
throw new BadRequestException('Email not found')
}

const user = await this.usersService.findOrCreate({
email: profile.email,
name: profile.name,
Expand Down
89 changes: 84 additions & 5 deletions apps/api/src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,98 @@
import { Test, TestingModule } from '@nestjs/testing'
import { TestBed } from '@automock/jest'

import { userFactory } from '../database/factories/user'
import { PrismaService } from '../database/prisma.service'

import { UsersService } from './users.service'

const mockUser = userFactory.build({
email: 'test-email',
name: 'test-name',
picture: 'test-picture'
})

describe('UsersService', () => {
let service: UsersService
const prisma = {
user: {
create: jest.fn().mockResolvedValue(mockUser),
upsert: jest.fn().mockResolvedValue(mockUser),
findMany: jest.fn().mockResolvedValue([mockUser]),
findUnique: jest.fn().mockResolvedValue(mockUser),
update: jest.fn().mockResolvedValue(mockUser),
delete: jest.fn().mockResolvedValue(mockUser)
}
}

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService]
}).compile()
const { unit } = TestBed.create(UsersService)
.mock(PrismaService)
.using(prisma)
.compile()

service = module.get<UsersService>(UsersService)
service = unit
})

it('should be defined', () => {
expect(service).toBeDefined()
})

it('should create a user', async () => {
const user = await service.create({
email: 'test-email',
name: 'test-name',
picture: 'test-picture'
})

expect(user).toMatchObject({ id: 1, email: 'test-email', name: 'test-name', picture: 'test-picture' })
})

it('should find or create a user', async () => {
const user = await service.findOrCreate({
email: 'test-email',
name: 'test-name',
picture: 'test-picture'
})

expect(user).toEqual(mockUser)
})

it('should return an array of users', async () => {
const users = await service.findAll()

expect(users).toEqual([mockUser])
})

it('should return a user by id', async () => {
const user = await service.findOne(1)

expect(user).toEqual(mockUser)
})

it('should update a user', async () => {
const user = await service.update(1, {
email: 'test-email',
name: 'test-name',
picture: 'test-picture'
})

expect(user).toEqual(mockUser)
})

it('should remove a user', async () => {
prisma.user.delete.mockResolvedValueOnce(mockUser)
const user = await service.remove(1)

expect(user).toEqual(mockUser)
})

describe('when user does not exist', () => {
const error = new Error('Prisma error')
Object.assign(error, { code: 'P2025' })

it('should throw an error', async () => {
prisma.user.delete.mockRejectedValueOnce(error)
await expect(service.remove(2)).rejects.toThrow('User not found')
})
})
})
14 changes: 10 additions & 4 deletions apps/api/src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common'
import { Injectable, NotFoundException } from '@nestjs/common'

import { PrismaService } from '../database/prisma.service'

Expand Down Expand Up @@ -39,8 +39,14 @@ export class UsersService {
}

async remove (id: number) {
return await this.db.user.delete({
where: { id }
})
try {
return await this.db.user.delete({
where: { id }
})
} catch (error) {
if (error.code === 'P2025') {
throw new NotFoundException('User not found')
}
}
}
}
Empty file added apps/api/test/auth.e2e-spec.ts
Empty file.

0 comments on commit 33cb851

Please sign in to comment.