From 2cc4f52d1a7586656fa494e3973363ab52bd9b22 Mon Sep 17 00:00:00 2001 From: DongHyeok Lim Date: Wed, 13 Dec 2023 17:22:01 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=85=20=EB=AA=A8=EA=B0=81=EC=BD=94=20?= =?UTF-8?q?=EB=AA=A8=EC=A7=91=20=ED=95=84=ED=84=B0=EB=A7=81=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20404=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mogaco-boards.repository.spec.ts | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) 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..eb4b39cdb 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 { NotFoundException } from '@nestjs/common'; +import { MogacoStatus } from './enum/mogaco-status.enum'; describe('MogacoRepository', () => { let repository: MogacoRepository; @@ -281,6 +283,7 @@ describe('MogacoRepository', () => { 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,20 +293,41 @@ 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', () => { it('특정 id에 해당하는 모각코를 반환해야 함', async () => { + const id = 999; jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); jest.spyOn(repository, 'getParticipants').mockResolvedValueOnce(mockParticipants); const result = await repository.getMogacoById(Number(mockMogaco.id), mockMember); + try { + await repository.getMogacoById(id, mockMember); + } catch (error) { + expect(error).toBeInstanceOf(NotFoundException); + expect(error.message).toBe(`Mogaco with id ${id} not found`); + } + expect(result).toEqual(expectedMogacoById); }); }); @@ -360,6 +384,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 +398,13 @@ describe('MogacoRepository', () => { userId: mockMember.id, }, }); + + expect(prismaService.mogaco.update).toHaveBeenCalledWith({ + where: { id: mockMogaco.id }, + data: { + status: MogacoStatus.CLOSED, + }, + }); }); }); @@ -404,28 +436,34 @@ 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, }, }, From 45cae3716583de592d73622538249cd1f41d5238 Mon Sep 17 00:00:00 2001 From: DongHyeok Lim Date: Wed, 13 Dec 2023 17:30:17 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=85=20=EB=AA=A8=EA=B0=81=EC=BD=94=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EC=9D=B4=20=EC=A7=80=EB=82=98=EB=A9=B4=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=EB=A1=9C=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mogaco-boards.repository.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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 eb4b39cdb..5bd1cd54a 100644 --- a/app/backend/src/mogaco-boards/mogaco-boards.repository.spec.ts +++ b/app/backend/src/mogaco-boards/mogaco-boards.repository.spec.ts @@ -280,6 +280,32 @@ 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); From 69b725ab0d7339e5a7b3e95005aad2bdc3b6ba7f Mon Sep 17 00:00:00 2001 From: DongHyeok Lim Date: Wed, 13 Dec 2023 18:17:40 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=85=20=EB=AA=A8=EA=B0=81=EC=BD=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=B2=98=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mogaco-boards.repository.spec.ts | 101 ++++++++++++++++-- 1 file changed, 90 insertions(+), 11 deletions(-) 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 5bd1cd54a..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,7 +3,7 @@ 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 { NotFoundException } from '@nestjs/common'; +import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { MogacoStatus } from './enum/mogaco-status.enum'; describe('MogacoRepository', () => { @@ -326,7 +326,7 @@ describe('MogacoRepository', () => { expect(result).toEqual(expectedMogaco); }); - it('해당 날짜에 모각코가 없으면 NotFoundException 처리', async () => { + it('해당 날짜에 모각코가 없으면 NotFoundException 발생', async () => { const date = '2023-12-31'; jest.spyOn(prismaService.mogaco, 'findMany').mockResolvedValue([]); @@ -341,20 +341,18 @@ describe('MogacoRepository', () => { describe('getMogacoById', () => { it('특정 id에 해당하는 모각코를 반환해야 함', async () => { - const id = 999; jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(mockMogaco); jest.spyOn(repository, 'getParticipants').mockResolvedValueOnce(mockParticipants); const result = await repository.getMogacoById(Number(mockMogaco.id), mockMember); + expect(result).toEqual(expectedMogacoById); + }); - try { - await repository.getMogacoById(id, mockMember); - } catch (error) { - expect(error).toBeInstanceOf(NotFoundException); - expect(error.message).toBe(`Mogaco with id ${id} not found`); - } + it('존재하지 않는 모각코를 조회할 경우 NotFoundException 발생', async () => { + const id = 999; + jest.spyOn(prismaService.mogaco, 'findUnique').mockResolvedValueOnce(null); - expect(result).toEqual(expectedMogacoById); + await expect(repository.getMogacoById(id, mockMember)).rejects.toThrowError(NotFoundException); }); }); @@ -377,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) }, @@ -390,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', () => { @@ -401,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', () => { @@ -432,6 +466,24 @@ describe('MogacoRepository', () => { }, }); }); + + 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); + }); }); describe('getParticipants', () => { @@ -495,5 +547,32 @@ describe('MogacoRepository', () => { }, }); }); + + 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); + } + }); }); }); From 8a6665723d60edf5b374150f7efabb304c244e75 Mon Sep 17 00:00:00 2001 From: DongHyeok Lim Date: Wed, 13 Dec 2023 22:13:35 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=85=20=EA=B7=B8=EB=A3=B9=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=9D=B8=EC=9B=90=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=98=A4=EB=A5=98=20=EC=B2=98=EB=A6=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/groups/groups.repository.spec.ts | 84 ++++++++++++------- 1 file changed, 54 insertions(+), 30 deletions(-) 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', () => {