diff --git a/app/backend/src/groups/groups.repository.spec.ts b/app/backend/src/groups/groups.repository.spec.ts index 7a6416af0..f1d94fbe8 100644 --- a/app/backend/src/groups/groups.repository.spec.ts +++ b/app/backend/src/groups/groups.repository.spec.ts @@ -3,6 +3,7 @@ import { GroupsRepository } from './groups.repository'; import { PrismaService } from 'prisma/prisma.service'; import { CreateGroupsDto } from './dto/create-groups.dto'; import { Member } from '@prisma/client'; +import { ForbiddenException, NotFoundException } from '@nestjs/common'; describe('GroupsRepository', () => { let repository: GroupsRepository; @@ -25,6 +26,7 @@ describe('GroupsRepository', () => { findUnique: jest.fn(), create: jest.fn(), delete: jest.fn(), + count: jest.fn(), }, }, }, @@ -35,13 +37,22 @@ describe('GroupsRepository', () => { prismaService = module.get(PrismaService); }); + const mockMember: Member = { + id: BigInt(1), + providerId: '11111122222222', + email: 'morak@gmail.com', + nickname: 'morak morak', + profilePicture: 'morak morak.jpg', + socialType: 'google', + createdAt: new Date(), + }; + describe('getAllGroups', () => { it('모든 그룹을 반환해야 함', async () => { - const expectedGroups = [ - { id: BigInt(1), title: '부스트캠프 웹·모바일 8기', membersCount: 212 }, - { id: BigInt(2), title: '부스트캠프 웹·모바일 9기', membersCount: 187 }, - ]; + const expectedGroups = [{ id: BigInt(1), title: '부스트캠프 웹·모바일 8기', membersCount: 212 }]; + const groupId = 212; jest.spyOn(prismaService.group, 'findMany').mockResolvedValue(expectedGroups); + jest.spyOn(prismaService.groupToUser, 'count').mockResolvedValue(groupId); const result = await repository.getAllGroups(); @@ -96,15 +107,6 @@ describe('GroupsRepository', () => { describe('joinGroup', () => { it('그룹에 참여해야 함', async () => { const groupId = 1; - const member: Member = { - id: BigInt(1), - providerId: '11111122222222', - email: 'morak@gmail.com', - nickname: 'morak morak', - profilePicture: 'morak morak.jpg', - socialType: 'google', - createdAt: new Date(), - }; jest.spyOn(prismaService.group, 'findUnique').mockResolvedValue({ id: BigInt(1), @@ -113,33 +115,43 @@ describe('GroupsRepository', () => { jest.spyOn(prismaService.groupToUser, 'create').mockResolvedValue({ groupId: BigInt(1), - userId: member.id, + userId: mockMember.id, }); - await repository.joinGroup(groupId, member); + await repository.joinGroup(groupId, mockMember); // 231204 ldhbenecia | 해당 함수가 특정 인자와 함께 호출되었는지 여부를 확인 expect(prismaService.groupToUser.create).toHaveBeenCalledWith({ data: { groupId: BigInt(1), - userId: member.id, + userId: mockMember.id, }, }); }); + + it('가입할 그룹이 없는 경우 NotFoundException 발생', async () => { + jest.spyOn(prismaService.group, 'findUnique').mockResolvedValueOnce(null); + + await expect(repository.joinGroup(1, mockMember)).rejects.toThrowError(NotFoundException); + }); + + it('그룹에 이미 가입한 멤버가 가입 요청을 하면 Forbidden 발생', async () => { + jest.spyOn(prismaService.group, 'findUnique').mockResolvedValue({ + id: BigInt(1), + title: '부스트캠프 웹·모바일 8기', + }); + jest.spyOn(prismaService.groupToUser, 'findUnique').mockResolvedValueOnce({ + groupId: BigInt(1), + userId: mockMember.id, + }); + + await expect(repository.joinGroup(1, mockMember)).rejects.toThrowError(ForbiddenException); + }); }); describe('leaveGroup', () => { it('그룹 탈퇴를 해야 함', async () => { const groupId = 1; - const member: Member = { - id: BigInt(1), - providerId: '11111122222222', - email: 'morak@gmail.com', - nickname: 'morak morak', - profilePicture: 'morak morak.jpg', - socialType: 'google', - createdAt: new Date(), - }; jest.spyOn(prismaService.group, 'findUnique').mockResolvedValue({ id: BigInt(1), @@ -148,21 +160,21 @@ describe('GroupsRepository', () => { jest.spyOn(prismaService.groupToUser, 'findUnique').mockResolvedValue({ groupId: BigInt(1), - userId: member.id, + userId: mockMember.id, }); jest.spyOn(prismaService.groupToUser, 'delete').mockResolvedValue({ groupId: BigInt(1), - userId: member.id, + userId: mockMember.id, }); - await repository.leaveGroup(groupId, member); + await repository.leaveGroup(groupId, mockMember); expect(prismaService.groupToUser.findUnique).toHaveBeenCalledWith({ where: { groupId_userId: { groupId: groupId, - userId: member.id, + userId: mockMember.id, }, }, }); @@ -171,11 +183,23 @@ describe('GroupsRepository', () => { where: { groupId_userId: { groupId: groupId, - userId: member.id, + userId: mockMember.id, }, }, }); }); + + it('탈퇴할 그룹이 없는 경우 NotFoundException 발생', async () => { + jest.spyOn(prismaService.group, 'findUnique').mockResolvedValueOnce(null); + + await expect(repository.leaveGroup(1, mockMember)).rejects.toThrowError(NotFoundException); + }); + + it('해당 멤버가 그룹에 없는데 탈퇴 요청을 할 경우 NotFoundException 발생', async () => { + jest.spyOn(prismaService.groupToUser, 'findUnique').mockResolvedValueOnce(null); + + await expect(repository.leaveGroup(1, mockMember)).rejects.toThrowError(NotFoundException); + }); }); describe('getMyGroups', () => { diff --git a/app/backend/src/mogaco-boards/mogaco-boards.repository.spec.ts b/app/backend/src/mogaco-boards/mogaco-boards.repository.spec.ts index eda66c645..1da3d576c 100644 --- a/app/backend/src/mogaco-boards/mogaco-boards.repository.spec.ts +++ b/app/backend/src/mogaco-boards/mogaco-boards.repository.spec.ts @@ -3,6 +3,8 @@ import { MogacoRepository } from './mogaco-boards.repository'; import { PrismaService } from 'prisma/prisma.service'; import { Member } from '@prisma/client'; import { Decimal } from '@prisma/client/runtime/library'; +import { ForbiddenException, NotFoundException } from '@nestjs/common'; +import { MogacoStatus } from './enum/mogaco-status.enum'; describe('MogacoRepository', () => { let repository: MogacoRepository; @@ -278,9 +280,36 @@ describe('MogacoRepository', () => { deletedAt: null, }; + describe('updateCompletedMogacos', () => { + it('모각코 시간이 지나면 "모집 중"에서 "종료"로 변경해야 함', async () => { + const currentDate = new Date(); + const mogacosMock = [ + { id: 1, date: new Date(currentDate.getTime() - 1000), status: '모집 중' }, + { id: 2, date: new Date(currentDate.getTime() + 1000), status: '종료' }, + ]; + + // findMany 메서드를 Jest의 Mock으로 형변환, Prisma의 Mock이 Jest Mock이 아니기 때문에 타입 캐스팅 + (prismaService.mogaco.findMany as jest.Mock).mockResolvedValue(mogacosMock); + + await repository['updateCompletedMogacos'](mogacosMock); + + expect(prismaService.mogaco.updateMany).toHaveBeenCalledWith({ + where: { + id: { + in: [1], + }, + }, + data: { + status: MogacoStatus.COMPLETED, + }, + }); + }); + }); + describe('getMogaco', () => { it('가입한 그룹의 모든 모각코 조회해야 함', async () => { jest.spyOn(prismaService.mogaco, 'findMany').mockResolvedValueOnce(mockResult); + jest.spyOn(repository, 'updateCompletedMogacos' as any).mockImplementationOnce(() => Promise.resolve()); const result = await repository.getAllMogaco(mockMember); @@ -290,11 +319,24 @@ describe('MogacoRepository', () => { it('가입한 그룹 내 특정 기간 모각코 조회해야 함', async () => { const date = '2023-12-01'; jest.spyOn(prismaService.mogaco, 'findMany').mockResolvedValue(mockResult); + jest.spyOn(repository, 'updateCompletedMogacos' as any).mockImplementationOnce(() => Promise.resolve()); const result = await repository.getMogacoByDate(date, mockMember); expect(result).toEqual(expectedMogaco); }); + + it('해당 날짜에 모각코가 없으면 NotFoundException 발생', async () => { + const date = '2023-12-31'; + jest.spyOn(prismaService.mogaco, 'findMany').mockResolvedValue([]); + + try { + await repository.getMogacoByDate(date, mockMember); + } catch (error) { + expect(error).toBeInstanceOf(NotFoundException); + expect(error.message).toBe(`No Mogaco events found for the date ${date}`); + } + }); }); describe('getMogacoById', () => { @@ -303,9 +345,15 @@ describe('MogacoRepository', () => { jest.spyOn(repository, 'getParticipants').mockResolvedValueOnce(mockParticipants); const result = await repository.getMogacoById(Number(mockMogaco.id), mockMember); - expect(result).toEqual(expectedMogacoById); }); + + it('존재하지 않는 모각코를 조회할 경우 NotFoundException 발생', async () => { + const id = 999; + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(null); + + await expect(repository.getMogacoById(id, mockMember)).rejects.toThrowError(NotFoundException); + }); }); describe('createMogaco', () => { @@ -327,7 +375,7 @@ describe('MogacoRepository', () => { jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); jest.spyOn(prismaService.mogaco, 'update').mockResolvedValueOnce(mockMogacoNoGroupMember); - await repository.deleteMogaco(1, mockMember); + await repository.deleteMogaco(Number(mockMogaco.id), mockMember); expect(prismaService.mogaco.findUnique).toHaveBeenCalledWith({ where: { id: Number(mockMogaco.id) }, @@ -340,6 +388,21 @@ describe('MogacoRepository', () => { }, }); }); + + it('존재하지 않는 모각코를 삭제할 경우 NotFoundException 발생', async () => { + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(null); + + await expect(repository.deleteMogaco(999, mockMember)).rejects.toThrowError(NotFoundException); + }); + + it('다른 멤버가 작성한 모각코를 삭제할 경우 ForbiddenException 발생', async () => { + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); + + // mockMember.id와 다른 멤버의 ID 설정 + const otherMember = { id: BigInt(999), providerId: 'otherProviderId' } as Member; + + await expect(repository.deleteMogaco(1, otherMember)).rejects.toThrowError(ForbiddenException); + }); }); describe('updateMogaco', () => { @@ -351,6 +414,27 @@ describe('MogacoRepository', () => { expect(result).toEqual(expectedUpdateMogaco); }); + + it('존재하지 않는 모각코인 경우 NotFoundException 발생', async () => { + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(null); + + await expect( + repository.updateMogaco(Number(mockMogaco.id), mockUpdateMogacoDto, mockMember), + ).rejects.toThrowError(NotFoundException); + }); + + it('멤버의 ID와 모각코의 작성자 ID가 다른 경우 ForbiddenException 발생', async () => { + const mockOtherMember = { + ...mockMember, + id: BigInt(2), + }; + + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); + + await expect( + repository.updateMogaco(Number(mockMogaco.id), mockUpdateMogacoDto, mockOtherMember), + ).rejects.toThrowError(ForbiddenException); + }); }); describe('joinMogaco', () => { @@ -360,6 +444,7 @@ describe('MogacoRepository', () => { postId: mockMogaco.id, userId: mockMember.id, }); + jest.spyOn(repository, 'getParticipantsCount' as any).mockResolvedValueOnce(mockMogaco.maxHumanCount - 1); await repository.joinMogaco(Number(mockMogaco.id), mockMember); @@ -373,6 +458,31 @@ describe('MogacoRepository', () => { userId: mockMember.id, }, }); + + expect(prismaService.mogaco.update).toHaveBeenCalledWith({ + where: { id: mockMogaco.id }, + data: { + status: MogacoStatus.CLOSED, + }, + }); + }); + + it('모각코가 이미 참가 인원이 가득 찬 경우 ForbiddenException 발생', async () => { + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); + jest.spyOn(prismaService.participant, 'findUnique').mockResolvedValueOnce(null); + jest.spyOn(repository, 'getParticipantsCount' as any).mockResolvedValueOnce(mockMogaco.maxHumanCount); + + await expect(repository.joinMogaco(Number(mockMogaco.id), mockMember)).rejects.toThrowError(ForbiddenException); + }); + + it('모각코에 이미 참가한 경우 ForbiddenException 발생', async () => { + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); + jest.spyOn(prismaService.participant, 'findUnique').mockResolvedValueOnce({ + postId: mockMogaco.id, + userId: mockMember.id, + }); + + await expect(repository.joinMogaco(Number(mockMogaco.id), mockMember)).rejects.toThrowError(ForbiddenException); }); }); @@ -404,32 +514,65 @@ describe('MogacoRepository', () => { describe('cancelMogacoJoin', () => { it('모각코 참가를 취소해야 함', async () => { - const mockParticipant = { - postId: mockMogaco.id, - userId: mockMember.id, + const mockClosedMogaco = { + ...mockMogaco, + status: MogacoStatus.CLOSED, }; - jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); - jest.spyOn(prismaService.participant, 'findMany').mockResolvedValueOnce([mockParticipant]); + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockClosedMogaco); jest.spyOn(prismaService.participant, 'findUnique').mockResolvedValueOnce({ - postId: mockMogaco.id, + postId: mockClosedMogaco.id, userId: mockMember.id, }); jest.spyOn(prismaService.participant, 'delete').mockResolvedValueOnce({ - postId: mockMogaco.id, + postId: mockClosedMogaco.id, userId: mockMember.id, }); - await repository.cancelMogacoJoin(Number(mockMogaco.id), mockMember); + await repository.cancelMogacoJoin(Number(mockClosedMogaco.id), mockMember); + + expect(prismaService.mogaco.update).toHaveBeenCalledWith({ + where: { id: mockClosedMogaco.id }, + data: { + status: MogacoStatus.RECRUITING, + }, + }); expect(prismaService.participant.delete).toHaveBeenCalledWith({ where: { postId_userId: { - postId: mockMogaco.id, + postId: mockClosedMogaco.id, userId: mockMember.id, }, }, }); }); + + it('취소할 모각코가 없는 경우 NotFoundException 발생', async () => { + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(null); + + await expect(repository.cancelMogacoJoin(1, mockMember)).rejects.toThrowError(NotFoundException); + }); + + it('해당 모각코 참여자에 없는 경우 NotFoundException 발생', async () => { + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); + jest.spyOn(prismaService.participant, 'findUnique').mockResolvedValueOnce(null); + + await expect(repository.cancelMogacoJoin(1, mockMember)).rejects.toThrowError(NotFoundException); + }); + + it('취소 권한이 없는 경우 Forbidden 발생', async () => { + const participantId = 999; // Some other user's ID + const mockParticipant = { postId: BigInt(1), userId: BigInt(participantId) }; + + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); + jest.spyOn(prismaService.participant, 'findUnique').mockResolvedValueOnce(mockParticipant); + + try { + await repository.cancelMogacoJoin(1, mockMember); + } catch (error) { + expect(error).toBeInstanceOf(ForbiddenException); + } + }); }); });