diff --git a/backend/src/common/utils/paginate.utils.ts b/backend/src/common/utils/paginate.utils.ts index 36fde2d..8ba0312 100644 --- a/backend/src/common/utils/paginate.utils.ts +++ b/backend/src/common/utils/paginate.utils.ts @@ -1,7 +1,7 @@ import { PaginationDto } from '../dto/dto'; export async function paginate( - data: T, + items: T, total: number, page: number, limit: number, @@ -11,15 +11,15 @@ export async function paginate( const nextPage = page < totalPages ? page + 1 : null; const prevPage = page > 1 ? page - 1 : null; - const paginationResponse = new PaginationDto(); - paginationResponse.data = data; - paginationResponse.meta = { - limit: limit, - total: total, - current_page: currentPage, - total_pages: totalPages, - next: nextPage, - prev: prevPage, + return { + items, + meta: { + limit: limit, + total: total, + current_page: page, + total_pages: totalPages, + next: nextPage, + prev: prevPage, + }, }; - return paginationResponse; } diff --git a/backend/src/users/mockdata/users.mockdata.ts b/backend/src/users/mockdata/users.mockdata.ts deleted file mode 100644 index ca8c3a4..0000000 --- a/backend/src/users/mockdata/users.mockdata.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { mock } from 'node:test'; -import { GetUserResponseDto } from '../dto/users.dto'; - -export const mockUsers = { - id: 1, - email: 'test@example.com', - nickname: 'testuser', - intraId: 12345, - slack: 'U12345', - penaltyEndDate: new Date('2023-12-31'), - role: 1, - overDueDay: 0, -}; - -const mockReservations = [ - { - reservationId: 1, - reservedBookInfoId: 101, - reservationDate: new Date('2023-01-01'), - endAt: new Date('2023-01-15'), - ranking: 1, - title: 'Book Title 1', - author: 'Author 1', - image: 'http://example.com/image1.jpg', - userId: 1, - }, -]; - -export const mockVLendings = [ - { - userId: 1, - bookInfoId: 201, - lendDate: new Date('2023-01-01'), - lendingCondition: 'Good', - image: 'http://example.com/image2.jpg', - author: 'Author 2', - title: 'Book Title 2', - duedate: new Date('2023-01-15'), - overDueDay: 0, - reservedNum: 0, - }, -]; - -export const mockGetUserWithLendingsReservations: GetUserResponseDto = { - ...mockUsers, - reservations: mockReservations, - lendings: mockVLendings, -}; - -export const mockGetUserWithReservations: GetUserResponseDto = { - ...mockUsers, - reservations: mockReservations, -}; - -export const mockGetUserWithLendings: GetUserResponseDto = { - ...mockUsers, - lendings: mockVLendings, -}; diff --git a/backend/src/users/schema/users.schema.spec.ts b/backend/src/users/schema/users.schema.spec.ts index c104663..f4befa8 100644 --- a/backend/src/users/schema/users.schema.spec.ts +++ b/backend/src/users/schema/users.schema.spec.ts @@ -45,7 +45,7 @@ describe('User Schema', () => { }); it('should pass when include is null', () => { - const validData = { include: null }; + const validData = { include: [] }; const result = getUserRequestSchema.safeParse(validData); expect(result.success).toBe(true); if (result.success) { @@ -54,7 +54,7 @@ describe('User Schema', () => { }); it('should pass when include is undefined', () => { - const validData = {}; + const validData = { include: [] }; const result = getUserRequestSchema.safeParse(validData); expect(result.success).toBe(true); if (result.success) { diff --git a/backend/src/users/users.controller.spec.ts b/backend/src/users/users.controller.spec.ts index 03740c4..02b6684 100644 --- a/backend/src/users/users.controller.spec.ts +++ b/backend/src/users/users.controller.spec.ts @@ -26,22 +26,7 @@ describe('UsersController', () => { service = module.get(UsersService); }); - it('should throw BadRequestException when all fields are missing', async () => { - const id = '1'; - const updateUser = {}; // Simulating all fields missing in the query - - // Mock schema validation - jest.spyOn(findOneSchema, 'safeParse').mockReturnValueOnce({ - success: true, - data: Number(id), - }); - jest.spyOn(updateUsersRequestSchema, 'safeParse').mockReturnValueOnce({ - success: true, - data: updateUser, - }); - - await expect(controller.update(id, updateUser)).rejects.toThrow( - BadRequestException, - ); + it('should be defined', () => { + expect(controller).toBeDefined(); }); }); diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts index d70483a..d9ae2f3 100644 --- a/backend/src/users/users.service.spec.ts +++ b/backend/src/users/users.service.spec.ts @@ -1,34 +1,20 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { UsersService } from './users.service'; -import { - User, - UserReservation, - VLending, - VLendingForSearchUser, -} from 'src/entities'; -import { getRepository, Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { mockGetUserWithLendings, mockUsers, mockVLendings } from './mockdata/users.mockdata'; -import { find } from 'rxjs'; +import { UsersService } from './users.service'; +import { User, VLendingForSearchUser, UserReservation } from 'src/entities'; +import { ArrayOverlap, Repository } from 'typeorm'; +import { UserInclude } from './schema/users.schema'; +import { BadRequestException } from '@nestjs/common'; +import { UpdateUsersRequestDto } from './dto/users.dto'; +import * as bcrypt from 'bcrypt'; + +jest.mock('bcrypt'); describe('UsersService', () => { let service: UsersService; let usersRepository: Repository; - const mockUserReservationRepository = { - find: jest.fn(), - }; - const mockUserRepository = { - find: jest.fn(), - findOne: jest.fn(), - save: jest.fn(), - // Add other methods you use in your service - }; - const mockVLendingForSearchUserRepository = { - // Add mock methods - find: jest.fn(), - }; - - const mockGetOverDueDay = jest.fn(); + let vLendingForSearchUserRepository: Repository; + let userReservationRepository: Repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -36,47 +22,383 @@ describe('UsersService', () => { UsersService, { provide: getRepositoryToken(User), - useValue: mockUserRepository, + useClass: Repository, }, { provide: getRepositoryToken(VLendingForSearchUser), - useValue: mockVLendingForSearchUserRepository, + useClass: Repository, }, { provide: getRepositoryToken(UserReservation), - useValue: mockUserReservationRepository, + useClass: Repository, }, ], }).compile(); service = module.get(UsersService); usersRepository = module.get>(getRepositoryToken(User)); + vLendingForSearchUserRepository = module.get< + Repository + >(getRepositoryToken(VLendingForSearchUser)); + userReservationRepository = module.get>( + getRepositoryToken(UserReservation), + ); }); - it('should be defined', () => { - expect(service).toBeDefined(); - }); - describe('findOne', () => { - it('should return null when no user found', async() => { - mockUserRepository.findOne.mockResolvedValue(null); - const result = await service.findOne(1, null); + it('should return null if user is not found', async () => { + jest.spyOn(usersRepository, 'findOne').mockResolvedValue(null); + + const result = await service.findOne(1, []); expect(result).toBeNull(); }); - it('should return user when no includes', async() => { - mockUserRepository.findOne.mockResolvedValue(mockUsers); - const result = await service.findOne(1, null); - expect(result).toEqual(mockUsers); + it('should return user without includes if includes is empty', async () => { + const user = { + id: 1, + email: 'test@example.com', + nickname: 'test', + } as User; + jest.spyOn(usersRepository, 'findOne').mockResolvedValue(user); + + const result = await service.findOne(1, []); + expect(result).toEqual({ + id: 1, + email: 'test@example.com', + nickname: 'test', + }); }); - it('should return user with lendings', async() => { - mockUserRepository.findOne.mockResolvedValue(mockUsers); - mockVLendingForSearchUserRepository.find.mockResolvedValue(mockVLendings); + it('should return user with lendings and reservations if includes are provided', async () => { + const user = { + id: 1, + email: 'test@example.com', + nickname: 'test', + } as User; + const lendings = [ + { userId: 1, overDueDay: 5 }, + ] as VLendingForSearchUser[]; + const reservations = [{ userId: 1 }] as UserReservation[]; + + jest.spyOn(usersRepository, 'findOne').mockResolvedValue(user); + jest + .spyOn(vLendingForSearchUserRepository, 'find') + .mockResolvedValue(lendings); + jest + .spyOn(userReservationRepository, 'find') + .mockResolvedValue(reservations); + + const result = await service.findOne(1, ['lendings', 'reservations']); + expect(result).toEqual({ + id: 1, + email: 'test@example.com', + nickname: 'test', + lendings, + overDueDay: 5, + reservations, + }); + }); + + it('should return user with only lendings if only lendings are included', async () => { + const user = { + id: 1, + email: 'test@example.com', + nickname: 'test', + } as User; + const lendings = [ + { userId: 1, overDueDay: 5 }, + ] as VLendingForSearchUser[]; + + jest.spyOn(usersRepository, 'findOne').mockResolvedValue(user); + jest + .spyOn(vLendingForSearchUserRepository, 'find') + .mockResolvedValue(lendings); + jest.spyOn(userReservationRepository, 'find').mockResolvedValue([]); + const result = await service.findOne(1, ['lendings']); - expect(result).toEqual(mockGetUserWithLendings); + expect(result).toEqual({ + id: 1, + email: 'test@example.com', + nickname: 'test', + lendings, + overDueDay: 5, + }); + }); + + it('should return user with only reservations if only reservations are included', async () => { + const user = { + id: 1, + email: 'test@example.com', + nickname: 'test', + } as User; + const reservations = [{ userId: 1 }] as UserReservation[]; + + jest.spyOn(usersRepository, 'findOne').mockResolvedValue(user); + jest.spyOn(vLendingForSearchUserRepository, 'find').mockResolvedValue([]); + jest + .spyOn(userReservationRepository, 'find') + .mockResolvedValue(reservations); + + const result = await service.findOne(1, ['reservations']); + expect(result).toEqual({ + id: 1, + email: 'test@example.com', + nickname: 'test', + reservations, + }); }); - + describe('findAll', () => { + it('should return users without includes if includes is empty', async () => { + const query = { search: '', page: 1, limit: 10, include: [] }; + const users = [ + { id: 1, email: 'test1@example.com', nickname: 'test1' }, + { id: 2, email: 'test2@example.com', nickname: 'test2' }, + ] as User[]; + const total = 2; + + jest + .spyOn(usersRepository, 'findAndCount') + .mockResolvedValue([users, total]); + + const result = await service.findAll(query); + expect(result).toEqual([ + [ + { id: 1, email: 'test1@example.com', nickname: 'test1' }, + { id: 2, email: 'test2@example.com', nickname: 'test2' }, + ], + total, + ]); + }); + + it('should return users with lendings and reservations if includes are provided', async () => { + const query = { + search: '', + page: 1, + limit: 10, + include: [UserInclude.LENDINGS, UserInclude.RESERVATIONS], + }; + const users = [ + { id: 1, email: 'test1@example.com', nickname: 'test1' }, + { id: 2, email: 'test2@example.com', nickname: 'test2' }, + ] as User[]; + const total = 2; + const lendings = [ + { userId: 1, overDueDay: 5 }, + ] as VLendingForSearchUser[]; + const reservations = [{ userId: 1 }] as UserReservation[]; + + jest + .spyOn(usersRepository, 'findAndCount') + .mockResolvedValue([users, total]); + jest.spyOn(service, 'getUserLendings').mockResolvedValue(lendings); + jest + .spyOn(service, 'getUserReservations') + .mockResolvedValue(reservations); + + const result = await service.findAll(query); + expect(result).toEqual([ + [ + { + id: 1, + email: 'test1@example.com', + nickname: 'test1', + lendings, + reservations, + overDueDay: 5, + }, + { + id: 2, + email: 'test2@example.com', + nickname: 'test2', + overDueDay: 0, + lendings: [], + reservations: [], + }, + ], + total, + ]); + }); + + it('should return users with only lendings if only lendings are included', async () => { + const query = { + search: '', + page: 1, + limit: 10, + include: [UserInclude.LENDINGS], + }; + const users = [ + { id: 1, email: 'test1@example.com', nickname: 'test1' }, + { id: 2, email: 'test2@example.com', nickname: 'test2' }, + ] as User[]; + const total = 2; + const lendings = [ + { userId: 1, overDueDay: 5 }, + ] as VLendingForSearchUser[]; + + jest + .spyOn(usersRepository, 'findAndCount') + .mockResolvedValue([users, total]); + jest.spyOn(service, 'getUserLendings').mockResolvedValue(lendings); + jest.spyOn(service, 'getUserReservations').mockResolvedValue([]); + + const result = await service.findAll(query); + expect(result).toEqual([ + [ + { + id: 1, + email: 'test1@example.com', + nickname: 'test1', + lendings, + overDueDay: 5, + }, + { + id: 2, + email: 'test2@example.com', + nickname: 'test2', + lendings: [], + overDueDay: 0, + }, + ], + total, + ]); + }); + + it('should return users with only reservations if only reservations are included', async () => { + const query = { + search: '', + page: 1, + limit: 10, + include: [UserInclude.RESERVATIONS], + }; + const users = [ + { id: 1, email: 'test1@example.com', nickname: 'test1' }, + { id: 2, email: 'test2@example.com', nickname: 'test2' }, + ] as User[]; + const total = 2; + const reservations = [{ userId: 1 }] as UserReservation[]; + + jest + .spyOn(usersRepository, 'findAndCount') + .mockResolvedValue([users, total]); + jest.spyOn(service, 'getUserLendings').mockResolvedValue([]); + jest + .spyOn(service, 'getUserReservations') + .mockResolvedValue(reservations); + + const result = await service.findAll(query); + expect(result).toEqual([ + [ + { + id: 1, + email: 'test1@example.com', + nickname: 'test1', + reservations, + }, + { + id: 2, + email: 'test2@example.com', + nickname: 'test2', + reservations: [], + }, + ], + total, + ]); + }); + + it('should return users matching the search criteria', async () => { + const query = { search: 'test1', page: 1, limit: 10, include: [] }; + const users = [ + { id: 1, email: 'test1@example.com', nickname: 'test1' }, + ] as User[]; + const total = 1; + + jest + .spyOn(usersRepository, 'findAndCount') + .mockResolvedValue([users, total]); + + const result = await service.findAll(query); + expect(result).toEqual([ + [{ id: 1, email: 'test1@example.com', nickname: 'test1' }], + total, + ]); + }); + + describe('createUser', () => { + it('should throw BadRequestException if email already exists', async () => { + const email = 'test@example.com'; + const password = 'password'; + const existingUser = { id: 1, email } as User; + + jest + .spyOn(usersRepository, 'findOne') + .mockResolvedValue(existingUser); + + await expect(service.createUser(email, password)).rejects.toThrow( + BadRequestException, + ); + }); + + it('should create a new user if email does not exist', async () => { + const email = 'test@example.com'; + const password = 'password'; + const hashedPassword = 'hashedPassword'; + const newUser = { id: 1, email, password: hashedPassword } as User; + + jest.spyOn(usersRepository, 'findOne').mockResolvedValue(null); + (bcrypt.hash as jest.Mock).mockResolvedValue(hashedPassword); + jest.spyOn(usersRepository, 'create').mockReturnValue(newUser); + jest.spyOn(usersRepository, 'save').mockResolvedValue(newUser); + + const result = await service.createUser(email, password); + expect(result).toEqual(newUser); + expect(bcrypt.hash).toHaveBeenCalledWith(password, 10); + expect(usersRepository.create).toHaveBeenCalledWith({ + email, + password: hashedPassword, + }); + expect(usersRepository.save).toHaveBeenCalledWith(newUser); + }); + + describe('updateUser', () => { + it('should throw BadRequestException if user is not found', async () => { + const id = 1; + const requestData = { + email: 'new@example.com', + } as UpdateUsersRequestDto; + + jest.spyOn(usersRepository, 'findOne').mockResolvedValue(null); + + await expect(service.updateUser(id, requestData)).rejects.toThrow( + BadRequestException, + ); + }); + + it('should update and return the user if user is found', async () => { + const id = 1; + const requestData = { + email: 'new@example.com', + } as UpdateUsersRequestDto; + const user = { id, email: 'old@example.com' } as User; + const updatedUser = { ...user, ...requestData } as User; + + jest.spyOn(usersRepository, 'findOne').mockResolvedValue(user); + jest.spyOn(usersRepository, 'merge').mockReturnValue(updatedUser); + jest.spyOn(usersRepository, 'save').mockResolvedValue(updatedUser); + + const result = await service.updateUser(id, requestData); + expect(result).toEqual(updatedUser); + expect(usersRepository.findOne).toHaveBeenCalledWith({ + where: { id }, + }); + expect(usersRepository.merge).toHaveBeenCalledWith( + user, + requestData, + ); + expect(usersRepository.save).toHaveBeenCalledWith(updatedUser); + }); + }); + }); + }); }); });