From b98741e9b8eb3c37f80b0b6e5f786c96baff73f4 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 17 Mar 2026 11:29:47 +0900 Subject: [PATCH 01/63] =?UTF-8?q?debug:=20=EB=AA=A8=EC=9E=84=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20=EC=A1=B0=ED=9A=8C=20(=EC=BD=95=ED=94=8C=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EB=AA=A8=EB=93=9C)=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EB=88=84=EB=9D=BD=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo/domain/party/service/PartyQueryServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/umc/cockple/demo/domain/party/service/PartyQueryServiceImpl.java b/src/main/java/umc/cockple/demo/domain/party/service/PartyQueryServiceImpl.java index eaf47b21c..d36e247f0 100644 --- a/src/main/java/umc/cockple/demo/domain/party/service/PartyQueryServiceImpl.java +++ b/src/main/java/umc/cockple/demo/domain/party/service/PartyQueryServiceImpl.java @@ -307,7 +307,7 @@ private Slice getCockpleRecommendedParties(Long memberId, String search, //이름 검색 필터 적용 List searchedParties = filterByName(filteredParties, search); //키워드 일치 개수로 정렬 - List sortedParties = sortPartiesByKeywordMatch(filteredParties, partiesInfo.keywords()); + List sortedParties = sortPartiesByKeywordMatch(searchedParties, partiesInfo.keywords()); //수동으로 페이징 Slice partySlice = paginate(sortedParties, pageable); From 47dcc4607b6925184fa324a40589cfea26db3c3d Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 16 Mar 2026 11:15:44 +0900 Subject: [PATCH 02/63] =?UTF-8?q?test:=20=EB=82=B4=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C(/api/my/parties)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 54 +++++++++++-- .../party/service/PartyQueryServiceTest.java | 76 +++++++++++++++++-- 2 files changed, 116 insertions(+), 14 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 87f8f8245..f759b64fb 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -31,13 +31,20 @@ class PartyIntegrationTest extends IntegrationTestBase { - @Autowired MockMvc mockMvc; - @Autowired MemberRepository memberRepository; - @Autowired PartyRepository partyRepository; - @Autowired MemberPartyRepository memberPartyRepository; - @Autowired PartyAddrRepository partyAddrRepository; - @Autowired ExerciseRepository exerciseRepository; - @Autowired MemberExerciseRepository memberExerciseRepository; + @Autowired + MockMvc mockMvc; + @Autowired + MemberRepository memberRepository; + @Autowired + PartyRepository partyRepository; + @Autowired + MemberPartyRepository memberPartyRepository; + @Autowired + PartyAddrRepository partyAddrRepository; + @Autowired + ExerciseRepository exerciseRepository; + @Autowired + MemberExerciseRepository memberExerciseRepository; private Member manager; private Member normalMember; @@ -127,4 +134,37 @@ void fail_partyInactive() throws Exception { .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_IS_DELETED.getMessage())); } } + + @Nested + @DisplayName("GET /api/my/parties - 내 모임 조회") + class GetMyParties { + + @Test + @DisplayName("200 - 사용자가 가입한 모임 목록을 페이징하여 반환한다") + void success_getMyParties() throws Exception { + mockMvc.perform(get("/api/my/parties") + .param("created", "false") + .param("sort", "최신순") + .param("size", "10") + .param("page", "0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("성공입니다.")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content.length()").value(1)) + .andExpect(jsonPath("$.data.content[0].partyName").value("테스트 모임")) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) + .andExpect(jsonPath("$.data.pageable.pageNumber").value(0)) + .andExpect(jsonPath("$.data.hasNext").value(false)); + } + + @Test + @DisplayName("401 - 인증되지 않은 사용자 접근시 에러를 반환한다") + void fail_unauthorized() throws Exception { + SecurityContextHelper.clearAuthentication(); + + mockMvc.perform(get("/api/my/parties")) + .andExpect(status().isUnauthorized()); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 5f6a9005e..59d02c7a7 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -29,6 +29,15 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; +import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; +import umc.cockple.demo.domain.party.dto.PartyDTO; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -51,6 +60,10 @@ class PartyQueryServiceTest { private MemberPartyRepository memberPartyRepository; @Mock private MemberExerciseRepository memberExerciseRepository; + @Mock + private ExerciseRepository exerciseRepository; + @Mock + private PartyBookmarkRepository partyBookmarkRepository; @Nested @DisplayName("getPartyMembers") @@ -76,7 +89,7 @@ void success() { List memberParties = List.of(mp1, mp2); LocalDate lastDate = LocalDate.of(2025, 1, 10); - List rawResult = List.of(new Object[]{20L, lastDate}); + List rawResult = List.of(new Object[] { 20L, lastDate }); PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() .summary(PartyMemberDTO.Summary.builder() @@ -101,8 +114,7 @@ void success() { verify(partyConverter).toPartyMemberDTO( eq(memberParties), eq(currentMemberId), - eq(Map.of(20L, lastDate)) - ); + eq(Map.of(20L, lastDate))); } @Test @@ -133,8 +145,7 @@ void noExerciseHistory() { verify(partyConverter).toPartyMemberDTO( eq(memberParties), eq(currentMemberId), - eq(Map.of()) - ); + eq(Map.of())); } @Test @@ -146,7 +157,8 @@ void partyNotFound() { // when & then assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); } @Test @@ -163,8 +175,58 @@ void partyInactive() { // when & then assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); } } + @Nested + @DisplayName("getMyParties") + class GetMyParties { + + @Test + @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + void success() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); + + PartyDTO.Response expectedResponse = PartyDTO.Response.builder() + .partyId(1L) + .partyName("테스트 모임") + .totalExerciseCount(5) + .nextExerciseInfo("05.01 오전 운동") + .isBookmarked(true) + .build(); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of(1L)); + given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) + .willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, "최신순", + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + assertThat(result.getContent().get(0).isBookmarked()).isTrue(); + + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); + } + } } From 378925f5c11cc20bbea0b4e3bc0433d0a8092578 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 16 Mar 2026 13:04:57 +0900 Subject: [PATCH 03/63] =?UTF-8?q?test:=20=EB=82=B4=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EA=B0=84=EB=9E=B5=ED=99=94=20=EC=A1=B0=ED=9A=8C(/api/my/partie?= =?UTF-8?q?s/simple)=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 67 ++++++++++++++++-- .../party/service/PartyQueryServiceTest.java | 70 ++++++++++++++++++- 2 files changed, 129 insertions(+), 8 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index f759b64fb..7f3fe8216 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -149,22 +149,75 @@ void success_getMyParties() throws Exception { .param("page", "0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) - .andExpect(jsonPath("$.message").value("성공입니다.")) + .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) .andExpect(jsonPath("$.data.content").isArray()) .andExpect(jsonPath("$.data.content.length()").value(1)) .andExpect(jsonPath("$.data.content[0].partyName").value("테스트 모임")) .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) .andExpect(jsonPath("$.data.pageable.pageNumber").value(0)) - .andExpect(jsonPath("$.data.hasNext").value(false)); + .andExpect(jsonPath("$.data.last").value(true)); } @Test - @DisplayName("401 - 인증되지 않은 사용자 접근시 에러를 반환한다") - void fail_unauthorized() throws Exception { - SecurityContextHelper.clearAuthentication(); + @DisplayName("200 - 가입한 모임이 없을 경우 빈 목록을 반환한다") + void success_emptyMyParties() throws Exception { + Member newMember = memberRepository.save(umc.cockple.demo.support.fixture.MemberFixture.createMember("뉴비", + umc.cockple.demo.global.enums.Gender.MALE, umc.cockple.demo.global.enums.Level.BEGINNER, 3003L)); + SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); - mockMvc.perform(get("/api/my/parties")) - .andExpect(status().isUnauthorized()); + mockMvc.perform(get("/api/my/parties") + .param("created", "false") + .param("sort", "최신순") + .param("size", "10") + .param("page", "0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content").isEmpty()) + .andExpect(jsonPath("$.data.empty").value(true)); + } + } + + @Nested + @DisplayName("GET /api/my/parties/simple - 내 모임 간략화 조회") + class GetSimpleMyParties { + + @Test + @DisplayName("200 - 사용자가 가입한 모임의 간략화된 목록을 페이징하여 반환한다") + void success_getSimpleMyParties() throws Exception { + mockMvc.perform(get("/api/my/parties/simple") + .param("page", "0") + .param("size", "10") + .param("sort", "createdAt,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content.length()").value(1)) + .andExpect(jsonPath("$.data.content[0].partyName").value("테스트 모임")) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) + .andExpect(jsonPath("$.data.pageable.pageNumber").value(0)) + .andExpect(jsonPath("$.data.last").value(true)); + } + + @Test + @DisplayName("200 - 가입한 모임이 없을 경우 빈 목록을 반환한다") + void success_emptySimpleMyParties() throws Exception { + Member newMember = memberRepository.save(umc.cockple.demo.support.fixture.MemberFixture.createMember("뉴비", + umc.cockple.demo.global.enums.Gender.MALE, umc.cockple.demo.global.enums.Level.BEGINNER, 3003L)); + SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); + + mockMvc.perform(get("/api/my/parties/simple") + .param("page", "0") + .param("size", "10") + .param("sort", "createdAt,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content").isEmpty()) + .andExpect(jsonPath("$.data.empty").value(true)); } } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 59d02c7a7..debc5426e 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -12,10 +12,12 @@ import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; +import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.dto.PartyMemberDTO; +import umc.cockple.demo.domain.party.dto.PartySimpleDTO; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; import umc.cockple.demo.domain.party.repository.PartyRepository; @@ -55,6 +57,8 @@ class PartyQueryServiceTest { @Mock private PartyRepository partyRepository; @Mock + private MemberRepository memberRepository; + @Mock private PartyConverter partyConverter; @Mock private MemberPartyRepository memberPartyRepository; @@ -89,7 +93,7 @@ void success() { List memberParties = List.of(mp1, mp2); LocalDate lastDate = LocalDate.of(2025, 1, 10); - List rawResult = List.of(new Object[] { 20L, lastDate }); + List rawResult = List.of(new Object[]{20L, lastDate}); PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() .summary(PartyMemberDTO.Summary.builder() @@ -229,4 +233,68 @@ void success() { verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); } } + + @Nested + @DisplayName("getSimpleMyParties") + class GetSimpleMyParties { + + @Test + @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + void success() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 10L); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + + Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); + + PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() + .partyId(10L) + .partyName("테스트 모임") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); + // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 + given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getSimpleMyParties(memberId, + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + + verify(memberRepository).findById(memberId); + verify(memberPartyRepository).findByMember(member, pageable); + verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); + } + + @Test + @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") + void memberNotFound() { + // given + Long invalidMemberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + + given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + } } From 3e4e95bb4388861d5e104087193b6c33ba5992d4 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 16 Mar 2026 15:35:30 +0900 Subject: [PATCH 04/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20=EC=A1=B0=ED=9A=8C(/api/my/parties/suggestions)=20A?= =?UTF-8?q?PI=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 94 +++++++++++- .../party/service/PartyQueryServiceTest.java | 140 ++++++++++++++++++ 2 files changed, 233 insertions(+), 1 deletion(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 7f3fe8216..950e0b986 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -6,6 +6,8 @@ import umc.cockple.demo.domain.exercise.domain.Exercise; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.domain.MemberAddr; +import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; @@ -25,6 +27,7 @@ import java.time.LocalDate; +import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -45,6 +48,8 @@ class PartyIntegrationTest extends IntegrationTestBase { ExerciseRepository exerciseRepository; @Autowired MemberExerciseRepository memberExerciseRepository; + @Autowired + MemberAddrRepository memberAddrRepository; private Member manager; private Member normalMember; @@ -52,7 +57,19 @@ class PartyIntegrationTest extends IntegrationTestBase { @BeforeEach void setUp() { - manager = memberRepository.save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L)); + manager = memberRepository + .save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, LocalDate.of(1995, 1, 1))); + memberAddrRepository.save(MemberAddr.builder() + .member(manager) + .addr1("서울특별시") + .addr2("강남구") + .addr3("역삼동") + .streetAddr("테헤란로") + .latitude(37.5) + .longitude(127.0) + .isMain(true) + .build()); + normalMember = memberRepository.save(MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L)); PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); @@ -61,6 +78,11 @@ void setUp() { memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + // 추천 조회용 모임 (manager가 가입하지 않은 모임) + Party suggestedParty = PartyFixture.createParty("추천 모임", normalMember.getId(), addr); + suggestedParty.addLevel(Gender.MALE, Level.A); // manager의 조건에 맞춤 + partyRepository.save(suggestedParty); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); } @@ -71,6 +93,7 @@ void tearDown() { memberPartyRepository.deleteAll(); partyRepository.deleteAll(); partyAddrRepository.deleteAll(); + memberAddrRepository.deleteAll(); memberRepository.deleteAll(); } @@ -220,4 +243,73 @@ void success_emptySimpleMyParties() throws Exception { .andExpect(jsonPath("$.data.empty").value(true)); } } + + @Nested + @DisplayName("GET /api/my/parties/suggestions - 모임 추천 조회") + class GetRecommendedParties { + + @Test + @DisplayName("200 - Cockple 추천 모드 시 추천된 모임 목록을 반환한다") + void success_cockpleRecommend() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "true") + .param("sort", "최신순") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].partyName").value("추천 모임")); + } + + @Test + @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 반환한다") + void success_filterMode() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "false") + .param("addr1", "서울특별시") + .param("addr2", "강남구") + .param("sort", "최신순") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].addr1").value("서울특별시")) + .andExpect(jsonPath("$.data.content[0].addr2").value("강남구")); + } + + @Test + @DisplayName("200 - 검색 모드 시 모임명으로 검색된 결과를 반환한다") + void success_searchMode() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("search", "추천") + .param("isCockpleRecommend", "false") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].partyName", containsString("추천"))); + } + + @Test + @DisplayName("400 - 유효하지 않은 정렬 기준 입력 시 INVALID_ORDER_TYPE 에러를 반환한다") + void fail_invalidOrderType() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "false") + .param("sort", "잘못된순")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("PARTY106")) + .andExpect(jsonPath("$.message").value("유효하지 않은 정렬 기준입니다. (최신순, 오래된 순, 운동 많은 순 중 하나여야 합니다.)")); + } + + @Test + @DisplayName("400 - isCockpleRecommend에 부적절한 타입 입력 시 400 에러를 반환한다") + void fail_invalidBooleanType() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "not-boolean")) + .andExpect(status().isBadRequest()); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index debc5426e..a8db95234 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -40,6 +40,16 @@ import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.party.dto.PartyDTO; +import umc.cockple.demo.domain.party.dto.PartyFilterDTO; +import umc.cockple.demo.domain.member.repository.MemberAddrRepository; +import umc.cockple.demo.domain.file.service.FileService; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; +import umc.cockple.demo.domain.member.domain.MemberAddr; + +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anyInt; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -68,6 +78,12 @@ class PartyQueryServiceTest { private ExerciseRepository exerciseRepository; @Mock private PartyBookmarkRepository partyBookmarkRepository; + @Mock + private MemberAddrRepository memberAddrRepository; + @Mock + private FileService fileService; + @Mock + private PartyJoinRequestRepository partyJoinRequestRepository; @Nested @DisplayName("getPartyMembers") @@ -297,4 +313,128 @@ void memberNotFound() { .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); } } + + @Nested + @DisplayName("getRecommendedParties") + class GetRecommendedParties { + + @Test + @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") + void success_cockpleRecommend() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, + LocalDate.of(1995, 1, 1)); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberAddr addr = MemberAddr.builder() + .member(member) + .addr1("서울특별시") + .isMain(true) + .build(); + + Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(suggestedParty, "id", 100L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); + given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) + .willReturn(List.of(suggestedParty)); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, true, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); + verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), + eq(Level.A), eq(memberId)); + } + + @Test + @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") + void success_filterMode() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() + .addr1("서울특별시") + .addr2("강남구") + .build(); + + Party filteredParty = PartyFixture.createParty("필터 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(filteredParty, "id", 200L); + Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); + verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); + } + + @Test + @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") + void fail_memberNotFound() { + // given + Long memberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") + void fail_mainAddressNotFound() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + } + } } From a281602a22ceddd7adf6fa50835fcf33a7923e5d Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 16 Mar 2026 16:42:45 +0900 Subject: [PATCH 05/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A1=B0=ED=9A=8C(/api/parties/{partyId})=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 61 +- .../party/service/PartyQueryServiceTest.java | 855 ++++++++++-------- 2 files changed, 532 insertions(+), 384 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 950e0b986..91512100b 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -141,8 +141,7 @@ void success_noExerciseHistory() throws Exception { void fail_partyNotFound() throws Exception { mockMvc.perform(get("/api/parties/{partyId}/members", 999L)) .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())) - .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_NOT_FOUND.getMessage())); + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); } @Test @@ -153,8 +152,7 @@ void fail_partyInactive() throws Exception { mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())) - .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_IS_DELETED.getMessage())); + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); } } @@ -300,8 +298,7 @@ void fail_invalidOrderType() throws Exception { .param("isCockpleRecommend", "false") .param("sort", "잘못된순")) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code").value("PARTY106")) - .andExpect(jsonPath("$.message").value("유효하지 않은 정렬 기준입니다. (최신순, 오래된 순, 운동 많은 순 중 하나여야 합니다.)")); + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_ORDER_TYPE.getCode())); } @Test @@ -312,4 +309,56 @@ void fail_invalidBooleanType() throws Exception { .andExpect(status().isBadRequest()); } } + + @Nested + @DisplayName("GET /api/parties/{partyId} - 모임 상세 조회") + class GetPartyDetails { + + @Test + @DisplayName("200 - 모임 상세 정보를 정상적으로 조회한다 (비회원 상태)") + void success_getDetails_nonMember() throws Exception { + // 모임에 가입하지 않은 새로운 유저 생성 및 인증 설정 + Member nonMember = memberRepository.save(MemberFixture.createMember("비회원", Gender.MALE, Level.C, 2001L)); + SecurityContextHelper.setAuthentication(nonMember.getId(), nonMember.getNickname()); + + mockMvc.perform(get("/api/parties/{partyId}", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.partyId").value(party.getId())) + .andExpect(jsonPath("$.data.memberStatus").value("NOT_MEMBER")) + .andExpect(jsonPath("$.data.hasPendingJoinRequest").value(false)); + } + + @Test + @DisplayName("200 - 모임원인 경우 memberStatus가 MEMBER로 반환된다") + void success_getDetails_member() throws Exception { + // manager는 setUp에서 이미 party의 멤버로 설정됨 + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(get("/api/parties/{partyId}", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.memberStatus").value("MEMBER")) + .andExpect(jsonPath("$.data.memberRole").value("party_MANAGER")); + } + + @Test + @DisplayName("404 - 존재하지 않는 모임 조회 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_partyNotFound() throws Exception { + mockMvc.perform(get("/api/parties/{partyId}", 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + + @Test + @DisplayName("400 - 삭제된 모임 조회 시 PARTY_IS_DELETED 에러를 반환한다") + void fail_partyDeleted() throws Exception { + // 모임 삭제 (비활성화) + party.delete(); + partyRepository.save(party); + + mockMvc.perform(get("/api/parties/{partyId}", party.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index a8db95234..ada3a71bb 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -16,8 +16,8 @@ import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; -import umc.cockple.demo.domain.party.dto.PartyMemberDTO; -import umc.cockple.demo.domain.party.dto.PartySimpleDTO; +import umc.cockple.demo.domain.party.dto.*; +import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; import umc.cockple.demo.domain.party.repository.PartyRepository; @@ -39,402 +39,501 @@ import org.springframework.data.domain.SliceImpl; import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; -import umc.cockple.demo.domain.party.dto.PartyDTO; -import umc.cockple.demo.domain.party.dto.PartyFilterDTO; import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.file.service.FileService; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.member.domain.MemberAddr; -import static org.mockito.ArgumentMatchers.contains; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.anyInt; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class PartyQueryServiceTest { - @InjectMocks - private PartyQueryServiceImpl partyQueryService; - - @Mock - private PartyRepository partyRepository; - @Mock - private MemberRepository memberRepository; - @Mock - private PartyConverter partyConverter; - @Mock - private MemberPartyRepository memberPartyRepository; - @Mock - private MemberExerciseRepository memberExerciseRepository; - @Mock - private ExerciseRepository exerciseRepository; - @Mock - private PartyBookmarkRepository partyBookmarkRepository; - @Mock - private MemberAddrRepository memberAddrRepository; - @Mock - private FileService fileService; - @Mock - private PartyJoinRequestRepository partyJoinRequestRepository; - - @Nested - @DisplayName("getPartyMembers") - class GetPartyMembers { - - @Test - @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") - void success() { - // given - Long partyId = 1L; - Long currentMemberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); - ReflectionTestUtils.setField(manager, "id", 10L); - ReflectionTestUtils.setField(member1, "id", 20L); - - MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); - List memberParties = List.of(mp1, mp2); - - LocalDate lastDate = LocalDate.of(2025, 1, 10); - List rawResult = List.of(new Object[]{20L, lastDate}); - - PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() - .summary(PartyMemberDTO.Summary.builder() - .totalCount(2).maleCount(1).femaleCount(1).build()) - .members(List.of()) - .build(); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); - given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId)).willReturn(rawResult); - given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) - .willReturn(expected); - - // when - PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); - - // then - assertThat(result).isEqualTo(expected); - verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId); - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of(20L, lastDate))); - } - - @Test - @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") - void noExerciseHistory() { - // given - Long partyId = 1L; - Long currentMemberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(manager, "id", 10L); - MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - List memberParties = List.of(mp); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); - given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L), partyId)).willReturn(List.of()); - given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); - - // when - partyQueryService.getPartyMembers(partyId, currentMemberId); - - // then - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of())); + @InjectMocks + private PartyQueryServiceImpl partyQueryService; + + @Mock + private PartyRepository partyRepository; + @Mock + private MemberRepository memberRepository; + @Mock + private PartyConverter partyConverter; + @Mock + private MemberPartyRepository memberPartyRepository; + @Mock + private MemberExerciseRepository memberExerciseRepository; + @Mock + private ExerciseRepository exerciseRepository; + @Mock + private PartyBookmarkRepository partyBookmarkRepository; + @Mock + private MemberAddrRepository memberAddrRepository; + @Mock + private FileService fileService; + @Mock + private PartyJoinRequestRepository partyJoinRequestRepository; + + @Nested + @DisplayName("getPartyMembers") + class GetPartyMembers { + + @Test + @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") + void success() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); + ReflectionTestUtils.setField(manager, "id", 10L); + ReflectionTestUtils.setField(member1, "id", 20L); + + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); + List memberParties = List.of(mp1, mp2); + + LocalDate lastDate = LocalDate.of(2025, 1, 10); + List rawResult = List.of(new Object[] { 20L, lastDate }); + + PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() + .summary(PartyMemberDTO.Summary.builder() + .totalCount(2).maleCount(1).femaleCount(1).build()) + .members(List.of()) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId)).willReturn(rawResult); + given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) + .willReturn(expected); + + // when + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + assertThat(result).isEqualTo(expected); + verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId); + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of(20L, lastDate))); + } + + @Test + @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") + void noExerciseHistory() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(manager, "id", 10L); + MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + List memberParties = List.of(mp); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L), partyId)).willReturn(List.of()); + given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); + + // when + partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of())); + } + + @Test + @DisplayName("존재하지 않는 파티면 PartyException을 던진다") + void partyNotFound() { + // given + given(partyRepository.findById(99L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("비활성화된 파티면 PartyException을 던진다") + void partyInactive() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(inactiveParty, "id", 1L); + inactiveParty.delete(); + + given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } } - @Test - @DisplayName("존재하지 않는 파티면 PartyException을 던진다") - void partyNotFound() { - // given - given(partyRepository.findById(99L)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); - } - - @Test - @DisplayName("비활성화된 파티면 PartyException을 던진다") - void partyInactive() { - // given - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(inactiveParty, "id", 1L); - inactiveParty.delete(); - - given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); - } - } - - @Nested - @DisplayName("getMyParties") - class GetMyParties { - - @Test - @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") - void success() { - // given - Long memberId = 10L; - Pageable pageable = PageRequest.of(0, 10); - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 1L); - - Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); - - PartyDTO.Response expectedResponse = PartyDTO.Response.builder() - .partyId(1L) - .partyName("테스트 모임") - .totalExerciseCount(5) - .nextExerciseInfo("05.01 오전 운동") - .isBookmarked(true) - .build(); - - given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) - .willReturn(partySlice); - given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) - .willReturn(List.of()); - given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) - .willReturn(List.of()); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) - .willReturn(Set.of(1L)); - given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) - .willReturn(expectedResponse); - - // when - Slice result = partyQueryService.getMyParties(memberId, false, "최신순", - pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - assertThat(result.getContent().get(0).isBookmarked()).isTrue(); - - verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); - verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); - } - } - - @Nested - @DisplayName("getSimpleMyParties") - class GetSimpleMyParties { - - @Test - @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") - void success() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - - Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(member, "id", memberId); - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 10L); - - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); - - Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); - - PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() - .partyId(10L) - .partyName("테스트 모임") - .build(); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); - // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 - given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); - - // when - Slice result = partyQueryService.getSimpleMyParties(memberId, - pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - - verify(memberRepository).findById(memberId); - verify(memberPartyRepository).findByMember(member, pageable); - verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); - } - - @Test - @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") - void memberNotFound() { - // given - Long invalidMemberId = 999L; - Pageable pageable = PageRequest.of(0, 10); - - given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); - } - } - - @Nested - @DisplayName("getRecommendedParties") - class GetRecommendedParties { - - @Test - @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") - void success_cockpleRecommend() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, - LocalDate.of(1995, 1, 1)); - ReflectionTestUtils.setField(member, "id", memberId); - - MemberAddr addr = MemberAddr.builder() - .member(member) - .addr1("서울특별시") - .isMain(true) - .build(); - - Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(suggestedParty, "id", 100L); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); - given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) - .willReturn(List.of(suggestedParty)); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") - .build()); - - // when - Slice result = partyQueryService.getRecommendedParties(memberId, true, - filter, "최신순", pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); - verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), - eq(Level.A), eq(memberId)); + @Nested + @DisplayName("getMyParties") + class GetMyParties { + + @Test + @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + void success() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); + + PartyDTO.Response expectedResponse = PartyDTO.Response.builder() + .partyId(1L) + .partyName("테스트 모임") + .totalExerciseCount(5) + .nextExerciseInfo("05.01 오전 운동") + .isBookmarked(true) + .build(); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of(1L)); + given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) + .willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, "최신순", + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + assertThat(result.getContent().get(0).isBookmarked()).isTrue(); + + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); + } } - @Test - @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") - void success_filterMode() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() - .addr1("서울특별시") - .addr2("강남구") - .build(); - - Party filteredParty = PartyFixture.createParty("필터 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(filteredParty, "id", 200L); - Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); - - given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) - .willReturn(partySlice); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") - .build()); - - // when - Slice result = partyQueryService.getRecommendedParties(memberId, false, - filter, "최신순", pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); - verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); + @Nested + @DisplayName("getSimpleMyParties") + class GetSimpleMyParties { + + @Test + @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + void success() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 10L); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + + Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); + + PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() + .partyId(10L) + .partyName("테스트 모임") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); + // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 + given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getSimpleMyParties(memberId, + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + + verify(memberRepository).findById(memberId); + verify(memberPartyRepository).findByMember(member, pageable); + verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); + } + + @Test + @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") + void memberNotFound() { + // given + Long invalidMemberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + + given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } - @Test - @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") - void fail_memberNotFound() { - // given - Long memberId = 999L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - given(memberRepository.findById(memberId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", - pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + @Nested + @DisplayName("getRecommendedParties") + class GetRecommendedParties { + + @Test + @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") + void success_cockpleRecommend() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, + LocalDate.of(1995, 1, 1)); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberAddr addr = MemberAddr.builder() + .member(member) + .addr1("서울특별시") + .isMain(true) + .build(); + + Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(suggestedParty, "id", 100L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); + given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) + .willReturn(List.of(suggestedParty)); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, true, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); + verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), + eq(Level.A), eq(memberId)); + } + + @Test + @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") + void success_filterMode() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() + .addr1("서울특별시") + .addr2("강남구") + .build(); + + Party filteredParty = PartyFixture.createParty("필터 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(filteredParty, "id", 200L); + Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); + verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); + } + + @Test + @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") + void fail_memberNotFound() { + // given + Long memberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") + void fail_mainAddressNotFound() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + } } - @Test - @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") - void fail_mainAddressNotFound() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(member, "id", memberId); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", - pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + @Nested + @DisplayName("getPartyDetails") + class GetPartyDetails { + + @Test + @DisplayName("모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") + void success_nonMember() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("상세 모임", 11L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() + .partyId(partyId) + .partyName("상세 모임") + .memberStatus("NOT_MEMBER") + .hasPendingJoinRequest(false) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.empty()); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, + RequestStatus.PENDING)).willReturn(false); + given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), eq(false))) + .willReturn(expected); + + // when + PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); + + // then + assertThat(result).isEqualTo(expected); + verify(partyRepository).findById(partyId); + } + + @Test + @DisplayName("모임원인 경우 memberStatus가 MEMBER로 반환된다") + void success_member() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("상세 모임", 11L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() + .partyId(partyId) + .memberStatus("MEMBER") + .memberRole("party_MEMBER") + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)) + .willReturn(Optional.of(memberParty)); + given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), anyBoolean())) + .willReturn(expected); + + // when + PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); + + // then + assertThat(result.memberStatus()).isEqualTo("MEMBER"); + } + + @Test + @DisplayName("존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") + void fail_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(999L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") + void fail_partyDeleted() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("삭제된 모임", 11L, addr); + party.delete(); + given(partyRepository.findById(1L)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn( + Optional.of(MemberFixture.createMember("테스터", Gender.MALE, Level.A, 1L))); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } } - } } From 259693959dd7ccb1eee26db5edf394552855172b Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 17 Mar 2026 01:21:40 +0900 Subject: [PATCH 06/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=A1=B0=ED=9A=8C(/api/parties/{partyId}/members)?= =?UTF-8?q?=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 21 + .../party/service/PartyQueryServiceTest.java | 985 +++++++++--------- 2 files changed, 532 insertions(+), 474 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 91512100b..95e12f4e4 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -101,6 +101,27 @@ void tearDown() { @DisplayName("GET /api/parties/{partyId}/members - 모임 멤버 조회") class GetPartyMembers { + @Test + @DisplayName("200 - 모임의 멤버들을 역할별로 성공적으로 조회한다.") + void success() throws Exception { + // 부모임장 추가 + Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 1003L)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + + // 모임장이 가입된 상태 + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.summary.totalCount").value(3)) + .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) + .andExpect(jsonPath("$.data.members[0].isMe").value(true)) + .andExpect(jsonPath("$.data.members[1].role").value("party_SUBMANAGER")) + .andExpect(jsonPath("$.data.members[1].isMe").value(false)) + .andExpect(jsonPath("$.data.members[2].role").value("party_MEMBER")) + .andExpect(jsonPath("$.data.members[2].isMe").value(false)); + } + @Test @DisplayName("200 - 멤버 목록과 마지막 운동일을 정상 반환한다") void success_withLastExerciseDate() throws Exception { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index ada3a71bb..8986e8110 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -53,487 +53,524 @@ @ExtendWith(MockitoExtension.class) class PartyQueryServiceTest { - @InjectMocks - private PartyQueryServiceImpl partyQueryService; - - @Mock - private PartyRepository partyRepository; - @Mock - private MemberRepository memberRepository; - @Mock - private PartyConverter partyConverter; - @Mock - private MemberPartyRepository memberPartyRepository; - @Mock - private MemberExerciseRepository memberExerciseRepository; - @Mock - private ExerciseRepository exerciseRepository; - @Mock - private PartyBookmarkRepository partyBookmarkRepository; - @Mock - private MemberAddrRepository memberAddrRepository; - @Mock - private FileService fileService; - @Mock - private PartyJoinRequestRepository partyJoinRequestRepository; - - @Nested - @DisplayName("getPartyMembers") - class GetPartyMembers { - - @Test - @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") - void success() { - // given - Long partyId = 1L; - Long currentMemberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); - ReflectionTestUtils.setField(manager, "id", 10L); - ReflectionTestUtils.setField(member1, "id", 20L); - - MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); - List memberParties = List.of(mp1, mp2); - - LocalDate lastDate = LocalDate.of(2025, 1, 10); - List rawResult = List.of(new Object[] { 20L, lastDate }); - - PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() - .summary(PartyMemberDTO.Summary.builder() - .totalCount(2).maleCount(1).femaleCount(1).build()) - .members(List.of()) - .build(); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); - given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId)).willReturn(rawResult); - given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) - .willReturn(expected); - - // when - PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); - - // then - assertThat(result).isEqualTo(expected); - verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId); - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of(20L, lastDate))); - } - - @Test - @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") - void noExerciseHistory() { - // given - Long partyId = 1L; - Long currentMemberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(manager, "id", 10L); - MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - List memberParties = List.of(mp); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); - given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L), partyId)).willReturn(List.of()); - given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); - - // when - partyQueryService.getPartyMembers(partyId, currentMemberId); - - // then - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of())); - } - - @Test - @DisplayName("존재하지 않는 파티면 PartyException을 던진다") - void partyNotFound() { - // given - given(partyRepository.findById(99L)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); - } - - @Test - @DisplayName("비활성화된 파티면 PartyException을 던진다") - void partyInactive() { - // given - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(inactiveParty, "id", 1L); - inactiveParty.delete(); - - given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); - } + @InjectMocks + private PartyQueryServiceImpl partyQueryService; + + @Mock + private PartyRepository partyRepository; + @Mock + private MemberRepository memberRepository; + @Mock + private PartyConverter partyConverter; + @Mock + private MemberPartyRepository memberPartyRepository; + @Mock + private MemberExerciseRepository memberExerciseRepository; + @Mock + private ExerciseRepository exerciseRepository; + @Mock + private PartyBookmarkRepository partyBookmarkRepository; + @Mock + private MemberAddrRepository memberAddrRepository; + @Mock + private FileService fileService; + @Mock + private PartyJoinRequestRepository partyJoinRequestRepository; + + @Nested + @DisplayName("getPartyMembers") + class GetPartyMembers { + + @Test + @DisplayName("모임의 멤버들을 역할별로 성공적으로 조회한다.") + void success() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member manager = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1001L); + Member subManager = MemberFixture.createMember("부모임장", Gender.FEMALE, Level.B, 1002L); + Member normalMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.C, 1003L); + + ReflectionTestUtils.setField(manager, "id", 10L); + ReflectionTestUtils.setField(subManager, "id", 20L); + ReflectionTestUtils.setField(normalMember, "id", 30L); + + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + MemberParty mp3 = MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER); + List memberParties = List.of(mp1, mp2, mp3); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId(anyList(), + eq(partyId))) + .willReturn(List.of()); + + // when + partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + verify(partyConverter).toPartyMemberDTO(eq(memberParties), eq(currentMemberId), anyMap()); } - @Nested - @DisplayName("getMyParties") - class GetMyParties { - - @Test - @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") - void success() { - // given - Long memberId = 10L; - Pageable pageable = PageRequest.of(0, 10); - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 1L); - - Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); - - PartyDTO.Response expectedResponse = PartyDTO.Response.builder() - .partyId(1L) - .partyName("테스트 모임") - .totalExerciseCount(5) - .nextExerciseInfo("05.01 오전 운동") - .isBookmarked(true) - .build(); - - given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) - .willReturn(partySlice); - given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) - .willReturn(List.of()); - given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) - .willReturn(List.of()); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) - .willReturn(Set.of(1L)); - given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) - .willReturn(expectedResponse); - - // when - Slice result = partyQueryService.getMyParties(memberId, false, "최신순", - pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - assertThat(result.getContent().get(0).isBookmarked()).isTrue(); - - verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); - verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); - } + @Test + @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") + void success_withExerciseHistory() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); + ReflectionTestUtils.setField(manager, "id", 10L); + ReflectionTestUtils.setField(member1, "id", 20L); + + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); + List memberParties = List.of(mp1, mp2); + + LocalDate lastDate = LocalDate.of(2025, 1, 10); + List rawResult = List.of(new Object[]{20L, lastDate}); + + PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() + .summary(PartyMemberDTO.Summary.builder() + .totalCount(2).maleCount(1).femaleCount(1).build()) + .members(List.of()) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId)).willReturn(rawResult); + given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) + .willReturn(expected); + + // when + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + assertThat(result).isEqualTo(expected); + verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId); + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of(20L, lastDate))); } - @Nested - @DisplayName("getSimpleMyParties") - class GetSimpleMyParties { - - @Test - @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") - void success() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - - Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(member, "id", memberId); - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 10L); - - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); - - Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); - - PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() - .partyId(10L) - .partyName("테스트 모임") - .build(); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); - // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 - given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); - - // when - Slice result = partyQueryService.getSimpleMyParties(memberId, - pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - - verify(memberRepository).findById(memberId); - verify(memberPartyRepository).findByMember(member, pageable); - verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); - } - - @Test - @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") - void memberNotFound() { - // given - Long invalidMemberId = 999L; - Pageable pageable = PageRequest.of(0, 10); - - given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); - } + @Test + @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") + void noExerciseHistory() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(manager, "id", 10L); + MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + List memberParties = List.of(mp); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L), partyId)).willReturn(List.of()); + given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); + + // when + partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of())); } - @Nested - @DisplayName("getRecommendedParties") - class GetRecommendedParties { - - @Test - @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") - void success_cockpleRecommend() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, - LocalDate.of(1995, 1, 1)); - ReflectionTestUtils.setField(member, "id", memberId); - - MemberAddr addr = MemberAddr.builder() - .member(member) - .addr1("서울특별시") - .isMain(true) - .build(); - - Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(suggestedParty, "id", 100L); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); - given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) - .willReturn(List.of(suggestedParty)); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") - .build()); - - // when - Slice result = partyQueryService.getRecommendedParties(memberId, true, - filter, "최신순", pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); - verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), - eq(Level.A), eq(memberId)); - } - - @Test - @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") - void success_filterMode() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() - .addr1("서울특별시") - .addr2("강남구") - .build(); - - Party filteredParty = PartyFixture.createParty("필터 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(filteredParty, "id", 200L); - Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); - - given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) - .willReturn(partySlice); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") - .build()); - - // when - Slice result = partyQueryService.getRecommendedParties(memberId, false, - filter, "최신순", pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); - verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); - } - - @Test - @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") - void fail_memberNotFound() { - // given - Long memberId = 999L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - given(memberRepository.findById(memberId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", - pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); - } - - @Test - @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") - void fail_mainAddressNotFound() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(member, "id", memberId); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", - pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); - } + @Test + @DisplayName("존재하지 않는 파티면 PartyException을 던진다") + void partyNotFound() { + // given + given(partyRepository.findById(99L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); } - @Nested - @DisplayName("getPartyDetails") - class GetPartyDetails { - - @Test - @DisplayName("모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") - void success_nonMember() { - // given - Long partyId = 1L; - Long memberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); - Party party = PartyFixture.createParty("상세 모임", 11L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); - ReflectionTestUtils.setField(member, "id", memberId); - - PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() - .partyId(partyId) - .partyName("상세 모임") - .memberStatus("NOT_MEMBER") - .hasPendingJoinRequest(false) - .build(); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.empty()); - given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); - given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, - RequestStatus.PENDING)).willReturn(false); - given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), eq(false))) - .willReturn(expected); - - // when - PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); - - // then - assertThat(result).isEqualTo(expected); - verify(partyRepository).findById(partyId); - } - - @Test - @DisplayName("모임원인 경우 memberStatus가 MEMBER로 반환된다") - void success_member() { - // given - Long partyId = 1L; - Long memberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); - Party party = PartyFixture.createParty("상세 모임", 11L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); - ReflectionTestUtils.setField(member, "id", memberId); - - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); - PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() - .partyId(partyId) - .memberStatus("MEMBER") - .memberRole("party_MEMBER") - .build(); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberPartyRepository.findByPartyAndMember(party, member)) - .willReturn(Optional.of(memberParty)); - given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), anyBoolean())) - .willReturn(expected); - - // when - PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); - - // then - assertThat(result.memberStatus()).isEqualTo("MEMBER"); - } - - @Test - @DisplayName("존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") - void fail_partyNotFound() { - // given - given(partyRepository.findById(999L)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyDetails(999L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); - } - - @Test - @DisplayName("삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") - void fail_partyDeleted() { - // given - PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); - Party party = PartyFixture.createParty("삭제된 모임", 11L, addr); - party.delete(); - given(partyRepository.findById(1L)).willReturn(Optional.of(party)); - given(memberRepository.findById(1L)).willReturn( - Optional.of(MemberFixture.createMember("테스터", Gender.MALE, Level.A, 1L))); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyDetails(1L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); - } + @Test + @DisplayName("비활성화된 파티면 PartyException을 던진다") + void partyInactive() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(inactiveParty, "id", 1L); + inactiveParty.delete(); + + given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); } + } + + @Nested + @DisplayName("getMyParties") + class GetMyParties { + + @Test + @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + void success() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); + + PartyDTO.Response expectedResponse = PartyDTO.Response.builder() + .partyId(1L) + .partyName("테스트 모임") + .totalExerciseCount(5) + .nextExerciseInfo("05.01 오전 운동") + .isBookmarked(true) + .build(); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of(1L)); + given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) + .willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, "최신순", + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + assertThat(result.getContent().get(0).isBookmarked()).isTrue(); + + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); + } + } + + @Nested + @DisplayName("getSimpleMyParties") + class GetSimpleMyParties { + + @Test + @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + void success() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 10L); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + + Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); + + PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() + .partyId(10L) + .partyName("테스트 모임") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); + // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 + given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getSimpleMyParties(memberId, + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + + verify(memberRepository).findById(memberId); + verify(memberPartyRepository).findByMember(member, pageable); + verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); + } + + @Test + @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") + void memberNotFound() { + // given + Long invalidMemberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + + given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + } + + @Nested + @DisplayName("getRecommendedParties") + class GetRecommendedParties { + + @Test + @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") + void success_cockpleRecommend() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, + LocalDate.of(1995, 1, 1)); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberAddr addr = MemberAddr.builder() + .member(member) + .addr1("서울특별시") + .isMain(true) + .build(); + + Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(suggestedParty, "id", 100L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); + given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) + .willReturn(List.of(suggestedParty)); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, true, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); + verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), + eq(Level.A), eq(memberId)); + } + + @Test + @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") + void success_filterMode() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() + .addr1("서울특별시") + .addr2("강남구") + .build(); + + Party filteredParty = PartyFixture.createParty("필터 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(filteredParty, "id", 200L); + Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); + verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); + } + + @Test + @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") + void fail_memberNotFound() { + // given + Long memberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") + void fail_mainAddressNotFound() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + } + } + + @Nested + @DisplayName("getPartyDetails") + class GetPartyDetails { + + @Test + @DisplayName("모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") + void success_nonMember() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("상세 모임", 11L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() + .partyId(partyId) + .partyName("상세 모임") + .memberStatus("NOT_MEMBER") + .hasPendingJoinRequest(false) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.empty()); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, + RequestStatus.PENDING)).willReturn(false); + given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), eq(false))) + .willReturn(expected); + + // when + PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); + + // then + assertThat(result).isEqualTo(expected); + verify(partyRepository).findById(partyId); + } + + @Test + @DisplayName("모임원인 경우 memberStatus가 MEMBER로 반환된다") + void success_member() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("상세 모임", 11L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() + .partyId(partyId) + .memberStatus("MEMBER") + .memberRole("party_MEMBER") + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)) + .willReturn(Optional.of(memberParty)); + given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), anyBoolean())) + .willReturn(expected); + + // when + PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); + + // then + assertThat(result.memberStatus()).isEqualTo("MEMBER"); + } + + @Test + @DisplayName("존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") + void fail_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(999L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") + void fail_partyDeleted() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("삭제된 모임", 11L, addr); + party.delete(); + given(partyRepository.findById(1L)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn( + Optional.of(MemberFixture.createMember("테스터", Gender.MALE, Level.A, 1L))); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } + } } From c0295065f4e620df80def8ca9dd7a9d170b8b05a Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 13:31:49 +0900 Subject: [PATCH 07/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=ED=83=88?= =?UTF-8?q?=ED=87=B4(/api/parties/{partyId}/members/my)=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 81 +++++++ .../service/PartyCommandServiceTest.java | 200 ++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 95e12f4e4..992a48aa3 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -4,6 +4,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; import umc.cockple.demo.domain.exercise.domain.Exercise; +import umc.cockple.demo.domain.chat.domain.ChatRoom; +import umc.cockple.demo.domain.chat.domain.ChatRoomMember; +import umc.cockple.demo.domain.chat.repository.ChatRoomMemberRepository; +import umc.cockple.demo.domain.chat.repository.ChatRoomRepository; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; @@ -27,7 +31,9 @@ import java.time.LocalDate; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -50,6 +56,10 @@ class PartyIntegrationTest extends IntegrationTestBase { MemberExerciseRepository memberExerciseRepository; @Autowired MemberAddrRepository memberAddrRepository; + @Autowired + ChatRoomRepository chatRoomRepository; + @Autowired + ChatRoomMemberRepository chatRoomMemberRepository; private Member manager; private Member normalMember; @@ -78,6 +88,11 @@ void setUp() { memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + // 채팅방 생성 및 멤버 추가 + ChatRoom chatRoom = chatRoomRepository.save(ChatRoom.createPartyChatRoom(party)); + chatRoomMemberRepository.save(ChatRoomMember.create(chatRoom, manager)); + chatRoomMemberRepository.save(ChatRoomMember.create(chatRoom, normalMember)); + // 추천 조회용 모임 (manager가 가입하지 않은 모임) Party suggestedParty = PartyFixture.createParty("추천 모임", normalMember.getId(), addr); suggestedParty.addLevel(Gender.MALE, Level.A); // manager의 조건에 맞춤 @@ -90,6 +105,8 @@ void setUp() { void tearDown() { memberExerciseRepository.deleteAll(); exerciseRepository.deleteAll(); + chatRoomMemberRepository.deleteAll(); + chatRoomRepository.deleteAll(); memberPartyRepository.deleteAll(); partyRepository.deleteAll(); partyAddrRepository.deleteAll(); @@ -177,6 +194,70 @@ void fail_partyInactive() throws Exception { } } + @Nested + @DisplayName("DELETE /api/parties/{partyId}/members/my - 모임 탈퇴") + class LeaveParty { + + @Test + @DisplayName("200 - 일반 멤버가 모임을 성공적으로 탈퇴한다") + void success_leaveParty() throws Exception { + // DB에서 최신 정보 보장 + Member member = memberRepository.findById(normalMember.getId()).orElseThrow(); + Party targetParty = partyRepository.findById(party.getId()).orElseThrow(); + + // normalMember 세션으로 설정 + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/members/my", targetParty.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // DB에서 제거되었는지 확인 + boolean exists = memberPartyRepository.existsByPartyAndMember(targetParty, member); + assertThat(exists).isFalse(); + } + + @Test + @DisplayName("403 - 모임장은 탈퇴할 수 없다") + void fail_leaveParty_owner() throws Exception { + // manager(모임장) 세션으로 설정 + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/members/my", party.getId())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_ACTION_FOR_OWNER.getCode())); + } + + @Test + @DisplayName("403 - 부모임장은 탈퇴할 수 없다") + void fail_leaveParty_subOwner() throws Exception { + // 부모임장 생성 및 가입 + Member subManager = memberRepository + .saveAndFlush(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 2001L)); + memberPartyRepository + .saveAndFlush(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + + // 부모임장 세션으로 설정 + SecurityContextHelper.setAuthentication(subManager.getId(), subManager.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/members/my", party.getId())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_ACTION_FOR_SUBOWNER.getCode())); + } + + @Test + @DisplayName("400 - 해당 모임의 멤버가 아니면 탈퇴할 수 없다") + void fail_leaveParty_notMember() throws Exception { + // 가입하지 않은 새로운 멤버 생성 + Member nonMember = memberRepository.save(MemberFixture.createMember("외부인", Gender.MALE, Level.A, 3001L)); + SecurityContextHelper.setAuthentication(nonMember.getId(), nonMember.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/members/my", party.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.NOT_MEMBER.getCode())); + } + } + @Nested @DisplayName("GET /api/my/parties - 내 모임 조회") class GetMyParties { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java new file mode 100644 index 000000000..3998eeda0 --- /dev/null +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -0,0 +1,200 @@ +package umc.cockple.demo.domain.party.service; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.test.util.ReflectionTestUtils; +import umc.cockple.demo.domain.chat.service.ChatRoomService; +import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.repository.MemberPartyRepository; +import umc.cockple.demo.domain.member.repository.MemberRepository; +import umc.cockple.demo.domain.party.domain.Party; +import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; +import umc.cockple.demo.domain.party.exception.PartyErrorCode; +import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyRepository; +import umc.cockple.demo.global.enums.Gender; +import umc.cockple.demo.global.enums.Level; +import umc.cockple.demo.global.enums.Role; +import umc.cockple.demo.support.fixture.MemberFixture; +import umc.cockple.demo.support.fixture.PartyFixture; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class PartyCommandServiceTest { + + @InjectMocks + private PartyCommandServiceImpl partyCommandService; + + @Mock + private PartyRepository partyRepository; + @Mock + private MemberRepository memberRepository; + @Mock + private MemberPartyRepository memberPartyRepository; + @Mock + private ChatRoomService chatRoomService; + @Mock + private ApplicationEventPublisher applicationEventPublisher; + + @Nested + @DisplayName("leaveParty") + class LeaveParty { + + @Test + @DisplayName("성공 - 일반 멤버가 모임을 탈퇴한다") + void success_leaveParty() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", 1L); + Party party = PartyFixture.createParty("탈퇴 테스트 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member member = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 10L); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.of(memberParty)); + + // when + partyCommandService.leaveParty(partyId, memberId); + + // then + verify(memberPartyRepository).delete(memberParty); + verify(chatRoomService).leavePartyChatRoom(partyId, memberId); + verify(applicationEventPublisher).publishEvent(any(PartyMemberJoinedEvent.class)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 모임인 경우 PARTY_NOT_FOUND 예외가 발생한다") + void fail_leaveParty_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(999L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies( + e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 삭제된 모임인 경우 PARTY_IS_DELETED 예외가 발생한다") + void fail_leaveParty_partyDeleted() { + // given + Long partyId = 1L; + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("삭제된 모임", 1L, addr); + party.delete(); + + Member member = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(member, "id", 1L); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, 1L)) + .isInstanceOf(PartyException.class) + .satisfies( + e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } + + @Test + @DisplayName("실패 - 모임장이 탈퇴하려 할 경우 INVALID_ACTION_FOR_OWNER 예외가 발생한다") + void fail_leaveParty_isOwner() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("탈퇴 테스트 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, ownerId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.INVALID_ACTION_FOR_OWNER)); + } + + @Test + @DisplayName("실패 - 부모임장이 탈퇴하려 할 경우 INVALID_ACTION_FOR_SUBOWNER 예외가 발생한다") + void fail_leaveParty_isSubOwner() { + // given + Long partyId = 1L; + Long subManagerId = 2L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", 1L); + Party party = PartyFixture.createParty("탈퇴 테스트 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member subManager = MemberFixture.createMember("부모임장", Gender.MALE, Level.A, 2L); + ReflectionTestUtils.setField(subManager, "id", subManagerId); + + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)) + .willReturn(Optional.of(subManagerParty)); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, subManagerId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.INVALID_ACTION_FOR_SUBOWNER)); + } + + @Test + @DisplayName("실패 - 모임 멤버가 아닌 경우 NOT_MEMBER 예외가 발생한다") + void fail_leaveParty_notMember() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("탈퇴 테스트 모임", 1L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("외부인", Gender.MALE, Level.A, 10L); + ReflectionTestUtils.setField(member, "id", memberId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER)); + } + } +} From 16b198bd18263b75705ad64fde68b4cae8308261 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 13:35:53 +0900 Subject: [PATCH 08/63] =?UTF-8?q?test:=20@DisplayName=20=EC=BB=A8=EB=B2=A4?= =?UTF-8?q?=EC=85=98=20=EB=A7=9E=EC=B6=94=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../party/service/PartyQueryServiceTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 8986e8110..18d18f1a3 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -82,7 +82,7 @@ class PartyQueryServiceTest { class GetPartyMembers { @Test - @DisplayName("모임의 멤버들을 역할별로 성공적으로 조회한다.") + @DisplayName("성공 - 모임의 멤버들을 역할별로 성공적으로 조회한다.") void success() { // given Long partyId = 1L; @@ -119,7 +119,7 @@ void success() { } @Test - @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") + @DisplayName("성공 - 멤버 목록과 마지막 운동일을 함께 반환한다") void success_withExerciseHistory() { // given Long partyId = 1L; @@ -167,7 +167,7 @@ void success_withExerciseHistory() { } @Test - @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") + @DisplayName("성공 - 운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") void noExerciseHistory() { // given Long partyId = 1L; @@ -198,7 +198,7 @@ void noExerciseHistory() { } @Test - @DisplayName("존재하지 않는 파티면 PartyException을 던진다") + @DisplayName("실패 - 존재하지 않는 파티면 PartyException을 던진다") void partyNotFound() { // given given(partyRepository.findById(99L)).willReturn(Optional.empty()); @@ -211,7 +211,7 @@ void partyNotFound() { } @Test - @DisplayName("비활성화된 파티면 PartyException을 던진다") + @DisplayName("실패 - 비활성화된 파티면 PartyException을 던진다") void partyInactive() { // given PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); @@ -234,7 +234,7 @@ void partyInactive() { class GetMyParties { @Test - @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + @DisplayName("성공 - 내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") void success() { // given Long memberId = 10L; @@ -284,7 +284,7 @@ void success() { class GetSimpleMyParties { @Test - @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + @DisplayName("성공 - 유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") void success() { // given Long memberId = 1L; @@ -325,7 +325,7 @@ void success() { } @Test - @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") + @DisplayName("실패 - 존재하지 않는 회원일 경우 MemberException을 던진다") void memberNotFound() { // given Long invalidMemberId = 999L; @@ -348,7 +348,7 @@ void memberNotFound() { class GetRecommendedParties { @Test - @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") + @DisplayName("성공 - Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") void success_cockpleRecommend() { // given Long memberId = 1L; @@ -390,7 +390,7 @@ void success_cockpleRecommend() { } @Test - @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") + @DisplayName("성공 - 필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") void success_filterMode() { // given Long memberId = 1L; @@ -423,7 +423,7 @@ void success_filterMode() { } @Test - @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") + @DisplayName("실패 - 존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") void fail_memberNotFound() { // given Long memberId = 999L; @@ -443,7 +443,7 @@ void fail_memberNotFound() { } @Test - @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") + @DisplayName("실패 - 대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") void fail_mainAddressNotFound() { // given Long memberId = 1L; @@ -472,7 +472,7 @@ void fail_mainAddressNotFound() { class GetPartyDetails { @Test - @DisplayName("모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") + @DisplayName("성공 - 모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") void success_nonMember() { // given Long partyId = 1L; @@ -509,7 +509,7 @@ void success_nonMember() { } @Test - @DisplayName("모임원인 경우 memberStatus가 MEMBER로 반환된다") + @DisplayName("성공 - 모임원인 경우 memberStatus가 MEMBER로 반환된다") void success_member() { // given Long partyId = 1L; @@ -543,7 +543,7 @@ void success_member() { } @Test - @DisplayName("존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") + @DisplayName("실패 - 존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") void fail_partyNotFound() { // given given(partyRepository.findById(999L)).willReturn(Optional.empty()); @@ -556,7 +556,7 @@ void fail_partyNotFound() { } @Test - @DisplayName("삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") + @DisplayName("실패 - 삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") void fail_partyDeleted() { // given PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); From 35f875d8df200037b6fd4386750202309371c908 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 15:19:21 +0900 Subject: [PATCH 09/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=A0=EC=B2=AD=20(/api/parties/{partyId}/join-r?= =?UTF-8?q?equests)=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 135 ++++++++++++----- .../service/PartyCommandServiceTest.java | 139 ++++++++++++++++++ 2 files changed, 237 insertions(+), 37 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 992a48aa3..4966d0a3c 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -17,8 +17,11 @@ import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -35,6 +38,7 @@ import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -60,6 +64,8 @@ class PartyIntegrationTest extends IntegrationTestBase { ChatRoomRepository chatRoomRepository; @Autowired ChatRoomMemberRepository chatRoomMemberRepository; + @Autowired + PartyJoinRequestRepository partyJoinRequestRepository; private Member manager; private Member normalMember; @@ -107,6 +113,7 @@ void tearDown() { exerciseRepository.deleteAll(); chatRoomMemberRepository.deleteAll(); chatRoomRepository.deleteAll(); + partyJoinRequestRepository.deleteAll(); memberPartyRepository.deleteAll(); partyRepository.deleteAll(); partyAddrRepository.deleteAll(); @@ -204,7 +211,7 @@ void success_leaveParty() throws Exception { // DB에서 최신 정보 보장 Member member = memberRepository.findById(normalMember.getId()).orElseThrow(); Party targetParty = partyRepository.findById(party.getId()).orElseThrow(); - + // normalMember 세션으로 설정 SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); @@ -232,10 +239,8 @@ void fail_leaveParty_owner() throws Exception { @DisplayName("403 - 부모임장은 탈퇴할 수 없다") void fail_leaveParty_subOwner() throws Exception { // 부모임장 생성 및 가입 - Member subManager = memberRepository - .saveAndFlush(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 2001L)); - memberPartyRepository - .saveAndFlush(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 3001L)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); // 부모임장 세션으로 설정 SecurityContextHelper.setAuthentication(subManager.getId(), subManager.getNickname()); @@ -249,7 +254,7 @@ void fail_leaveParty_subOwner() throws Exception { @DisplayName("400 - 해당 모임의 멤버가 아니면 탈퇴할 수 없다") void fail_leaveParty_notMember() throws Exception { // 가입하지 않은 새로운 멤버 생성 - Member nonMember = memberRepository.save(MemberFixture.createMember("외부인", Gender.MALE, Level.A, 3001L)); + Member nonMember = memberRepository.save(MemberFixture.createMember("외부인", Gender.MALE, Level.A, 4002L)); SecurityContextHelper.setAuthentication(nonMember.getId(), nonMember.getNickname()); mockMvc.perform(delete("/api/parties/{partyId}/members/my", party.getId())) @@ -266,10 +271,10 @@ class GetMyParties { @DisplayName("200 - 사용자가 가입한 모임 목록을 페이징하여 반환한다") void success_getMyParties() throws Exception { mockMvc.perform(get("/api/my/parties") - .param("created", "false") - .param("sort", "최신순") - .param("size", "10") - .param("page", "0")) + .param("created", "false") + .param("sort", "최신순") + .param("size", "10") + .param("page", "0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) @@ -289,10 +294,10 @@ void success_emptyMyParties() throws Exception { SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); mockMvc.perform(get("/api/my/parties") - .param("created", "false") - .param("sort", "최신순") - .param("size", "10") - .param("page", "0")) + .param("created", "false") + .param("sort", "최신순") + .param("size", "10") + .param("page", "0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) @@ -310,9 +315,9 @@ class GetSimpleMyParties { @DisplayName("200 - 사용자가 가입한 모임의 간략화된 목록을 페이징하여 반환한다") void success_getSimpleMyParties() throws Exception { mockMvc.perform(get("/api/my/parties/simple") - .param("page", "0") - .param("size", "10") - .param("sort", "createdAt,DESC")) + .param("page", "0") + .param("size", "10") + .param("sort", "createdAt,DESC")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) @@ -332,9 +337,9 @@ void success_emptySimpleMyParties() throws Exception { SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); mockMvc.perform(get("/api/my/parties/simple") - .param("page", "0") - .param("size", "10") - .param("sort", "createdAt,DESC")) + .param("page", "0") + .param("size", "10") + .param("sort", "createdAt,DESC")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) @@ -352,10 +357,10 @@ class GetRecommendedParties { @DisplayName("200 - Cockple 추천 모드 시 추천된 모임 목록을 반환한다") void success_cockpleRecommend() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("isCockpleRecommend", "true") - .param("sort", "최신순") - .param("page", "0") - .param("size", "10")) + .param("isCockpleRecommend", "true") + .param("sort", "최신순") + .param("page", "0") + .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) @@ -366,12 +371,12 @@ void success_cockpleRecommend() throws Exception { @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 반환한다") void success_filterMode() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("isCockpleRecommend", "false") - .param("addr1", "서울특별시") - .param("addr2", "강남구") - .param("sort", "최신순") - .param("page", "0") - .param("size", "10")) + .param("isCockpleRecommend", "false") + .param("addr1", "서울특별시") + .param("addr2", "강남구") + .param("sort", "최신순") + .param("page", "0") + .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) @@ -383,10 +388,10 @@ void success_filterMode() throws Exception { @DisplayName("200 - 검색 모드 시 모임명으로 검색된 결과를 반환한다") void success_searchMode() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("search", "추천") - .param("isCockpleRecommend", "false") - .param("page", "0") - .param("size", "10")) + .param("search", "추천") + .param("isCockpleRecommend", "false") + .param("page", "0") + .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) @@ -397,8 +402,8 @@ void success_searchMode() throws Exception { @DisplayName("400 - 유효하지 않은 정렬 기준 입력 시 INVALID_ORDER_TYPE 에러를 반환한다") void fail_invalidOrderType() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("isCockpleRecommend", "false") - .param("sort", "잘못된순")) + .param("isCockpleRecommend", "false") + .param("sort", "잘못된순")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_ORDER_TYPE.getCode())); } @@ -407,7 +412,7 @@ void fail_invalidOrderType() throws Exception { @DisplayName("400 - isCockpleRecommend에 부적절한 타입 입력 시 400 에러를 반환한다") void fail_invalidBooleanType() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("isCockpleRecommend", "not-boolean")) + .param("isCockpleRecommend", "not-boolean")) .andExpect(status().isBadRequest()); } } @@ -463,4 +468,60 @@ void fail_partyDeleted() throws Exception { .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); } } + + @Nested + @DisplayName("POST /api/parties/{partyId}/join-requests - 모임 가입 신청") + class CreateJoinRequest { + + @Test + @DisplayName("200 - 가입하지 않은 회원이 모임 가입을 신청한다") + void success_createJoinRequest() throws Exception { + // 가입하지 않은 멤버 + Member applicant = memberRepository.save(MemberFixture.createMember("신청자", Gender.MALE, Level.A, 5001L, LocalDate.of(1995, 1, 1))); + SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON201")); + } + + @Test + @DisplayName("409 - 이미 가입된 회원이 다시 가입 신청을 한다") + void fail_createJoinRequest_alreadyMember() throws Exception { + // 이미 가입된 normalMember 사용 + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", party.getId())) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.ALREADY_MEMBER.getCode())); + } + + @Test + @DisplayName("400 - 성별 조건이 맞지 않는 모임에 신청한다") + void fail_createJoinRequest_genderMismatch() throws Exception { + // 여복 모임 생성 + PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울", "강남")); + Party womenParty = partyRepository.save(Party.builder() + .partyName("여복 전용 모임") + .partyType(ParticipationType.WOMEN_DOUBLES) + .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .ownerId(manager.getId()) + .partyAddr(addr) + .minBirthYear(1900) + .maxBirthYear(2099) + .activityTime(ActivityTime.MORNING) + .designatedCock("테스트콕") + .exerciseCount(0) + .price(0) + .joinPrice(0) + .build()); + + // 남성 사용자로 신청 시도 + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", womenParty.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.GENDER_NOT_MATCH.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 3998eeda0..7e1b5a3cb 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -14,11 +14,17 @@ import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; +import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.domain.PartyJoinRequest; +import umc.cockple.demo.domain.party.dto.PartyJoinCreateDTO; +import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -26,6 +32,7 @@ import umc.cockple.demo.support.fixture.MemberFixture; import umc.cockple.demo.support.fixture.PartyFixture; +import java.time.LocalDate; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -50,6 +57,10 @@ class PartyCommandServiceTest { private ChatRoomService chatRoomService; @Mock private ApplicationEventPublisher applicationEventPublisher; + @Mock + private PartyJoinRequestRepository partyJoinRequestRepository; + @Mock + private PartyConverter partyConverter; @Nested @DisplayName("leaveParty") @@ -197,4 +208,132 @@ void fail_leaveParty_notMember() { .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER)); } } + + @Nested + @DisplayName("createJoinRequest") + class CreateJoinRequest { + + @Test + @DisplayName("성공 - 사용자가 특정 모임에 가입 신청을 성공적으로 완료한다") + void success_createJoinRequest() { + // given + Long partyId = 1L; + Long memberId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("가입 신청 모임", 10L, addr); + Member member = MemberFixture.createMember("지원자", Gender.MALE, Level.B, 1L, LocalDate.of(1995, 1, 1)); + ReflectionTestUtils.setField(member, "id", memberId); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(false); + given(partyJoinRequestRepository.save(any(PartyJoinRequest.class))).willAnswer(invocation -> invocation.getArgument(0)); + + // when + partyCommandService.createJoinRequest(partyId, memberId); + + // then + verify(partyJoinRequestRepository).save(any(PartyJoinRequest.class)); + verify(partyConverter).toJoinResponseDTO(any(PartyJoinRequest.class)); + } + + @Test + @DisplayName("실패 - 이미 해당 모임의 멤버인 경우 ALREADY_MEMBER 예외가 발생한다") + void fail_createJoinRequest_alreadyMember() { + // given + Long partyId = 1L; + Long memberId = 1L; + + Party party = PartyFixture.createParty("가입 신청 모임", 10L, null); + Member member = MemberFixture.createMember("지원자", Gender.MALE, Level.B, 1L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(true); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.ALREADY_MEMBER)); + } + + @Test + @DisplayName("실패 - 대기 중인 가입 신청이 이미 존재하는 경우 JOIN_REQUEST_ALREADY_EXISTS 예외가 발생한다") + void fail_createJoinRequest_alreadyRequested() { + // given + Long partyId = 1L; + Long memberId = 1L; + + Party party = PartyFixture.createParty("가입 신청 모임", 10L, null); + Member member = MemberFixture.createMember("지원자", Gender.MALE, Level.B, 1L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(true); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_ALREADY_EXISTS)); + } + + @Test + @DisplayName("실패 - 모임 유형에 맞지 않는 성별인 경우 GENDER_NOT_MATCH 예외가 발생한다") + void fail_createJoinRequest_genderMismatch() { + // given + Long partyId = 1L; + Long memberId = 1L; + + // 여복 모임 생성 + Party party = Party.builder() + .partyName("여복 모임") + .partyType(ParticipationType.WOMEN_DOUBLES) + .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .ownerId(10L) + .build(); + Member member = MemberFixture.createMember("남자지원자", Gender.MALE, Level.B, 1L); // 남성 지원 + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(false); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.GENDER_NOT_MATCH)); + } + + @Test + @DisplayName("실패 - 모임의 나이 조건에 맞지 않는 경우 AGE_NOT_MATCH 예외가 발생한다") + void fail_createJoinRequest_ageMismatch() { + // given + Long partyId = 1L; + Long memberId = 1L; + + // 1990~2000년생 모임 + Party party = Party.builder() + .partyName("나이 제한 모임") + .minBirthYear(1990) + .maxBirthYear(2000) + .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .ownerId(10L) + .build(); + // 1980년생 지원자 (범위 밖) + Member member = MemberFixture.createMember("나이많은지원자", Gender.MALE, Level.B, 1L, LocalDate.of(1980, 1, 1)); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(false); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH)); + } + } } From 336df9b09897b9826bdfa3c6da79ec77bca278ca Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 16:05:43 +0900 Subject: [PATCH 10/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20(/api/parties)=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 120 ++++++++- .../service/PartyCommandServiceTest.java | 250 +++++++++++++++++- 2 files changed, 363 insertions(+), 7 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 4966d0a3c..ef905ee65 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -1,13 +1,14 @@ package umc.cockple.demo.domain.party.integration; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; -import umc.cockple.demo.domain.exercise.domain.Exercise; import umc.cockple.demo.domain.chat.domain.ChatRoom; import umc.cockple.demo.domain.chat.domain.ChatRoomMember; import umc.cockple.demo.domain.chat.repository.ChatRoomMemberRepository; import umc.cockple.demo.domain.chat.repository.ChatRoomRepository; +import umc.cockple.demo.domain.exercise.domain.Exercise; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; @@ -17,8 +18,9 @@ import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; -import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.dto.PartyCreateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; +import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; @@ -33,12 +35,11 @@ import umc.cockple.demo.support.fixture.PartyFixture; import java.time.LocalDate; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -66,6 +67,8 @@ class PartyIntegrationTest extends IntegrationTestBase { ChatRoomMemberRepository chatRoomMemberRepository; @Autowired PartyJoinRequestRepository partyJoinRequestRepository; + @Autowired + ObjectMapper objectMapper; private Member manager; private Member normalMember; @@ -524,4 +527,111 @@ void fail_createJoinRequest_genderMismatch() throws Exception { .andExpect(jsonPath("$.code").value(PartyErrorCode.GENDER_NOT_MATCH.getCode())); } } + + @Nested + @DisplayName("POST /api/parties - 모임 생성") + class CreateParty { + + @Test + @DisplayName("200 - 모임을 성공적으로 생성하고 DB 저장 상태를 확인한다") + void success_createParty() throws Exception { + // given + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("새로운 통합 모임") + .partyType("혼복") + .minBirthYear(1990) + .maxBirthYear(2000) + .activityTime("오전") + .addr1("서울특별시") + .addr2("강남구") + .activityDay(List.of("월", "수")) + .price(10000) + .joinPrice(5000) + .designatedCock("통합테스트콕") + .maleLevel(List.of("A조")) + .femaleLevel(List.of("B조")) + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON201")) + .andExpect(jsonPath("$.data.partyId").exists()); + + // DB 검증 + List parties = partyRepository.findAll(); + Party createdParty = parties.stream() + .filter(p -> p.getPartyName().equals("새로운 통합 모임")) + .findFirst() + .orElseThrow(); + + assertThat(createdParty.getOwnerId()).isEqualTo(manager.getId()); + assertThat(createdParty.getDesignatedCock()).isEqualTo("통합테스트콕"); + } + + @Test + @DisplayName("400 - 본인의 나이가 모임 조건에 맞지 않을 때 에러를 반환한다") + void fail_createParty_invalidAgeRange() throws Exception { + // given + // manager는 1995년생. 모임 조건을 2000~2010으로 설정. + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("청년 모임") + .partyType("혼복") + .minBirthYear(2000) + .maxBirthYear(2010) + .activityTime("오후") + .activityDay(List.of("금")) + .addr1("서울특별시") + .addr2("강남구") + .price(10000) + .joinPrice(0) + .femaleLevel(List.of("A조")) + .maleLevel(List.of("A조")) + .designatedCock("청년콕") + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.AGE_NOT_MATCH.getCode())); + } + + @Test + @DisplayName("400 - 혼복 모임에서 남자 급수 정보가 누락되었을 때 에러를 반환한다") + void fail_createParty_missingMaleLevelInMixDoubles() throws Exception { + // given + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("혼복 모임") + .partyType("혼복") + .minBirthYear(1990) + .maxBirthYear(2005) + .activityTime("오전") + .activityDay(List.of("토")) + .addr1("서울특별시") + .addr2("강남구") + .price(10000) + .joinPrice(0) + .designatedCock("혼복콕") + .maleLevel(null) + .femaleLevel(List.of("A조")) + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.MALE_LEVEL_REQUIRED.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 7e1b5a3cb..c35ab53c0 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -18,12 +18,15 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; -import umc.cockple.demo.domain.party.dto.PartyJoinCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.enums.ActiveDay; +import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; @@ -33,12 +36,15 @@ import umc.cockple.demo.support.fixture.PartyFixture; import java.time.LocalDate; +import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) @@ -52,6 +58,8 @@ class PartyCommandServiceTest { @Mock private MemberRepository memberRepository; @Mock + private PartyAddrRepository partyAddrRepository; + @Mock private MemberPartyRepository memberPartyRepository; @Mock private ChatRoomService chatRoomService; @@ -336,4 +344,242 @@ void fail_createJoinRequest_ageMismatch() { .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH)); } } + + @Nested + @DisplayName("createParty - 모임 생성") + class CreateParty { + + @Test + @DisplayName("성공 - 올바른 데이터 입력 시 모임이 생성되고 채팅방이 개설된다") + void success_createParty() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("테스트 모임") + .partyType("혼복") + .minBirthYear(1990) + .maxBirthYear(2000) + .activityTime("오전") + .addr1("서울") + .addr2("강남") + .activityDay(List.of("월", "수")) + .price(10000) + .joinPrice(5000) + .designatedCock("테스트콕") + .maleLevel(List.of("A조")) + .femaleLevel(List.of("B조")) + .build(); + + Member owner = Member.builder() + .id(memberId) + .gender(Gender.MALE) + .level(Level.A) + .birth(LocalDate.of(1995, 1, 1)) + .build(); + + PartyAddr partyAddr = PartyAddr.builder().id(1L).build(); + Party savedParty = Party.builder().id(1L).partyName("테스트 모임").build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyName(request.partyName()) + .partyType(ParticipationType.fromKorean(request.partyType())) + .femaleLevel(List.of(Level.B)) + .maleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY, ActiveDay.WEDNESDAY)) + .activityTime(ActivityTime.MORNING) + .price(10000) + .joinPrice(5000) + .designatedCock("테스트콕") + .minBirthYear(request.minBirthYear()) + .maxBirthYear(request.maxBirthYear()) + .build(); + + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder() + .addr1(request.addr1()) + .addr2(request.addr2()) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + given(partyAddrRepository.findByAddr1AndAddr2(anyString(), anyString())).willReturn(Optional.of(partyAddr)); + given(partyRepository.save(any(Party.class))).willReturn(savedParty); + given(partyConverter.toCreateResponseDTO(any())).willReturn(PartyCreateDTO.Response.builder().partyId(1L).build()); + + // when + PartyCreateDTO.Response response = partyCommandService.createParty(memberId, request); + + // then + assertThat(response).isNotNull(); + assertThat(response.partyId()).isEqualTo(1L); + verify(partyRepository, times(1)).save(any(Party.class)); + verify(chatRoomService, times(1)).createPartyChatRoom(any(Party.class), eq(owner)); + } + + @Test + @DisplayName("실패 - 혼복 모임 생성 시 남자 급수 정보가 누락되면 MALE_LEVEL_REQUIRED 예외가 발생한다") + void fail_createParty_mixDoubles_maleLevelMissing() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("혼복 모임") + .partyType("혼복") + .minBirthYear(1990) + .maxBirthYear(2000) + .activityTime("오전") + .activityDay(List.of("월")) + .femaleLevel(List.of("A조")) + .maleLevel(null) // 누락 + .build(); + + Member owner = Member.builder() + .id(memberId) + .gender(Gender.MALE) + .birth(LocalDate.of(1995, 1, 1)) + .build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyType(ParticipationType.MIX_DOUBLES) + .maleLevel(null) + .femaleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY)) + .minBirthYear(1990) + .maxBirthYear(2000) + .build(); + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createParty(memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.MALE_LEVEL_REQUIRED); + } + + @Test + @DisplayName("실패 - 여복 모임 생성 시 남자 급수 정보가 포함되면 MALE_LEVEL_NOT_NEEDED 예외가 발생한다") + void fail_createParty_womenDoubles_maleLevelProvided() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("여복 모임") + .partyType("여복") + .minBirthYear(1990) + .maxBirthYear(2010) + .activityTime("오전") + .activityDay(List.of("토")) + .femaleLevel(List.of("A조")) + .maleLevel(List.of("A조")) // 포함됨 + .build(); + + Member owner = Member.builder() + .id(memberId) + .gender(Gender.FEMALE) + .birth(LocalDate.of(2000, 1, 1)) + .build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyType(ParticipationType.WOMEN_DOUBLES) + .maleLevel(List.of(Level.A)) + .femaleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY)) + .minBirthYear(1990) + .maxBirthYear(2010) + .build(); + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createParty(memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.MALE_LEVEL_NOT_NEEDED); + } + + @Test + @DisplayName("실패 - 모임 유형의 성별 조건과 생성자의 성별이 맞지 않으면 GENDER_NOT_MATCH 예외가 발생한다") + void fail_createParty_genderMismatch() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("여복 모임") + .partyType("여복") + .minBirthYear(1990) + .maxBirthYear(2010) + .activityTime("오전") + .activityDay(List.of("일")) + .femaleLevel(List.of("A조")) + .build(); + + Member maleOwner = Member.builder() + .id(memberId) + .gender(Gender.MALE) // 남성이 여복 모임 생성 시도 + .birth(LocalDate.of(2000, 1, 1)) + .build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyType(ParticipationType.WOMEN_DOUBLES) + .femaleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY)) + .minBirthYear(1990) + .maxBirthYear(2010) + .build(); + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(maleOwner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createParty(memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.GENDER_NOT_MATCH); + } + + @Test + @DisplayName("실패 - 생성자의 나이가 모임의 나이 제한 범위를 벗어나면 AGE_NOT_MATCH 예외가 발생한다") + void fail_createParty_ageMismatch() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("청년 모임") + .partyType("혼복") + .minBirthYear(2000) + .maxBirthYear(2010) + .maleLevel(List.of("A조")) + .femaleLevel(List.of("A조")) + .activityTime("오후") + .activityDay(List.of("금")) + .build(); + + Member oldOwner = Member.builder() + .id(memberId) + .gender(Gender.MALE) + .birth(LocalDate.of(1980, 1, 1)) // 80년생이 00~10년생 모임 생성 시도 + .build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyType(ParticipationType.MIX_DOUBLES) + .femaleLevel(List.of(Level.A)) + .maleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY)) + .minBirthYear(2000) + .maxBirthYear(2010) + .build(); + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(oldOwner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createParty(memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH); + } + } } From b504ecdddeb41ee61424bffbf7a37e934ef33889 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 16:43:14 +0900 Subject: [PATCH 11/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9/=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=20=EB=A7=9E=EC=B6=94=EA=B8=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 89 ++++++------------- .../service/PartyCommandServiceTest.java | 87 +++--------------- .../party/service/PartyQueryServiceTest.java | 87 ++++++++---------- 3 files changed, 80 insertions(+), 183 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index ef905ee65..15210d073 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -1,9 +1,13 @@ package umc.cockple.demo.domain.party.integration; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import umc.cockple.demo.domain.chat.domain.ChatRoom; import umc.cockple.demo.domain.chat.domain.ChatRoomMember; import umc.cockple.demo.domain.chat.repository.ChatRoomMemberRepository; @@ -21,6 +25,7 @@ import umc.cockple.demo.domain.party.dto.PartyCreateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; @@ -43,6 +48,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@Transactional class PartyIntegrationTest extends IntegrationTestBase { @Autowired @@ -76,8 +82,8 @@ class PartyIntegrationTest extends IntegrationTestBase { @BeforeEach void setUp() { - manager = memberRepository - .save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, LocalDate.of(1995, 1, 1))); + // 매니저 및 주소 정보 생성 + manager = memberRepository.save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, LocalDate.of(1995, 1, 1))); memberAddrRepository.save(MemberAddr.builder() .member(manager) .addr1("서울특별시") @@ -89,99 +95,56 @@ void setUp() { .isMain(true) .build()); + // 일반 멤버 생성 normalMember = memberRepository.save(MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L)); + // 모임 및 주소 정보 생성 PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); + // 모임 멤버 생성 memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); - // 채팅방 생성 및 멤버 추가 + // 채팅방 생성 ChatRoom chatRoom = chatRoomRepository.save(ChatRoom.createPartyChatRoom(party)); chatRoomMemberRepository.save(ChatRoomMember.create(chatRoom, manager)); chatRoomMemberRepository.save(ChatRoomMember.create(chatRoom, normalMember)); - // 추천 조회용 모임 (manager가 가입하지 않은 모임) + // 추천 조회용 모임 (manager의 조건에 맞춤) Party suggestedParty = PartyFixture.createParty("추천 모임", normalMember.getId(), addr); - suggestedParty.addLevel(Gender.MALE, Level.A); // manager의 조건에 맞춤 + suggestedParty.addLevel(Gender.MALE, Level.A); partyRepository.save(suggestedParty); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); } - @AfterEach - void tearDown() { - memberExerciseRepository.deleteAll(); - exerciseRepository.deleteAll(); - chatRoomMemberRepository.deleteAll(); - chatRoomRepository.deleteAll(); - partyJoinRequestRepository.deleteAll(); - memberPartyRepository.deleteAll(); - partyRepository.deleteAll(); - partyAddrRepository.deleteAll(); - memberAddrRepository.deleteAll(); - memberRepository.deleteAll(); - } @Nested @DisplayName("GET /api/parties/{partyId}/members - 모임 멤버 조회") class GetPartyMembers { @Test - @DisplayName("200 - 모임의 멤버들을 역할별로 성공적으로 조회한다.") - void success() throws Exception { + @DisplayName("200 - 멤버 목록을 역할, 성별 통계 및 마지막 운동일과 함께 조회한다") + void success_getMembersWithDetails() throws Exception { // 부모임장 추가 Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 1003L)); memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); - // 모임장이 가입된 상태 - SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + // 운동 기록 추가 + Exercise exercise = exerciseRepository.save(ExerciseFixture.createExercise(party, LocalDate.of(2025, 1, 10))); + memberExerciseRepository.save(MemberFixture.createMemberExercise(normalMember, exercise)); mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.summary.totalCount").value(3)) + .andExpect(jsonPath("$.data.summary.maleCount").value(2)) + .andExpect(jsonPath("$.data.summary.femaleCount").value(1)) .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) .andExpect(jsonPath("$.data.members[0].isMe").value(true)) .andExpect(jsonPath("$.data.members[1].role").value("party_SUBMANAGER")) - .andExpect(jsonPath("$.data.members[1].isMe").value(false)) .andExpect(jsonPath("$.data.members[2].role").value("party_MEMBER")) - .andExpect(jsonPath("$.data.members[2].isMe").value(false)); - } - - @Test - @DisplayName("200 - 멤버 목록과 마지막 운동일을 정상 반환한다") - void success_withLastExerciseDate() throws Exception { - Exercise exercise = exerciseRepository.save( - ExerciseFixture.createExercise(party, LocalDate.of(2025, 1, 10))); - memberExerciseRepository.save(MemberFixture.createMemberExercise(normalMember, exercise)); - - mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.summary.totalCount").value(2)) - .andExpect(jsonPath("$.data.summary.maleCount").value(1)) - .andExpect(jsonPath("$.data.summary.femaleCount").value(1)) - // 첫 번째 멤버(매니저) 전체 필드 검증 - .andExpect(jsonPath("$.data.members[0].memberId").value(manager.getId())) - .andExpect(jsonPath("$.data.members[0].nickname").value("매니저")) - .andExpect(jsonPath("$.data.members[0].profileImageUrl").doesNotExist()) - .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) - .andExpect(jsonPath("$.data.members[0].gender").value("MALE")) - .andExpect(jsonPath("$.data.members[0].level").value("A조")) - .andExpect(jsonPath("$.data.members[0].isMe").value(true)) - .andExpect(jsonPath("$.data.members[0].lastExerciseDate").doesNotExist()) - // 두 번째 멤버(일반멤버) 마지막 운동일 검증 - .andExpect(jsonPath("$.data.members[1].lastExerciseDate").value("2025-01-10")); - } - - @Test - @DisplayName("200 - 운동 기록이 없는 멤버의 lastExerciseDate는 null이다") - void success_noExerciseHistory() throws Exception { - mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.summary.totalCount").value(2)) - .andExpect(jsonPath("$.data.members[0].lastExerciseDate").isEmpty()) - .andExpect(jsonPath("$.data.members[1].lastExerciseDate").isEmpty()); + .andExpect(jsonPath("$.data.members[2].lastExerciseDate").value("2025-01-10")); } @Test @@ -479,13 +442,17 @@ class CreateJoinRequest { @Test @DisplayName("200 - 가입하지 않은 회원이 모임 가입을 신청한다") void success_createJoinRequest() throws Exception { - // 가입하지 않은 멤버 + // 가입하지 않은 멤버 생성 Member applicant = memberRepository.save(MemberFixture.createMember("신청자", Gender.MALE, Level.A, 5001L, LocalDate.of(1995, 1, 1))); SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); mockMvc.perform(post("/api/parties/{partyId}/join-requests", party.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON201")); + + // 가입 신청 데이터 확인 + boolean exists = partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, applicant, RequestStatus.PENDING); + assertThat(exists).isTrue(); } @Test diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index c35ab53c0..4f612bf29 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -1,5 +1,6 @@ package umc.cockple.demo.domain.party.service; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -10,6 +11,7 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.test.util.ReflectionTestUtils; import umc.cockple.demo.domain.chat.service.ChatRoomService; +import umc.cockple.demo.domain.file.service.FileService; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; @@ -18,9 +20,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; -import umc.cockple.demo.domain.party.dto.PartyCreateDTO; -import umc.cockple.demo.domain.party.enums.ActiveDay; -import umc.cockple.demo.domain.party.enums.ActivityTime; +import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; @@ -68,8 +68,16 @@ class PartyCommandServiceTest { @Mock private PartyJoinRequestRepository partyJoinRequestRepository; @Mock + private FileService fileService; + private PartyConverter partyConverter; + @BeforeEach + void setUp() { + partyConverter = new PartyConverter(fileService); + ReflectionTestUtils.setField(partyCommandService, "partyConverter", partyConverter); + } + @Nested @DisplayName("leaveParty") class LeaveParty { @@ -240,11 +248,11 @@ void success_createJoinRequest() { given(partyJoinRequestRepository.save(any(PartyJoinRequest.class))).willAnswer(invocation -> invocation.getArgument(0)); // when - partyCommandService.createJoinRequest(partyId, memberId); + PartyJoinCreateDTO.Response response = partyCommandService.createJoinRequest(partyId, memberId); // then + assertThat(response).isNotNull(); verify(partyJoinRequestRepository).save(any(PartyJoinRequest.class)); - verify(partyConverter).toJoinResponseDTO(any(PartyJoinRequest.class)); } @Test @@ -380,31 +388,9 @@ void success_createParty() { PartyAddr partyAddr = PartyAddr.builder().id(1L).build(); Party savedParty = Party.builder().id(1L).partyName("테스트 모임").build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyName(request.partyName()) - .partyType(ParticipationType.fromKorean(request.partyType())) - .femaleLevel(List.of(Level.B)) - .maleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY, ActiveDay.WEDNESDAY)) - .activityTime(ActivityTime.MORNING) - .price(10000) - .joinPrice(5000) - .designatedCock("테스트콕") - .minBirthYear(request.minBirthYear()) - .maxBirthYear(request.maxBirthYear()) - .build(); - - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder() - .addr1(request.addr1()) - .addr2(request.addr2()) - .build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); given(partyAddrRepository.findByAddr1AndAddr2(anyString(), anyString())).willReturn(Optional.of(partyAddr)); given(partyRepository.save(any(Party.class))).willReturn(savedParty); - given(partyConverter.toCreateResponseDTO(any())).willReturn(PartyCreateDTO.Response.builder().partyId(1L).build()); // when PartyCreateDTO.Response response = partyCommandService.createParty(memberId, request); @@ -438,19 +424,7 @@ void fail_createParty_mixDoubles_maleLevelMissing() { .birth(LocalDate.of(1995, 1, 1)) .build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyType(ParticipationType.MIX_DOUBLES) - .maleLevel(null) - .femaleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY)) - .minBirthYear(1990) - .maxBirthYear(2000) - .build(); - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); // when & then PartyException exception = assertThrows(PartyException.class, @@ -480,19 +454,7 @@ void fail_createParty_womenDoubles_maleLevelProvided() { .birth(LocalDate.of(2000, 1, 1)) .build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyType(ParticipationType.WOMEN_DOUBLES) - .maleLevel(List.of(Level.A)) - .femaleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY)) - .minBirthYear(1990) - .maxBirthYear(2010) - .build(); - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); // when & then PartyException exception = assertThrows(PartyException.class, @@ -521,18 +483,7 @@ void fail_createParty_genderMismatch() { .birth(LocalDate.of(2000, 1, 1)) .build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyType(ParticipationType.WOMEN_DOUBLES) - .femaleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY)) - .minBirthYear(1990) - .maxBirthYear(2010) - .build(); - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(maleOwner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); // when & then PartyException exception = assertThrows(PartyException.class, @@ -562,19 +513,7 @@ void fail_createParty_ageMismatch() { .birth(LocalDate.of(1980, 1, 1)) // 80년생이 00~10년생 모임 생성 시도 .build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyType(ParticipationType.MIX_DOUBLES) - .femaleLevel(List.of(Level.A)) - .maleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY)) - .minBirthYear(2000) - .maxBirthYear(2010) - .build(); - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(oldOwner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); // when & then PartyException exception = assertThrows(PartyException.class, diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 18d18f1a3..89ae54686 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -1,5 +1,6 @@ package umc.cockple.demo.domain.party.service; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -7,9 +8,18 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.test.util.ReflectionTestUtils; +import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; +import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; +import umc.cockple.demo.domain.file.service.FileService; import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.domain.MemberAddr; import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; @@ -20,6 +30,7 @@ import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -29,21 +40,9 @@ import java.time.LocalDate; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; -import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; -import umc.cockple.demo.domain.member.repository.MemberAddrRepository; -import umc.cockple.demo.domain.file.service.FileService; -import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; -import umc.cockple.demo.domain.member.domain.MemberAddr; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.*; @@ -60,8 +59,9 @@ class PartyQueryServiceTest { private PartyRepository partyRepository; @Mock private MemberRepository memberRepository; - @Mock + private PartyConverter partyConverter; + @Mock private MemberPartyRepository memberPartyRepository; @Mock @@ -77,6 +77,12 @@ class PartyQueryServiceTest { @Mock private PartyJoinRequestRepository partyJoinRequestRepository; + @BeforeEach + void setUp() { + partyConverter = new PartyConverter(fileService); + ReflectionTestUtils.setField(partyQueryService, "partyConverter", partyConverter); + } + @Nested @DisplayName("getPartyMembers") class GetPartyMembers { @@ -112,10 +118,13 @@ void success() { .willReturn(List.of()); // when - partyQueryService.getPartyMembers(partyId, currentMemberId); + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); // then - verify(partyConverter).toPartyMemberDTO(eq(memberParties), eq(currentMemberId), anyMap()); + assertThat(result.members()).hasSize(3); + assertThat(result.summary().totalCount()).isEqualTo(3); + assertThat(result.summary().maleCount()).isEqualTo(2); + assertThat(result.summary().femaleCount()).isEqualTo(1); } @Test @@ -150,20 +159,18 @@ void success_withExerciseHistory() { given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( List.of(10L, 20L), partyId)).willReturn(rawResult); - given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) - .willReturn(expected); // when PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); // then - assertThat(result).isEqualTo(expected); - verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId); - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of(20L, lastDate))); + assertThat(result.summary().totalCount()).isEqualTo(2); + assertThat(result.members()).hasSize(2); + // 마지막 운동일 확인 (멤버1 id: 20L) + assertThat(result.members().stream() + .filter(m -> m.memberId().equals(20L)) + .findFirst() + .get().lastExerciseDate()).isEqualTo(lastDate); } @Test @@ -185,16 +192,13 @@ void noExerciseHistory() { given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( List.of(10L), partyId)).willReturn(List.of()); - given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); // when - partyQueryService.getPartyMembers(partyId, currentMemberId); + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); // then - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of())); + assertThat(result.members()).hasSize(1); + assertThat(result.members().get(0).lastExerciseDate()).isNull(); } @Test @@ -262,9 +266,6 @@ void success() { .willReturn(List.of()); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) .willReturn(Set.of(1L)); - given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) - .willReturn(expectedResponse); - // when Slice result = partyQueryService.getMyParties(memberId, false, "최신순", pageable); @@ -275,7 +276,6 @@ void success() { assertThat(result.getContent().get(0).isBookmarked()).isTrue(); verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); - verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); } } @@ -308,8 +308,6 @@ void success() { given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); - // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 - given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); // when Slice result = partyQueryService.getSimpleMyParties(memberId, @@ -321,7 +319,6 @@ void success() { verify(memberRepository).findById(memberId); verify(memberPartyRepository).findByMember(member, pageable); - verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); } @Test @@ -374,9 +371,6 @@ void success_cockpleRecommend() { given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) .willReturn(List.of(suggestedParty)); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") - .build()); // when Slice result = partyQueryService.getRecommendedParties(memberId, true, @@ -408,9 +402,6 @@ void success_filterMode() { given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) .willReturn(partySlice); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") - .build()); // when Slice result = partyQueryService.getRecommendedParties(memberId, false, @@ -497,14 +488,15 @@ void success_nonMember() { given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(false); - given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), eq(false))) - .willReturn(expected); // when PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); // then - assertThat(result).isEqualTo(expected); + assertThat(result.partyId()).isEqualTo(partyId); + assertThat(result.partyName()).isEqualTo("상세 모임"); + assertThat(result.memberStatus()).isEqualTo("NOT_MEMBER"); + assertThat(result.hasPendingJoinRequest()).isFalse(); verify(partyRepository).findById(partyId); } @@ -532,14 +524,13 @@ void success_member() { given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); given(memberPartyRepository.findByPartyAndMember(party, member)) .willReturn(Optional.of(memberParty)); - given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), anyBoolean())) - .willReturn(expected); // when PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); // then assertThat(result.memberStatus()).isEqualTo("MEMBER"); + assertThat(result.memberRole()).isEqualTo("party_MEMBER"); } @Test From 9de1dacc6be62c90bfde87a2e07e0d9909ade9fb Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 17:24:58 +0900 Subject: [PATCH 12/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20(/api/parties/{partyId})=20API?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 75 +++++++++++++++ .../service/PartyCommandServiceTest.java | 92 +++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 15210d073..45bae65c3 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -23,6 +23,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; @@ -495,6 +496,80 @@ void fail_createJoinRequest_genderMismatch() throws Exception { } } + @Nested + @DisplayName("PATCH /api/parties/{partyId} - 모임 정보 수정") + class UpdateParty { + + @Test + @DisplayName("200 - 모임장이 유효한 데이터로 모임 정보를 정상적으로 수정한다") + void success_updateParty() throws Exception { + // given + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(List.of("월", "수")) + .activityTime("오전") + .designatedCock("수정된 콕") + .joinPrice(2000) + .price(15000) + .content("수정된 내용입니다.") + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}", party.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // DB 검증 + Party updatedParty = partyRepository.findById(party.getId()).orElseThrow(); + assertThat(updatedParty.getDesignatedCock()).isEqualTo("수정된 콕"); + assertThat(updatedParty.getJoinPrice()).isEqualTo(2000); + assertThat(updatedParty.getPrice()).isEqualTo(15000); + assertThat(updatedParty.getContent()).isEqualTo("수정된 내용입니다."); + } + + @Test + @DisplayName("400 - 필수 필드(activityDay, activityTime) 누락 시 에러를 반환한다") + void fail_updateParty_missingRequiredFields() throws Exception { + // given + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(null) + .activityTime("") + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}", party.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("COMMON400_VALIDATION")); + } + + @Test + @DisplayName("403 - 모임장이 아닌 일반 멤버가 수정을 시도하면 INSUFFICIENT_PERMISSION 에러를 반환한다") + void fail_updateParty_notOwner() throws Exception { + // given + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(List.of("토", "일")) + .activityTime("오후") + .build(); + + // 일반 멤버로 세션 설정 + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}", party.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 4f612bf29..20b106ee4 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -20,6 +20,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; +import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; @@ -58,6 +59,8 @@ class PartyCommandServiceTest { @Mock private MemberRepository memberRepository; @Mock + private NotificationCommandService notificationCommandService; + @Mock private PartyAddrRepository partyAddrRepository; @Mock private MemberPartyRepository memberPartyRepository; @@ -521,4 +524,93 @@ void fail_createParty_ageMismatch() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH); } } + + @Nested + @DisplayName("updateParty") + class UpdateParty { + + @Test + @DisplayName("성공 - 모임장이 모임 정보를 정상적으로 수정한다") + void success_updateParty() { + // given + Long partyId = 1L; + Long memberId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", memberId); + + Party party = PartyFixture.createParty("기존 모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(List.of("토", "일")) + .activityTime("오전") + .designatedCock("새 콕") + .joinPrice(0) + .price(10000) + .content("새로운 내용") + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); + + // when + partyCommandService.updateParty(partyId, memberId, request); + + // then + assertThat(party.getDesignatedCock()).isEqualTo("새 콕"); + assertThat(party.getActiveDays().size()).isEqualTo(2); // 토, 일 + assertThat(party.getJoinPrice()).isEqualTo(0); + assertThat(party.getPrice()).isEqualTo(10000); + assertThat(party.getContent()).isEqualTo("새로운 내용"); + + verify(notificationCommandService, times(1)).createNotification(any()); + } + + @Test + @DisplayName("실패 - 조회된 모임이 없는 경우 PARTY_NOT_FOUND 예외 발생") + void fail_updateParty_partyNotFound() { + // given + Long partyId = 99L; + Long memberId = 1L; + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder().build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.updateParty(partyId, memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 수정을 시도할 경우 INSUFFICIENT_PERMISSION 예외 발생") + void fail_updateParty_insufficientPermission() { + // given + Long partyId = 1L; + Long memberId = 10L; // 일반 멤버 (ownerId=1 과 다름) + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", 1L); + + Member normalMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 2L); + ReflectionTestUtils.setField(normalMember, "id", memberId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityTime("오전").build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(normalMember)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.updateParty(partyId, memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + } } From 8ea3743ca6aa9a39d20adc859ea053d03e2dc463 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sat, 28 Mar 2026 20:25:33 +0900 Subject: [PATCH 13/63] =?UTF-8?q?test:=20=EB=A9=A4=EB=B2=84=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0(=EB=B6=80=EB=AA=A8=EC=9E=84=EC=9E=A5)=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20(/api/parties/{partyId}/members/{memberId}/role)=20?= =?UTF-8?q?API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 57 ++++++++++ .../service/PartyCommandServiceTest.java | 105 ++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 45bae65c3..cf8f65154 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -16,6 +16,7 @@ import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; +import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; @@ -23,6 +24,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; @@ -676,4 +678,59 @@ void fail_createParty_missingMaleLevelInMixDoubles() throws Exception { .andExpect(jsonPath("$.code").value(PartyErrorCode.MALE_LEVEL_REQUIRED.getCode())); } } + + @Nested + @DisplayName("PATCH /api/parties/{partyId}/members/{memberId}/role - 멤버 역할(부모임장) 설정") + class UpdateMemberRole { + + @Test + @DisplayName("200 - 모임장이 일반 멤버를 부모임장으로 성공적으로 임명한다") + void success_assignSubManager() throws Exception { + // given + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", party.getId(), normalMember.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + MemberParty targetMemberParty = memberPartyRepository.findByPartyAndMember(party, normalMember).orElseThrow(); + assertThat(targetMemberParty.getRole()).isEqualTo(Role.party_SUBMANAGER); + } + + @Test + @DisplayName("403 - 모임장이 아닌 멤버가 역할 수정을 시도하면 INSUFFICIENT_PERMISSION 예외를 반환한다") + void fail_assignSubManager_notOwner() throws Exception { + // given + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + // 일반 멤버가 권한 변경 시도 + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", party.getId(), normalMember.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("403 - 대상자가 모임장인 경우 권한 변경은 실패하며 CANNOT_ASSIGN_TO_OWNER 예외를 반환한다") + void fail_assignSubManager_targetIsOwner() throws Exception { + // given + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_MEMBER); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", party.getId(), manager.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 20b106ee4..0aeb85616 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -613,4 +613,109 @@ void fail_updateParty_insufficientPermission() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } } + + @Nested + @DisplayName("updateMemberRole") + class UpdateMemberRole { + + @Test + @DisplayName("성공 - 모임장이 일반 멤버를 부모임장으로 지정하고 알림을 발생시킨다") + void success_updateMemberRole() { + // given + Long partyId = 1L; + Long currentOwnerId = 1L; + Long targetMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, currentOwnerId); + ReflectionTestUtils.setField(owner, "id", currentOwnerId); + + Member targetMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, targetMemberId); + ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty memberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(memberParty)); + // 만약 기존 부모임장이 있으면 해제하는 로직에 대한 빈 Optional 반환 + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)).willReturn(Optional.empty()); + // 알림 발송 시 파티 내 전체 멤버를 조회 + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(memberParty)); + + // when + partyCommandService.updateMemberRole(partyId, targetMemberId, currentOwnerId, request); + + // then + assertThat(memberParty.getRole()).isEqualTo(Role.party_SUBMANAGER); + verify(notificationCommandService, times(1)).createNotification(any()); + } + + @Test + @DisplayName("실패 - 대상 멤버가 이미 모임장인 경우 권한을 변경하려 하면 CANNOT_ASSIGN_TO_OWNER 발생") + void fail_updateMemberRole_targetIsOwner() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + // 타겟이 이미 모임장 권한을 가짐 + MemberParty memberParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(memberParty)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.updateMemberRole(partyId, ownerId, ownerId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER); + } + + @Test + @DisplayName("실패 - 현재 사용자가 모임장이 아닐 경우 INSUFFICIENT_PERMISSION 발생") + void fail_updateMemberRole_notOwner() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long notOwnerId = 2L; + Long targetId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Member notOwner = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, notOwnerId); + ReflectionTestUtils.setField(notOwner, "id", notOwnerId); + + Member targetMember = MemberFixture.createMember("타겟", Gender.MALE, Level.A, targetId); + ReflectionTestUtils.setField(targetMember, "id", targetId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(targetId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + + // when & then (notOwnerId를 currentMemberId로 전달하여 실행) + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.updateMemberRole(partyId, targetId, notOwnerId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + } } From e9921b64cacd4ce1befb61728574baabcc82a540 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sat, 28 Mar 2026 22:35:24 +0900 Subject: [PATCH 14/63] =?UTF-8?q?test:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 30 ++++++++--------- .../party/service/PartyQueryServiceTest.java | 32 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index cf8f65154..80c268763 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -129,7 +129,7 @@ class GetPartyMembers { @Test @DisplayName("200 - 멤버 목록을 역할, 성별 통계 및 마지막 운동일과 함께 조회한다") - void success_getMembersWithDetails() throws Exception { + void success_getPartyMembers() throws Exception { // 부모임장 추가 Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 1003L)); memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); @@ -152,7 +152,7 @@ void success_getMembersWithDetails() throws Exception { @Test @DisplayName("404 - 존재하지 않는 파티면 에러를 반환한다") - void fail_partyNotFound() throws Exception { + void fail_getPartyMembers_partyNotFound() throws Exception { mockMvc.perform(get("/api/parties/{partyId}/members", 999L)) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); @@ -160,7 +160,7 @@ void fail_partyNotFound() throws Exception { @Test @DisplayName("400 - 비활성화된 파티면 에러를 반환한다") - void fail_partyInactive() throws Exception { + void fail_getPartyMembers_partyInactive() throws Exception { party.delete(); partyRepository.save(party); @@ -324,7 +324,7 @@ class GetRecommendedParties { @Test @DisplayName("200 - Cockple 추천 모드 시 추천된 모임 목록을 반환한다") - void success_cockpleRecommend() throws Exception { + void success_getRecommendedParties_cockpleRecommend() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "true") .param("sort", "최신순") @@ -338,7 +338,7 @@ void success_cockpleRecommend() throws Exception { @Test @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 반환한다") - void success_filterMode() throws Exception { + void success_getRecommendedParties_filterMode() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "false") .param("addr1", "서울특별시") @@ -355,7 +355,7 @@ void success_filterMode() throws Exception { @Test @DisplayName("200 - 검색 모드 시 모임명으로 검색된 결과를 반환한다") - void success_searchMode() throws Exception { + void success_getRecommendedParties_searchMode() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("search", "추천") .param("isCockpleRecommend", "false") @@ -369,7 +369,7 @@ void success_searchMode() throws Exception { @Test @DisplayName("400 - 유효하지 않은 정렬 기준 입력 시 INVALID_ORDER_TYPE 에러를 반환한다") - void fail_invalidOrderType() throws Exception { + void fail_getRecommendedParties_invalidOrderType() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "false") .param("sort", "잘못된순")) @@ -379,7 +379,7 @@ void fail_invalidOrderType() throws Exception { @Test @DisplayName("400 - isCockpleRecommend에 부적절한 타입 입력 시 400 에러를 반환한다") - void fail_invalidBooleanType() throws Exception { + void fail_getRecommendedParties_invalidBooleanType() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "not-boolean")) .andExpect(status().isBadRequest()); @@ -392,7 +392,7 @@ class GetPartyDetails { @Test @DisplayName("200 - 모임 상세 정보를 정상적으로 조회한다 (비회원 상태)") - void success_getDetails_nonMember() throws Exception { + void success_getPartyDetails_nonMember() throws Exception { // 모임에 가입하지 않은 새로운 유저 생성 및 인증 설정 Member nonMember = memberRepository.save(MemberFixture.createMember("비회원", Gender.MALE, Level.C, 2001L)); SecurityContextHelper.setAuthentication(nonMember.getId(), nonMember.getNickname()); @@ -407,7 +407,7 @@ void success_getDetails_nonMember() throws Exception { @Test @DisplayName("200 - 모임원인 경우 memberStatus가 MEMBER로 반환된다") - void success_getDetails_member() throws Exception { + void success_getPartyDetails_member() throws Exception { // manager는 setUp에서 이미 party의 멤버로 설정됨 SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); @@ -419,7 +419,7 @@ void success_getDetails_member() throws Exception { @Test @DisplayName("404 - 존재하지 않는 모임 조회 시 PARTY_NOT_FOUND 에러를 반환한다") - void fail_partyNotFound() throws Exception { + void fail_getPartyDetails_partyNotFound() throws Exception { mockMvc.perform(get("/api/parties/{partyId}", 9999L)) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); @@ -427,7 +427,7 @@ void fail_partyNotFound() throws Exception { @Test @DisplayName("400 - 삭제된 모임 조회 시 PARTY_IS_DELETED 에러를 반환한다") - void fail_partyDeleted() throws Exception { + void fail_getPartyDetails_partyDeleted() throws Exception { // 모임 삭제 (비활성화) party.delete(); partyRepository.save(party); @@ -685,7 +685,7 @@ class UpdateMemberRole { @Test @DisplayName("200 - 모임장이 일반 멤버를 부모임장으로 성공적으로 임명한다") - void success_assignSubManager() throws Exception { + void success_updateMemberRole() throws Exception { // given PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); @@ -704,7 +704,7 @@ void success_assignSubManager() throws Exception { @Test @DisplayName("403 - 모임장이 아닌 멤버가 역할 수정을 시도하면 INSUFFICIENT_PERMISSION 예외를 반환한다") - void fail_assignSubManager_notOwner() throws Exception { + void fail_updateMemberRole_notOwner() throws Exception { // given PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); // 일반 멤버가 권한 변경 시도 @@ -720,7 +720,7 @@ void fail_assignSubManager_notOwner() throws Exception { @Test @DisplayName("403 - 대상자가 모임장인 경우 권한 변경은 실패하며 CANNOT_ASSIGN_TO_OWNER 예외를 반환한다") - void fail_assignSubManager_targetIsOwner() throws Exception { + void fail_updateMemberRole_targetIsOwner() throws Exception { // given PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_MEMBER); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 89ae54686..454fd2318 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -89,7 +89,7 @@ class GetPartyMembers { @Test @DisplayName("성공 - 모임의 멤버들을 역할별로 성공적으로 조회한다.") - void success() { + void success_getPartyMembers() { // given Long partyId = 1L; Long currentMemberId = 10L; @@ -129,7 +129,7 @@ void success() { @Test @DisplayName("성공 - 멤버 목록과 마지막 운동일을 함께 반환한다") - void success_withExerciseHistory() { + void success_getPartyMembers_withExerciseHistory() { // given Long partyId = 1L; Long currentMemberId = 10L; @@ -175,7 +175,7 @@ void success_withExerciseHistory() { @Test @DisplayName("성공 - 운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") - void noExerciseHistory() { + void success_getPartyMembers_noExerciseHistory() { // given Long partyId = 1L; Long currentMemberId = 10L; @@ -203,7 +203,7 @@ void noExerciseHistory() { @Test @DisplayName("실패 - 존재하지 않는 파티면 PartyException을 던진다") - void partyNotFound() { + void fail_getPartyMembers_partyNotFound() { // given given(partyRepository.findById(99L)).willReturn(Optional.empty()); @@ -216,7 +216,7 @@ void partyNotFound() { @Test @DisplayName("실패 - 비활성화된 파티면 PartyException을 던진다") - void partyInactive() { + void fail_getPartyMembers_partyInactive() { // given PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); @@ -239,7 +239,7 @@ class GetMyParties { @Test @DisplayName("성공 - 내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") - void success() { + void success_getMyParties() { // given Long memberId = 10L; Pageable pageable = PageRequest.of(0, 10); @@ -285,7 +285,7 @@ class GetSimpleMyParties { @Test @DisplayName("성공 - 유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") - void success() { + void success_getSimpleMyParties() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -323,7 +323,7 @@ void success() { @Test @DisplayName("실패 - 존재하지 않는 회원일 경우 MemberException을 던진다") - void memberNotFound() { + void fail_getSimpleMyParties_memberNotFound() { // given Long invalidMemberId = 999L; Pageable pageable = PageRequest.of(0, 10); @@ -346,7 +346,7 @@ class GetRecommendedParties { @Test @DisplayName("성공 - Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") - void success_cockpleRecommend() { + void success_getRecommendedParties_cockpleRecommend() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -385,7 +385,7 @@ void success_cockpleRecommend() { @Test @DisplayName("성공 - 필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") - void success_filterMode() { + void success_getRecommendedParties_filterMode() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -415,7 +415,7 @@ void success_filterMode() { @Test @DisplayName("실패 - 존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") - void fail_memberNotFound() { + void fail_getRecommendedParties_memberNotFound() { // given Long memberId = 999L; Pageable pageable = PageRequest.of(0, 10); @@ -435,7 +435,7 @@ void fail_memberNotFound() { @Test @DisplayName("실패 - 대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") - void fail_mainAddressNotFound() { + void fail_getRecommendedParties_mainAddressNotFound() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -464,7 +464,7 @@ class GetPartyDetails { @Test @DisplayName("성공 - 모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") - void success_nonMember() { + void success_getPartyDetails_nonMember() { // given Long partyId = 1L; Long memberId = 10L; @@ -502,7 +502,7 @@ void success_nonMember() { @Test @DisplayName("성공 - 모임원인 경우 memberStatus가 MEMBER로 반환된다") - void success_member() { + void success_getPartyDetails_member() { // given Long partyId = 1L; Long memberId = 10L; @@ -535,7 +535,7 @@ void success_member() { @Test @DisplayName("실패 - 존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") - void fail_partyNotFound() { + void fail_getPartyDetails_partyNotFound() { // given given(partyRepository.findById(999L)).willReturn(Optional.empty()); @@ -548,7 +548,7 @@ void fail_partyNotFound() { @Test @DisplayName("실패 - 삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") - void fail_partyDeleted() { + void fail_getPartyDetails_partyDeleted() { // given PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Party party = PartyFixture.createParty("삭제된 모임", 11L, addr); From 409a628949598b1aee3c2e06ccc17893b47cdc84 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sat, 28 Mar 2026 23:28:04 +0900 Subject: [PATCH 15/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20(/api/parties/{partyId}/status)=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 48 ++++++++++ .../service/PartyCommandServiceTest.java | 93 +++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 80c268763..de1a810e8 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -572,6 +572,54 @@ void fail_updateParty_notOwner() throws Exception { } } + @Nested + @DisplayName("PATCH /api/parties/{partyId}/status - 모임 삭제") + class DeleteParty { + + @Test + @DisplayName("200 - 모임장이 모임을 성공적으로 삭제(비활성화)한다") + void success_deleteParty() throws Exception { + // given + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/status", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + Party deletedParty = partyRepository.findById(party.getId()).orElseThrow(); + assertThat(deletedParty.getStatus()).isEqualTo(umc.cockple.demo.domain.party.enums.PartyStatus.INACTIVE); + } + + @Test + @DisplayName("403 - 모임장이 아닌 멤버가 삭제를 시도하면 INSUFFICIENT_PERMISSION 예외를 반환한다") + void fail_deleteParty_notOwner() throws Exception { + // given + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/status", party.getId())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("400 - 이미 삭제된 모임을 다시 삭제 시도하면 PARTY_IS_DELETED 예외를 반환한다") + void fail_deleteParty_partyDeleted() throws Exception { + // given + party.delete(); // 상태 INACTIVE 변경 + partyRepository.save(party); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/status", party.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 0aeb85616..2ccc03a66 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -614,6 +614,99 @@ void fail_updateParty_insufficientPermission() { } } + @Nested + @DisplayName("deleteParty") + class DeleteParty { + + @Test + @DisplayName("성공 - 모임장이 모임을 정상적으로 삭제(비활성화)한다") + void success_deleteParty() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party party = PartyFixture.createParty("삭제할 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + + // when + partyCommandService.deleteParty(partyId, ownerId); + + // then + assertThat(party.getStatus()).isEqualTo(umc.cockple.demo.domain.party.enums.PartyStatus.INACTIVE); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 멤버가 모임 삭제를 시도할 경우 INSUFFICIENT_PERMISSION 발생") + void fail_deleteParty_notOwner() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long notOwnerId = 2L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Member notOwner = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, notOwnerId); + ReflectionTestUtils.setField(notOwner, "id", notOwnerId); + + Party party = PartyFixture.createParty("삭제할 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(notOwnerId)).willReturn(Optional.of(notOwner)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.deleteParty(partyId, notOwnerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 이미 삭제된 모임을 삭제하려고 시도할 경우 PARTY_IS_DELETED 발생") + void fail_deleteParty_partyDeleted() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party party = PartyFixture.createParty("이미 삭제된 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + party.delete(); // 상태를 INACTIVE로 변경 + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.deleteParty(partyId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_IS_DELETED); + } + + @Test + @DisplayName("실패 - 조회된 모임이 존재하지 않을 경우 PARTY_NOT_FOUND 발생") + void fail_deleteParty_partyNotFound() { + // given + Long invalidId = 999L; + given(partyRepository.findById(invalidId)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.deleteParty(invalidId, 1L)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); + } + } + @Nested @DisplayName("updateMemberRole") class UpdateMemberRole { From 9cc941fe82825c3573636270055012f6081503c7 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 00:35:25 +0900 Subject: [PATCH 16/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=82=AD=EC=A0=9C(/api/parties/{partyId}/members/{?= =?UTF-8?q?memberId})=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 47 +++++++++ .../service/PartyCommandServiceTest.java | 97 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index de1a810e8..511cb0e50 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -620,6 +620,53 @@ void fail_deleteParty_partyDeleted() throws Exception { } } + @Nested + @DisplayName("DELETE /api/parties/{partyId}/members/{memberId} - 모임 멤버 삭제") + class RemoveMember { + + @Test + @DisplayName("200 - 모임장이 일반 멤버를 성공적으로 강퇴한다") + void success_removeMember() throws Exception { + // given + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", party.getId(), normalMember.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + boolean exists = memberPartyRepository.existsByPartyAndMember(party, normalMember); + assertThat(exists).isFalse(); + } + + @Test + @DisplayName("403 - 모임장이 아닌 멤버가 삭제를 시도하면 INSUFFICIENT_PERMISSION 에러를 반환한다") + void fail_removeMember_notOwner() throws Exception { + // given + Member someoneElse = memberRepository.save(MemberFixture.createMember("다른멤버", Gender.MALE, Level.B, 1010L)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, someoneElse, Role.party_MEMBER)); + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", party.getId(), someoneElse.getId())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("400 - 모임장이 자기 자신을 강퇴하려 할 경우 CANNOT_REMOVE_SELF 에러를 반환한다") + void fail_removeMember_selfAsManager() throws Exception { + // given + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", party.getId(), manager.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_REMOVE_SELF.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 2ccc03a66..c34eda4d8 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -811,4 +811,101 @@ void fail_updateMemberRole_notOwner() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } } + + @Nested + @DisplayName("removeMember") + class RemoveMember { + + @Test + @DisplayName("성공 - 모임장이 일반 멤버를 성공적으로 강퇴한다") + void success_removeMember() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long targetMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member targetMember = MemberFixture.createMember("타겟", Gender.MALE, Level.A, targetMemberId); + ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(ownerParty)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + + // when + partyCommandService.removeMember(partyId, targetMemberId, ownerId); + + // then + verify(memberPartyRepository, times(1)).delete(targetMemberParty); + verify(chatRoomService, times(1)).leavePartyChatRoom(partyId, targetMemberId); + } + + @Test + @DisplayName("실패 - 권한이 없는 멤버가 타인을 강퇴하려 하면 INSUFFICIENT_PERMISSION 발생") + void fail_removeMember_insufficientPermission() { + // given + Long partyId = 1L; + Long subManagerId = 2L; + Long targetOwnerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, targetOwnerId); + ReflectionTestUtils.setField(owner, "id", targetOwnerId); + Member subManager = MemberFixture.createMember("부모임장", Gender.MALE, Level.A, subManagerId); + ReflectionTestUtils.setField(subManager, "id", subManagerId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); + given(memberRepository.findById(targetOwnerId)).willReturn(Optional.of(owner)); + given(memberPartyRepository.findByPartyAndMember(party, subManager)).willReturn(Optional.of(subManagerParty)); + given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(ownerParty)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.removeMember(partyId, targetOwnerId, subManagerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 모임장이 자신을 강퇴하려 할 경우 CANNOT_REMOVE_SELF 발생") + void fail_removeMember_cannotRemoveSelf() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(ownerParty)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.removeMember(partyId, ownerId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_REMOVE_SELF); + } + } } From 1e3e675f9877560804158b22f3e14b6e4b85ddf7 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 03:31:45 +0900 Subject: [PATCH 17/63] =?UTF-8?q?test:=20=EA=B0=80=EC=9E=85=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20(/api/par?= =?UTF-8?q?ties/{partyId}/join-requests)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 57 +++++++++++ .../party/service/PartyQueryServiceTest.java | 99 +++++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 511cb0e50..78411ca37 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -23,6 +23,7 @@ import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; @@ -667,6 +668,62 @@ void fail_removeMember_selfAsManager() throws Exception { } } + @Nested + @DisplayName("GET /api/parties/{partyId}/join-requests - 모임 가입 신청 조회") + class GetJoinRequests { + + @Test + @DisplayName("200 - 모임장이 가입 신청 목록을 정상적으로 조회한다") + void success_getJoinRequests() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("가입희망자", Gender.FEMALE, Level.B, 1010L)); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + partyJoinRequestRepository.save(joinRequest); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/join-requests", party.getId()) + .param("status", "PENDING") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content[0].userId").value(applicant.getId())); + } + + @Test + @DisplayName("403 - 모임장이 아닌 사용자가 조회하면 INSUFFICIENT_PERMISSION 예외가 반환된다") + void fail_getJoinRequests_notOwner() throws Exception { + // given + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/join-requests", party.getId()) + .param("status", "PENDING")) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("400 - 잘못된 상태값을 전달하면 INVALID_REQUEST_STATUS 예외가 반환된다") + void fail_getJoinRequests_invalidStatus() throws Exception { + // given + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/join-requests", party.getId()) + .param("status", "UNKNOWN_STATUS")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_REQUEST_STATUS.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 454fd2318..685f5a092 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -26,6 +26,7 @@ import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; @@ -564,4 +565,102 @@ void fail_getPartyDetails_partyDeleted() { .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); } } + + @Nested + @DisplayName("getJoinRequests") + class GetJoinRequests { + + @Test + @DisplayName("성공 - 모임장이 가입 신청 목록을 정상적으로 조회한다") + void success_getJoinRequests() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Pageable pageable = PageRequest.of(0, 10); + String status = "PENDING"; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", 100L); + + Slice requestSlice = new SliceImpl<>(List.of(joinRequest), pageable, false); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + given(partyJoinRequestRepository.findByPartyAndStatus(party, RequestStatus.PENDING, pageable)) + .willReturn(requestSlice); + + // when + Slice result = partyQueryService.getJoinRequests(partyId, ownerId, status, pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).joinRequestId()).isEqualTo(100L); + verify(partyJoinRequestRepository).findByPartyAndStatus(party, RequestStatus.PENDING, pageable); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 조회하면 INSUFFICIENT_PERMISSION 발생") + void fail_getJoinRequests_notOwner() { + // given + Long partyId = 1L; + Long nonOwnerId = 20L; + Pageable pageable = PageRequest.of(0, 10); + String status = "PENDING"; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member nonOwner = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, nonOwnerId); + ReflectionTestUtils.setField(nonOwner, "id", nonOwnerId); + MemberParty nonOwnerParty = MemberFixture.createMemberParty(party, nonOwner, Role.party_MEMBER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + + // when & then + assertThatThrownBy(() -> partyQueryService.getJoinRequests(partyId, nonOwnerId, status, pageable)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION)); + } + + @Test + @DisplayName("실패 - 잘못된 상태값을 입력하면 INVALID_REQUEST_STATUS 발생") + void fail_getJoinRequests_invalidStatus() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Pageable pageable = PageRequest.of(0, 10); + String invalidStatus = "UNKNOWN"; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + + // when & then + assertThatThrownBy(() -> partyQueryService.getJoinRequests(partyId, ownerId, invalidStatus, pageable)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.INVALID_REQUEST_STATUS)); + } + } } From cb51dfb9583615961e7aa64b5c11dc4ce467e800 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 03:58:11 +0900 Subject: [PATCH 18/63] =?UTF-8?q?test:=20=EA=B0=80=EC=9E=85=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=B2=98=EB=A6=AC=20(/api/parties/{partyId}/join-r?= =?UTF-8?q?equests/{requestId})=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 117 ++++++++++++- .../service/PartyCommandServiceTest.java | 159 ++++++++++++++++++ 2 files changed, 273 insertions(+), 3 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 78411ca37..11c488cf2 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -25,10 +25,12 @@ import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; @@ -45,6 +47,7 @@ import java.time.LocalDate; import java.util.List; +import org.springframework.http.MediaType; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -490,7 +493,6 @@ void fail_createJoinRequest_genderMismatch() throws Exception { .joinPrice(0) .build()); - // 남성 사용자로 신청 시도 SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); mockMvc.perform(post("/api/parties/{partyId}/join-requests", womenParty.getId())) @@ -525,7 +527,7 @@ void success_updateParty() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")); - // DB 검증 + // 검증 Party updatedParty = partyRepository.findById(party.getId()).orElseThrow(); assertThat(updatedParty.getDesignatedCock()).isEqualTo("수정된 콕"); assertThat(updatedParty.getJoinPrice()).isEqualTo(2000); @@ -724,6 +726,115 @@ void fail_getJoinRequests_invalidStatus() throws Exception { } } + @Nested + @DisplayName("PATCH /api/parties/{partyId}/join-requests/{requestId} - 모임 가입 신청 처리") + class ActionJoinRequest { + + @Test + @DisplayName("200 - 모임장이 가입 신청을 성공적으로 승인한다") + void success_actionJoinRequest_approve() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1020L)); + + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", party.getId(), joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + PartyJoinRequest updatedRequest = partyJoinRequestRepository.findById(joinRequest.getId()).orElseThrow(); + assertThat(updatedRequest.getStatus()).isEqualTo(RequestStatus.APPROVED); + boolean isMember = memberPartyRepository.existsByPartyAndMember(party, applicant); + assertThat(isMember).isTrue(); + } + + @Test + @DisplayName("200 - 모임장이 가입 신청을 성공적으로 거절한다") + void success_actionJoinRequest_reject() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("탈락자", Gender.FEMALE, Level.B, 1030L)); + + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.REJECT); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", party.getId(), joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + PartyJoinRequest updatedRequest = partyJoinRequestRepository.findById(joinRequest.getId()).orElseThrow(); + assertThat(updatedRequest.getStatus()).isEqualTo(RequestStatus.REJECTED); + boolean isMember = memberPartyRepository.existsByPartyAndMember(party, applicant); + assertThat(isMember).isFalse(); + } + + @Test + @DisplayName("403 - 모임장이 아닌 사용자가 가입 신청을 처리하려 하면 INSUFFICIENT_PERMISSION 발생") + void fail_actionJoinRequest_notOwner() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1040L)); + + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", party.getId(), joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("409 - 이미 처리된 가입 신청을 다시 처리하려 할 때 JOIN_REQUEST_ALREADY_ACTIONS 상태 반환") + void fail_actionJoinRequest_alreadyHandled() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1050L)); + + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.APPROVED) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", party.getId(), joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { @@ -758,7 +869,7 @@ void success_createParty() throws Exception { .andExpect(jsonPath("$.code").value("COMMON201")) .andExpect(jsonPath("$.data.partyId").exists()); - // DB 검증 + // 검증 List parties = partyRepository.findAll(); Party createdParty = parties.stream() .filter(p -> p.getPartyName().equals("새로운 통합 모임")) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index c34eda4d8..fc6f11a20 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -23,6 +23,7 @@ import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; import umc.cockple.demo.domain.party.exception.PartyErrorCode; @@ -47,6 +48,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; @ExtendWith(MockitoExtension.class) class PartyCommandServiceTest { @@ -908,4 +910,161 @@ void fail_removeMember_cannotRemoveSelf() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_REMOVE_SELF); } } + + @Nested + @DisplayName("actionJoinRequest") + class ActionJoinRequest { + + @Test + @DisplayName("성공 - 모임장이 가입 신청을 승인하고 알림과 채팅방 진입, 이벤트를 발생시킨다") + void success_actionJoinRequest_approve() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + given(memberPartyRepository.existsByPartyAndMember(party, applicant)).willReturn(false); + + // when + partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId); + + // then + assertThat(joinRequest.getStatus()).isEqualTo(RequestStatus.APPROVED); + verify(chatRoomService).joinPartyChatRoom(partyId, applicant); + verify(applicationEventPublisher).publishEvent(any(umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent.class)); + verify(notificationCommandService).createNotification(any()); + } + + @Test + @DisplayName("성공 - 모임장이 가입 신청을 거절하면 상태만 REJECTED로 바뀌고 다른 사이드이펙트가 발생하지 않는다") + void success_actionJoinRequest_reject() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.REJECT); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + given(memberPartyRepository.existsByPartyAndMember(party, applicant)).willReturn(false); + + // when + partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId); + + // then + assertThat(joinRequest.getStatus()).isEqualTo(RequestStatus.REJECTED); + verifyNoInteractions(chatRoomService); + verifyNoInteractions(applicationEventPublisher); + verifyNoInteractions(notificationCommandService); + } + + @Test + @DisplayName("실패 - 대상자가 이미 모임 멤버인 경우 ALREADY_MEMBER 검증 에러가 발생한다") + void fail_actionJoinRequest_alreadyMember() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + given(memberPartyRepository.existsByPartyAndMember(party, applicant)).willReturn(true); // 이미 멤버 + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.ALREADY_MEMBER); + } + + @Test + @DisplayName("실패 - 이미 처리된 가입 신청을 다시 처리하려 할 때 JOIN_REQUEST_ALREADY_ACTIONS 발생") + void fail_actionJoinRequest_alreadyActions() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.APPROVED) // 이미 승인됨 + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.REJECT); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + given(memberPartyRepository.existsByPartyAndMember(party, applicant)).willReturn(false); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS); + } + } } From 0623b6a4bfcfbef52530a7f677f421a430a29d34 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 04:04:29 +0900 Subject: [PATCH 19/63] =?UTF-8?q?test:=20=EA=B0=80=EC=9E=85=20=EC=8A=B9?= =?UTF-8?q?=EC=9D=B8=20=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20(/api/par?= =?UTF-8?q?ties/{partyId}/join-requests)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 25 ++++++++++++ .../party/service/PartyQueryServiceTest.java | 39 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 11c488cf2..29513f935 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -699,6 +699,31 @@ void success_getJoinRequests() throws Exception { .andExpect(jsonPath("$.data.content[0].userId").value(applicant.getId())); } + @Test + @DisplayName("200 - 모임장이 가입 승인된 멤버 목록(APPROVED)을 정상적으로 조회한다") + void success_getJoinRequests_approved() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("승인된멤버", Gender.MALE, Level.C, 1015L)); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.APPROVED) + .build(); + partyJoinRequestRepository.save(joinRequest); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/join-requests", party.getId()) + .param("status", "APPROVED") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content[0].userId").value(applicant.getId())); + } + @Test @DisplayName("403 - 모임장이 아닌 사용자가 조회하면 INSUFFICIENT_PERMISSION 예외가 반환된다") void fail_getJoinRequests_notOwner() throws Exception { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 685f5a092..722f0590e 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -611,6 +611,45 @@ void success_getJoinRequests() { verify(partyJoinRequestRepository).findByPartyAndStatus(party, RequestStatus.PENDING, pageable); } + @Test + @DisplayName("성공 - 모임장이 가입 승인된 멤버 목록(APPROVED)을 정상적으로 조회한다") + void success_getJoinRequests_approved() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Pageable pageable = PageRequest.of(0, 10); + String status = "APPROVED"; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.APPROVED) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", 101L); + + Slice requestSlice = new SliceImpl<>(List.of(joinRequest), pageable, false); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + given(partyJoinRequestRepository.findByPartyAndStatus(party, RequestStatus.APPROVED, pageable)) + .willReturn(requestSlice); + + // when + Slice result = partyQueryService.getJoinRequests(partyId, ownerId, status, pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).joinRequestId()).isEqualTo(101L); + verify(partyJoinRequestRepository).findByPartyAndStatus(party, RequestStatus.APPROVED, pageable); + } + @Test @DisplayName("실패 - 모임장이 아닌 사용자가 조회하면 INSUFFICIENT_PERMISSION 발생") void fail_getJoinRequests_notOwner() { From 837d1b395094fce986bd1655fb0198cf705e3253 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 04:24:42 +0900 Subject: [PATCH 20/63] =?UTF-8?q?test:=20=EC=8B=A0=EA=B7=9C=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=B6=94=EC=B2=9C=EB=B0=9B=EA=B8=B0=20(/api/partie?= =?UTF-8?q?s/{partyId}/members/suggestions)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 42 +++++++++++++++ .../party/service/PartyQueryServiceTest.java | 52 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 29513f935..2dc7b68c8 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -751,6 +751,48 @@ void fail_getJoinRequests_invalidStatus() throws Exception { } } + @Nested + @DisplayName("GET /api/parties/{partyId}/members/suggestions - 신규 멤버 추천받기") + class GetRecommendedMembers { + + @Test + @DisplayName("200 - 신규 멤버 추천 목록을 정상적으로 조회한다") + void success_getRecommendedMembers() throws Exception { + // given + Member suggestedMember = memberRepository.save(MemberFixture.createMember("추천회원", Gender.MALE, Level.B, 1080L)); + + // Note: The logic inside memberRepository.findRecommendedMembers determines the slice to return. + // In integration tests with real DB queries, we insert the member so that they are picked up. + // But since this varies highly depending on algorithms and other data, we just verify the route and response format. + // If the algorithm returns no elements because of specific conditions, testing size==0 or size>0 is fine. + // Let's simply test that the request yields a 200 OK and valid COMMON200 code. + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/members/suggestions", party.getId()) + .param("levelSearch", "B") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()); // 배열 형식인지 확인 + } + + @Test + @DisplayName("404 - 존재하지 않는 모임의 추천 멤버를 조회하면 PARTY_NOT_FOUND 예외 발생") + void fail_getRecommendedMembers_partyNotFound() throws Exception { + // given + Long invalidPartyId = 9999L; + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/members/suggestions", invalidPartyId)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + } + @Nested @DisplayName("PATCH /api/parties/{partyId}/join-requests/{requestId} - 모임 가입 신청 처리") class ActionJoinRequest { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 722f0590e..7c82bfaa9 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -46,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; @@ -702,4 +703,55 @@ void fail_getJoinRequests_invalidStatus() { .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.INVALID_REQUEST_STATUS)); } } + + @Nested + @DisplayName("getRecommendedMembers") + class GetRecommendedMembers { + + @Test + @DisplayName("성공 - 조건에 맞는 추천 멤버 목록을 정상적으로 조회한다") + void success_getRecommendedMembers() { + // given + Long partyId = 1L; + String levelSearch = "B"; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", 1L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member suggestedMember = MemberFixture.createMember("추천회원", Gender.MALE, Level.B, 20L); + ReflectionTestUtils.setField(suggestedMember, "id", 20L); + + Slice membersSlice = new SliceImpl<>(List.of(suggestedMember), pageable, false); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findRecommendedMembers(party, levelSearch, pageable)) + .willReturn(membersSlice); + + // when + Slice result = partyQueryService.getRecommendedMembers(partyId, levelSearch, pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).userId()).isEqualTo(20L); + verify(memberRepository).findRecommendedMembers(party, levelSearch, pageable); + } + + @Test + @DisplayName("실패 - 존재하지 않는 모임의 추천 멤버를 조회하면 PARTY_NOT_FOUND 발생") + void fail_getRecommendedMembers_partyNotFound() { + // given + Long partyId = 999L; + String levelSearch = null; + Pageable pageable = PageRequest.of(0, 10); + + given(partyRepository.findById(partyId)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyQueryService.getRecommendedMembers(partyId, levelSearch, pageable)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); + } + } } From 4578d98ba568b87747024098103ee7ce0585eb1b Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 06:43:45 +0900 Subject: [PATCH 21/63] =?UTF-8?q?test:=20=EC=8B=A0=EA=B7=9C=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=B4=88=EB=8C=80=20=EB=B3=B4=EB=82=B4=EA=B8=B0(/a?= =?UTF-8?q?pi/parties/{partyId}/invitations)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 110 ++++++++++++++-- .../service/PartyCommandServiceTest.java | 124 ++++++++++++++++++ 2 files changed, 223 insertions(+), 11 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 2dc7b68c8..75ee8dfcd 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -23,8 +23,10 @@ import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyInviteCreateDTO; import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; @@ -34,6 +36,7 @@ import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; +import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; @@ -81,6 +84,8 @@ class PartyIntegrationTest extends IntegrationTestBase { @Autowired PartyJoinRequestRepository partyJoinRequestRepository; @Autowired + PartyInvitationRepository partyInvitationRepository; + @Autowired ObjectMapper objectMapper; private Member manager; @@ -756,27 +761,39 @@ void fail_getJoinRequests_invalidStatus() throws Exception { class GetRecommendedMembers { @Test - @DisplayName("200 - 신규 멤버 추천 목록을 정상적으로 조회한다") + @DisplayName("200 - 추천 조건(지역/나이/급수)에 맞는 멤버가 추천 목록에 포함된다") void success_getRecommendedMembers() throws Exception { // given - Member suggestedMember = memberRepository.save(MemberFixture.createMember("추천회원", Gender.MALE, Level.B, 1080L)); - - // Note: The logic inside memberRepository.findRecommendedMembers determines the slice to return. - // In integration tests with real DB queries, we insert the member so that they are picked up. - // But since this varies highly depending on algorithms and other data, we just verify the route and response format. - // If the algorithm returns no elements because of specific conditions, testing size==0 or size>0 is fine. - // Let's simply test that the request yields a 200 OK and valid COMMON200 code. - + // party의 추천 조건: addr1=서울특별시, minBirthYear=1990, maxBirthYear=2005 + // party에 남성 A급 레벨 추가 + party.addLevel(Gender.MALE, Level.A); + partyRepository.save(party); + + // 추천 조건을 모두 만족하는 멤버: 남성, A급, 생년 1995, 서울특별시 주소(isMain=true) + Member suggestedMember = memberRepository.save( + MemberFixture.createMember("추천회원", Gender.MALE, Level.A, 1080L, LocalDate.of(1995, 6, 1)) + ); + memberAddrRepository.save(MemberAddr.builder() + .member(suggestedMember) + .addr1("서울특별시") + .addr2("강남구") + .addr3("역삼동") + .streetAddr("테헤란로") + .latitude(37.5) + .longitude(127.0) + .isMain(true) + .build()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); // when & then mockMvc.perform(get("/api/parties/{partyId}/members/suggestions", party.getId()) - .param("levelSearch", "B") .param("page", "0") .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) - .andExpect(jsonPath("$.data.content").isArray()); // 배열 형식인지 확인 + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].userId").value(suggestedMember.getId())); } @Test @@ -793,6 +810,77 @@ void fail_getRecommendedMembers_partyNotFound() throws Exception { } } + @Nested + @DisplayName("POST /api/parties/{partyId}/invitations - 신규 멤버 초대 보내기") + class CreateInvitation { + + @Test + @DisplayName("200 - 모임장이 새로운 멤버를 초대하고 invitationId를 반환한다") + void success_createInvitation() throws Exception { + // given + Member newMember = memberRepository.save(MemberFixture.createMember("새멤버", Gender.FEMALE, Level.B, 1090L)); + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(newMember.getId()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON201")) + .andExpect(jsonPath("$.data.invitationId").exists()); + } + + @Test + @DisplayName("403 - 모임장이 아닌 사용자가 초대하면 INSUFFICIENT_PERMISSION 발생") + void fail_createInvitation_notOwner() throws Exception { + // given + Member newMember = memberRepository.save(MemberFixture.createMember("새멤버", Gender.FEMALE, Level.B, 1091L)); + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(newMember.getId()); + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("409 - 이미 모임 멤버인 사람을 초대하면 ALREADY_MEMBER 발생") + void fail_createInvitation_alreadyMember() throws Exception { + // given - normalMember는 setUp()에서 이미 모임 멤버로 추가된 상태 + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(normalMember.getId()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.ALREADY_MEMBER.getCode())); + } + + @Test + @DisplayName("409 - 이미 대기 중인 초대가 있는 멤버를 중복 초대하면 INVITATION_ALREADY_EXISTS 발생") + void fail_createInvitation_duplicateInvitation() throws Exception { + // given + Member newMember = memberRepository.save(MemberFixture.createMember("새멤버", Gender.FEMALE, Level.B, 1092L)); + partyInvitationRepository.save(PartyInvitation.create(party, manager, newMember)); + + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(newMember.getId()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVITATION_ALREADY_EXISTS.getCode())); + } + } + @Nested @DisplayName("PATCH /api/parties/{partyId}/join-requests/{requestId} - 모임 가입 신청 처리") class ActionJoinRequest { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index fc6f11a20..295730132 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -20,6 +20,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; +import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; @@ -30,6 +31,7 @@ import umc.cockple.demo.domain.party.exception.PartyException; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; +import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -73,6 +75,8 @@ class PartyCommandServiceTest { @Mock private PartyJoinRequestRepository partyJoinRequestRepository; @Mock + private PartyInvitationRepository partyInvitationRepository; + @Mock private FileService fileService; private PartyConverter partyConverter; @@ -1067,4 +1071,124 @@ void fail_actionJoinRequest_alreadyActions() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS); } } + + @Nested + @DisplayName("createInvitation") + class CreateInvitation { + + @Test + @DisplayName("성공 - 모임장이 새로운 멤버를 초대하고 invitationId를 반환한다") + void success_createInvitation() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + given(partyInvitationRepository.existsByPartyAndInviteeAndStatus(party, invitee, RequestStatus.PENDING)).willReturn(false); + + PartyInvitation savedInvitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(savedInvitation, "id", 100L); + given(partyInvitationRepository.save(any())).willReturn(savedInvitation); + + // when + PartyInviteCreateDTO.Response response = partyCommandService.createInvitation(partyId, inviteeId, ownerId); + + // then + assertThat(response.invitationId()).isEqualTo(100L); + verify(notificationCommandService).createNotification(any()); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 초대하려 하면 INSUFFICIENT_PERMISSION 발생") + void fail_createInvitation_notOwner() { + // given + Long partyId = 1L; + Long nonOwnerId = 99L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member nonOwner = MemberFixture.createMember("일반멤버", Gender.MALE, Level.B, nonOwnerId); + ReflectionTestUtils.setField(nonOwner, "id", nonOwnerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", 10L, addr); // ownerId = 10L + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(nonOwnerId)).willReturn(Optional.of(nonOwner)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createInvitation(partyId, inviteeId, nonOwnerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 이미 모임에 가입한 멤버를 초대하면 ALREADY_MEMBER 발생") + void fail_createInvitation_alreadyMember() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("대상멤버", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(true); // 이미 멤버 + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createInvitation(partyId, inviteeId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.ALREADY_MEMBER); + } + + @Test + @DisplayName("실패 - 이미 대기 중인 초대가 있는 멤버를 중복 초대하면 INVITATION_ALREADY_EXISTS 발생") + void fail_createInvitation_duplicateInvitation() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("대상멤버", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + given(partyInvitationRepository.existsByPartyAndInviteeAndStatus(party, invitee, RequestStatus.PENDING)).willReturn(true); // 이미 대기중 초대 + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createInvitation(partyId, inviteeId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_EXISTS); + } + } } From 7bf46f19c1ad175854474495ce40e87158f1263e Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 17:26:33 +0900 Subject: [PATCH 22/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=B4=88?= =?UTF-8?q?=EB=8C=80=20=EC=B2=98=EB=A6=AC(/api/parties/invitations/{invita?= =?UTF-8?q?tionId})=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 104 +++++++++++++ .../service/PartyCommandServiceTest.java | 142 ++++++++++++++++++ 2 files changed, 246 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 75ee8dfcd..87dc686de 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -27,6 +27,7 @@ import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; import umc.cockple.demo.domain.party.dto.PartyInviteCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; @@ -881,6 +882,109 @@ void fail_createInvitation_duplicateInvitation() throws Exception { } } + @Nested + @DisplayName("PATCH /api/parties/invitations/{invitationId} - 모임 초대 처리") + class ActionInvitation { + + @Test + @DisplayName("200 - 초대받은 멤버가 승인하면 모임 멤버로 추가된다") + void success_actionInvitation_approve() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1100L)); + + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(invitee.getId(), invitee.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + PartyInvitation updated = partyInvitationRepository.findById(invitation.getId()).orElseThrow(); + assertThat(updated.getStatus()).isEqualTo(RequestStatus.APPROVED); + assertThat(memberPartyRepository.existsByPartyAndMember(party, invitee)).isTrue(); + } + + @Test + @DisplayName("200 - 초대받은 멤버가 거절하면 상태가 REJECTED로 바뀌고 멤버로 추가되지 않는다") + void success_actionInvitation_reject() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1101L)); + + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.REJECT); + SecurityContextHelper.setAuthentication(invitee.getId(), invitee.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + PartyInvitation updated = partyInvitationRepository.findById(invitation.getId()).orElseThrow(); + assertThat(updated.getStatus()).isEqualTo(RequestStatus.REJECTED); + assertThat(memberPartyRepository.existsByPartyAndMember(party, invitee)).isFalse(); + } + + @Test + @DisplayName("403 - 초대받은 사람이 아닌 제3자가 처리하면 NOT_YOUR_INVITATION 발생") + void fail_actionInvitation_notYourInvitation() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1102L)); + + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + // normalMember는 초대받은 사람이 아님 + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.NOT_YOUR_INVITATION.getCode())); + } + + @Test + @DisplayName("409 - 이미 처리된 초대를 다시 처리하면 INVITATION_ALREADY_ACTIONS 발생") + void fail_actionInvitation_alreadyActions() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1103L)); + + // 이미 APPROVED 처리된 초대 + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + invitation.updateStatus(RequestStatus.APPROVED); + partyInvitationRepository.save(invitation); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.REJECT); + SecurityContextHelper.setAuthentication(invitee.getId(), invitee.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVITATION_ALREADY_ACTIONS.getCode())); + } + } + @Nested @DisplayName("PATCH /api/parties/{partyId}/join-requests/{requestId} - 모임 가입 신청 처리") class ActionJoinRequest { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 295730132..48b829961 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -23,6 +23,7 @@ import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.dto.*; +import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; @@ -1191,4 +1192,145 @@ void fail_createInvitation_duplicateInvitation() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_EXISTS); } } + + @Nested + @DisplayName("actionInvitation") + class ActionInvitation { + + @Test + @DisplayName("성공 - 초대받은 멤버가 승인하면 모임 멤버로 추가되고 알림이 발생한다") + void success_actionInvitation_approve() { + // given + Long invitationId = 100L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(invitation, "id", invitationId); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + + // when + partyCommandService.actionInvitation(inviteeId, request, invitationId); + + // then + assertThat(invitation.getStatus()).isEqualTo(RequestStatus.APPROVED); + verify(chatRoomService).joinPartyChatRoom(party.getId(), invitee); + verify(applicationEventPublisher).publishEvent(any(PartyMemberJoinedEvent.class)); + verify(notificationCommandService).createNotification(any()); + } + + @Test + @DisplayName("성공 - 초대받은 멤버가 거절하면 상태만 REJECTED로 바뀌고 사이드이펙트가 없다") + void success_actionInvitation_reject() { + // given + Long invitationId = 100L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(invitation, "id", invitationId); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.REJECT); + + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + + // when + partyCommandService.actionInvitation(inviteeId, request, invitationId); + + // then + assertThat(invitation.getStatus()).isEqualTo(RequestStatus.REJECTED); + verifyNoInteractions(chatRoomService); + verifyNoInteractions(applicationEventPublisher); + verifyNoInteractions(notificationCommandService); + } + + @Test + @DisplayName("실패 - 초대받은 사람이 아닌 제3자가 처리하려 하면 NOT_YOUR_INVITATION 발생") + void fail_actionInvitation_notYourInvitation() { + // given + Long invitationId = 100L; + Long ownerId = 10L; + Long inviteeId = 20L; + Long otherId = 99L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Member other = MemberFixture.createMember("제3자", Gender.MALE, Level.C, otherId); + ReflectionTestUtils.setField(other, "id", otherId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(invitation, "id", invitationId); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(otherId)).willReturn(Optional.of(other)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionInvitation(otherId, request, invitationId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.NOT_YOUR_INVITATION); + } + + @Test + @DisplayName("실패 - 이미 처리된 초대를 다시 처리하려 할 때 INVITATION_ALREADY_ACTIONS 발생") + void fail_actionInvitation_alreadyActions() { + // given + Long invitationId = 100L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + // 이미 승인된 초대 + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(invitation, "id", invitationId); + invitation.updateStatus(RequestStatus.APPROVED); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.REJECT); + + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionInvitation(inviteeId, request, invitationId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_ACTIONS); + } + } } From 66dd9f17de846ec52491d572d0cea4f4a9cdac6d Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 18:55:57 +0900 Subject: [PATCH 23/63] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(/api/parties/{partyId}/keywords)=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 57 ++++++++++++++ .../service/PartyCommandServiceTest.java | 74 ++++++++++++++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 87dc686de..31ba6d088 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -29,6 +29,7 @@ import umc.cockple.demo.domain.party.dto.PartyInviteCreateDTO; import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; +import umc.cockple.demo.domain.party.dto.PartyKeywordDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; @@ -1255,4 +1256,60 @@ void fail_updateMemberRole_targetIsOwner() throws Exception { .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER.getCode())); } } + + @Nested + @DisplayName("POST /api/parties/{partyId}/keywords - 키워드 추가") + class AddKeyword { + + @Test + @DisplayName("200 - 모임장이 유효한 키워드를 정상적으로 추가한다") + void success_addKeyword() throws Exception { + // given + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request( + List.of("친목", "가입비 무료") + ); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/keywords", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 - DB에 키워드가 실제로 저장됐는지 확인 + Party updatedParty = partyRepository.findById(party.getId()).orElseThrow(); + assertThat(updatedParty.getKeywords()).hasSize(2); + } + + @Test + @DisplayName("403 - 모임장이 아닌 사용자가 키워드를 추가하면 INSUFFICIENT_PERMISSION 발생") + void fail_addKeyword_notOwner() throws Exception { + // given + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("친목")); + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/keywords", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("400 - 유효하지 않은 키워드 문자열을 전달하면 INVALID_KEYWORD 발생") + void fail_addKeyword_invalidKeyword() throws Exception { + // given + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("존재하지않는키워드")); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/keywords", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_KEYWORD.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 48b829961..b14b77ca6 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -742,9 +742,7 @@ void success_updateMemberRole() { given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(memberParty)); - // 만약 기존 부모임장이 있으면 해제하는 로직에 대한 빈 Optional 반환 given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)).willReturn(Optional.empty()); - // 알림 발송 시 파티 내 전체 멤버를 조회 given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(memberParty)); // when @@ -1333,4 +1331,76 @@ void fail_actionInvitation_alreadyActions() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_ACTIONS); } } + + @Nested + @DisplayName("addKeyword") + class AddKeyword { + + @Test + @DisplayName("성공 - 모임장이 유효한 키워드 목록을 모임에 추가한다") + void success_addKeyword() { + // given + Long partyId = 1L; + Long ownerId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request( + List.of("친목", "가입비 무료") + ); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + // when + partyCommandService.addKeyword(partyId, ownerId, request); + + // then + assertThat(party.getKeywords()).hasSize(2); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 키워드를 추가하면 INSUFFICIENT_PERMISSION 발생") + void fail_addKeyword_notOwner() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long nonOwnerId = 99L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("친목")); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.addKeyword(partyId, nonOwnerId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 유효하지 않은 키워드 문자열을 전달하면 INVALID_KEYWORD 발생") + void fail_addKeyword_invalidKeyword() { + // given + Long partyId = 1L; + Long ownerId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("존재하지않는키워드")); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.addKeyword(partyId, ownerId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVALID_KEYWORD); + } + } } From 3becfe6ab126c87eb1b6a27b5dfe33684032a04b Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 19:52:04 +0900 Subject: [PATCH 24/63] =?UTF-8?q?chore:=20=EB=AA=A8=EC=9E=84=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=20enum=20=EB=8C=80=EB=AC=B8=EC=9E=90=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ExerciseQueryService.java | 2 +- .../exercise/service/ExerciseValidator.java | 4 +- .../domain/member/domain/MemberParty.java | 8 +-- .../party/converter/PartyConverter.java | 6 +- .../party/exception/PartyErrorCode.java | 2 +- .../service/PartyCommandServiceImpl.java | 18 +++--- .../umc/cockple/demo/global/enums/Role.java | 6 +- .../integration/BookmarkIntegrationTest.java | 2 +- .../chat/integration/ChatIntegrationTest.java | 4 +- .../ExerciseCommandIntegrationTest.java | 8 +-- .../ExerciseQueryIntegrationTest.java | 20 +++---- ...ExerciseRecommendationIntegrationTest.java | 4 +- .../service/ExerciseLifecycleServiceTest.java | 24 ++++---- .../ExerciseParticipationServiceTest.java | 8 +-- .../service/ExerciseQueryServiceTest.java | 56 +++++++++---------- .../integration/MemberIntegrationTest.java | 12 ++-- .../service/MemberCommandServiceTest.java | 6 +- .../service/MemberQueryServiceTest.java | 4 +- .../integration/PartyIntegrationTest.java | 26 ++++----- .../service/PartyCommandServiceTest.java | 32 +++++------ .../party/service/PartyQueryServiceTest.java | 24 ++++---- 21 files changed, 137 insertions(+), 139 deletions(-) diff --git a/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryService.java b/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryService.java index 6876cee7a..adf7ae4b5 100644 --- a/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryService.java +++ b/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryService.java @@ -391,7 +391,7 @@ private void validatePartyIsActive(Party party) { private boolean checkManagerPermission(Party party, Member member) { return memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), member.getId(), Role.party_MANAGER); + party.getId(), member.getId(), Role.PARTY_MANAGER); } private ExerciseDetailDTO.ExerciseInfo createExerciseInfo(Exercise exercise) { diff --git a/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java b/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java index a81174b3f..fabda2ec8 100644 --- a/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java +++ b/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java @@ -90,9 +90,9 @@ private void validatePartyIsActive(Party party) { private void validateSubManagerPermission(Long memberId, Party party) { boolean isOwner = party.getOwnerId().equals(memberId); boolean isManager = memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), memberId, Role.party_MANAGER); + party.getId(), memberId, Role.PARTY_MANAGER); boolean isSubManager = memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), memberId, Role.party_SUBMANAGER); + party.getId(), memberId, Role.PARTY_SUBMANAGER); if (!isOwner && !isManager && !isSubManager) throw new ExerciseException(ExerciseErrorCode.INSUFFICIENT_PERMISSION); diff --git a/src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java b/src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java index 8d3cc6794..cf85ec7cd 100644 --- a/src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java +++ b/src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java @@ -47,7 +47,7 @@ public static MemberParty createOwner(Member member, Party party) { return MemberParty.builder() .member(member) .party(party) - .role(Role.party_MANAGER) + .role(Role.PARTY_MANAGER) .joinedAt(LocalDateTime.now()) .status(ACTIVE) .build(); @@ -57,20 +57,20 @@ public static MemberParty create(Party party, Member member) { return MemberParty.builder() .member(member) .party(party) - .role(Role.party_MEMBER) + .role(Role.PARTY_MEMBER) .joinedAt(LocalDateTime.now()) .status(ACTIVE) .build(); } public boolean isLeader() { - if (this.role == Role.party_MANAGER) return true; + if (this.role == Role.PARTY_MANAGER) return true; return false; } public boolean isViceLeader() { - if (this.role == Role.party_SUBMANAGER) return true; + if (this.role == Role.PARTY_SUBMANAGER) return true; return false; } diff --git a/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java b/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java index 4f87a3a86..e331e6c68 100644 --- a/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java +++ b/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java @@ -211,9 +211,9 @@ public PartyMemberSuggestionDTO.Response toPartyMemberSuggestionDTO(Member membe private int getRolePriority(String role) { return switch (role) { - case "party_MANAGER" -> 0; // 모임장 역할 - case "party_SUBMANAGER" -> 1; // 부모임장 - case "party_MEMBER" -> 2; // 일반 멤버 + case "PARTY_MANAGER" -> 0; // 모임장 역할 + case "PARTY_SUBMANAGER" -> 1; // 부모임장 + case "PARTY_MEMBER" -> 2; // 일반 멤버 default -> 99; }; } diff --git a/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java b/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java index e844ab093..cbba931fd 100644 --- a/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java +++ b/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java @@ -24,7 +24,7 @@ public enum PartyErrorCode implements BaseErrorCode { INVALID_ORDER_TYPE(HttpStatus.BAD_REQUEST, "PARTY106", "유효하지 않은 정렬 기준입니다. (최신순, 오래된 순, 운동 많은 순 중 하나여야 합니다.)"), INVALID_KEYWORD(HttpStatus.BAD_REQUEST, "PARTY107", "유효하지 않은 키워드입니다."), MALE_LEVEL_NOT_NEEDED(HttpStatus.BAD_REQUEST, "PARTY108", "여복 모임은 남자 급수를 설정할 수 없습니다."), - INVALID_ROLE_VALUE(HttpStatus.BAD_REQUEST, "PARTY411", "유효하지 않은 역할 값입니다. (party_SUBMANAGER 또는 party_MEMBER를 입력해주세요.)"), + INVALID_ROLE_VALUE(HttpStatus.BAD_REQUEST, "PARTY411", "유효하지 않은 역할 값입니다. (PARTY_SUBMANAGER 또는 PARTY_MEMBER를 입력해주세요.)"), PARTY_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY201", "존재하지 않는 모임입니다."), JoinRequest_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY202", "존재하지 않는 가입신청입니다."), diff --git a/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java b/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java index f3624188a..948060ea1 100644 --- a/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java +++ b/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java @@ -190,7 +190,7 @@ public void updateMemberRole(Long partyId, Long targetMemberId, Long currentMemb // 모임장 권한 검증 validateOwnerPermission(party, currentMemberId); // 대상이 모임장인 경우 변경 불가 - if (targetMemberParty.getRole() == Role.party_MANAGER) { + if (targetMemberParty.getRole() == Role.PARTY_MANAGER) { throw new PartyException(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER); } // 이미 같은 역할인 경우 @@ -199,10 +199,10 @@ public void updateMemberRole(Long partyId, Long targetMemberId, Long currentMemb } // SUBOWNER 지정 시, 기존 부모임장 자동 해제 - if (newRole == Role.party_SUBMANAGER) { - memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER) + if (newRole == Role.PARTY_SUBMANAGER) { + memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER) .ifPresent(mp -> { - mp.changeRole(Role.party_MEMBER); + mp.changeRole(Role.PARTY_MEMBER); createRoleNotification(partyId, NotificationTarget.PARTY_SUBOWNER_RELEASED, mp.getMember().getNickname()); }); @@ -212,7 +212,7 @@ public void updateMemberRole(Long partyId, Long targetMemberId, Long currentMemb targetMemberParty.changeRole(newRole); // 알림 발송 (전체 멤버 대상) - NotificationTarget notifTarget = (newRole == Role.party_SUBMANAGER) + NotificationTarget notifTarget = (newRole == Role.PARTY_SUBMANAGER) ? NotificationTarget.PARTY_SUBOWNER_ASSIGNED : NotificationTarget.PARTY_SUBOWNER_RELEASED; createRoleNotification(partyId, notifTarget, targetMember.getNickname()); @@ -387,7 +387,7 @@ private void validateIsNotOwner(Party party, Long memberId) { // 부모임장은 권한이 없음을 검증 private void validateIsNotSubOwner(Party party, Long memberId) { - memberPartyRepository.findByPartyIdAndRole(party.getId(), Role.party_SUBMANAGER) + memberPartyRepository.findByPartyIdAndRole(party.getId(), Role.PARTY_SUBMANAGER) .ifPresent(mp -> { if (mp.getMember().getId().equals(memberId)) { throw new PartyException(PartyErrorCode.INVALID_ACTION_FOR_SUBOWNER); @@ -427,7 +427,7 @@ private void validateRemovalPermission(Party party, Member remover, MemberParty if (remover.getId().equals(memberPartyToRemove.getMember().getId())) { //부모임장인 경우에만 가능 MemberParty removerMemberParty = findMemberPartyOrThrow(party, remover); - if (removerMemberParty.getRole() == Role.party_SUBMANAGER) { + if (removerMemberParty.getRole() == Role.PARTY_SUBMANAGER) { return; } else { throw new PartyException(PartyErrorCode.CANNOT_REMOVE_SELF); @@ -439,11 +439,11 @@ private void validateRemovalPermission(Party party, Member remover, MemberParty Role removerRole = removerMemberParty.getRole(); Role targetRole = memberPartyToRemove.getRole(); //모임장은 모두 삭제 가능 - if (removerRole == Role.party_MANAGER) { + if (removerRole == Role.PARTY_MANAGER) { return; } //부모임장은 일반 멤버만 삭제 가능 (모임장을 삭제하려할 경우 권한 없음) - if (removerRole == Role.party_SUBMANAGER && targetRole == Role.party_MEMBER) { + if (removerRole == Role.PARTY_SUBMANAGER && targetRole == Role.PARTY_MEMBER) { return; } //일반 멤버는 권한 없음 diff --git a/src/main/java/umc/cockple/demo/global/enums/Role.java b/src/main/java/umc/cockple/demo/global/enums/Role.java index b347e011f..0daea6101 100644 --- a/src/main/java/umc/cockple/demo/global/enums/Role.java +++ b/src/main/java/umc/cockple/demo/global/enums/Role.java @@ -2,9 +2,9 @@ public enum Role { - party_MEMBER, //일반 멤버 + PARTY_MEMBER, //일반 멤버 - party_MANAGER, //모임장 + PARTY_MANAGER, //모임장 - party_SUBMANAGER //부모임장 + PARTY_SUBMANAGER //부모임장 } diff --git a/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java index 984dfb50f..8ffea1ca0 100644 --- a/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java @@ -63,7 +63,7 @@ void setUp() { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("경기도", "안산시")); bookmarkParty = partyRepository.save(PartyFixture.createParty("테스트 모임", member.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(bookmarkParty, member, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(bookmarkParty, member, Role.PARTY_MANAGER)); bookmarkExercise = exerciseRepository.save(Exercise.builder() .party(bookmarkParty) diff --git a/src/test/java/umc/cockple/demo/domain/chat/integration/ChatIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/chat/integration/ChatIntegrationTest.java index 0671cf4df..4d3643739 100644 --- a/src/test/java/umc/cockple/demo/domain/chat/integration/ChatIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/chat/integration/ChatIntegrationTest.java @@ -58,8 +58,8 @@ void setUp() { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); party = partyRepository.save(PartyFixture.createParty("배드민턴 모임", member.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.party_MANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, otherMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.PARTY_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, otherMember, Role.PARTY_MEMBER)); partyChatRoom = chatRoomRepository.save(ChatFixture.createPartyChatRoom(party)); directChatRoom = chatRoomRepository.save(ChatFixture.createDirectChatRoom()); diff --git a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseCommandIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseCommandIntegrationTest.java index fa378c12c..bcaef277e 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseCommandIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseCommandIntegrationTest.java @@ -70,9 +70,9 @@ void setUp() { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER)); } @AfterEach @@ -606,7 +606,7 @@ void notPartyMember_outsideNotAccepted() throws Exception { void ageNotAllowed() throws Exception { Member youngMember = memberRepository.save( MemberFixture.createMember("어린회원", Gender.MALE, Level.B, 4001L, LocalDate.of(2010, 1, 1))); - memberPartyRepository.save(MemberFixture.createMemberParty(party, youngMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, youngMember, Role.PARTY_MEMBER)); SecurityContextHelper.setAuthentication(youngMember.getId(), youngMember.getNickname()); diff --git a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseQueryIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseQueryIntegrationTest.java index 6e8b1b8e7..de9e789f5 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseQueryIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseQueryIntegrationTest.java @@ -2,7 +2,6 @@ import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import umc.cockple.demo.domain.bookmark.domain.ExerciseBookmark; @@ -39,7 +38,6 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.List; -import java.util.Map; import javax.sql.DataSource; @@ -79,9 +77,9 @@ void setUp() { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER)); } @AfterEach @@ -139,7 +137,7 @@ class Success { .andExpect(jsonPath("$.data.participants.list[0].gender").value("MALE")) .andExpect(jsonPath("$.data.participants.list[0].level").isString()) .andExpect(jsonPath("$.data.participants.list[0].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.participants.list[0].partyPosition").value("party_MEMBER")) + .andExpect(jsonPath("$.data.participants.list[0].partyPosition").value("PARTY_MEMBER")) .andExpect(jsonPath("$.data.participants.list[0].isWithdrawn").value(false)) .andExpect(jsonPath("$.data.waiting.currentWaitingCount").value(1)) .andExpect(jsonPath("$.data.waiting.manCount").value(0)) @@ -147,7 +145,7 @@ class Success { .andExpect(jsonPath("$.data.waiting.list[0].name").value(subManager.getMemberName())) .andExpect(jsonPath("$.data.waiting.list[0].gender").value("FEMALE")) .andExpect(jsonPath("$.data.waiting.list[0].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.waiting.list[0].partyPosition").value("party_SUBMANAGER")) + .andExpect(jsonPath("$.data.waiting.list[0].partyPosition").value("PARTY_SUBMANAGER")) .andExpect(jsonPath("$.data.waiting.list[0].isWithdrawn").value(false)); } @@ -277,13 +275,13 @@ class Success { .andExpect(status().isOk()) .andExpect(jsonPath("$.data.participants.list[0].name").value(manager.getMemberName())) .andExpect(jsonPath("$.data.participants.list[0].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.participants.list[0].partyPosition").value("party_MANAGER")) + .andExpect(jsonPath("$.data.participants.list[0].partyPosition").value("PARTY_MANAGER")) .andExpect(jsonPath("$.data.participants.list[1].name").value(subManager.getMemberName())) .andExpect(jsonPath("$.data.participants.list[1].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.participants.list[1].partyPosition").value("party_SUBMANAGER")) + .andExpect(jsonPath("$.data.participants.list[1].partyPosition").value("PARTY_SUBMANAGER")) .andExpect(jsonPath("$.data.participants.list[2].name").value(normalMember.getMemberName())) .andExpect(jsonPath("$.data.participants.list[2].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.participants.list[2].partyPosition").value("party_MEMBER")) + .andExpect(jsonPath("$.data.participants.list[2].partyPosition").value("PARTY_MEMBER")) .andExpect(jsonPath("$.data.participants.list[3].name").value(outsider.getMemberName())) .andExpect(jsonPath("$.data.participants.list[3].participantType").value("EXTERNAL_PARTICIPANT")) .andExpect(jsonPath("$.data.participants.list[3].partyPosition").value(nullValue())) @@ -1501,7 +1499,7 @@ void setUp() { filteredParty = partyRepository.save(filteredParty); filteredParty.addLevel(Gender.MALE, Level.B); filteredParty = partyRepository.save(filteredParty); - memberPartyRepository.save(MemberFixture.createMemberParty(filteredParty, manager, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(filteredParty, manager, Role.PARTY_MANAGER)); startDate = LocalDate.of(2026, 3, 23); endDate = LocalDate.of(2026, 4, 5); diff --git a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseRecommendationIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseRecommendationIntegrationTest.java index cb51d226d..64e04dced 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseRecommendationIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseRecommendationIntegrationTest.java @@ -72,7 +72,7 @@ void setUp() { partyRepository.save(party); // 모임장을 모임 멤버로 등록 - memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER)); } @AfterEach @@ -155,7 +155,7 @@ class Success { void 소속된_모임의_운동은_추천되지_않는다() throws Exception { // given - outsider를 모임에 가입시킴 memberPartyRepository.save( - MemberFixture.createMemberParty(party, outsider, Role.party_MEMBER)); + MemberFixture.createMemberParty(party, outsider, Role.PARTY_MEMBER)); exerciseRepository.save(ExerciseFixture.createRecommendableExercise(party, LocalDate.now().plusDays(3), 37.5, 127.0, "테스트 체육관")); diff --git a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseLifecycleServiceTest.java b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseLifecycleServiceTest.java index af8355184..c317929bd 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseLifecycleServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseLifecycleServiceTest.java @@ -128,9 +128,9 @@ void subManagerCreatesExercise_success() { Member subManager = MemberFixture.createMember("부모임장", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(subManager, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_SUBMANAGER)) .willReturn(true); Exercise savedExercise = Exercise.builder() @@ -175,9 +175,9 @@ void normalMember_throwsException() { Member normalMember = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(normalMember, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_SUBMANAGER)) .willReturn(false); assertThatThrownBy(() -> @@ -278,9 +278,9 @@ void subManagerDeletesExercise_success() { Member subManager = MemberFixture.createMember("부모임장", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(subManager, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_SUBMANAGER)) .willReturn(true); // when @@ -303,9 +303,9 @@ void normalMember_throwsException() { Member normalMember = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(normalMember, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_SUBMANAGER)) .willReturn(false); assertThatThrownBy(() -> @@ -384,9 +384,9 @@ void subManagerUpdatesExercise_success() { Member subManager = MemberFixture.createMember("부모임장", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(subManager, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_SUBMANAGER)) .willReturn(true); Exercise savedExercise = Exercise.builder() @@ -418,9 +418,9 @@ void normalMember_throwsException() { Member normalMember = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(normalMember, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_SUBMANAGER)) .willReturn(false); assertThatThrownBy(() -> diff --git a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseParticipationServiceTest.java b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseParticipationServiceTest.java index 68ce83bee..62be82d77 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseParticipationServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseParticipationServiceTest.java @@ -341,9 +341,9 @@ void subManagerCancelsMemberParticipation_success() { ExerciseCancelDTO.ByManagerRequest request = new ExerciseCancelDTO.ByManagerRequest(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_SUBMANAGER)) .willReturn(true); given(memberRepository.findById(participant.getId())).willReturn(Optional.of(participant)); given(memberExerciseRepository.findByExerciseAndMember(exercise, participant)) @@ -392,9 +392,9 @@ void normalMember_throwsException() { ExerciseCancelDTO.ByManagerRequest request = new ExerciseCancelDTO.ByManagerRequest(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_SUBMANAGER)) .willReturn(false); assertThatThrownBy(() -> diff --git a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryServiceTest.java index 117fd8d8f..1a8c53480 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryServiceTest.java @@ -142,7 +142,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); // when @@ -169,7 +169,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), subManager.getId(), Role.party_MANAGER)) + party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); // when @@ -196,7 +196,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), normalMember.getId(), Role.party_MANAGER)) + party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); // when @@ -223,7 +223,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), outsider.getId(), Role.party_MANAGER)) + party.getId(), outsider.getId(), Role.PARTY_MANAGER)) .willReturn(false); // when @@ -253,7 +253,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(withdrawnMember.getId()))) @@ -278,7 +278,7 @@ class Success { MemberExercise memberExercise = MemberFixture.createMemberExercise(activeMember, exercise); - MemberParty memberParty = MemberFixture.createMemberParty(party, activeMember, Role.party_MEMBER); + MemberParty memberParty = MemberFixture.createMemberParty(party, activeMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -289,7 +289,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(activeMember.getId()))) @@ -321,7 +321,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of(guest)); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberRepository.findMemberNamesByIds(any())) .willReturn(Map.of(manager.getId(), "모임장")); @@ -366,9 +366,9 @@ class Success { ReflectionTestUtils.setField(guest, "id", 71L); ReflectionTestUtils.setField(guest, "createdAt", LocalDateTime.now().minusMinutes(1)); - MemberParty managerParty = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); - MemberParty memberParty = MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER); + MemberParty managerParty = MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER); + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); + MemberParty memberParty = MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -379,7 +379,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of(guest)); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(manager.getId(), subManager.getId(), normalMember.getId(), outsider.getId()))) @@ -398,9 +398,9 @@ class Success { ExerciseDetailDTO.ParticipantInfo::participantType, ExerciseDetailDTO.ParticipantInfo::partyPosition) .containsExactly( - tuple("모임장", "PARTY_MEMBER", "party_MANAGER"), - tuple("부모임장", "PARTY_MEMBER", "party_SUBMANAGER"), - tuple("일반멤버", "PARTY_MEMBER", "party_MEMBER"), + tuple("모임장", "PARTY_MEMBER", "PARTY_MANAGER"), + tuple("부모임장", "PARTY_MEMBER", "PARTY_SUBMANAGER"), + tuple("일반멤버", "PARTY_MEMBER", "PARTY_MEMBER"), tuple("외부회원", "EXTERNAL_PARTICIPANT", null), tuple("게스트", "GUEST", null) ); @@ -424,8 +424,8 @@ class Success { MemberExercise second = MemberFixture.createMemberExercise(secondMember, exercise); ReflectionTestUtils.setField(second, "createdAt", LocalDateTime.now()); - MemberParty firstParty = MemberFixture.createMemberParty(party, firstMember, Role.party_MEMBER); - MemberParty secondParty = MemberFixture.createMemberParty(party, secondMember, Role.party_MEMBER); + MemberParty firstParty = MemberFixture.createMemberParty(party, firstMember, Role.PARTY_MEMBER); + MemberParty secondParty = MemberFixture.createMemberParty(party, secondMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -436,7 +436,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(firstMember.getId(), secondMember.getId()))) @@ -468,7 +468,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of(guest)); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberRepository.findMemberNamesByIds(any())) .willReturn(Map.of(manager.getId(), "모임장")); @@ -500,8 +500,8 @@ class Success { MemberExercise second = MemberFixture.createMemberExercise(secondMember, exercise); ReflectionTestUtils.setField(second, "createdAt", LocalDateTime.now()); - MemberParty firstParty = MemberFixture.createMemberParty(party, firstMember, Role.party_MEMBER); - MemberParty secondParty = MemberFixture.createMemberParty(party, secondMember, Role.party_MEMBER); + MemberParty firstParty = MemberFixture.createMemberParty(party, firstMember, Role.PARTY_MEMBER); + MemberParty secondParty = MemberFixture.createMemberParty(party, secondMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -512,7 +512,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(firstMember.getId(), secondMember.getId()))) @@ -549,8 +549,8 @@ class Success { MemberExercise second = MemberFixture.createMemberExercise(femaleMember, exercise); ReflectionTestUtils.setField(second, "createdAt", LocalDateTime.now()); - MemberParty maleParty = MemberFixture.createMemberParty(party, maleMember, Role.party_MEMBER); - MemberParty femaleParty = MemberFixture.createMemberParty(party, femaleMember, Role.party_MEMBER); + MemberParty maleParty = MemberFixture.createMemberParty(party, maleMember, Role.PARTY_MEMBER); + MemberParty femaleParty = MemberFixture.createMemberParty(party, femaleMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -561,7 +561,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(maleMember.getId(), femaleMember.getId()))) @@ -594,8 +594,8 @@ class Success { MemberExercise femaleExercise = MemberFixture.createMemberExercise(femaleMember, exercise); ReflectionTestUtils.setField(femaleExercise, "createdAt", LocalDateTime.now()); - MemberParty maleParty = MemberFixture.createMemberParty(party, maleMember, Role.party_MEMBER); - MemberParty femaleParty = MemberFixture.createMemberParty(party, femaleMember, Role.party_MEMBER); + MemberParty maleParty = MemberFixture.createMemberParty(party, maleMember, Role.PARTY_MEMBER); + MemberParty femaleParty = MemberFixture.createMemberParty(party, femaleMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -606,7 +606,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(maleMember.getId(), femaleMember.getId()))) diff --git a/src/test/java/umc/cockple/demo/domain/member/integration/MemberIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/member/integration/MemberIntegrationTest.java index bba271356..28d577edc 100644 --- a/src/test/java/umc/cockple/demo/domain/member/integration/MemberIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/member/integration/MemberIntegrationTest.java @@ -112,7 +112,7 @@ class Failure { void manager_cannotWithdraw() throws Exception { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); Party party = partyRepository.save(PartyFixture.createParty("테스트 모임", member.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.PARTY_MANAGER)); SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); @@ -127,7 +127,7 @@ void manager_cannotWithdraw() throws Exception { void subManager_cannotWithdraw() throws Exception { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); Party party = partyRepository.save(PartyFixture.createParty("테스트 모임", member.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.party_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.PARTY_SUBMANAGER)); SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); @@ -204,8 +204,8 @@ class Success { .build()); // 모임 2개 - memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.party_MEMBER)); - memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.PARTY_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.PARTY_MEMBER)); SecurityContextHelper.setAuthentication(freshMember.getId(), freshMember.getNickname()); @@ -322,8 +322,8 @@ void getMyProfile_success() throws Exception { .build()); // 모임 2개, 운동 2개, 키워드 2개 - memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.party_MEMBER)); - memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.PARTY_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.PARTY_MEMBER)); memberExerciseRepository.save(MemberExercise.builder() .member(freshMember) diff --git a/src/test/java/umc/cockple/demo/domain/member/service/MemberCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/member/service/MemberCommandServiceTest.java index 290c391a4..f74620c03 100644 --- a/src/test/java/umc/cockple/demo/domain/member/service/MemberCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/member/service/MemberCommandServiceTest.java @@ -703,7 +703,7 @@ class Failure { void 활성_모임의_모임장이면_MANAGER_CANNOT_LEAVE_예외를_던진다() { // given MemberParty leaderParty = MemberParty.builder() - .role(Role.party_MANAGER) + .role(Role.PARTY_MANAGER) .status(MemberPartyStatus.ACTIVE) .joinedAt(LocalDateTime.now()) .build(); @@ -723,7 +723,7 @@ class Failure { void 활성_모임의_부모임장이면_SUBMANAGER_CANNOT_LEAVE_예외를_던진다() { // given MemberParty subManagerParty = MemberParty.builder() - .role(Role.party_SUBMANAGER) + .role(Role.PARTY_SUBMANAGER) .status(MemberPartyStatus.ACTIVE) .joinedAt(LocalDateTime.now()) .build(); @@ -743,7 +743,7 @@ class Failure { void 비활성_모임의_모임장이면_탈퇴가_가능하다() { // given: BANNED 상태의 모임이라면 탈퇴 검증을 통과해야 한다 MemberParty bannedParty = MemberParty.builder() - .role(Role.party_MANAGER) + .role(Role.PARTY_MANAGER) .status(MemberPartyStatus.BANNED) .joinedAt(LocalDateTime.now()) .build(); diff --git a/src/test/java/umc/cockple/demo/domain/member/service/MemberQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/member/service/MemberQueryServiceTest.java index b7c521268..9c1eb0a16 100644 --- a/src/test/java/umc/cockple/demo/domain/member/service/MemberQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/member/service/MemberQueryServiceTest.java @@ -125,8 +125,8 @@ class Success { @DisplayName("참여한_모임_수가_올바르게_반환된다") void 참여한_모임_수가_올바르게_반환된다() { // given - member.getMemberParties().add(MemberFixture.createMemberParty(null, member, umc.cockple.demo.global.enums.Role.party_MEMBER)); - member.getMemberParties().add(MemberFixture.createMemberParty(null, member, umc.cockple.demo.global.enums.Role.party_MEMBER)); + member.getMemberParties().add(MemberFixture.createMemberParty(null, member, umc.cockple.demo.global.enums.Role.PARTY_MEMBER)); + member.getMemberParties().add(MemberFixture.createMemberParty(null, member, umc.cockple.demo.global.enums.Role.PARTY_MEMBER)); given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 31ba6d088..d4e920fa9 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -117,8 +117,8 @@ void setUp() { party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); // 모임 멤버 생성 - memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER)); // 채팅방 생성 ChatRoom chatRoom = chatRoomRepository.save(ChatRoom.createPartyChatRoom(party)); @@ -143,7 +143,7 @@ class GetPartyMembers { void success_getPartyMembers() throws Exception { // 부모임장 추가 Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 1003L)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER)); // 운동 기록 추가 Exercise exercise = exerciseRepository.save(ExerciseFixture.createExercise(party, LocalDate.of(2025, 1, 10))); @@ -154,10 +154,10 @@ void success_getPartyMembers() throws Exception { .andExpect(jsonPath("$.data.summary.totalCount").value(3)) .andExpect(jsonPath("$.data.summary.maleCount").value(2)) .andExpect(jsonPath("$.data.summary.femaleCount").value(1)) - .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) + .andExpect(jsonPath("$.data.members[0].role").value("PARTY_MANAGER")) .andExpect(jsonPath("$.data.members[0].isMe").value(true)) - .andExpect(jsonPath("$.data.members[1].role").value("party_SUBMANAGER")) - .andExpect(jsonPath("$.data.members[2].role").value("party_MEMBER")) + .andExpect(jsonPath("$.data.members[1].role").value("PARTY_SUBMANAGER")) + .andExpect(jsonPath("$.data.members[2].role").value("PARTY_MEMBER")) .andExpect(jsonPath("$.data.members[2].lastExerciseDate").value("2025-01-10")); } @@ -220,7 +220,7 @@ void fail_leaveParty_owner() throws Exception { void fail_leaveParty_subOwner() throws Exception { // 부모임장 생성 및 가입 Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 3001L)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER)); // 부모임장 세션으로 설정 SecurityContextHelper.setAuthentication(subManager.getId(), subManager.getNickname()); @@ -425,7 +425,7 @@ void success_getPartyDetails_member() throws Exception { mockMvc.perform(get("/api/parties/{partyId}", party.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.memberStatus").value("MEMBER")) - .andExpect(jsonPath("$.data.memberRole").value("party_MANAGER")); + .andExpect(jsonPath("$.data.memberRole").value("PARTY_MANAGER")); } @Test @@ -655,7 +655,7 @@ void success_removeMember() throws Exception { void fail_removeMember_notOwner() throws Exception { // given Member someoneElse = memberRepository.save(MemberFixture.createMember("다른멤버", Gender.MALE, Level.B, 1010L)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, someoneElse, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, someoneElse, Role.PARTY_MEMBER)); SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); // when & then @@ -1210,7 +1210,7 @@ class UpdateMemberRole { @DisplayName("200 - 모임장이 일반 멤버를 부모임장으로 성공적으로 임명한다") void success_updateMemberRole() throws Exception { // given - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); // when & then @@ -1222,14 +1222,14 @@ void success_updateMemberRole() throws Exception { // 검증 MemberParty targetMemberParty = memberPartyRepository.findByPartyAndMember(party, normalMember).orElseThrow(); - assertThat(targetMemberParty.getRole()).isEqualTo(Role.party_SUBMANAGER); + assertThat(targetMemberParty.getRole()).isEqualTo(Role.PARTY_SUBMANAGER); } @Test @DisplayName("403 - 모임장이 아닌 멤버가 역할 수정을 시도하면 INSUFFICIENT_PERMISSION 예외를 반환한다") void fail_updateMemberRole_notOwner() throws Exception { // given - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); // 일반 멤버가 권한 변경 시도 SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); @@ -1245,7 +1245,7 @@ void fail_updateMemberRole_notOwner() throws Exception { @DisplayName("403 - 대상자가 모임장인 경우 권한 변경은 실패하며 CANNOT_ASSIGN_TO_OWNER 예외를 반환한다") void fail_updateMemberRole_targetIsOwner() throws Exception { // given - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_MEMBER); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); // when & then diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index b14b77ca6..df54ebbe9 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -108,7 +108,7 @@ void success_leaveParty() { Member member = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 10L); ReflectionTestUtils.setField(member, "id", memberId); - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.PARTY_MEMBER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); @@ -197,11 +197,11 @@ void fail_leaveParty_isSubOwner() { Member subManager = MemberFixture.createMember("부모임장", Gender.MALE, Level.A, 2L); ReflectionTestUtils.setField(subManager, "id", subManagerId); - MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); - given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)) + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER)) .willReturn(Optional.of(subManagerParty)); // when & then @@ -736,20 +736,20 @@ void success_updateMemberRole() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty memberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + MemberParty memberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(memberParty)); - given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)).willReturn(Optional.empty()); + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER)).willReturn(Optional.empty()); given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(memberParty)); // when partyCommandService.updateMemberRole(partyId, targetMemberId, currentOwnerId, request); // then - assertThat(memberParty.getRole()).isEqualTo(Role.party_SUBMANAGER); + assertThat(memberParty.getRole()).isEqualTo(Role.PARTY_SUBMANAGER); verify(notificationCommandService, times(1)).createNotification(any()); } @@ -768,8 +768,8 @@ void fail_updateMemberRole_targetIsOwner() { ReflectionTestUtils.setField(party, "id", partyId); // 타겟이 이미 모임장 권한을 가짐 - MemberParty memberParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + MemberParty memberParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); @@ -803,8 +803,8 @@ void fail_updateMemberRole_notOwner() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(targetId)).willReturn(Optional.of(targetMember)); @@ -838,8 +838,8 @@ void success_removeMember() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); - MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); @@ -872,8 +872,8 @@ void fail_removeMember_insufficientPermission() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); - MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); @@ -901,7 +901,7 @@ void fail_removeMember_cannotRemoveSelf() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 7c82bfaa9..9813ea1ba 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -108,9 +108,9 @@ void success_getPartyMembers() { ReflectionTestUtils.setField(subManager, "id", 20L); ReflectionTestUtils.setField(normalMember, "id", 30L); - MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty mp2 = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); - MemberParty mp3 = MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER); + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); + MemberParty mp3 = MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER); List memberParties = List.of(mp1, mp2, mp3); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); @@ -144,8 +144,8 @@ void success_getPartyMembers_withExerciseHistory() { ReflectionTestUtils.setField(manager, "id", 10L); ReflectionTestUtils.setField(member1, "id", 20L); - MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.PARTY_MEMBER); List memberParties = List.of(mp1, mp2); LocalDate lastDate = LocalDate.of(2025, 1, 10); @@ -187,7 +187,7 @@ void success_getPartyMembers_noExerciseHistory() { ReflectionTestUtils.setField(party, "id", partyId); Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); ReflectionTestUtils.setField(manager, "id", 10L); - MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER); List memberParties = List.of(mp); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); @@ -299,7 +299,7 @@ void success_getSimpleMyParties() { Party party = PartyFixture.createParty("테스트 모임", 10L, addr); ReflectionTestUtils.setField(party, "id", 10L); - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.PARTY_MEMBER); Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); @@ -515,11 +515,11 @@ void success_getPartyDetails_member() { Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); ReflectionTestUtils.setField(member, "id", memberId); - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.PARTY_MEMBER); PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() .partyId(partyId) .memberStatus("MEMBER") - .memberRole("party_MEMBER") + .memberRole("PARTY_MEMBER") .build(); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); @@ -532,7 +532,7 @@ void success_getPartyDetails_member() { // then assertThat(result.memberStatus()).isEqualTo("MEMBER"); - assertThat(result.memberRole()).isEqualTo("party_MEMBER"); + assertThat(result.memberRole()).isEqualTo("PARTY_MEMBER"); } @Test @@ -666,7 +666,7 @@ void fail_getJoinRequests_notOwner() { Member nonOwner = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, nonOwnerId); ReflectionTestUtils.setField(nonOwner, "id", nonOwnerId); - MemberParty nonOwnerParty = MemberFixture.createMemberParty(party, nonOwner, Role.party_MEMBER); + MemberParty nonOwnerParty = MemberFixture.createMemberParty(party, nonOwner, Role.PARTY_MEMBER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); @@ -692,7 +692,7 @@ void fail_getJoinRequests_invalidStatus() { Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); ReflectionTestUtils.setField(owner, "id", ownerId); - MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); From 3af0065f77b068ee4f0503234692b1e0d94652de Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 10:52:13 +0900 Subject: [PATCH 25/63] =?UTF-8?q?chore:=20import=EB=AC=B8=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EC=A4=84=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 24 +++++++----------- .../service/PartyCommandServiceTest.java | 25 ++++++++----------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index d4e920fa9..f52ad5915 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; import umc.cockple.demo.domain.chat.domain.ChatRoom; @@ -25,13 +26,7 @@ import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; -import umc.cockple.demo.domain.party.dto.PartyCreateDTO; -import umc.cockple.demo.domain.party.dto.PartyInviteCreateDTO; -import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; -import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; -import umc.cockple.demo.domain.party.dto.PartyKeywordDTO; -import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; -import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; +import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestAction; @@ -52,7 +47,6 @@ import java.time.LocalDate; import java.util.List; -import org.springframework.http.MediaType; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -127,7 +121,7 @@ void setUp() { // 추천 조회용 모임 (manager의 조건에 맞춤) Party suggestedParty = PartyFixture.createParty("추천 모임", normalMember.getId(), addr); - suggestedParty.addLevel(Gender.MALE, Level.A); + suggestedParty.addLevel(Gender.MALE, Level.A); partyRepository.save(suggestedParty); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); @@ -686,7 +680,7 @@ class GetJoinRequests { void success_getJoinRequests() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("가입희망자", Gender.FEMALE, Level.B, 1010L)); - + PartyJoinRequest joinRequest = PartyJoinRequest.builder() .party(party) .member(applicant) @@ -711,7 +705,7 @@ void success_getJoinRequests() throws Exception { void success_getJoinRequests_approved() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("승인된멤버", Gender.MALE, Level.C, 1015L)); - + PartyJoinRequest joinRequest = PartyJoinRequest.builder() .party(party) .member(applicant) @@ -995,7 +989,7 @@ class ActionJoinRequest { void success_actionJoinRequest_approve() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1020L)); - + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() .party(party) .member(applicant) @@ -1024,7 +1018,7 @@ void success_actionJoinRequest_approve() throws Exception { void success_actionJoinRequest_reject() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("탈락자", Gender.FEMALE, Level.B, 1030L)); - + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() .party(party) .member(applicant) @@ -1053,7 +1047,7 @@ void success_actionJoinRequest_reject() throws Exception { void fail_actionJoinRequest_notOwner() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1040L)); - + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() .party(party) .member(applicant) @@ -1076,7 +1070,7 @@ void fail_actionJoinRequest_notOwner() throws Exception { void fail_actionJoinRequest_alreadyHandled() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1050L)); - + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() .party(party) .member(applicant) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index df54ebbe9..2b92cfc11 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -16,14 +16,13 @@ import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; +import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; -import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.domain.PartyInvitation; -import umc.cockple.demo.domain.notification.service.NotificationCommandService; +import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; -import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; @@ -31,8 +30,8 @@ import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; -import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -49,9 +48,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class PartyCommandServiceTest { @@ -571,7 +568,7 @@ void success_updateParty() { assertThat(party.getJoinPrice()).isEqualTo(0); assertThat(party.getPrice()).isEqualTo(10000); assertThat(party.getContent()).isEqualTo("새로운 내용"); - + verify(notificationCommandService, times(1)).createNotification(any()); } @@ -586,7 +583,7 @@ void fail_updateParty_partyNotFound() { given(partyRepository.findById(partyId)).willReturn(Optional.empty()); // when & then - PartyException exception = assertThrows(PartyException.class, + PartyException exception = assertThrows(PartyException.class, () -> partyCommandService.updateParty(partyId, memberId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); } @@ -601,7 +598,7 @@ void fail_updateParty_insufficientPermission() { PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); ReflectionTestUtils.setField(owner, "id", 1L); - + Member normalMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 2L); ReflectionTestUtils.setField(normalMember, "id", memberId); @@ -615,7 +612,7 @@ void fail_updateParty_insufficientPermission() { given(memberRepository.findById(memberId)).willReturn(Optional.of(normalMember)); // when & then - PartyException exception = assertThrows(PartyException.class, + PartyException exception = assertThrows(PartyException.class, () -> partyCommandService.updateParty(partyId, memberId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } @@ -776,7 +773,7 @@ void fail_updateMemberRole_targetIsOwner() { given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(memberParty)); // when & then - PartyException exception = assertThrows(PartyException.class, + PartyException exception = assertThrows(PartyException.class, () -> partyCommandService.updateMemberRole(partyId, ownerId, ownerId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER); } @@ -793,7 +790,7 @@ void fail_updateMemberRole_notOwner() { PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); ReflectionTestUtils.setField(owner, "id", ownerId); - + Member notOwner = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, notOwnerId); ReflectionTestUtils.setField(notOwner, "id", notOwnerId); @@ -811,7 +808,7 @@ void fail_updateMemberRole_notOwner() { given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); // when & then (notOwnerId를 currentMemberId로 전달하여 실행) - PartyException exception = assertThrows(PartyException.class, + PartyException exception = assertThrows(PartyException.class, () -> partyCommandService.updateMemberRole(partyId, targetId, notOwnerId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } From 21d73b8cef509195992dcd1e4eef6da4dc68987e Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 11:11:42 +0900 Subject: [PATCH 26/63] =?UTF-8?q?TEST:=20=EB=82=B4=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 31 ++++++- .../party/service/PartyQueryServiceTest.java | 85 +++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index f52ad5915..e99f10057 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -36,6 +36,7 @@ import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; +import umc.cockple.demo.domain.party.enums.PartyOrderType; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; import umc.cockple.demo.global.enums.Role; @@ -242,7 +243,7 @@ void fail_leaveParty_notMember() throws Exception { class GetMyParties { @Test - @DisplayName("200 - 사용자가 가입한 모임 목록을 페이징하여 반환한다") + @DisplayName("200 - 사용자가 가입한 모임 목록을 최신순으로 페이징하여 반환한다") void success_getMyParties() throws Exception { mockMvc.perform(get("/api/my/parties") .param("created", "false") @@ -260,6 +261,34 @@ void success_getMyParties() throws Exception { .andExpect(jsonPath("$.data.last").value(true)); } + @Test + @DisplayName("200 - 사용자가 가입한 모임 목록을 오래된 순으로 페이징하여 반환한다") + void success_getMyParties_oldest() throws Exception { + mockMvc.perform(get("/api/my/parties") + .param("created", "false") + .param("sort", "오래된 순") + .param("size", "10") + .param("page", "0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())); + } + + @Test + @DisplayName("200 - 사용자가 가입한 모임 목록을 운동 많은 순으로 페이징하여 반환한다") + void success_getMyParties_exerciseCount() throws Exception { + mockMvc.perform(get("/api/my/parties") + .param("created", "false") + .param("sort", "운동 많은 순") + .param("size", "10") + .param("page", "0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())); + } + @Test @DisplayName("200 - 가입한 모임이 없을 경우 빈 목록을 반환한다") void success_emptyMyParties() throws Exception { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 9813ea1ba..79052ab58 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -28,6 +28,7 @@ import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; +import umc.cockple.demo.domain.party.enums.PartyOrderType; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; @@ -279,6 +280,90 @@ void success_getMyParties() { verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); } + + @Test + @DisplayName("성공 - 사용자가 가입한 모임 목록을 오래된 순으로 페이징하여 반환한다") + void success_getMyParties_oldest() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + + Party party1 = PartyFixture.createParty("테스트 모임1", 10L, addr); + ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("테스트 모임2", 10L, addr); + ReflectionTestUtils.setField(party2, "id", 2L); + + // 오래된 순: party1, party2 순서 + Slice partySlice = new SliceImpl<>(List.of(party1, party2), pageable, false); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L, 2L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L, 2L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of()); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, + "오래된 순", pageable); + + // then + assertThat(result.getContent()).hasSize(2); + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + } + + @Test + @DisplayName("성공 - 사용자가 가입한 모임 목록을 운동 많은 순으로 페이징하여 반환한다") + void success_getMyParties_exerciseCount() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + + Party party1 = PartyFixture.createParty("운동많은모임", 10L, addr); + ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("운동적은모임", 10L, addr); + ReflectionTestUtils.setField(party2, "id", 2L); + + // 운동 많은 순: party1, party2 순서 + Slice partySlice = new SliceImpl<>(List.of(party1, party2), pageable, false); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L, 2L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L, 2L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of()); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, + "운동 많은 순", pageable); + + // then + assertThat(result.getContent()).hasSize(2); + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + } + + @Test + @DisplayName("실패 - 유효하지 않은 정렬 기준을 전달하면 INVALID_ORDER_TYPE 발생") + void fail_getMyParties_invalidSort() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + // when & then + assertThatThrownBy(() -> partyQueryService.getMyParties(memberId, false, "존재하지않는정렬", pageable)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.INVALID_ORDER_TYPE)); + } } @Nested From b45fd611ac0c83f6ef85eb9b97504aae1303e46c Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 12:17:02 +0900 Subject: [PATCH 27/63] =?UTF-8?q?TEST:=20=EB=A9=A4=EB=B2=84=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=20=EC=84=A4=EC=A0=95=20API=20=EC=8B=A4=ED=8C=A8,=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PartyCommandServiceTest.java | 54 ++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 2b92cfc11..2e7e0aa24 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -716,12 +716,13 @@ void fail_deleteParty_partyNotFound() { class UpdateMemberRole { @Test - @DisplayName("성공 - 모임장이 일반 멤버를 부모임장으로 지정하고 알림을 발생시킨다") + @DisplayName("성공 - 모임장이 일반 멤버를 부모임장으로 지정하면 기존 부모임장은 일반 멤버로 강등되고 새 부모임장이 지정된다") void success_updateMemberRole() { // given Long partyId = 1L; Long currentOwnerId = 1L; Long targetMemberId = 10L; + Long oldSubManagerId = 20L; PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, currentOwnerId); @@ -730,24 +731,63 @@ void success_updateMemberRole() { Member targetMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, targetMemberId); ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + Member oldSubManager = MemberFixture.createMember("기존부모임장", Gender.MALE, Level.A, oldSubManagerId); + ReflectionTestUtils.setField(oldSubManager, "id", oldSubManagerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty memberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + MemberParty oldSubManagerParty = MemberFixture.createMemberParty(party, oldSubManager, Role.PARTY_SUBMANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); - given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(memberParty)); - given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER)).willReturn(Optional.empty()); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(memberParty)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER)).willReturn(Optional.of(oldSubManagerParty)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(targetMemberParty, oldSubManagerParty)); // when partyCommandService.updateMemberRole(partyId, targetMemberId, currentOwnerId, request); // then - assertThat(memberParty.getRole()).isEqualTo(Role.PARTY_SUBMANAGER); - verify(notificationCommandService, times(1)).createNotification(any()); + assertThat(targetMemberParty.getRole()).isEqualTo(Role.PARTY_SUBMANAGER); + assertThat(oldSubManagerParty.getRole()).isEqualTo(Role.PARTY_MEMBER); + verify(notificationCommandService, times(4)).createNotification(any()); + } + + @Test + @DisplayName("실패 - 이미 요청한 역할과 같은 역할인 경우 변경 없이 반환된다") + void fail_updateMemberRole_sameRole() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long targetId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Member targetMember = MemberFixture.createMember("타겟", Gender.MALE, Level.A, targetId); + ReflectionTestUtils.setField(targetMember, "id", targetId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty targetMemberParty = spy(MemberFixture.createMemberParty(party, targetMember, Role.PARTY_SUBMANAGER)); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(targetId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + + // when + partyCommandService.updateMemberRole(partyId, targetId, ownerId, request); + + // then + verify(targetMemberParty, never()).changeRole(any()); + verify(notificationCommandService, never()).createNotification(any()); } @Test From c0005ef9b6458cb07025743630a5b2f21b816833 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 12:43:37 +0900 Subject: [PATCH 28/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=A0=EC=B2=AD=20=EC=B2=98=EB=A6=AC=20API=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20enum=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../party/exception/PartyErrorCode.java | 2 +- .../service/PartyCommandServiceImpl.java | 2 +- .../integration/PartyIntegrationTest.java | 45 ++++++++ .../service/PartyCommandServiceTest.java | 102 ++++++++++++++++++ 4 files changed, 149 insertions(+), 2 deletions(-) diff --git a/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java b/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java index cbba931fd..abc159c4d 100644 --- a/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java +++ b/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java @@ -27,7 +27,7 @@ public enum PartyErrorCode implements BaseErrorCode { INVALID_ROLE_VALUE(HttpStatus.BAD_REQUEST, "PARTY411", "유효하지 않은 역할 값입니다. (PARTY_SUBMANAGER 또는 PARTY_MEMBER를 입력해주세요.)"), PARTY_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY201", "존재하지 않는 모임입니다."), - JoinRequest_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY202", "존재하지 않는 가입신청입니다."), + JOIN_REQUEST_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY202", "존재하지 않는 가입신청입니다."), JOIN_REQUEST_PARTY_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY203", "해당 모임에서 존재하지 않는 가입신청입니다."), NOT_MEMBER(HttpStatus.BAD_REQUEST, "PARTY204", "해당 모임의 멤버가 아닙니다."), INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY205", "존재하지 않는 모임 초대입니다."), diff --git a/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java b/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java index 948060ea1..9616b587a 100644 --- a/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java +++ b/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java @@ -335,7 +335,7 @@ public void addKeyword(Long partyId, Long memberId, PartyKeywordDTO.Request requ //가입신청 조회 private PartyJoinRequest findJoinRequestOrThrow(Long requestId) { return partyJoinRequestRepository.findById(requestId) - .orElseThrow(() -> new PartyException(PartyErrorCode.JoinRequest_NOT_FOUND)); + .orElseThrow(() -> new PartyException(PartyErrorCode.JOIN_REQUEST_NOT_FOUND)); } private PartyInvitation findInvitationOrThrow(Long invitationId) { diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index e99f10057..9a62fb068 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -529,6 +529,51 @@ void fail_createJoinRequest_genderMismatch() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.GENDER_NOT_MATCH.getCode())); } + + @Test + @DisplayName("409 - 이미 대기중인 가입 신청이 있는 상태에서 다시 신청하면 JOIN_REQUEST_ALREADY_EXISTS 에러를 반환한다") + void fail_createJoinRequest_alreadyExists() throws Exception { + // 가입하지 않은 멤버 생성 + Member applicant = memberRepository.save(MemberFixture.createMember("신청자2", Gender.MALE, Level.A, 5002L, LocalDate.of(1995, 1, 1))); + + // 기존 가입 신청 추가 + PartyJoinRequest joinRequest = PartyJoinRequest.create(applicant, party); + partyJoinRequestRepository.save(joinRequest); + + SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", party.getId())) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.JOIN_REQUEST_ALREADY_EXISTS.getCode())); + } + + @Test + @DisplayName("400 - 삭제된(비활성화된) 모임에 가입 신청하면 PARTY_IS_DELETED 에러를 반환한다") + void fail_createJoinRequest_partyDeleted() throws Exception { + // 파티 생성 후 삭제 + PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울", "금천")); + Party deletedParty = partyRepository.save(PartyFixture.createParty("삭제된 모임", manager.getId(), addr)); + deletedParty.delete(); + partyRepository.save(deletedParty); + + Member applicant = memberRepository.save(MemberFixture.createMember("신청자3", Gender.MALE, Level.A, 5003L)); + SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", deletedParty.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); + } + + @Test + @DisplayName("404 - 존재하지 않는 파티에 가입 신청하면 PARTY_NOT_FOUND 에러를 반환한다") + void fail_createJoinRequest_partyNotFound() throws Exception { + Member applicant = memberRepository.save(MemberFixture.createMember("신청자4", Gender.MALE, Level.A, 5004L)); + SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 2e7e0aa24..f10983082 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -1106,6 +1106,108 @@ void fail_actionJoinRequest_alreadyActions() { () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS); } + + @Test + @DisplayName("실패 - 해당 가입 요청을 찾을 수 없는 경우 JOIN_REQUEST_NOT_FOUND 발생") + void fail_actionJoinRequest_notFound() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 999L; // 존재하지 않는 ID + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_NOT_FOUND); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 가입 신청을 처리하려 할 때 INSUFFICIENT_PERMISSION 발생") + void fail_actionJoinRequest_insufficientPermission() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long notOwnerId = 99L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); // 실제 모임장 + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, notOwnerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 처리하려는 가입 신청이 해당 모임의 것이 아닌 경우 JOIN_REQUEST_PARTY_NOT_FOUND 발생") + void fail_actionJoinRequest_partyNotFound() { + // given + Long partyId = 1L; + Long wrongPartyId = 2L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party targetParty = PartyFixture.createParty("대상 모임", owner.getId(), addr); + ReflectionTestUtils.setField(targetParty, "id", partyId); + + Party wrongParty = PartyFixture.createParty("다른 모임", owner.getId(), addr); + ReflectionTestUtils.setField(wrongParty, "id", wrongPartyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + // 다른 모임으로 가입신청 + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(wrongParty) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(targetParty)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_PARTY_NOT_FOUND); + } } @Nested From ae3ee68ba20ef25c95367174064886d1c1935023 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 13:35:38 +0900 Subject: [PATCH 29/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A1=B0=ED=9A=8C=20API=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo/domain/party/service/PartyQueryServiceTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 79052ab58..95f5bb67f 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -584,6 +584,7 @@ void success_getPartyDetails_nonMember() { assertThat(result.partyName()).isEqualTo("상세 모임"); assertThat(result.memberStatus()).isEqualTo("NOT_MEMBER"); assertThat(result.hasPendingJoinRequest()).isFalse(); + assertThat(result.isBookmarked()).isFalse(); verify(partyRepository).findById(partyId); } @@ -611,6 +612,7 @@ void success_getPartyDetails_member() { given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); given(memberPartyRepository.findByPartyAndMember(party, member)) .willReturn(Optional.of(memberParty)); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(true); // when PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); @@ -618,6 +620,8 @@ void success_getPartyDetails_member() { // then assertThat(result.memberStatus()).isEqualTo("MEMBER"); assertThat(result.memberRole()).isEqualTo("PARTY_MEMBER"); + assertThat(result.hasPendingJoinRequest()).isNull(); // 멤버이므로 null 반환 + assertThat(result.isBookmarked()).isTrue(); } @Test From c926a2ad2036552495c7d6be485be46500b0d967 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 14:52:14 +0900 Subject: [PATCH 30/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=82=AD=EC=A0=9C=20API=20=EC=84=B1=EA=B3=B5,=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PartyCommandServiceTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index f10983082..1265256e0 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -892,6 +892,40 @@ void success_removeMember() { verify(chatRoomService, times(1)).leavePartyChatRoom(partyId, targetMemberId); } + @Test + @DisplayName("성공 - 부모임장이 일반 멤버를 성공적으로 강퇴한다") + void success_removeMember_bySubManager() { + // given + Long partyId = 1L; + Long subManagerId = 2L; + Long targetMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member subManager = MemberFixture.createMember("부모임장", Gender.MALE, Level.A, subManagerId); + ReflectionTestUtils.setField(subManager, "id", subManagerId); + Member targetMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, targetMemberId); + ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + + Party party = PartyFixture.createParty("모임명", 1L, addr); // ownerId = 1L + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); + given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, subManager)).willReturn(Optional.of(subManagerParty)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + + // when + partyCommandService.removeMember(partyId, targetMemberId, subManagerId); + + // then + verify(memberPartyRepository, times(1)).delete(targetMemberParty); + verify(chatRoomService, times(1)).leavePartyChatRoom(partyId, targetMemberId); + } + @Test @DisplayName("실패 - 권한이 없는 멤버가 타인을 강퇴하려 하면 INSUFFICIENT_PERMISSION 발생") void fail_removeMember_insufficientPermission() { @@ -949,6 +983,36 @@ void fail_removeMember_cannotRemoveSelf() { () -> partyCommandService.removeMember(partyId, ownerId, ownerId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_REMOVE_SELF); } + + @Test + @DisplayName("실패 - 대상 멤버가 모임 소속이 아닐 경우 NOT_MEMBER 발생") + void fail_removeMember_notMember() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long targetMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member targetMember = MemberFixture.createMember("타겟", Gender.MALE, Level.A, targetMemberId); + ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); + + // 타겟 멤버가 모임 소속이 아님 -> findMemberPartyOrThrow 에서 NOT_MEMBER 발생 + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.removeMember(partyId, targetMemberId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER); + } } @Nested From dd0f3ba1a4aba279d97a558dd8a3e8c287f2cd05 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Wed, 1 Apr 2026 00:27:34 +0900 Subject: [PATCH 31/63] =?UTF-8?q?test:=20MEMBER=5FNOT=5FFOUND,=20PARTY=5FN?= =?UTF-8?q?OT=5FFOUND=20=EC=8B=A4=ED=8C=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 170 +++++++++++- .../service/PartyCommandServiceTest.java | 246 +++++++++++++++++- .../party/service/PartyQueryServiceTest.java | 33 ++- 3 files changed, 443 insertions(+), 6 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 9a62fb068..e9d54e156 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -18,6 +18,7 @@ import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.exception.MemberErrorCode; import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; @@ -36,7 +37,6 @@ import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; -import umc.cockple.demo.domain.party.enums.PartyOrderType; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; import umc.cockple.demo.global.enums.Role; @@ -236,6 +236,14 @@ void fail_leaveParty_notMember() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.NOT_MEMBER.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티에서 탈퇴 시도 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_leaveParty_partyNotFound() throws Exception { + mockMvc.perform(delete("/api/parties/{partyId}/members/my", 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -535,7 +543,7 @@ void fail_createJoinRequest_genderMismatch() throws Exception { void fail_createJoinRequest_alreadyExists() throws Exception { // 가입하지 않은 멤버 생성 Member applicant = memberRepository.save(MemberFixture.createMember("신청자2", Gender.MALE, Level.A, 5002L, LocalDate.of(1995, 1, 1))); - + // 기존 가입 신청 추가 PartyJoinRequest joinRequest = PartyJoinRequest.create(applicant, party); partyJoinRequestRepository.save(joinRequest); @@ -648,6 +656,23 @@ void fail_updateParty_notOwner() throws Exception { .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티 수정 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_updateParty_partyNotFound() throws Exception { + // given + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(List.of("월")) + .activityTime("오전") + .build(); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}", 9999L) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -696,6 +721,14 @@ void fail_deleteParty_partyDeleted() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티 삭제 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_deleteParty_partyNotFound() throws Exception { + mockMvc.perform(patch("/api/parties/{partyId}/status", 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -743,6 +776,22 @@ void fail_removeMember_selfAsManager() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_REMOVE_SELF.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티의 멤버 삭제 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_removeMember_partyNotFound() throws Exception { + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", 9999L, normalMember.getId())) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + + @Test + @DisplayName("404 - 존재하지 않는 멤버를 강퇴하려 할 때 MEMBER_NOT_FOUND 에러를 반환한다") + void fail_removeMember_memberNotFound() throws Exception { + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", party.getId(), 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(MemberErrorCode.MEMBER_NOT_FOUND.getCode())); + } } @Nested @@ -824,6 +873,15 @@ void fail_getJoinRequests_invalidStatus() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_REQUEST_STATUS.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티의 가입 신청 목록 조회 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_getJoinRequests_partyNotFound() throws Exception { + mockMvc.perform(get("/api/parties/{partyId}/join-requests", 9999L) + .param("status", "PENDING")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -949,6 +1007,32 @@ void fail_createInvitation_duplicateInvitation() throws Exception { .andExpect(status().isConflict()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVITATION_ALREADY_EXISTS.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티에서 멤버 초대 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_createInvitation_partyNotFound() throws Exception { + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(normalMember.getId()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/invitations", 9999L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + + @Test + @DisplayName("404 - 존재하지 않는 회원을 모임에 초대하면 MEMBER_NOT_FOUND 에러를 반환한다") + void fail_createInvitation_memberNotFound() throws Exception { + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(9999L); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(MemberErrorCode.MEMBER_NOT_FOUND.getCode())); + } } @Nested @@ -1052,6 +1136,27 @@ void fail_actionInvitation_alreadyActions() throws Exception { .andExpect(status().isConflict()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVITATION_ALREADY_ACTIONS.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 회원이 초대를 처리하려 할 때 MEMBER_NOT_FOUND 에러를 반환한다") + void fail_actionInvitation_memberNotFound() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1104L)); + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + // 인증 정보에는 유효하지만 DB에는 없는 ID 설정 + SecurityContextHelper.setAuthentication(9999L, "ghost"); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(MemberErrorCode.MEMBER_NOT_FOUND.getCode())); + } } @Nested @@ -1161,6 +1266,28 @@ void fail_actionJoinRequest_alreadyHandled() throws Exception { .andExpect(status().isConflict()) .andExpect(jsonPath("$.code").value(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티의 가입 신청 요청 처리 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_actionJoinRequest_partyNotFound() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1060L)); + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", 9999L, joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -1323,6 +1450,32 @@ void fail_updateMemberRole_targetIsOwner() throws Exception { .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티의 멤버 역할 수정 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_updateMemberRole_partyNotFound() throws Exception { + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", 9999L, normalMember.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + + @Test + @DisplayName("404 - 존재하지 않는 멤버의 역할 수정 시 MEMBER_NOT_FOUND 에러를 반환한다") + void fail_updateMemberRole_memberNotFound() throws Exception { + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", party.getId(), 9999L) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(MemberErrorCode.MEMBER_NOT_FOUND.getCode())); + } } @Nested @@ -1379,5 +1532,18 @@ void fail_addKeyword_invalidKeyword() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_KEYWORD.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티에 키워드 추가 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_addKeyword_partyNotFound() throws Exception { + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("새키워드")); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/keywords", 9999L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 1265256e0..48739c70a 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -230,6 +230,24 @@ void fail_leaveParty_notMember() { .isInstanceOf(PartyException.class) .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER)); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외가 발생한다") + void fail_leaveParty_memberNotFound() { + // given + Long partyId = 1L; + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", 1L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(10L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, 10L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -358,6 +376,33 @@ void fail_createJoinRequest_ageMismatch() { .isInstanceOf(PartyException.class) .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH)); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_createJoinRequest_partyNotFound() { + // given + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.B, 1L); + ReflectionTestUtils.setField(member, "id", 1L); + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(999L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_createJoinRequest_memberNotFound() { + // given + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(1L, 1L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -527,6 +572,33 @@ void fail_createParty_ageMismatch() { () -> partyCommandService.createParty(memberId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_createParty_memberNotFound() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("테스트 모임") + .partyType("혼복") + .activityTime("오전") + .addr1("서울") + .addr2("강남") + .activityDay(List.of("월", "수")) + .price(10000) + .joinPrice(5000) + .designatedCock("테스트콕") + .maleLevel(List.of("A조")) + .femaleLevel(List.of("B조")) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createParty(memberId, request)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -616,6 +688,26 @@ void fail_updateParty_insufficientPermission() { () -> partyCommandService.updateParty(partyId, memberId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_updateParty_memberNotFound() { + // given + Long partyId = 1L; + Long memberId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityTime("오전") + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.updateParty(partyId, memberId, request)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -709,6 +801,23 @@ void fail_deleteParty_partyNotFound() { () -> partyCommandService.deleteParty(invalidId, 1L)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_deleteParty_memberNotFound() { + // given + Long partyId = 1L; + Long memberId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.deleteParty(partyId, memberId)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -852,6 +961,36 @@ void fail_updateMemberRole_notOwner() { () -> partyCommandService.updateMemberRole(partyId, targetId, notOwnerId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_updateMemberRole_partyNotFound() { + // given + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.updateMemberRole(999L, 1L, 1L, request)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_updateMemberRole_memberNotFound() { + // given + Long partyId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.updateMemberRole(partyId, 1L, 1L, request)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -1004,7 +1143,7 @@ void fail_removeMember_notMember() { given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); - + // 타겟 멤버가 모임 소속이 아님 -> findMemberPartyOrThrow 에서 NOT_MEMBER 발생 given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.empty()); @@ -1013,6 +1152,34 @@ void fail_removeMember_notMember() { () -> partyCommandService.removeMember(partyId, targetMemberId, ownerId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_removeMember_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.removeMember(999L, 1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_removeMember_memberNotFound() { + // given + Long partyId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.removeMember(partyId, 10L, 1L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -1234,7 +1401,7 @@ void fail_actionJoinRequest_insufficientPermission() { @Test @DisplayName("실패 - 처리하려는 가입 신청이 해당 모임의 것이 아닌 경우 JOIN_REQUEST_PARTY_NOT_FOUND 발생") - void fail_actionJoinRequest_partyNotFound() { + void fail_actionJoinRequest_joinRequestPartyNotFound() { // given Long partyId = 1L; Long wrongPartyId = 2L; @@ -1244,7 +1411,7 @@ void fail_actionJoinRequest_partyNotFound() { PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); ReflectionTestUtils.setField(owner, "id", ownerId); - + Party targetParty = PartyFixture.createParty("대상 모임", owner.getId(), addr); ReflectionTestUtils.setField(targetParty, "id", partyId); @@ -1272,6 +1439,18 @@ void fail_actionJoinRequest_partyNotFound() { () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_PARTY_NOT_FOUND); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_actionJoinRequest_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.actionJoinRequest(999L, 1L, new PartyJoinActionDTO.Request(RequestAction.APPROVE), 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } } @Nested @@ -1392,6 +1571,34 @@ void fail_createInvitation_duplicateInvitation() { () -> partyCommandService.createInvitation(partyId, inviteeId, ownerId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_EXISTS); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_createInvitation_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createInvitation(999L, 1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_createInvitation_memberNotFound() { + // given + Long partyId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createInvitation(partyId, 1L, 1L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -1533,6 +1740,26 @@ void fail_actionInvitation_alreadyActions() { () -> partyCommandService.actionInvitation(inviteeId, request, invitationId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_ACTIONS); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_actionInvitation_memberNotFound() { + // given + Long invitationId = 100L; + Party party = PartyFixture.createParty("모임명", 1L, null); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 20L); + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(20L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.actionInvitation(20L, request, invitationId)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -1605,5 +1832,18 @@ void fail_addKeyword_invalidKeyword() { () -> partyCommandService.addKeyword(partyId, ownerId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVALID_KEYWORD); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_addKeyword_partyNotFound() { + // given + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("새키워드")); + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.addKeyword(999L, 10L, request)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 95f5bb67f..098e07411 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -28,7 +28,6 @@ import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; -import umc.cockple.demo.domain.party.enums.PartyOrderType; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; @@ -654,6 +653,24 @@ void fail_getPartyDetails_partyDeleted() { .satisfies(e -> assertThat(((PartyException) e).getCode()) .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_getPartyDetails_memberNotFound() { + // given + Long partyId = 1L; + Party party = PartyFixture.createParty("상세 모임", 11L, null); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(partyId, 1L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -791,6 +808,20 @@ void fail_getJoinRequests_invalidStatus() { .isInstanceOf(PartyException.class) .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.INVALID_REQUEST_STATUS)); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_getJoinRequests_partyNotFound() { + // given + Long invalidId = 999L; + given(partyRepository.findById(invalidId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getJoinRequests(invalidId, 1L, "PENDING", PageRequest.of(0, 10))) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } } @Nested From 436862bb5cee9a257dbb845660eb490aa4fac280 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 16 Mar 2026 11:15:44 +0900 Subject: [PATCH 32/63] =?UTF-8?q?test:=20=EB=82=B4=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C(/api/my/parties)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 54 +++++++++++-- .../party/service/PartyQueryServiceTest.java | 76 +++++++++++++++++-- 2 files changed, 116 insertions(+), 14 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 87f8f8245..f759b64fb 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -31,13 +31,20 @@ class PartyIntegrationTest extends IntegrationTestBase { - @Autowired MockMvc mockMvc; - @Autowired MemberRepository memberRepository; - @Autowired PartyRepository partyRepository; - @Autowired MemberPartyRepository memberPartyRepository; - @Autowired PartyAddrRepository partyAddrRepository; - @Autowired ExerciseRepository exerciseRepository; - @Autowired MemberExerciseRepository memberExerciseRepository; + @Autowired + MockMvc mockMvc; + @Autowired + MemberRepository memberRepository; + @Autowired + PartyRepository partyRepository; + @Autowired + MemberPartyRepository memberPartyRepository; + @Autowired + PartyAddrRepository partyAddrRepository; + @Autowired + ExerciseRepository exerciseRepository; + @Autowired + MemberExerciseRepository memberExerciseRepository; private Member manager; private Member normalMember; @@ -127,4 +134,37 @@ void fail_partyInactive() throws Exception { .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_IS_DELETED.getMessage())); } } + + @Nested + @DisplayName("GET /api/my/parties - 내 모임 조회") + class GetMyParties { + + @Test + @DisplayName("200 - 사용자가 가입한 모임 목록을 페이징하여 반환한다") + void success_getMyParties() throws Exception { + mockMvc.perform(get("/api/my/parties") + .param("created", "false") + .param("sort", "최신순") + .param("size", "10") + .param("page", "0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("성공입니다.")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content.length()").value(1)) + .andExpect(jsonPath("$.data.content[0].partyName").value("테스트 모임")) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) + .andExpect(jsonPath("$.data.pageable.pageNumber").value(0)) + .andExpect(jsonPath("$.data.hasNext").value(false)); + } + + @Test + @DisplayName("401 - 인증되지 않은 사용자 접근시 에러를 반환한다") + void fail_unauthorized() throws Exception { + SecurityContextHelper.clearAuthentication(); + + mockMvc.perform(get("/api/my/parties")) + .andExpect(status().isUnauthorized()); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 5f6a9005e..59d02c7a7 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -29,6 +29,15 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; +import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; +import umc.cockple.demo.domain.party.dto.PartyDTO; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -51,6 +60,10 @@ class PartyQueryServiceTest { private MemberPartyRepository memberPartyRepository; @Mock private MemberExerciseRepository memberExerciseRepository; + @Mock + private ExerciseRepository exerciseRepository; + @Mock + private PartyBookmarkRepository partyBookmarkRepository; @Nested @DisplayName("getPartyMembers") @@ -76,7 +89,7 @@ void success() { List memberParties = List.of(mp1, mp2); LocalDate lastDate = LocalDate.of(2025, 1, 10); - List rawResult = List.of(new Object[]{20L, lastDate}); + List rawResult = List.of(new Object[] { 20L, lastDate }); PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() .summary(PartyMemberDTO.Summary.builder() @@ -101,8 +114,7 @@ void success() { verify(partyConverter).toPartyMemberDTO( eq(memberParties), eq(currentMemberId), - eq(Map.of(20L, lastDate)) - ); + eq(Map.of(20L, lastDate))); } @Test @@ -133,8 +145,7 @@ void noExerciseHistory() { verify(partyConverter).toPartyMemberDTO( eq(memberParties), eq(currentMemberId), - eq(Map.of()) - ); + eq(Map.of())); } @Test @@ -146,7 +157,8 @@ void partyNotFound() { // when & then assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); } @Test @@ -163,8 +175,58 @@ void partyInactive() { // when & then assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); } } + @Nested + @DisplayName("getMyParties") + class GetMyParties { + + @Test + @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + void success() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); + + PartyDTO.Response expectedResponse = PartyDTO.Response.builder() + .partyId(1L) + .partyName("테스트 모임") + .totalExerciseCount(5) + .nextExerciseInfo("05.01 오전 운동") + .isBookmarked(true) + .build(); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of(1L)); + given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) + .willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, "최신순", + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + assertThat(result.getContent().get(0).isBookmarked()).isTrue(); + + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); + } + } } From 28d98a36c75f0c7d78c6e5bd1d29242e2447bb72 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 16 Mar 2026 13:04:57 +0900 Subject: [PATCH 33/63] =?UTF-8?q?test:=20=EB=82=B4=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EA=B0=84=EB=9E=B5=ED=99=94=20=EC=A1=B0=ED=9A=8C(/api/my/partie?= =?UTF-8?q?s/simple)=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 67 ++++++++++++++++-- .../party/service/PartyQueryServiceTest.java | 70 ++++++++++++++++++- 2 files changed, 129 insertions(+), 8 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index f759b64fb..7f3fe8216 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -149,22 +149,75 @@ void success_getMyParties() throws Exception { .param("page", "0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) - .andExpect(jsonPath("$.message").value("성공입니다.")) + .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) .andExpect(jsonPath("$.data.content").isArray()) .andExpect(jsonPath("$.data.content.length()").value(1)) .andExpect(jsonPath("$.data.content[0].partyName").value("테스트 모임")) .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) .andExpect(jsonPath("$.data.pageable.pageNumber").value(0)) - .andExpect(jsonPath("$.data.hasNext").value(false)); + .andExpect(jsonPath("$.data.last").value(true)); } @Test - @DisplayName("401 - 인증되지 않은 사용자 접근시 에러를 반환한다") - void fail_unauthorized() throws Exception { - SecurityContextHelper.clearAuthentication(); + @DisplayName("200 - 가입한 모임이 없을 경우 빈 목록을 반환한다") + void success_emptyMyParties() throws Exception { + Member newMember = memberRepository.save(umc.cockple.demo.support.fixture.MemberFixture.createMember("뉴비", + umc.cockple.demo.global.enums.Gender.MALE, umc.cockple.demo.global.enums.Level.BEGINNER, 3003L)); + SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); - mockMvc.perform(get("/api/my/parties")) - .andExpect(status().isUnauthorized()); + mockMvc.perform(get("/api/my/parties") + .param("created", "false") + .param("sort", "최신순") + .param("size", "10") + .param("page", "0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content").isEmpty()) + .andExpect(jsonPath("$.data.empty").value(true)); + } + } + + @Nested + @DisplayName("GET /api/my/parties/simple - 내 모임 간략화 조회") + class GetSimpleMyParties { + + @Test + @DisplayName("200 - 사용자가 가입한 모임의 간략화된 목록을 페이징하여 반환한다") + void success_getSimpleMyParties() throws Exception { + mockMvc.perform(get("/api/my/parties/simple") + .param("page", "0") + .param("size", "10") + .param("sort", "createdAt,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content.length()").value(1)) + .andExpect(jsonPath("$.data.content[0].partyName").value("테스트 모임")) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) + .andExpect(jsonPath("$.data.pageable.pageNumber").value(0)) + .andExpect(jsonPath("$.data.last").value(true)); + } + + @Test + @DisplayName("200 - 가입한 모임이 없을 경우 빈 목록을 반환한다") + void success_emptySimpleMyParties() throws Exception { + Member newMember = memberRepository.save(umc.cockple.demo.support.fixture.MemberFixture.createMember("뉴비", + umc.cockple.demo.global.enums.Gender.MALE, umc.cockple.demo.global.enums.Level.BEGINNER, 3003L)); + SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); + + mockMvc.perform(get("/api/my/parties/simple") + .param("page", "0") + .param("size", "10") + .param("sort", "createdAt,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content").isEmpty()) + .andExpect(jsonPath("$.data.empty").value(true)); } } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 59d02c7a7..debc5426e 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -12,10 +12,12 @@ import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; +import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.dto.PartyMemberDTO; +import umc.cockple.demo.domain.party.dto.PartySimpleDTO; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; import umc.cockple.demo.domain.party.repository.PartyRepository; @@ -55,6 +57,8 @@ class PartyQueryServiceTest { @Mock private PartyRepository partyRepository; @Mock + private MemberRepository memberRepository; + @Mock private PartyConverter partyConverter; @Mock private MemberPartyRepository memberPartyRepository; @@ -89,7 +93,7 @@ void success() { List memberParties = List.of(mp1, mp2); LocalDate lastDate = LocalDate.of(2025, 1, 10); - List rawResult = List.of(new Object[] { 20L, lastDate }); + List rawResult = List.of(new Object[]{20L, lastDate}); PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() .summary(PartyMemberDTO.Summary.builder() @@ -229,4 +233,68 @@ void success() { verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); } } + + @Nested + @DisplayName("getSimpleMyParties") + class GetSimpleMyParties { + + @Test + @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + void success() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 10L); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + + Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); + + PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() + .partyId(10L) + .partyName("테스트 모임") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); + // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 + given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getSimpleMyParties(memberId, + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + + verify(memberRepository).findById(memberId); + verify(memberPartyRepository).findByMember(member, pageable); + verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); + } + + @Test + @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") + void memberNotFound() { + // given + Long invalidMemberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + + given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + } } From ac2b867745e20d76b33cc201fc6e1b8496411826 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 16 Mar 2026 15:35:30 +0900 Subject: [PATCH 34/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20=EC=A1=B0=ED=9A=8C(/api/my/parties/suggestions)=20A?= =?UTF-8?q?PI=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 94 +++++++++++- .../party/service/PartyQueryServiceTest.java | 140 ++++++++++++++++++ 2 files changed, 233 insertions(+), 1 deletion(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 7f3fe8216..950e0b986 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -6,6 +6,8 @@ import umc.cockple.demo.domain.exercise.domain.Exercise; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.domain.MemberAddr; +import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; @@ -25,6 +27,7 @@ import java.time.LocalDate; +import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -45,6 +48,8 @@ class PartyIntegrationTest extends IntegrationTestBase { ExerciseRepository exerciseRepository; @Autowired MemberExerciseRepository memberExerciseRepository; + @Autowired + MemberAddrRepository memberAddrRepository; private Member manager; private Member normalMember; @@ -52,7 +57,19 @@ class PartyIntegrationTest extends IntegrationTestBase { @BeforeEach void setUp() { - manager = memberRepository.save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L)); + manager = memberRepository + .save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, LocalDate.of(1995, 1, 1))); + memberAddrRepository.save(MemberAddr.builder() + .member(manager) + .addr1("서울특별시") + .addr2("강남구") + .addr3("역삼동") + .streetAddr("테헤란로") + .latitude(37.5) + .longitude(127.0) + .isMain(true) + .build()); + normalMember = memberRepository.save(MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L)); PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); @@ -61,6 +78,11 @@ void setUp() { memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + // 추천 조회용 모임 (manager가 가입하지 않은 모임) + Party suggestedParty = PartyFixture.createParty("추천 모임", normalMember.getId(), addr); + suggestedParty.addLevel(Gender.MALE, Level.A); // manager의 조건에 맞춤 + partyRepository.save(suggestedParty); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); } @@ -71,6 +93,7 @@ void tearDown() { memberPartyRepository.deleteAll(); partyRepository.deleteAll(); partyAddrRepository.deleteAll(); + memberAddrRepository.deleteAll(); memberRepository.deleteAll(); } @@ -220,4 +243,73 @@ void success_emptySimpleMyParties() throws Exception { .andExpect(jsonPath("$.data.empty").value(true)); } } + + @Nested + @DisplayName("GET /api/my/parties/suggestions - 모임 추천 조회") + class GetRecommendedParties { + + @Test + @DisplayName("200 - Cockple 추천 모드 시 추천된 모임 목록을 반환한다") + void success_cockpleRecommend() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "true") + .param("sort", "최신순") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].partyName").value("추천 모임")); + } + + @Test + @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 반환한다") + void success_filterMode() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "false") + .param("addr1", "서울특별시") + .param("addr2", "강남구") + .param("sort", "최신순") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].addr1").value("서울특별시")) + .andExpect(jsonPath("$.data.content[0].addr2").value("강남구")); + } + + @Test + @DisplayName("200 - 검색 모드 시 모임명으로 검색된 결과를 반환한다") + void success_searchMode() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("search", "추천") + .param("isCockpleRecommend", "false") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].partyName", containsString("추천"))); + } + + @Test + @DisplayName("400 - 유효하지 않은 정렬 기준 입력 시 INVALID_ORDER_TYPE 에러를 반환한다") + void fail_invalidOrderType() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "false") + .param("sort", "잘못된순")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("PARTY106")) + .andExpect(jsonPath("$.message").value("유효하지 않은 정렬 기준입니다. (최신순, 오래된 순, 운동 많은 순 중 하나여야 합니다.)")); + } + + @Test + @DisplayName("400 - isCockpleRecommend에 부적절한 타입 입력 시 400 에러를 반환한다") + void fail_invalidBooleanType() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "not-boolean")) + .andExpect(status().isBadRequest()); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index debc5426e..a8db95234 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -40,6 +40,16 @@ import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.party.dto.PartyDTO; +import umc.cockple.demo.domain.party.dto.PartyFilterDTO; +import umc.cockple.demo.domain.member.repository.MemberAddrRepository; +import umc.cockple.demo.domain.file.service.FileService; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; +import umc.cockple.demo.domain.member.domain.MemberAddr; + +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anyInt; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -68,6 +78,12 @@ class PartyQueryServiceTest { private ExerciseRepository exerciseRepository; @Mock private PartyBookmarkRepository partyBookmarkRepository; + @Mock + private MemberAddrRepository memberAddrRepository; + @Mock + private FileService fileService; + @Mock + private PartyJoinRequestRepository partyJoinRequestRepository; @Nested @DisplayName("getPartyMembers") @@ -297,4 +313,128 @@ void memberNotFound() { .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); } } + + @Nested + @DisplayName("getRecommendedParties") + class GetRecommendedParties { + + @Test + @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") + void success_cockpleRecommend() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, + LocalDate.of(1995, 1, 1)); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberAddr addr = MemberAddr.builder() + .member(member) + .addr1("서울특별시") + .isMain(true) + .build(); + + Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(suggestedParty, "id", 100L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); + given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) + .willReturn(List.of(suggestedParty)); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, true, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); + verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), + eq(Level.A), eq(memberId)); + } + + @Test + @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") + void success_filterMode() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() + .addr1("서울특별시") + .addr2("강남구") + .build(); + + Party filteredParty = PartyFixture.createParty("필터 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(filteredParty, "id", 200L); + Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); + verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); + } + + @Test + @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") + void fail_memberNotFound() { + // given + Long memberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") + void fail_mainAddressNotFound() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + } + } } From aae6d8a937fbd84d7a13724f4af001435908b6e2 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 16 Mar 2026 16:42:45 +0900 Subject: [PATCH 35/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A1=B0=ED=9A=8C(/api/parties/{partyId})=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 61 +- .../party/service/PartyQueryServiceTest.java | 855 ++++++++++-------- 2 files changed, 532 insertions(+), 384 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 950e0b986..91512100b 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -141,8 +141,7 @@ void success_noExerciseHistory() throws Exception { void fail_partyNotFound() throws Exception { mockMvc.perform(get("/api/parties/{partyId}/members", 999L)) .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())) - .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_NOT_FOUND.getMessage())); + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); } @Test @@ -153,8 +152,7 @@ void fail_partyInactive() throws Exception { mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())) - .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_IS_DELETED.getMessage())); + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); } } @@ -300,8 +298,7 @@ void fail_invalidOrderType() throws Exception { .param("isCockpleRecommend", "false") .param("sort", "잘못된순")) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code").value("PARTY106")) - .andExpect(jsonPath("$.message").value("유효하지 않은 정렬 기준입니다. (최신순, 오래된 순, 운동 많은 순 중 하나여야 합니다.)")); + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_ORDER_TYPE.getCode())); } @Test @@ -312,4 +309,56 @@ void fail_invalidBooleanType() throws Exception { .andExpect(status().isBadRequest()); } } + + @Nested + @DisplayName("GET /api/parties/{partyId} - 모임 상세 조회") + class GetPartyDetails { + + @Test + @DisplayName("200 - 모임 상세 정보를 정상적으로 조회한다 (비회원 상태)") + void success_getDetails_nonMember() throws Exception { + // 모임에 가입하지 않은 새로운 유저 생성 및 인증 설정 + Member nonMember = memberRepository.save(MemberFixture.createMember("비회원", Gender.MALE, Level.C, 2001L)); + SecurityContextHelper.setAuthentication(nonMember.getId(), nonMember.getNickname()); + + mockMvc.perform(get("/api/parties/{partyId}", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.partyId").value(party.getId())) + .andExpect(jsonPath("$.data.memberStatus").value("NOT_MEMBER")) + .andExpect(jsonPath("$.data.hasPendingJoinRequest").value(false)); + } + + @Test + @DisplayName("200 - 모임원인 경우 memberStatus가 MEMBER로 반환된다") + void success_getDetails_member() throws Exception { + // manager는 setUp에서 이미 party의 멤버로 설정됨 + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(get("/api/parties/{partyId}", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.memberStatus").value("MEMBER")) + .andExpect(jsonPath("$.data.memberRole").value("party_MANAGER")); + } + + @Test + @DisplayName("404 - 존재하지 않는 모임 조회 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_partyNotFound() throws Exception { + mockMvc.perform(get("/api/parties/{partyId}", 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + + @Test + @DisplayName("400 - 삭제된 모임 조회 시 PARTY_IS_DELETED 에러를 반환한다") + void fail_partyDeleted() throws Exception { + // 모임 삭제 (비활성화) + party.delete(); + partyRepository.save(party); + + mockMvc.perform(get("/api/parties/{partyId}", party.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index a8db95234..ada3a71bb 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -16,8 +16,8 @@ import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; -import umc.cockple.demo.domain.party.dto.PartyMemberDTO; -import umc.cockple.demo.domain.party.dto.PartySimpleDTO; +import umc.cockple.demo.domain.party.dto.*; +import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; import umc.cockple.demo.domain.party.repository.PartyRepository; @@ -39,402 +39,501 @@ import org.springframework.data.domain.SliceImpl; import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; -import umc.cockple.demo.domain.party.dto.PartyDTO; -import umc.cockple.demo.domain.party.dto.PartyFilterDTO; import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.file.service.FileService; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.member.domain.MemberAddr; -import static org.mockito.ArgumentMatchers.contains; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.anyInt; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class PartyQueryServiceTest { - @InjectMocks - private PartyQueryServiceImpl partyQueryService; - - @Mock - private PartyRepository partyRepository; - @Mock - private MemberRepository memberRepository; - @Mock - private PartyConverter partyConverter; - @Mock - private MemberPartyRepository memberPartyRepository; - @Mock - private MemberExerciseRepository memberExerciseRepository; - @Mock - private ExerciseRepository exerciseRepository; - @Mock - private PartyBookmarkRepository partyBookmarkRepository; - @Mock - private MemberAddrRepository memberAddrRepository; - @Mock - private FileService fileService; - @Mock - private PartyJoinRequestRepository partyJoinRequestRepository; - - @Nested - @DisplayName("getPartyMembers") - class GetPartyMembers { - - @Test - @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") - void success() { - // given - Long partyId = 1L; - Long currentMemberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); - ReflectionTestUtils.setField(manager, "id", 10L); - ReflectionTestUtils.setField(member1, "id", 20L); - - MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); - List memberParties = List.of(mp1, mp2); - - LocalDate lastDate = LocalDate.of(2025, 1, 10); - List rawResult = List.of(new Object[]{20L, lastDate}); - - PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() - .summary(PartyMemberDTO.Summary.builder() - .totalCount(2).maleCount(1).femaleCount(1).build()) - .members(List.of()) - .build(); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); - given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId)).willReturn(rawResult); - given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) - .willReturn(expected); - - // when - PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); - - // then - assertThat(result).isEqualTo(expected); - verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId); - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of(20L, lastDate))); - } - - @Test - @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") - void noExerciseHistory() { - // given - Long partyId = 1L; - Long currentMemberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(manager, "id", 10L); - MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - List memberParties = List.of(mp); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); - given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L), partyId)).willReturn(List.of()); - given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); - - // when - partyQueryService.getPartyMembers(partyId, currentMemberId); - - // then - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of())); + @InjectMocks + private PartyQueryServiceImpl partyQueryService; + + @Mock + private PartyRepository partyRepository; + @Mock + private MemberRepository memberRepository; + @Mock + private PartyConverter partyConverter; + @Mock + private MemberPartyRepository memberPartyRepository; + @Mock + private MemberExerciseRepository memberExerciseRepository; + @Mock + private ExerciseRepository exerciseRepository; + @Mock + private PartyBookmarkRepository partyBookmarkRepository; + @Mock + private MemberAddrRepository memberAddrRepository; + @Mock + private FileService fileService; + @Mock + private PartyJoinRequestRepository partyJoinRequestRepository; + + @Nested + @DisplayName("getPartyMembers") + class GetPartyMembers { + + @Test + @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") + void success() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); + ReflectionTestUtils.setField(manager, "id", 10L); + ReflectionTestUtils.setField(member1, "id", 20L); + + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); + List memberParties = List.of(mp1, mp2); + + LocalDate lastDate = LocalDate.of(2025, 1, 10); + List rawResult = List.of(new Object[] { 20L, lastDate }); + + PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() + .summary(PartyMemberDTO.Summary.builder() + .totalCount(2).maleCount(1).femaleCount(1).build()) + .members(List.of()) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId)).willReturn(rawResult); + given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) + .willReturn(expected); + + // when + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + assertThat(result).isEqualTo(expected); + verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId); + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of(20L, lastDate))); + } + + @Test + @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") + void noExerciseHistory() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(manager, "id", 10L); + MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + List memberParties = List.of(mp); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L), partyId)).willReturn(List.of()); + given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); + + // when + partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of())); + } + + @Test + @DisplayName("존재하지 않는 파티면 PartyException을 던진다") + void partyNotFound() { + // given + given(partyRepository.findById(99L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("비활성화된 파티면 PartyException을 던진다") + void partyInactive() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(inactiveParty, "id", 1L); + inactiveParty.delete(); + + given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } } - @Test - @DisplayName("존재하지 않는 파티면 PartyException을 던진다") - void partyNotFound() { - // given - given(partyRepository.findById(99L)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); - } - - @Test - @DisplayName("비활성화된 파티면 PartyException을 던진다") - void partyInactive() { - // given - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(inactiveParty, "id", 1L); - inactiveParty.delete(); - - given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); - } - } - - @Nested - @DisplayName("getMyParties") - class GetMyParties { - - @Test - @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") - void success() { - // given - Long memberId = 10L; - Pageable pageable = PageRequest.of(0, 10); - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 1L); - - Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); - - PartyDTO.Response expectedResponse = PartyDTO.Response.builder() - .partyId(1L) - .partyName("테스트 모임") - .totalExerciseCount(5) - .nextExerciseInfo("05.01 오전 운동") - .isBookmarked(true) - .build(); - - given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) - .willReturn(partySlice); - given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) - .willReturn(List.of()); - given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) - .willReturn(List.of()); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) - .willReturn(Set.of(1L)); - given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) - .willReturn(expectedResponse); - - // when - Slice result = partyQueryService.getMyParties(memberId, false, "최신순", - pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - assertThat(result.getContent().get(0).isBookmarked()).isTrue(); - - verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); - verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); - } - } - - @Nested - @DisplayName("getSimpleMyParties") - class GetSimpleMyParties { - - @Test - @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") - void success() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - - Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(member, "id", memberId); - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 10L); - - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); - - Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); - - PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() - .partyId(10L) - .partyName("테스트 모임") - .build(); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); - // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 - given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); - - // when - Slice result = partyQueryService.getSimpleMyParties(memberId, - pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - - verify(memberRepository).findById(memberId); - verify(memberPartyRepository).findByMember(member, pageable); - verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); - } - - @Test - @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") - void memberNotFound() { - // given - Long invalidMemberId = 999L; - Pageable pageable = PageRequest.of(0, 10); - - given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); - } - } - - @Nested - @DisplayName("getRecommendedParties") - class GetRecommendedParties { - - @Test - @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") - void success_cockpleRecommend() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, - LocalDate.of(1995, 1, 1)); - ReflectionTestUtils.setField(member, "id", memberId); - - MemberAddr addr = MemberAddr.builder() - .member(member) - .addr1("서울특별시") - .isMain(true) - .build(); - - Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(suggestedParty, "id", 100L); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); - given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) - .willReturn(List.of(suggestedParty)); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") - .build()); - - // when - Slice result = partyQueryService.getRecommendedParties(memberId, true, - filter, "최신순", pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); - verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), - eq(Level.A), eq(memberId)); + @Nested + @DisplayName("getMyParties") + class GetMyParties { + + @Test + @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + void success() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); + + PartyDTO.Response expectedResponse = PartyDTO.Response.builder() + .partyId(1L) + .partyName("테스트 모임") + .totalExerciseCount(5) + .nextExerciseInfo("05.01 오전 운동") + .isBookmarked(true) + .build(); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of(1L)); + given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) + .willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, "최신순", + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + assertThat(result.getContent().get(0).isBookmarked()).isTrue(); + + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); + } } - @Test - @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") - void success_filterMode() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() - .addr1("서울특별시") - .addr2("강남구") - .build(); - - Party filteredParty = PartyFixture.createParty("필터 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(filteredParty, "id", 200L); - Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); - - given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) - .willReturn(partySlice); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") - .build()); - - // when - Slice result = partyQueryService.getRecommendedParties(memberId, false, - filter, "최신순", pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); - verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); + @Nested + @DisplayName("getSimpleMyParties") + class GetSimpleMyParties { + + @Test + @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + void success() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 10L); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + + Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); + + PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() + .partyId(10L) + .partyName("테스트 모임") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); + // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 + given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getSimpleMyParties(memberId, + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + + verify(memberRepository).findById(memberId); + verify(memberPartyRepository).findByMember(member, pageable); + verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); + } + + @Test + @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") + void memberNotFound() { + // given + Long invalidMemberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + + given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } - @Test - @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") - void fail_memberNotFound() { - // given - Long memberId = 999L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - given(memberRepository.findById(memberId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", - pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + @Nested + @DisplayName("getRecommendedParties") + class GetRecommendedParties { + + @Test + @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") + void success_cockpleRecommend() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, + LocalDate.of(1995, 1, 1)); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberAddr addr = MemberAddr.builder() + .member(member) + .addr1("서울특별시") + .isMain(true) + .build(); + + Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(suggestedParty, "id", 100L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); + given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) + .willReturn(List.of(suggestedParty)); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, true, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); + verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), + eq(Level.A), eq(memberId)); + } + + @Test + @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") + void success_filterMode() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() + .addr1("서울특별시") + .addr2("강남구") + .build(); + + Party filteredParty = PartyFixture.createParty("필터 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(filteredParty, "id", 200L); + Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); + verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); + } + + @Test + @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") + void fail_memberNotFound() { + // given + Long memberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") + void fail_mainAddressNotFound() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + } } - @Test - @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") - void fail_mainAddressNotFound() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(member, "id", memberId); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", - pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + @Nested + @DisplayName("getPartyDetails") + class GetPartyDetails { + + @Test + @DisplayName("모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") + void success_nonMember() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("상세 모임", 11L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() + .partyId(partyId) + .partyName("상세 모임") + .memberStatus("NOT_MEMBER") + .hasPendingJoinRequest(false) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.empty()); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, + RequestStatus.PENDING)).willReturn(false); + given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), eq(false))) + .willReturn(expected); + + // when + PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); + + // then + assertThat(result).isEqualTo(expected); + verify(partyRepository).findById(partyId); + } + + @Test + @DisplayName("모임원인 경우 memberStatus가 MEMBER로 반환된다") + void success_member() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("상세 모임", 11L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() + .partyId(partyId) + .memberStatus("MEMBER") + .memberRole("party_MEMBER") + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)) + .willReturn(Optional.of(memberParty)); + given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), anyBoolean())) + .willReturn(expected); + + // when + PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); + + // then + assertThat(result.memberStatus()).isEqualTo("MEMBER"); + } + + @Test + @DisplayName("존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") + void fail_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(999L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") + void fail_partyDeleted() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("삭제된 모임", 11L, addr); + party.delete(); + given(partyRepository.findById(1L)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn( + Optional.of(MemberFixture.createMember("테스터", Gender.MALE, Level.A, 1L))); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } } - } } From cb8463668709068d9e987654c02289900a076914 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 17 Mar 2026 01:21:40 +0900 Subject: [PATCH 36/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=A1=B0=ED=9A=8C(/api/parties/{partyId}/members)?= =?UTF-8?q?=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 21 + .../party/service/PartyQueryServiceTest.java | 985 +++++++++--------- 2 files changed, 532 insertions(+), 474 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 91512100b..95e12f4e4 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -101,6 +101,27 @@ void tearDown() { @DisplayName("GET /api/parties/{partyId}/members - 모임 멤버 조회") class GetPartyMembers { + @Test + @DisplayName("200 - 모임의 멤버들을 역할별로 성공적으로 조회한다.") + void success() throws Exception { + // 부모임장 추가 + Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 1003L)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + + // 모임장이 가입된 상태 + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.summary.totalCount").value(3)) + .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) + .andExpect(jsonPath("$.data.members[0].isMe").value(true)) + .andExpect(jsonPath("$.data.members[1].role").value("party_SUBMANAGER")) + .andExpect(jsonPath("$.data.members[1].isMe").value(false)) + .andExpect(jsonPath("$.data.members[2].role").value("party_MEMBER")) + .andExpect(jsonPath("$.data.members[2].isMe").value(false)); + } + @Test @DisplayName("200 - 멤버 목록과 마지막 운동일을 정상 반환한다") void success_withLastExerciseDate() throws Exception { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index ada3a71bb..8986e8110 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -53,487 +53,524 @@ @ExtendWith(MockitoExtension.class) class PartyQueryServiceTest { - @InjectMocks - private PartyQueryServiceImpl partyQueryService; - - @Mock - private PartyRepository partyRepository; - @Mock - private MemberRepository memberRepository; - @Mock - private PartyConverter partyConverter; - @Mock - private MemberPartyRepository memberPartyRepository; - @Mock - private MemberExerciseRepository memberExerciseRepository; - @Mock - private ExerciseRepository exerciseRepository; - @Mock - private PartyBookmarkRepository partyBookmarkRepository; - @Mock - private MemberAddrRepository memberAddrRepository; - @Mock - private FileService fileService; - @Mock - private PartyJoinRequestRepository partyJoinRequestRepository; - - @Nested - @DisplayName("getPartyMembers") - class GetPartyMembers { - - @Test - @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") - void success() { - // given - Long partyId = 1L; - Long currentMemberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); - ReflectionTestUtils.setField(manager, "id", 10L); - ReflectionTestUtils.setField(member1, "id", 20L); - - MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); - List memberParties = List.of(mp1, mp2); - - LocalDate lastDate = LocalDate.of(2025, 1, 10); - List rawResult = List.of(new Object[] { 20L, lastDate }); - - PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() - .summary(PartyMemberDTO.Summary.builder() - .totalCount(2).maleCount(1).femaleCount(1).build()) - .members(List.of()) - .build(); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); - given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId)).willReturn(rawResult); - given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) - .willReturn(expected); - - // when - PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); - - // then - assertThat(result).isEqualTo(expected); - verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId); - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of(20L, lastDate))); - } - - @Test - @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") - void noExerciseHistory() { - // given - Long partyId = 1L; - Long currentMemberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(manager, "id", 10L); - MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - List memberParties = List.of(mp); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); - given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L), partyId)).willReturn(List.of()); - given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); - - // when - partyQueryService.getPartyMembers(partyId, currentMemberId); - - // then - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of())); - } - - @Test - @DisplayName("존재하지 않는 파티면 PartyException을 던진다") - void partyNotFound() { - // given - given(partyRepository.findById(99L)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); - } - - @Test - @DisplayName("비활성화된 파티면 PartyException을 던진다") - void partyInactive() { - // given - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(inactiveParty, "id", 1L); - inactiveParty.delete(); - - given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); - } + @InjectMocks + private PartyQueryServiceImpl partyQueryService; + + @Mock + private PartyRepository partyRepository; + @Mock + private MemberRepository memberRepository; + @Mock + private PartyConverter partyConverter; + @Mock + private MemberPartyRepository memberPartyRepository; + @Mock + private MemberExerciseRepository memberExerciseRepository; + @Mock + private ExerciseRepository exerciseRepository; + @Mock + private PartyBookmarkRepository partyBookmarkRepository; + @Mock + private MemberAddrRepository memberAddrRepository; + @Mock + private FileService fileService; + @Mock + private PartyJoinRequestRepository partyJoinRequestRepository; + + @Nested + @DisplayName("getPartyMembers") + class GetPartyMembers { + + @Test + @DisplayName("모임의 멤버들을 역할별로 성공적으로 조회한다.") + void success() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member manager = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1001L); + Member subManager = MemberFixture.createMember("부모임장", Gender.FEMALE, Level.B, 1002L); + Member normalMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.C, 1003L); + + ReflectionTestUtils.setField(manager, "id", 10L); + ReflectionTestUtils.setField(subManager, "id", 20L); + ReflectionTestUtils.setField(normalMember, "id", 30L); + + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + MemberParty mp3 = MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER); + List memberParties = List.of(mp1, mp2, mp3); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId(anyList(), + eq(partyId))) + .willReturn(List.of()); + + // when + partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + verify(partyConverter).toPartyMemberDTO(eq(memberParties), eq(currentMemberId), anyMap()); } - @Nested - @DisplayName("getMyParties") - class GetMyParties { - - @Test - @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") - void success() { - // given - Long memberId = 10L; - Pageable pageable = PageRequest.of(0, 10); - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 1L); - - Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); - - PartyDTO.Response expectedResponse = PartyDTO.Response.builder() - .partyId(1L) - .partyName("테스트 모임") - .totalExerciseCount(5) - .nextExerciseInfo("05.01 오전 운동") - .isBookmarked(true) - .build(); - - given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) - .willReturn(partySlice); - given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) - .willReturn(List.of()); - given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) - .willReturn(List.of()); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) - .willReturn(Set.of(1L)); - given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) - .willReturn(expectedResponse); - - // when - Slice result = partyQueryService.getMyParties(memberId, false, "최신순", - pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - assertThat(result.getContent().get(0).isBookmarked()).isTrue(); - - verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); - verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); - } + @Test + @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") + void success_withExerciseHistory() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); + ReflectionTestUtils.setField(manager, "id", 10L); + ReflectionTestUtils.setField(member1, "id", 20L); + + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); + List memberParties = List.of(mp1, mp2); + + LocalDate lastDate = LocalDate.of(2025, 1, 10); + List rawResult = List.of(new Object[]{20L, lastDate}); + + PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() + .summary(PartyMemberDTO.Summary.builder() + .totalCount(2).maleCount(1).femaleCount(1).build()) + .members(List.of()) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId)).willReturn(rawResult); + given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) + .willReturn(expected); + + // when + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + assertThat(result).isEqualTo(expected); + verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId); + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of(20L, lastDate))); } - @Nested - @DisplayName("getSimpleMyParties") - class GetSimpleMyParties { - - @Test - @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") - void success() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - - Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(member, "id", memberId); - - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 10L); - - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); - - Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); - - PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() - .partyId(10L) - .partyName("테스트 모임") - .build(); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); - // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 - given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); - - // when - Slice result = partyQueryService.getSimpleMyParties(memberId, - pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - - verify(memberRepository).findById(memberId); - verify(memberPartyRepository).findByMember(member, pageable); - verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); - } - - @Test - @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") - void memberNotFound() { - // given - Long invalidMemberId = 999L; - Pageable pageable = PageRequest.of(0, 10); - - given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); - } + @Test + @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") + void noExerciseHistory() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(manager, "id", 10L); + MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + List memberParties = List.of(mp); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L), partyId)).willReturn(List.of()); + given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); + + // when + partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of())); } - @Nested - @DisplayName("getRecommendedParties") - class GetRecommendedParties { - - @Test - @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") - void success_cockpleRecommend() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, - LocalDate.of(1995, 1, 1)); - ReflectionTestUtils.setField(member, "id", memberId); - - MemberAddr addr = MemberAddr.builder() - .member(member) - .addr1("서울특별시") - .isMain(true) - .build(); - - Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(suggestedParty, "id", 100L); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); - given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) - .willReturn(List.of(suggestedParty)); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") - .build()); - - // when - Slice result = partyQueryService.getRecommendedParties(memberId, true, - filter, "최신순", pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); - verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), - eq(Level.A), eq(memberId)); - } - - @Test - @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") - void success_filterMode() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() - .addr1("서울특별시") - .addr2("강남구") - .build(); - - Party filteredParty = PartyFixture.createParty("필터 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(filteredParty, "id", 200L); - Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); - - given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) - .willReturn(partySlice); - given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") - .build()); - - // when - Slice result = partyQueryService.getRecommendedParties(memberId, false, - filter, "최신순", pageable); - - // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); - verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); - } - - @Test - @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") - void fail_memberNotFound() { - // given - Long memberId = 999L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - given(memberRepository.findById(memberId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", - pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); - } - - @Test - @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") - void fail_mainAddressNotFound() { - // given - Long memberId = 1L; - Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); - - Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); - ReflectionTestUtils.setField(member, "id", memberId); - - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", - pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) - .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); - } + @Test + @DisplayName("존재하지 않는 파티면 PartyException을 던진다") + void partyNotFound() { + // given + given(partyRepository.findById(99L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); } - @Nested - @DisplayName("getPartyDetails") - class GetPartyDetails { - - @Test - @DisplayName("모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") - void success_nonMember() { - // given - Long partyId = 1L; - Long memberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); - Party party = PartyFixture.createParty("상세 모임", 11L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); - ReflectionTestUtils.setField(member, "id", memberId); - - PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() - .partyId(partyId) - .partyName("상세 모임") - .memberStatus("NOT_MEMBER") - .hasPendingJoinRequest(false) - .build(); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.empty()); - given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); - given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, - RequestStatus.PENDING)).willReturn(false); - given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), eq(false))) - .willReturn(expected); - - // when - PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); - - // then - assertThat(result).isEqualTo(expected); - verify(partyRepository).findById(partyId); - } - - @Test - @DisplayName("모임원인 경우 memberStatus가 MEMBER로 반환된다") - void success_member() { - // given - Long partyId = 1L; - Long memberId = 10L; - - PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); - Party party = PartyFixture.createParty("상세 모임", 11L, addr); - ReflectionTestUtils.setField(party, "id", partyId); - Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); - ReflectionTestUtils.setField(member, "id", memberId); - - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); - PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() - .partyId(partyId) - .memberStatus("MEMBER") - .memberRole("party_MEMBER") - .build(); - - given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); - given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(memberPartyRepository.findByPartyAndMember(party, member)) - .willReturn(Optional.of(memberParty)); - given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), anyBoolean())) - .willReturn(expected); - - // when - PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); - - // then - assertThat(result.memberStatus()).isEqualTo("MEMBER"); - } - - @Test - @DisplayName("존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") - void fail_partyNotFound() { - // given - given(partyRepository.findById(999L)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyDetails(999L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); - } - - @Test - @DisplayName("삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") - void fail_partyDeleted() { - // given - PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); - Party party = PartyFixture.createParty("삭제된 모임", 11L, addr); - party.delete(); - given(partyRepository.findById(1L)).willReturn(Optional.of(party)); - given(memberRepository.findById(1L)).willReturn( - Optional.of(MemberFixture.createMember("테스터", Gender.MALE, Level.A, 1L))); - - // when & then - assertThatThrownBy(() -> partyQueryService.getPartyDetails(1L, 1L)) - .isInstanceOf(PartyException.class) - .satisfies(e -> assertThat(((PartyException) e).getCode()) - .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); - } + @Test + @DisplayName("비활성화된 파티면 PartyException을 던진다") + void partyInactive() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(inactiveParty, "id", 1L); + inactiveParty.delete(); + + given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); } + } + + @Nested + @DisplayName("getMyParties") + class GetMyParties { + + @Test + @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + void success() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); + + PartyDTO.Response expectedResponse = PartyDTO.Response.builder() + .partyId(1L) + .partyName("테스트 모임") + .totalExerciseCount(5) + .nextExerciseInfo("05.01 오전 운동") + .isBookmarked(true) + .build(); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of(1L)); + given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) + .willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, "최신순", + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + assertThat(result.getContent().get(0).isBookmarked()).isTrue(); + + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); + } + } + + @Nested + @DisplayName("getSimpleMyParties") + class GetSimpleMyParties { + + @Test + @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + void success() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", 10L); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + + Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); + + PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() + .partyId(10L) + .partyName("테스트 모임") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); + // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 + given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); + + // when + Slice result = partyQueryService.getSimpleMyParties(memberId, + pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + + verify(memberRepository).findById(memberId); + verify(memberPartyRepository).findByMember(member, pageable); + verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); + } + + @Test + @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") + void memberNotFound() { + // given + Long invalidMemberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + + given(memberRepository.findById(invalidMemberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + } + + @Nested + @DisplayName("getRecommendedParties") + class GetRecommendedParties { + + @Test + @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") + void success_cockpleRecommend() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, + LocalDate.of(1995, 1, 1)); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberAddr addr = MemberAddr.builder() + .member(member) + .addr1("서울특별시") + .isMain(true) + .build(); + + Party suggestedParty = PartyFixture.createParty("추천 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(suggestedParty, "id", 100L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.of(addr)); + given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) + .willReturn(List.of(suggestedParty)); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, true, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("추천 모임"); + verify(partyRepository).findRecommendedParties(eq("서울특별시"), eq(1995), eq(Gender.MALE), + eq(Level.A), eq(memberId)); + } + + @Test + @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") + void success_filterMode() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() + .addr1("서울특별시") + .addr2("강남구") + .build(); + + Party filteredParty = PartyFixture.createParty("필터 모임", 2L, + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(filteredParty, "id", 200L); + Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) + .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") + .build()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, + filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); + verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); + } + + @Test + @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") + void fail_memberNotFound() { + // given + Long memberId = 999L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") + void fail_mainAddressNotFound() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + Member member = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", memberId); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberAddrRepository.findByMemberAndIsMain(member, true)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", + pageable)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat( + ((umc.cockple.demo.domain.member.exception.MemberException) e) + .getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + } + } + + @Nested + @DisplayName("getPartyDetails") + class GetPartyDetails { + + @Test + @DisplayName("모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") + void success_nonMember() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("상세 모임", 11L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); + ReflectionTestUtils.setField(member, "id", memberId); + + PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() + .partyId(partyId) + .partyName("상세 모임") + .memberStatus("NOT_MEMBER") + .hasPendingJoinRequest(false) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.empty()); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, + RequestStatus.PENDING)).willReturn(false); + given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), eq(false))) + .willReturn(expected); + + // when + PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); + + // then + assertThat(result).isEqualTo(expected); + verify(partyRepository).findById(partyId); + } + + @Test + @DisplayName("모임원인 경우 memberStatus가 MEMBER로 반환된다") + void success_member() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("상세 모임", 11L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() + .partyId(partyId) + .memberStatus("MEMBER") + .memberRole("party_MEMBER") + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)) + .willReturn(Optional.of(memberParty)); + given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), anyBoolean())) + .willReturn(expected); + + // when + PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); + + // then + assertThat(result.memberStatus()).isEqualTo("MEMBER"); + } + + @Test + @DisplayName("존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") + void fail_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(999L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") + void fail_partyDeleted() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("삭제된 모임", 11L, addr); + party.delete(); + given(partyRepository.findById(1L)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn( + Optional.of(MemberFixture.createMember("테스터", Gender.MALE, Level.A, 1L))); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } + } } From aa7fa0d415aa9280b3fa505670b0677aa966eaf3 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 13:31:49 +0900 Subject: [PATCH 37/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=ED=83=88?= =?UTF-8?q?=ED=87=B4(/api/parties/{partyId}/members/my)=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 81 +++++++ .../service/PartyCommandServiceTest.java | 200 ++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 95e12f4e4..992a48aa3 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -4,6 +4,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; import umc.cockple.demo.domain.exercise.domain.Exercise; +import umc.cockple.demo.domain.chat.domain.ChatRoom; +import umc.cockple.demo.domain.chat.domain.ChatRoomMember; +import umc.cockple.demo.domain.chat.repository.ChatRoomMemberRepository; +import umc.cockple.demo.domain.chat.repository.ChatRoomRepository; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; @@ -27,7 +31,9 @@ import java.time.LocalDate; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -50,6 +56,10 @@ class PartyIntegrationTest extends IntegrationTestBase { MemberExerciseRepository memberExerciseRepository; @Autowired MemberAddrRepository memberAddrRepository; + @Autowired + ChatRoomRepository chatRoomRepository; + @Autowired + ChatRoomMemberRepository chatRoomMemberRepository; private Member manager; private Member normalMember; @@ -78,6 +88,11 @@ void setUp() { memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + // 채팅방 생성 및 멤버 추가 + ChatRoom chatRoom = chatRoomRepository.save(ChatRoom.createPartyChatRoom(party)); + chatRoomMemberRepository.save(ChatRoomMember.create(chatRoom, manager)); + chatRoomMemberRepository.save(ChatRoomMember.create(chatRoom, normalMember)); + // 추천 조회용 모임 (manager가 가입하지 않은 모임) Party suggestedParty = PartyFixture.createParty("추천 모임", normalMember.getId(), addr); suggestedParty.addLevel(Gender.MALE, Level.A); // manager의 조건에 맞춤 @@ -90,6 +105,8 @@ void setUp() { void tearDown() { memberExerciseRepository.deleteAll(); exerciseRepository.deleteAll(); + chatRoomMemberRepository.deleteAll(); + chatRoomRepository.deleteAll(); memberPartyRepository.deleteAll(); partyRepository.deleteAll(); partyAddrRepository.deleteAll(); @@ -177,6 +194,70 @@ void fail_partyInactive() throws Exception { } } + @Nested + @DisplayName("DELETE /api/parties/{partyId}/members/my - 모임 탈퇴") + class LeaveParty { + + @Test + @DisplayName("200 - 일반 멤버가 모임을 성공적으로 탈퇴한다") + void success_leaveParty() throws Exception { + // DB에서 최신 정보 보장 + Member member = memberRepository.findById(normalMember.getId()).orElseThrow(); + Party targetParty = partyRepository.findById(party.getId()).orElseThrow(); + + // normalMember 세션으로 설정 + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/members/my", targetParty.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // DB에서 제거되었는지 확인 + boolean exists = memberPartyRepository.existsByPartyAndMember(targetParty, member); + assertThat(exists).isFalse(); + } + + @Test + @DisplayName("403 - 모임장은 탈퇴할 수 없다") + void fail_leaveParty_owner() throws Exception { + // manager(모임장) 세션으로 설정 + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/members/my", party.getId())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_ACTION_FOR_OWNER.getCode())); + } + + @Test + @DisplayName("403 - 부모임장은 탈퇴할 수 없다") + void fail_leaveParty_subOwner() throws Exception { + // 부모임장 생성 및 가입 + Member subManager = memberRepository + .saveAndFlush(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 2001L)); + memberPartyRepository + .saveAndFlush(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + + // 부모임장 세션으로 설정 + SecurityContextHelper.setAuthentication(subManager.getId(), subManager.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/members/my", party.getId())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_ACTION_FOR_SUBOWNER.getCode())); + } + + @Test + @DisplayName("400 - 해당 모임의 멤버가 아니면 탈퇴할 수 없다") + void fail_leaveParty_notMember() throws Exception { + // 가입하지 않은 새로운 멤버 생성 + Member nonMember = memberRepository.save(MemberFixture.createMember("외부인", Gender.MALE, Level.A, 3001L)); + SecurityContextHelper.setAuthentication(nonMember.getId(), nonMember.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/members/my", party.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.NOT_MEMBER.getCode())); + } + } + @Nested @DisplayName("GET /api/my/parties - 내 모임 조회") class GetMyParties { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java new file mode 100644 index 000000000..3998eeda0 --- /dev/null +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -0,0 +1,200 @@ +package umc.cockple.demo.domain.party.service; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.test.util.ReflectionTestUtils; +import umc.cockple.demo.domain.chat.service.ChatRoomService; +import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.repository.MemberPartyRepository; +import umc.cockple.demo.domain.member.repository.MemberRepository; +import umc.cockple.demo.domain.party.domain.Party; +import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; +import umc.cockple.demo.domain.party.exception.PartyErrorCode; +import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyRepository; +import umc.cockple.demo.global.enums.Gender; +import umc.cockple.demo.global.enums.Level; +import umc.cockple.demo.global.enums.Role; +import umc.cockple.demo.support.fixture.MemberFixture; +import umc.cockple.demo.support.fixture.PartyFixture; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class PartyCommandServiceTest { + + @InjectMocks + private PartyCommandServiceImpl partyCommandService; + + @Mock + private PartyRepository partyRepository; + @Mock + private MemberRepository memberRepository; + @Mock + private MemberPartyRepository memberPartyRepository; + @Mock + private ChatRoomService chatRoomService; + @Mock + private ApplicationEventPublisher applicationEventPublisher; + + @Nested + @DisplayName("leaveParty") + class LeaveParty { + + @Test + @DisplayName("성공 - 일반 멤버가 모임을 탈퇴한다") + void success_leaveParty() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", 1L); + Party party = PartyFixture.createParty("탈퇴 테스트 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member member = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 10L); + ReflectionTestUtils.setField(member, "id", memberId); + + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.of(memberParty)); + + // when + partyCommandService.leaveParty(partyId, memberId); + + // then + verify(memberPartyRepository).delete(memberParty); + verify(chatRoomService).leavePartyChatRoom(partyId, memberId); + verify(applicationEventPublisher).publishEvent(any(PartyMemberJoinedEvent.class)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 모임인 경우 PARTY_NOT_FOUND 예외가 발생한다") + void fail_leaveParty_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(999L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies( + e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 삭제된 모임인 경우 PARTY_IS_DELETED 예외가 발생한다") + void fail_leaveParty_partyDeleted() { + // given + Long partyId = 1L; + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("삭제된 모임", 1L, addr); + party.delete(); + + Member member = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(member, "id", 1L); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, 1L)) + .isInstanceOf(PartyException.class) + .satisfies( + e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } + + @Test + @DisplayName("실패 - 모임장이 탈퇴하려 할 경우 INVALID_ACTION_FOR_OWNER 예외가 발생한다") + void fail_leaveParty_isOwner() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("탈퇴 테스트 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, ownerId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.INVALID_ACTION_FOR_OWNER)); + } + + @Test + @DisplayName("실패 - 부모임장이 탈퇴하려 할 경우 INVALID_ACTION_FOR_SUBOWNER 예외가 발생한다") + void fail_leaveParty_isSubOwner() { + // given + Long partyId = 1L; + Long subManagerId = 2L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", 1L); + Party party = PartyFixture.createParty("탈퇴 테스트 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member subManager = MemberFixture.createMember("부모임장", Gender.MALE, Level.A, 2L); + ReflectionTestUtils.setField(subManager, "id", subManagerId); + + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)) + .willReturn(Optional.of(subManagerParty)); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, subManagerId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.INVALID_ACTION_FOR_SUBOWNER)); + } + + @Test + @DisplayName("실패 - 모임 멤버가 아닌 경우 NOT_MEMBER 예외가 발생한다") + void fail_leaveParty_notMember() { + // given + Long partyId = 1L; + Long memberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("탈퇴 테스트 모임", 1L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member member = MemberFixture.createMember("외부인", Gender.MALE, Level.A, 10L); + ReflectionTestUtils.setField(member, "id", memberId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberPartyRepository.findByPartyAndMember(party, member)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER)); + } + } +} From 06538d71a941e6a1babb8115afd5c2d4c1f865ca Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 13:35:53 +0900 Subject: [PATCH 38/63] =?UTF-8?q?test:=20@DisplayName=20=EC=BB=A8=EB=B2=A4?= =?UTF-8?q?=EC=85=98=20=EB=A7=9E=EC=B6=94=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../party/service/PartyQueryServiceTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 8986e8110..18d18f1a3 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -82,7 +82,7 @@ class PartyQueryServiceTest { class GetPartyMembers { @Test - @DisplayName("모임의 멤버들을 역할별로 성공적으로 조회한다.") + @DisplayName("성공 - 모임의 멤버들을 역할별로 성공적으로 조회한다.") void success() { // given Long partyId = 1L; @@ -119,7 +119,7 @@ void success() { } @Test - @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") + @DisplayName("성공 - 멤버 목록과 마지막 운동일을 함께 반환한다") void success_withExerciseHistory() { // given Long partyId = 1L; @@ -167,7 +167,7 @@ void success_withExerciseHistory() { } @Test - @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") + @DisplayName("성공 - 운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") void noExerciseHistory() { // given Long partyId = 1L; @@ -198,7 +198,7 @@ void noExerciseHistory() { } @Test - @DisplayName("존재하지 않는 파티면 PartyException을 던진다") + @DisplayName("실패 - 존재하지 않는 파티면 PartyException을 던진다") void partyNotFound() { // given given(partyRepository.findById(99L)).willReturn(Optional.empty()); @@ -211,7 +211,7 @@ void partyNotFound() { } @Test - @DisplayName("비활성화된 파티면 PartyException을 던진다") + @DisplayName("실패 - 비활성화된 파티면 PartyException을 던진다") void partyInactive() { // given PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); @@ -234,7 +234,7 @@ void partyInactive() { class GetMyParties { @Test - @DisplayName("내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + @DisplayName("성공 - 내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") void success() { // given Long memberId = 10L; @@ -284,7 +284,7 @@ void success() { class GetSimpleMyParties { @Test - @DisplayName("유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + @DisplayName("성공 - 유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") void success() { // given Long memberId = 1L; @@ -325,7 +325,7 @@ void success() { } @Test - @DisplayName("존재하지 않는 회원일 경우 MemberException을 던진다") + @DisplayName("실패 - 존재하지 않는 회원일 경우 MemberException을 던진다") void memberNotFound() { // given Long invalidMemberId = 999L; @@ -348,7 +348,7 @@ void memberNotFound() { class GetRecommendedParties { @Test - @DisplayName("Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") + @DisplayName("성공 - Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") void success_cockpleRecommend() { // given Long memberId = 1L; @@ -390,7 +390,7 @@ void success_cockpleRecommend() { } @Test - @DisplayName("필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") + @DisplayName("성공 - 필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") void success_filterMode() { // given Long memberId = 1L; @@ -423,7 +423,7 @@ void success_filterMode() { } @Test - @DisplayName("존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") + @DisplayName("실패 - 존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") void fail_memberNotFound() { // given Long memberId = 999L; @@ -443,7 +443,7 @@ void fail_memberNotFound() { } @Test - @DisplayName("대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") + @DisplayName("실패 - 대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") void fail_mainAddressNotFound() { // given Long memberId = 1L; @@ -472,7 +472,7 @@ void fail_mainAddressNotFound() { class GetPartyDetails { @Test - @DisplayName("모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") + @DisplayName("성공 - 모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") void success_nonMember() { // given Long partyId = 1L; @@ -509,7 +509,7 @@ void success_nonMember() { } @Test - @DisplayName("모임원인 경우 memberStatus가 MEMBER로 반환된다") + @DisplayName("성공 - 모임원인 경우 memberStatus가 MEMBER로 반환된다") void success_member() { // given Long partyId = 1L; @@ -543,7 +543,7 @@ void success_member() { } @Test - @DisplayName("존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") + @DisplayName("실패 - 존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") void fail_partyNotFound() { // given given(partyRepository.findById(999L)).willReturn(Optional.empty()); @@ -556,7 +556,7 @@ void fail_partyNotFound() { } @Test - @DisplayName("삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") + @DisplayName("실패 - 삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") void fail_partyDeleted() { // given PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); From c90e4d0cd1c1b59e87abf59ddd9aa92201c4adec Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 15:19:21 +0900 Subject: [PATCH 39/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=A0=EC=B2=AD=20(/api/parties/{partyId}/join-r?= =?UTF-8?q?equests)=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 135 ++++++++++++----- .../service/PartyCommandServiceTest.java | 139 ++++++++++++++++++ 2 files changed, 237 insertions(+), 37 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 992a48aa3..4966d0a3c 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -17,8 +17,11 @@ import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -35,6 +38,7 @@ import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -60,6 +64,8 @@ class PartyIntegrationTest extends IntegrationTestBase { ChatRoomRepository chatRoomRepository; @Autowired ChatRoomMemberRepository chatRoomMemberRepository; + @Autowired + PartyJoinRequestRepository partyJoinRequestRepository; private Member manager; private Member normalMember; @@ -107,6 +113,7 @@ void tearDown() { exerciseRepository.deleteAll(); chatRoomMemberRepository.deleteAll(); chatRoomRepository.deleteAll(); + partyJoinRequestRepository.deleteAll(); memberPartyRepository.deleteAll(); partyRepository.deleteAll(); partyAddrRepository.deleteAll(); @@ -204,7 +211,7 @@ void success_leaveParty() throws Exception { // DB에서 최신 정보 보장 Member member = memberRepository.findById(normalMember.getId()).orElseThrow(); Party targetParty = partyRepository.findById(party.getId()).orElseThrow(); - + // normalMember 세션으로 설정 SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); @@ -232,10 +239,8 @@ void fail_leaveParty_owner() throws Exception { @DisplayName("403 - 부모임장은 탈퇴할 수 없다") void fail_leaveParty_subOwner() throws Exception { // 부모임장 생성 및 가입 - Member subManager = memberRepository - .saveAndFlush(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 2001L)); - memberPartyRepository - .saveAndFlush(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 3001L)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); // 부모임장 세션으로 설정 SecurityContextHelper.setAuthentication(subManager.getId(), subManager.getNickname()); @@ -249,7 +254,7 @@ void fail_leaveParty_subOwner() throws Exception { @DisplayName("400 - 해당 모임의 멤버가 아니면 탈퇴할 수 없다") void fail_leaveParty_notMember() throws Exception { // 가입하지 않은 새로운 멤버 생성 - Member nonMember = memberRepository.save(MemberFixture.createMember("외부인", Gender.MALE, Level.A, 3001L)); + Member nonMember = memberRepository.save(MemberFixture.createMember("외부인", Gender.MALE, Level.A, 4002L)); SecurityContextHelper.setAuthentication(nonMember.getId(), nonMember.getNickname()); mockMvc.perform(delete("/api/parties/{partyId}/members/my", party.getId())) @@ -266,10 +271,10 @@ class GetMyParties { @DisplayName("200 - 사용자가 가입한 모임 목록을 페이징하여 반환한다") void success_getMyParties() throws Exception { mockMvc.perform(get("/api/my/parties") - .param("created", "false") - .param("sort", "최신순") - .param("size", "10") - .param("page", "0")) + .param("created", "false") + .param("sort", "최신순") + .param("size", "10") + .param("page", "0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) @@ -289,10 +294,10 @@ void success_emptyMyParties() throws Exception { SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); mockMvc.perform(get("/api/my/parties") - .param("created", "false") - .param("sort", "최신순") - .param("size", "10") - .param("page", "0")) + .param("created", "false") + .param("sort", "최신순") + .param("size", "10") + .param("page", "0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) @@ -310,9 +315,9 @@ class GetSimpleMyParties { @DisplayName("200 - 사용자가 가입한 모임의 간략화된 목록을 페이징하여 반환한다") void success_getSimpleMyParties() throws Exception { mockMvc.perform(get("/api/my/parties/simple") - .param("page", "0") - .param("size", "10") - .param("sort", "createdAt,DESC")) + .param("page", "0") + .param("size", "10") + .param("sort", "createdAt,DESC")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) @@ -332,9 +337,9 @@ void success_emptySimpleMyParties() throws Exception { SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); mockMvc.perform(get("/api/my/parties/simple") - .param("page", "0") - .param("size", "10") - .param("sort", "createdAt,DESC")) + .param("page", "0") + .param("size", "10") + .param("sort", "createdAt,DESC")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) @@ -352,10 +357,10 @@ class GetRecommendedParties { @DisplayName("200 - Cockple 추천 모드 시 추천된 모임 목록을 반환한다") void success_cockpleRecommend() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("isCockpleRecommend", "true") - .param("sort", "최신순") - .param("page", "0") - .param("size", "10")) + .param("isCockpleRecommend", "true") + .param("sort", "최신순") + .param("page", "0") + .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) @@ -366,12 +371,12 @@ void success_cockpleRecommend() throws Exception { @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 반환한다") void success_filterMode() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("isCockpleRecommend", "false") - .param("addr1", "서울특별시") - .param("addr2", "강남구") - .param("sort", "최신순") - .param("page", "0") - .param("size", "10")) + .param("isCockpleRecommend", "false") + .param("addr1", "서울특별시") + .param("addr2", "강남구") + .param("sort", "최신순") + .param("page", "0") + .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) @@ -383,10 +388,10 @@ void success_filterMode() throws Exception { @DisplayName("200 - 검색 모드 시 모임명으로 검색된 결과를 반환한다") void success_searchMode() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("search", "추천") - .param("isCockpleRecommend", "false") - .param("page", "0") - .param("size", "10")) + .param("search", "추천") + .param("isCockpleRecommend", "false") + .param("page", "0") + .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) @@ -397,8 +402,8 @@ void success_searchMode() throws Exception { @DisplayName("400 - 유효하지 않은 정렬 기준 입력 시 INVALID_ORDER_TYPE 에러를 반환한다") void fail_invalidOrderType() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("isCockpleRecommend", "false") - .param("sort", "잘못된순")) + .param("isCockpleRecommend", "false") + .param("sort", "잘못된순")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_ORDER_TYPE.getCode())); } @@ -407,7 +412,7 @@ void fail_invalidOrderType() throws Exception { @DisplayName("400 - isCockpleRecommend에 부적절한 타입 입력 시 400 에러를 반환한다") void fail_invalidBooleanType() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("isCockpleRecommend", "not-boolean")) + .param("isCockpleRecommend", "not-boolean")) .andExpect(status().isBadRequest()); } } @@ -463,4 +468,60 @@ void fail_partyDeleted() throws Exception { .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); } } + + @Nested + @DisplayName("POST /api/parties/{partyId}/join-requests - 모임 가입 신청") + class CreateJoinRequest { + + @Test + @DisplayName("200 - 가입하지 않은 회원이 모임 가입을 신청한다") + void success_createJoinRequest() throws Exception { + // 가입하지 않은 멤버 + Member applicant = memberRepository.save(MemberFixture.createMember("신청자", Gender.MALE, Level.A, 5001L, LocalDate.of(1995, 1, 1))); + SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON201")); + } + + @Test + @DisplayName("409 - 이미 가입된 회원이 다시 가입 신청을 한다") + void fail_createJoinRequest_alreadyMember() throws Exception { + // 이미 가입된 normalMember 사용 + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", party.getId())) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.ALREADY_MEMBER.getCode())); + } + + @Test + @DisplayName("400 - 성별 조건이 맞지 않는 모임에 신청한다") + void fail_createJoinRequest_genderMismatch() throws Exception { + // 여복 모임 생성 + PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울", "강남")); + Party womenParty = partyRepository.save(Party.builder() + .partyName("여복 전용 모임") + .partyType(ParticipationType.WOMEN_DOUBLES) + .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .ownerId(manager.getId()) + .partyAddr(addr) + .minBirthYear(1900) + .maxBirthYear(2099) + .activityTime(ActivityTime.MORNING) + .designatedCock("테스트콕") + .exerciseCount(0) + .price(0) + .joinPrice(0) + .build()); + + // 남성 사용자로 신청 시도 + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", womenParty.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.GENDER_NOT_MATCH.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 3998eeda0..7e1b5a3cb 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -14,11 +14,17 @@ import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; +import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.domain.PartyJoinRequest; +import umc.cockple.demo.domain.party.dto.PartyJoinCreateDTO; +import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -26,6 +32,7 @@ import umc.cockple.demo.support.fixture.MemberFixture; import umc.cockple.demo.support.fixture.PartyFixture; +import java.time.LocalDate; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -50,6 +57,10 @@ class PartyCommandServiceTest { private ChatRoomService chatRoomService; @Mock private ApplicationEventPublisher applicationEventPublisher; + @Mock + private PartyJoinRequestRepository partyJoinRequestRepository; + @Mock + private PartyConverter partyConverter; @Nested @DisplayName("leaveParty") @@ -197,4 +208,132 @@ void fail_leaveParty_notMember() { .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER)); } } + + @Nested + @DisplayName("createJoinRequest") + class CreateJoinRequest { + + @Test + @DisplayName("성공 - 사용자가 특정 모임에 가입 신청을 성공적으로 완료한다") + void success_createJoinRequest() { + // given + Long partyId = 1L; + Long memberId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("가입 신청 모임", 10L, addr); + Member member = MemberFixture.createMember("지원자", Gender.MALE, Level.B, 1L, LocalDate.of(1995, 1, 1)); + ReflectionTestUtils.setField(member, "id", memberId); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(false); + given(partyJoinRequestRepository.save(any(PartyJoinRequest.class))).willAnswer(invocation -> invocation.getArgument(0)); + + // when + partyCommandService.createJoinRequest(partyId, memberId); + + // then + verify(partyJoinRequestRepository).save(any(PartyJoinRequest.class)); + verify(partyConverter).toJoinResponseDTO(any(PartyJoinRequest.class)); + } + + @Test + @DisplayName("실패 - 이미 해당 모임의 멤버인 경우 ALREADY_MEMBER 예외가 발생한다") + void fail_createJoinRequest_alreadyMember() { + // given + Long partyId = 1L; + Long memberId = 1L; + + Party party = PartyFixture.createParty("가입 신청 모임", 10L, null); + Member member = MemberFixture.createMember("지원자", Gender.MALE, Level.B, 1L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(true); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.ALREADY_MEMBER)); + } + + @Test + @DisplayName("실패 - 대기 중인 가입 신청이 이미 존재하는 경우 JOIN_REQUEST_ALREADY_EXISTS 예외가 발생한다") + void fail_createJoinRequest_alreadyRequested() { + // given + Long partyId = 1L; + Long memberId = 1L; + + Party party = PartyFixture.createParty("가입 신청 모임", 10L, null); + Member member = MemberFixture.createMember("지원자", Gender.MALE, Level.B, 1L); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(true); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_ALREADY_EXISTS)); + } + + @Test + @DisplayName("실패 - 모임 유형에 맞지 않는 성별인 경우 GENDER_NOT_MATCH 예외가 발생한다") + void fail_createJoinRequest_genderMismatch() { + // given + Long partyId = 1L; + Long memberId = 1L; + + // 여복 모임 생성 + Party party = Party.builder() + .partyName("여복 모임") + .partyType(ParticipationType.WOMEN_DOUBLES) + .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .ownerId(10L) + .build(); + Member member = MemberFixture.createMember("남자지원자", Gender.MALE, Level.B, 1L); // 남성 지원 + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(false); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.GENDER_NOT_MATCH)); + } + + @Test + @DisplayName("실패 - 모임의 나이 조건에 맞지 않는 경우 AGE_NOT_MATCH 예외가 발생한다") + void fail_createJoinRequest_ageMismatch() { + // given + Long partyId = 1L; + Long memberId = 1L; + + // 1990~2000년생 모임 + Party party = Party.builder() + .partyName("나이 제한 모임") + .minBirthYear(1990) + .maxBirthYear(2000) + .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .ownerId(10L) + .build(); + // 1980년생 지원자 (범위 밖) + Member member = MemberFixture.createMember("나이많은지원자", Gender.MALE, Level.B, 1L, LocalDate.of(1980, 1, 1)); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.existsByPartyAndMember(party, member)).willReturn(false); + given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(false); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(partyId, memberId)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH)); + } + } } From e5ef0fd32cef793eeadfeed5928ed99705475834 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 16:05:43 +0900 Subject: [PATCH 40/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20(/api/parties)=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 120 ++++++++- .../service/PartyCommandServiceTest.java | 250 +++++++++++++++++- 2 files changed, 363 insertions(+), 7 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 4966d0a3c..ef905ee65 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -1,13 +1,14 @@ package umc.cockple.demo.domain.party.integration; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; -import umc.cockple.demo.domain.exercise.domain.Exercise; import umc.cockple.demo.domain.chat.domain.ChatRoom; import umc.cockple.demo.domain.chat.domain.ChatRoomMember; import umc.cockple.demo.domain.chat.repository.ChatRoomMemberRepository; import umc.cockple.demo.domain.chat.repository.ChatRoomRepository; +import umc.cockple.demo.domain.exercise.domain.Exercise; import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; @@ -17,8 +18,9 @@ import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; -import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.dto.PartyCreateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; +import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; @@ -33,12 +35,11 @@ import umc.cockple.demo.support.fixture.PartyFixture; import java.time.LocalDate; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -66,6 +67,8 @@ class PartyIntegrationTest extends IntegrationTestBase { ChatRoomMemberRepository chatRoomMemberRepository; @Autowired PartyJoinRequestRepository partyJoinRequestRepository; + @Autowired + ObjectMapper objectMapper; private Member manager; private Member normalMember; @@ -524,4 +527,111 @@ void fail_createJoinRequest_genderMismatch() throws Exception { .andExpect(jsonPath("$.code").value(PartyErrorCode.GENDER_NOT_MATCH.getCode())); } } + + @Nested + @DisplayName("POST /api/parties - 모임 생성") + class CreateParty { + + @Test + @DisplayName("200 - 모임을 성공적으로 생성하고 DB 저장 상태를 확인한다") + void success_createParty() throws Exception { + // given + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("새로운 통합 모임") + .partyType("혼복") + .minBirthYear(1990) + .maxBirthYear(2000) + .activityTime("오전") + .addr1("서울특별시") + .addr2("강남구") + .activityDay(List.of("월", "수")) + .price(10000) + .joinPrice(5000) + .designatedCock("통합테스트콕") + .maleLevel(List.of("A조")) + .femaleLevel(List.of("B조")) + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON201")) + .andExpect(jsonPath("$.data.partyId").exists()); + + // DB 검증 + List parties = partyRepository.findAll(); + Party createdParty = parties.stream() + .filter(p -> p.getPartyName().equals("새로운 통합 모임")) + .findFirst() + .orElseThrow(); + + assertThat(createdParty.getOwnerId()).isEqualTo(manager.getId()); + assertThat(createdParty.getDesignatedCock()).isEqualTo("통합테스트콕"); + } + + @Test + @DisplayName("400 - 본인의 나이가 모임 조건에 맞지 않을 때 에러를 반환한다") + void fail_createParty_invalidAgeRange() throws Exception { + // given + // manager는 1995년생. 모임 조건을 2000~2010으로 설정. + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("청년 모임") + .partyType("혼복") + .minBirthYear(2000) + .maxBirthYear(2010) + .activityTime("오후") + .activityDay(List.of("금")) + .addr1("서울특별시") + .addr2("강남구") + .price(10000) + .joinPrice(0) + .femaleLevel(List.of("A조")) + .maleLevel(List.of("A조")) + .designatedCock("청년콕") + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.AGE_NOT_MATCH.getCode())); + } + + @Test + @DisplayName("400 - 혼복 모임에서 남자 급수 정보가 누락되었을 때 에러를 반환한다") + void fail_createParty_missingMaleLevelInMixDoubles() throws Exception { + // given + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("혼복 모임") + .partyType("혼복") + .minBirthYear(1990) + .maxBirthYear(2005) + .activityTime("오전") + .activityDay(List.of("토")) + .addr1("서울특별시") + .addr2("강남구") + .price(10000) + .joinPrice(0) + .designatedCock("혼복콕") + .maleLevel(null) + .femaleLevel(List.of("A조")) + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.MALE_LEVEL_REQUIRED.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 7e1b5a3cb..c35ab53c0 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -18,12 +18,15 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; -import umc.cockple.demo.domain.party.dto.PartyJoinCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.enums.ActiveDay; +import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; @@ -33,12 +36,15 @@ import umc.cockple.demo.support.fixture.PartyFixture; import java.time.LocalDate; +import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) @@ -52,6 +58,8 @@ class PartyCommandServiceTest { @Mock private MemberRepository memberRepository; @Mock + private PartyAddrRepository partyAddrRepository; + @Mock private MemberPartyRepository memberPartyRepository; @Mock private ChatRoomService chatRoomService; @@ -336,4 +344,242 @@ void fail_createJoinRequest_ageMismatch() { .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH)); } } + + @Nested + @DisplayName("createParty - 모임 생성") + class CreateParty { + + @Test + @DisplayName("성공 - 올바른 데이터 입력 시 모임이 생성되고 채팅방이 개설된다") + void success_createParty() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("테스트 모임") + .partyType("혼복") + .minBirthYear(1990) + .maxBirthYear(2000) + .activityTime("오전") + .addr1("서울") + .addr2("강남") + .activityDay(List.of("월", "수")) + .price(10000) + .joinPrice(5000) + .designatedCock("테스트콕") + .maleLevel(List.of("A조")) + .femaleLevel(List.of("B조")) + .build(); + + Member owner = Member.builder() + .id(memberId) + .gender(Gender.MALE) + .level(Level.A) + .birth(LocalDate.of(1995, 1, 1)) + .build(); + + PartyAddr partyAddr = PartyAddr.builder().id(1L).build(); + Party savedParty = Party.builder().id(1L).partyName("테스트 모임").build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyName(request.partyName()) + .partyType(ParticipationType.fromKorean(request.partyType())) + .femaleLevel(List.of(Level.B)) + .maleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY, ActiveDay.WEDNESDAY)) + .activityTime(ActivityTime.MORNING) + .price(10000) + .joinPrice(5000) + .designatedCock("테스트콕") + .minBirthYear(request.minBirthYear()) + .maxBirthYear(request.maxBirthYear()) + .build(); + + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder() + .addr1(request.addr1()) + .addr2(request.addr2()) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + given(partyAddrRepository.findByAddr1AndAddr2(anyString(), anyString())).willReturn(Optional.of(partyAddr)); + given(partyRepository.save(any(Party.class))).willReturn(savedParty); + given(partyConverter.toCreateResponseDTO(any())).willReturn(PartyCreateDTO.Response.builder().partyId(1L).build()); + + // when + PartyCreateDTO.Response response = partyCommandService.createParty(memberId, request); + + // then + assertThat(response).isNotNull(); + assertThat(response.partyId()).isEqualTo(1L); + verify(partyRepository, times(1)).save(any(Party.class)); + verify(chatRoomService, times(1)).createPartyChatRoom(any(Party.class), eq(owner)); + } + + @Test + @DisplayName("실패 - 혼복 모임 생성 시 남자 급수 정보가 누락되면 MALE_LEVEL_REQUIRED 예외가 발생한다") + void fail_createParty_mixDoubles_maleLevelMissing() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("혼복 모임") + .partyType("혼복") + .minBirthYear(1990) + .maxBirthYear(2000) + .activityTime("오전") + .activityDay(List.of("월")) + .femaleLevel(List.of("A조")) + .maleLevel(null) // 누락 + .build(); + + Member owner = Member.builder() + .id(memberId) + .gender(Gender.MALE) + .birth(LocalDate.of(1995, 1, 1)) + .build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyType(ParticipationType.MIX_DOUBLES) + .maleLevel(null) + .femaleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY)) + .minBirthYear(1990) + .maxBirthYear(2000) + .build(); + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createParty(memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.MALE_LEVEL_REQUIRED); + } + + @Test + @DisplayName("실패 - 여복 모임 생성 시 남자 급수 정보가 포함되면 MALE_LEVEL_NOT_NEEDED 예외가 발생한다") + void fail_createParty_womenDoubles_maleLevelProvided() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("여복 모임") + .partyType("여복") + .minBirthYear(1990) + .maxBirthYear(2010) + .activityTime("오전") + .activityDay(List.of("토")) + .femaleLevel(List.of("A조")) + .maleLevel(List.of("A조")) // 포함됨 + .build(); + + Member owner = Member.builder() + .id(memberId) + .gender(Gender.FEMALE) + .birth(LocalDate.of(2000, 1, 1)) + .build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyType(ParticipationType.WOMEN_DOUBLES) + .maleLevel(List.of(Level.A)) + .femaleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY)) + .minBirthYear(1990) + .maxBirthYear(2010) + .build(); + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createParty(memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.MALE_LEVEL_NOT_NEEDED); + } + + @Test + @DisplayName("실패 - 모임 유형의 성별 조건과 생성자의 성별이 맞지 않으면 GENDER_NOT_MATCH 예외가 발생한다") + void fail_createParty_genderMismatch() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("여복 모임") + .partyType("여복") + .minBirthYear(1990) + .maxBirthYear(2010) + .activityTime("오전") + .activityDay(List.of("일")) + .femaleLevel(List.of("A조")) + .build(); + + Member maleOwner = Member.builder() + .id(memberId) + .gender(Gender.MALE) // 남성이 여복 모임 생성 시도 + .birth(LocalDate.of(2000, 1, 1)) + .build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyType(ParticipationType.WOMEN_DOUBLES) + .femaleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY)) + .minBirthYear(1990) + .maxBirthYear(2010) + .build(); + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(maleOwner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createParty(memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.GENDER_NOT_MATCH); + } + + @Test + @DisplayName("실패 - 생성자의 나이가 모임의 나이 제한 범위를 벗어나면 AGE_NOT_MATCH 예외가 발생한다") + void fail_createParty_ageMismatch() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("청년 모임") + .partyType("혼복") + .minBirthYear(2000) + .maxBirthYear(2010) + .maleLevel(List.of("A조")) + .femaleLevel(List.of("A조")) + .activityTime("오후") + .activityDay(List.of("금")) + .build(); + + Member oldOwner = Member.builder() + .id(memberId) + .gender(Gender.MALE) + .birth(LocalDate.of(1980, 1, 1)) // 80년생이 00~10년생 모임 생성 시도 + .build(); + + PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() + .partyType(ParticipationType.MIX_DOUBLES) + .femaleLevel(List.of(Level.A)) + .maleLevel(List.of(Level.A)) + .activityDay(List.of(ActiveDay.MONDAY)) + .minBirthYear(2000) + .maxBirthYear(2010) + .build(); + PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(oldOwner)); + given(partyConverter.toCreateCommand(any())).willReturn(command); + given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createParty(memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH); + } + } } From e0bc92d9d31b4c7c00bf339f125698910b690e6d Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 16:43:14 +0900 Subject: [PATCH 41/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9/=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=20=EB=A7=9E=EC=B6=94=EA=B8=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 89 ++++++------------- .../service/PartyCommandServiceTest.java | 87 +++--------------- .../party/service/PartyQueryServiceTest.java | 87 ++++++++---------- 3 files changed, 80 insertions(+), 183 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index ef905ee65..15210d073 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -1,9 +1,13 @@ package umc.cockple.demo.domain.party.integration; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import umc.cockple.demo.domain.chat.domain.ChatRoom; import umc.cockple.demo.domain.chat.domain.ChatRoomMember; import umc.cockple.demo.domain.chat.repository.ChatRoomMemberRepository; @@ -21,6 +25,7 @@ import umc.cockple.demo.domain.party.dto.PartyCreateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; @@ -43,6 +48,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@Transactional class PartyIntegrationTest extends IntegrationTestBase { @Autowired @@ -76,8 +82,8 @@ class PartyIntegrationTest extends IntegrationTestBase { @BeforeEach void setUp() { - manager = memberRepository - .save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, LocalDate.of(1995, 1, 1))); + // 매니저 및 주소 정보 생성 + manager = memberRepository.save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L, LocalDate.of(1995, 1, 1))); memberAddrRepository.save(MemberAddr.builder() .member(manager) .addr1("서울특별시") @@ -89,99 +95,56 @@ void setUp() { .isMain(true) .build()); + // 일반 멤버 생성 normalMember = memberRepository.save(MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L)); + // 모임 및 주소 정보 생성 PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); + // 모임 멤버 생성 memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); - // 채팅방 생성 및 멤버 추가 + // 채팅방 생성 ChatRoom chatRoom = chatRoomRepository.save(ChatRoom.createPartyChatRoom(party)); chatRoomMemberRepository.save(ChatRoomMember.create(chatRoom, manager)); chatRoomMemberRepository.save(ChatRoomMember.create(chatRoom, normalMember)); - // 추천 조회용 모임 (manager가 가입하지 않은 모임) + // 추천 조회용 모임 (manager의 조건에 맞춤) Party suggestedParty = PartyFixture.createParty("추천 모임", normalMember.getId(), addr); - suggestedParty.addLevel(Gender.MALE, Level.A); // manager의 조건에 맞춤 + suggestedParty.addLevel(Gender.MALE, Level.A); partyRepository.save(suggestedParty); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); } - @AfterEach - void tearDown() { - memberExerciseRepository.deleteAll(); - exerciseRepository.deleteAll(); - chatRoomMemberRepository.deleteAll(); - chatRoomRepository.deleteAll(); - partyJoinRequestRepository.deleteAll(); - memberPartyRepository.deleteAll(); - partyRepository.deleteAll(); - partyAddrRepository.deleteAll(); - memberAddrRepository.deleteAll(); - memberRepository.deleteAll(); - } @Nested @DisplayName("GET /api/parties/{partyId}/members - 모임 멤버 조회") class GetPartyMembers { @Test - @DisplayName("200 - 모임의 멤버들을 역할별로 성공적으로 조회한다.") - void success() throws Exception { + @DisplayName("200 - 멤버 목록을 역할, 성별 통계 및 마지막 운동일과 함께 조회한다") + void success_getMembersWithDetails() throws Exception { // 부모임장 추가 Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 1003L)); memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); - // 모임장이 가입된 상태 - SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + // 운동 기록 추가 + Exercise exercise = exerciseRepository.save(ExerciseFixture.createExercise(party, LocalDate.of(2025, 1, 10))); + memberExerciseRepository.save(MemberFixture.createMemberExercise(normalMember, exercise)); mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.summary.totalCount").value(3)) + .andExpect(jsonPath("$.data.summary.maleCount").value(2)) + .andExpect(jsonPath("$.data.summary.femaleCount").value(1)) .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) .andExpect(jsonPath("$.data.members[0].isMe").value(true)) .andExpect(jsonPath("$.data.members[1].role").value("party_SUBMANAGER")) - .andExpect(jsonPath("$.data.members[1].isMe").value(false)) .andExpect(jsonPath("$.data.members[2].role").value("party_MEMBER")) - .andExpect(jsonPath("$.data.members[2].isMe").value(false)); - } - - @Test - @DisplayName("200 - 멤버 목록과 마지막 운동일을 정상 반환한다") - void success_withLastExerciseDate() throws Exception { - Exercise exercise = exerciseRepository.save( - ExerciseFixture.createExercise(party, LocalDate.of(2025, 1, 10))); - memberExerciseRepository.save(MemberFixture.createMemberExercise(normalMember, exercise)); - - mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.summary.totalCount").value(2)) - .andExpect(jsonPath("$.data.summary.maleCount").value(1)) - .andExpect(jsonPath("$.data.summary.femaleCount").value(1)) - // 첫 번째 멤버(매니저) 전체 필드 검증 - .andExpect(jsonPath("$.data.members[0].memberId").value(manager.getId())) - .andExpect(jsonPath("$.data.members[0].nickname").value("매니저")) - .andExpect(jsonPath("$.data.members[0].profileImageUrl").doesNotExist()) - .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) - .andExpect(jsonPath("$.data.members[0].gender").value("MALE")) - .andExpect(jsonPath("$.data.members[0].level").value("A조")) - .andExpect(jsonPath("$.data.members[0].isMe").value(true)) - .andExpect(jsonPath("$.data.members[0].lastExerciseDate").doesNotExist()) - // 두 번째 멤버(일반멤버) 마지막 운동일 검증 - .andExpect(jsonPath("$.data.members[1].lastExerciseDate").value("2025-01-10")); - } - - @Test - @DisplayName("200 - 운동 기록이 없는 멤버의 lastExerciseDate는 null이다") - void success_noExerciseHistory() throws Exception { - mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.summary.totalCount").value(2)) - .andExpect(jsonPath("$.data.members[0].lastExerciseDate").isEmpty()) - .andExpect(jsonPath("$.data.members[1].lastExerciseDate").isEmpty()); + .andExpect(jsonPath("$.data.members[2].lastExerciseDate").value("2025-01-10")); } @Test @@ -479,13 +442,17 @@ class CreateJoinRequest { @Test @DisplayName("200 - 가입하지 않은 회원이 모임 가입을 신청한다") void success_createJoinRequest() throws Exception { - // 가입하지 않은 멤버 + // 가입하지 않은 멤버 생성 Member applicant = memberRepository.save(MemberFixture.createMember("신청자", Gender.MALE, Level.A, 5001L, LocalDate.of(1995, 1, 1))); SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); mockMvc.perform(post("/api/parties/{partyId}/join-requests", party.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON201")); + + // 가입 신청 데이터 확인 + boolean exists = partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, applicant, RequestStatus.PENDING); + assertThat(exists).isTrue(); } @Test diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index c35ab53c0..4f612bf29 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -1,5 +1,6 @@ package umc.cockple.demo.domain.party.service; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -10,6 +11,7 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.test.util.ReflectionTestUtils; import umc.cockple.demo.domain.chat.service.ChatRoomService; +import umc.cockple.demo.domain.file.service.FileService; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; @@ -18,9 +20,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; -import umc.cockple.demo.domain.party.dto.PartyCreateDTO; -import umc.cockple.demo.domain.party.enums.ActiveDay; -import umc.cockple.demo.domain.party.enums.ActivityTime; +import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; @@ -68,8 +68,16 @@ class PartyCommandServiceTest { @Mock private PartyJoinRequestRepository partyJoinRequestRepository; @Mock + private FileService fileService; + private PartyConverter partyConverter; + @BeforeEach + void setUp() { + partyConverter = new PartyConverter(fileService); + ReflectionTestUtils.setField(partyCommandService, "partyConverter", partyConverter); + } + @Nested @DisplayName("leaveParty") class LeaveParty { @@ -240,11 +248,11 @@ void success_createJoinRequest() { given(partyJoinRequestRepository.save(any(PartyJoinRequest.class))).willAnswer(invocation -> invocation.getArgument(0)); // when - partyCommandService.createJoinRequest(partyId, memberId); + PartyJoinCreateDTO.Response response = partyCommandService.createJoinRequest(partyId, memberId); // then + assertThat(response).isNotNull(); verify(partyJoinRequestRepository).save(any(PartyJoinRequest.class)); - verify(partyConverter).toJoinResponseDTO(any(PartyJoinRequest.class)); } @Test @@ -380,31 +388,9 @@ void success_createParty() { PartyAddr partyAddr = PartyAddr.builder().id(1L).build(); Party savedParty = Party.builder().id(1L).partyName("테스트 모임").build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyName(request.partyName()) - .partyType(ParticipationType.fromKorean(request.partyType())) - .femaleLevel(List.of(Level.B)) - .maleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY, ActiveDay.WEDNESDAY)) - .activityTime(ActivityTime.MORNING) - .price(10000) - .joinPrice(5000) - .designatedCock("테스트콕") - .minBirthYear(request.minBirthYear()) - .maxBirthYear(request.maxBirthYear()) - .build(); - - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder() - .addr1(request.addr1()) - .addr2(request.addr2()) - .build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); given(partyAddrRepository.findByAddr1AndAddr2(anyString(), anyString())).willReturn(Optional.of(partyAddr)); given(partyRepository.save(any(Party.class))).willReturn(savedParty); - given(partyConverter.toCreateResponseDTO(any())).willReturn(PartyCreateDTO.Response.builder().partyId(1L).build()); // when PartyCreateDTO.Response response = partyCommandService.createParty(memberId, request); @@ -438,19 +424,7 @@ void fail_createParty_mixDoubles_maleLevelMissing() { .birth(LocalDate.of(1995, 1, 1)) .build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyType(ParticipationType.MIX_DOUBLES) - .maleLevel(null) - .femaleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY)) - .minBirthYear(1990) - .maxBirthYear(2000) - .build(); - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); // when & then PartyException exception = assertThrows(PartyException.class, @@ -480,19 +454,7 @@ void fail_createParty_womenDoubles_maleLevelProvided() { .birth(LocalDate.of(2000, 1, 1)) .build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyType(ParticipationType.WOMEN_DOUBLES) - .maleLevel(List.of(Level.A)) - .femaleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY)) - .minBirthYear(1990) - .maxBirthYear(2010) - .build(); - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); // when & then PartyException exception = assertThrows(PartyException.class, @@ -521,18 +483,7 @@ void fail_createParty_genderMismatch() { .birth(LocalDate.of(2000, 1, 1)) .build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyType(ParticipationType.WOMEN_DOUBLES) - .femaleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY)) - .minBirthYear(1990) - .maxBirthYear(2010) - .build(); - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(maleOwner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); // when & then PartyException exception = assertThrows(PartyException.class, @@ -562,19 +513,7 @@ void fail_createParty_ageMismatch() { .birth(LocalDate.of(1980, 1, 1)) // 80년생이 00~10년생 모임 생성 시도 .build(); - PartyCreateDTO.Command command = PartyCreateDTO.Command.builder() - .partyType(ParticipationType.MIX_DOUBLES) - .femaleLevel(List.of(Level.A)) - .maleLevel(List.of(Level.A)) - .activityDay(List.of(ActiveDay.MONDAY)) - .minBirthYear(2000) - .maxBirthYear(2010) - .build(); - PartyCreateDTO.AddrCommand addrCommand = PartyCreateDTO.AddrCommand.builder().addr1("서울").addr2("강남").build(); - given(memberRepository.findById(memberId)).willReturn(Optional.of(oldOwner)); - given(partyConverter.toCreateCommand(any())).willReturn(command); - given(partyConverter.toAddrCreateCommand(any())).willReturn(addrCommand); // when & then PartyException exception = assertThrows(PartyException.class, diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 18d18f1a3..89ae54686 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -1,5 +1,6 @@ package umc.cockple.demo.domain.party.service; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -7,9 +8,18 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.test.util.ReflectionTestUtils; +import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; +import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; +import umc.cockple.demo.domain.file.service.FileService; import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.domain.MemberAddr; import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; @@ -20,6 +30,7 @@ import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -29,21 +40,9 @@ import java.time.LocalDate; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; -import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; -import umc.cockple.demo.domain.member.repository.MemberAddrRepository; -import umc.cockple.demo.domain.file.service.FileService; -import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; -import umc.cockple.demo.domain.member.domain.MemberAddr; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.*; @@ -60,8 +59,9 @@ class PartyQueryServiceTest { private PartyRepository partyRepository; @Mock private MemberRepository memberRepository; - @Mock + private PartyConverter partyConverter; + @Mock private MemberPartyRepository memberPartyRepository; @Mock @@ -77,6 +77,12 @@ class PartyQueryServiceTest { @Mock private PartyJoinRequestRepository partyJoinRequestRepository; + @BeforeEach + void setUp() { + partyConverter = new PartyConverter(fileService); + ReflectionTestUtils.setField(partyQueryService, "partyConverter", partyConverter); + } + @Nested @DisplayName("getPartyMembers") class GetPartyMembers { @@ -112,10 +118,13 @@ void success() { .willReturn(List.of()); // when - partyQueryService.getPartyMembers(partyId, currentMemberId); + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); // then - verify(partyConverter).toPartyMemberDTO(eq(memberParties), eq(currentMemberId), anyMap()); + assertThat(result.members()).hasSize(3); + assertThat(result.summary().totalCount()).isEqualTo(3); + assertThat(result.summary().maleCount()).isEqualTo(2); + assertThat(result.summary().femaleCount()).isEqualTo(1); } @Test @@ -150,20 +159,18 @@ void success_withExerciseHistory() { given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( List.of(10L, 20L), partyId)).willReturn(rawResult); - given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) - .willReturn(expected); // when PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); // then - assertThat(result).isEqualTo(expected); - verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( - List.of(10L, 20L), partyId); - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of(20L, lastDate))); + assertThat(result.summary().totalCount()).isEqualTo(2); + assertThat(result.members()).hasSize(2); + // 마지막 운동일 확인 (멤버1 id: 20L) + assertThat(result.members().stream() + .filter(m -> m.memberId().equals(20L)) + .findFirst() + .get().lastExerciseDate()).isEqualTo(lastDate); } @Test @@ -185,16 +192,13 @@ void noExerciseHistory() { given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( List.of(10L), partyId)).willReturn(List.of()); - given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); // when - partyQueryService.getPartyMembers(partyId, currentMemberId); + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); // then - verify(partyConverter).toPartyMemberDTO( - eq(memberParties), - eq(currentMemberId), - eq(Map.of())); + assertThat(result.members()).hasSize(1); + assertThat(result.members().get(0).lastExerciseDate()).isNull(); } @Test @@ -262,9 +266,6 @@ void success() { .willReturn(List.of()); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) .willReturn(Set.of(1L)); - given(partyConverter.toMyPartyDTO(eq(party), any(), any(), any(), eq(true))) - .willReturn(expectedResponse); - // when Slice result = partyQueryService.getMyParties(memberId, false, "최신순", pageable); @@ -275,7 +276,6 @@ void success() { assertThat(result.getContent().get(0).isBookmarked()).isTrue(); verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); - verify(partyConverter).toMyPartyDTO(eq(party), any(), any(), any(), eq(true)); } } @@ -308,8 +308,6 @@ void success() { given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); - // fileService.getUrlFromKey 의 경우 partyImg 가 없으므로 널이 전달됨 - given(partyConverter.toPartySimpleDTO(eq(memberParty), any())).willReturn(expectedResponse); // when Slice result = partyQueryService.getSimpleMyParties(memberId, @@ -321,7 +319,6 @@ void success() { verify(memberRepository).findById(memberId); verify(memberPartyRepository).findByMember(member, pageable); - verify(partyConverter).toPartySimpleDTO(eq(memberParty), any()); } @Test @@ -374,9 +371,6 @@ void success_cockpleRecommend() { given(partyRepository.findRecommendedParties(anyString(), anyInt(), any(), any(), anyLong())) .willReturn(List.of(suggestedParty)); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(suggestedParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(100L).partyName("추천 모임") - .build()); // when Slice result = partyQueryService.getRecommendedParties(memberId, true, @@ -408,9 +402,6 @@ void success_filterMode() { given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) .willReturn(partySlice); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); - given(partyConverter.toMyPartyDTO(eq(filteredParty), any(), any(), any(), eq(false))) - .willReturn(PartyDTO.Response.builder().partyId(200L).partyName("필터 모임") - .build()); // when Slice result = partyQueryService.getRecommendedParties(memberId, false, @@ -497,14 +488,15 @@ void success_nonMember() { given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); given(partyJoinRequestRepository.existsByPartyAndMemberAndStatus(party, member, RequestStatus.PENDING)).willReturn(false); - given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), eq(false))) - .willReturn(expected); // when PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); // then - assertThat(result).isEqualTo(expected); + assertThat(result.partyId()).isEqualTo(partyId); + assertThat(result.partyName()).isEqualTo("상세 모임"); + assertThat(result.memberStatus()).isEqualTo("NOT_MEMBER"); + assertThat(result.hasPendingJoinRequest()).isFalse(); verify(partyRepository).findById(partyId); } @@ -532,14 +524,13 @@ void success_member() { given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); given(memberPartyRepository.findByPartyAndMember(party, member)) .willReturn(Optional.of(memberParty)); - given(partyConverter.toPartyDetailResponseDTO(eq(party), any(), any(), eq(false), anyBoolean())) - .willReturn(expected); // when PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); // then assertThat(result.memberStatus()).isEqualTo("MEMBER"); + assertThat(result.memberRole()).isEqualTo("party_MEMBER"); } @Test From e2ef2c7d35efba27dffcb9f7925f36f19db57003 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Mon, 23 Mar 2026 17:24:58 +0900 Subject: [PATCH 42/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20(/api/parties/{partyId})=20API?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 75 +++++++++++++++ .../service/PartyCommandServiceTest.java | 92 +++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 15210d073..45bae65c3 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -23,6 +23,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; @@ -495,6 +496,80 @@ void fail_createJoinRequest_genderMismatch() throws Exception { } } + @Nested + @DisplayName("PATCH /api/parties/{partyId} - 모임 정보 수정") + class UpdateParty { + + @Test + @DisplayName("200 - 모임장이 유효한 데이터로 모임 정보를 정상적으로 수정한다") + void success_updateParty() throws Exception { + // given + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(List.of("월", "수")) + .activityTime("오전") + .designatedCock("수정된 콕") + .joinPrice(2000) + .price(15000) + .content("수정된 내용입니다.") + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}", party.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // DB 검증 + Party updatedParty = partyRepository.findById(party.getId()).orElseThrow(); + assertThat(updatedParty.getDesignatedCock()).isEqualTo("수정된 콕"); + assertThat(updatedParty.getJoinPrice()).isEqualTo(2000); + assertThat(updatedParty.getPrice()).isEqualTo(15000); + assertThat(updatedParty.getContent()).isEqualTo("수정된 내용입니다."); + } + + @Test + @DisplayName("400 - 필수 필드(activityDay, activityTime) 누락 시 에러를 반환한다") + void fail_updateParty_missingRequiredFields() throws Exception { + // given + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(null) + .activityTime("") + .build(); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}", party.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("COMMON400_VALIDATION")); + } + + @Test + @DisplayName("403 - 모임장이 아닌 일반 멤버가 수정을 시도하면 INSUFFICIENT_PERMISSION 에러를 반환한다") + void fail_updateParty_notOwner() throws Exception { + // given + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(List.of("토", "일")) + .activityTime("오후") + .build(); + + // 일반 멤버로 세션 설정 + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}", party.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 4f612bf29..20b106ee4 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -20,6 +20,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; +import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; @@ -58,6 +59,8 @@ class PartyCommandServiceTest { @Mock private MemberRepository memberRepository; @Mock + private NotificationCommandService notificationCommandService; + @Mock private PartyAddrRepository partyAddrRepository; @Mock private MemberPartyRepository memberPartyRepository; @@ -521,4 +524,93 @@ void fail_createParty_ageMismatch() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH); } } + + @Nested + @DisplayName("updateParty") + class UpdateParty { + + @Test + @DisplayName("성공 - 모임장이 모임 정보를 정상적으로 수정한다") + void success_updateParty() { + // given + Long partyId = 1L; + Long memberId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", memberId); + + Party party = PartyFixture.createParty("기존 모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(List.of("토", "일")) + .activityTime("오전") + .designatedCock("새 콕") + .joinPrice(0) + .price(10000) + .content("새로운 내용") + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(owner)); + + // when + partyCommandService.updateParty(partyId, memberId, request); + + // then + assertThat(party.getDesignatedCock()).isEqualTo("새 콕"); + assertThat(party.getActiveDays().size()).isEqualTo(2); // 토, 일 + assertThat(party.getJoinPrice()).isEqualTo(0); + assertThat(party.getPrice()).isEqualTo(10000); + assertThat(party.getContent()).isEqualTo("새로운 내용"); + + verify(notificationCommandService, times(1)).createNotification(any()); + } + + @Test + @DisplayName("실패 - 조회된 모임이 없는 경우 PARTY_NOT_FOUND 예외 발생") + void fail_updateParty_partyNotFound() { + // given + Long partyId = 99L; + Long memberId = 1L; + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder().build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.updateParty(partyId, memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 수정을 시도할 경우 INSUFFICIENT_PERMISSION 예외 발생") + void fail_updateParty_insufficientPermission() { + // given + Long partyId = 1L; + Long memberId = 10L; // 일반 멤버 (ownerId=1 과 다름) + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + ReflectionTestUtils.setField(owner, "id", 1L); + + Member normalMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 2L); + ReflectionTestUtils.setField(normalMember, "id", memberId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityTime("오전").build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.of(normalMember)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.updateParty(partyId, memberId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + } } From 72f7541ac317c6592cb46648477e2822676acc50 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sat, 28 Mar 2026 20:25:33 +0900 Subject: [PATCH 43/63] =?UTF-8?q?test:=20=EB=A9=A4=EB=B2=84=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0(=EB=B6=80=EB=AA=A8=EC=9E=84=EC=9E=A5)=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20(/api/parties/{partyId}/members/{memberId}/role)=20?= =?UTF-8?q?API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 57 ++++++++++ .../service/PartyCommandServiceTest.java | 105 ++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 45bae65c3..cf8f65154 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -16,6 +16,7 @@ import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; +import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; @@ -23,6 +24,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; @@ -676,4 +678,59 @@ void fail_createParty_missingMaleLevelInMixDoubles() throws Exception { .andExpect(jsonPath("$.code").value(PartyErrorCode.MALE_LEVEL_REQUIRED.getCode())); } } + + @Nested + @DisplayName("PATCH /api/parties/{partyId}/members/{memberId}/role - 멤버 역할(부모임장) 설정") + class UpdateMemberRole { + + @Test + @DisplayName("200 - 모임장이 일반 멤버를 부모임장으로 성공적으로 임명한다") + void success_assignSubManager() throws Exception { + // given + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", party.getId(), normalMember.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + MemberParty targetMemberParty = memberPartyRepository.findByPartyAndMember(party, normalMember).orElseThrow(); + assertThat(targetMemberParty.getRole()).isEqualTo(Role.party_SUBMANAGER); + } + + @Test + @DisplayName("403 - 모임장이 아닌 멤버가 역할 수정을 시도하면 INSUFFICIENT_PERMISSION 예외를 반환한다") + void fail_assignSubManager_notOwner() throws Exception { + // given + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + // 일반 멤버가 권한 변경 시도 + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", party.getId(), normalMember.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("403 - 대상자가 모임장인 경우 권한 변경은 실패하며 CANNOT_ASSIGN_TO_OWNER 예외를 반환한다") + void fail_assignSubManager_targetIsOwner() throws Exception { + // given + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_MEMBER); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", party.getId(), manager.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 20b106ee4..0aeb85616 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -613,4 +613,109 @@ void fail_updateParty_insufficientPermission() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } } + + @Nested + @DisplayName("updateMemberRole") + class UpdateMemberRole { + + @Test + @DisplayName("성공 - 모임장이 일반 멤버를 부모임장으로 지정하고 알림을 발생시킨다") + void success_updateMemberRole() { + // given + Long partyId = 1L; + Long currentOwnerId = 1L; + Long targetMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, currentOwnerId); + ReflectionTestUtils.setField(owner, "id", currentOwnerId); + + Member targetMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, targetMemberId); + ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty memberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(memberParty)); + // 만약 기존 부모임장이 있으면 해제하는 로직에 대한 빈 Optional 반환 + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)).willReturn(Optional.empty()); + // 알림 발송 시 파티 내 전체 멤버를 조회 + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(memberParty)); + + // when + partyCommandService.updateMemberRole(partyId, targetMemberId, currentOwnerId, request); + + // then + assertThat(memberParty.getRole()).isEqualTo(Role.party_SUBMANAGER); + verify(notificationCommandService, times(1)).createNotification(any()); + } + + @Test + @DisplayName("실패 - 대상 멤버가 이미 모임장인 경우 권한을 변경하려 하면 CANNOT_ASSIGN_TO_OWNER 발생") + void fail_updateMemberRole_targetIsOwner() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + // 타겟이 이미 모임장 권한을 가짐 + MemberParty memberParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(memberParty)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.updateMemberRole(partyId, ownerId, ownerId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER); + } + + @Test + @DisplayName("실패 - 현재 사용자가 모임장이 아닐 경우 INSUFFICIENT_PERMISSION 발생") + void fail_updateMemberRole_notOwner() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long notOwnerId = 2L; + Long targetId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Member notOwner = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, notOwnerId); + ReflectionTestUtils.setField(notOwner, "id", notOwnerId); + + Member targetMember = MemberFixture.createMember("타겟", Gender.MALE, Level.A, targetId); + ReflectionTestUtils.setField(targetMember, "id", targetId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(targetId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + + // when & then (notOwnerId를 currentMemberId로 전달하여 실행) + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.updateMemberRole(partyId, targetId, notOwnerId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + } } From 2dafee6d342c6cecca715a53adadcb46baf50756 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sat, 28 Mar 2026 22:35:24 +0900 Subject: [PATCH 44/63] =?UTF-8?q?test:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 30 ++++++++--------- .../party/service/PartyQueryServiceTest.java | 32 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index cf8f65154..80c268763 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -129,7 +129,7 @@ class GetPartyMembers { @Test @DisplayName("200 - 멤버 목록을 역할, 성별 통계 및 마지막 운동일과 함께 조회한다") - void success_getMembersWithDetails() throws Exception { + void success_getPartyMembers() throws Exception { // 부모임장 추가 Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 1003L)); memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); @@ -152,7 +152,7 @@ void success_getMembersWithDetails() throws Exception { @Test @DisplayName("404 - 존재하지 않는 파티면 에러를 반환한다") - void fail_partyNotFound() throws Exception { + void fail_getPartyMembers_partyNotFound() throws Exception { mockMvc.perform(get("/api/parties/{partyId}/members", 999L)) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); @@ -160,7 +160,7 @@ void fail_partyNotFound() throws Exception { @Test @DisplayName("400 - 비활성화된 파티면 에러를 반환한다") - void fail_partyInactive() throws Exception { + void fail_getPartyMembers_partyInactive() throws Exception { party.delete(); partyRepository.save(party); @@ -324,7 +324,7 @@ class GetRecommendedParties { @Test @DisplayName("200 - Cockple 추천 모드 시 추천된 모임 목록을 반환한다") - void success_cockpleRecommend() throws Exception { + void success_getRecommendedParties_cockpleRecommend() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "true") .param("sort", "최신순") @@ -338,7 +338,7 @@ void success_cockpleRecommend() throws Exception { @Test @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 반환한다") - void success_filterMode() throws Exception { + void success_getRecommendedParties_filterMode() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "false") .param("addr1", "서울특별시") @@ -355,7 +355,7 @@ void success_filterMode() throws Exception { @Test @DisplayName("200 - 검색 모드 시 모임명으로 검색된 결과를 반환한다") - void success_searchMode() throws Exception { + void success_getRecommendedParties_searchMode() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("search", "추천") .param("isCockpleRecommend", "false") @@ -369,7 +369,7 @@ void success_searchMode() throws Exception { @Test @DisplayName("400 - 유효하지 않은 정렬 기준 입력 시 INVALID_ORDER_TYPE 에러를 반환한다") - void fail_invalidOrderType() throws Exception { + void fail_getRecommendedParties_invalidOrderType() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "false") .param("sort", "잘못된순")) @@ -379,7 +379,7 @@ void fail_invalidOrderType() throws Exception { @Test @DisplayName("400 - isCockpleRecommend에 부적절한 타입 입력 시 400 에러를 반환한다") - void fail_invalidBooleanType() throws Exception { + void fail_getRecommendedParties_invalidBooleanType() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "not-boolean")) .andExpect(status().isBadRequest()); @@ -392,7 +392,7 @@ class GetPartyDetails { @Test @DisplayName("200 - 모임 상세 정보를 정상적으로 조회한다 (비회원 상태)") - void success_getDetails_nonMember() throws Exception { + void success_getPartyDetails_nonMember() throws Exception { // 모임에 가입하지 않은 새로운 유저 생성 및 인증 설정 Member nonMember = memberRepository.save(MemberFixture.createMember("비회원", Gender.MALE, Level.C, 2001L)); SecurityContextHelper.setAuthentication(nonMember.getId(), nonMember.getNickname()); @@ -407,7 +407,7 @@ void success_getDetails_nonMember() throws Exception { @Test @DisplayName("200 - 모임원인 경우 memberStatus가 MEMBER로 반환된다") - void success_getDetails_member() throws Exception { + void success_getPartyDetails_member() throws Exception { // manager는 setUp에서 이미 party의 멤버로 설정됨 SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); @@ -419,7 +419,7 @@ void success_getDetails_member() throws Exception { @Test @DisplayName("404 - 존재하지 않는 모임 조회 시 PARTY_NOT_FOUND 에러를 반환한다") - void fail_partyNotFound() throws Exception { + void fail_getPartyDetails_partyNotFound() throws Exception { mockMvc.perform(get("/api/parties/{partyId}", 9999L)) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); @@ -427,7 +427,7 @@ void fail_partyNotFound() throws Exception { @Test @DisplayName("400 - 삭제된 모임 조회 시 PARTY_IS_DELETED 에러를 반환한다") - void fail_partyDeleted() throws Exception { + void fail_getPartyDetails_partyDeleted() throws Exception { // 모임 삭제 (비활성화) party.delete(); partyRepository.save(party); @@ -685,7 +685,7 @@ class UpdateMemberRole { @Test @DisplayName("200 - 모임장이 일반 멤버를 부모임장으로 성공적으로 임명한다") - void success_assignSubManager() throws Exception { + void success_updateMemberRole() throws Exception { // given PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); @@ -704,7 +704,7 @@ void success_assignSubManager() throws Exception { @Test @DisplayName("403 - 모임장이 아닌 멤버가 역할 수정을 시도하면 INSUFFICIENT_PERMISSION 예외를 반환한다") - void fail_assignSubManager_notOwner() throws Exception { + void fail_updateMemberRole_notOwner() throws Exception { // given PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); // 일반 멤버가 권한 변경 시도 @@ -720,7 +720,7 @@ void fail_assignSubManager_notOwner() throws Exception { @Test @DisplayName("403 - 대상자가 모임장인 경우 권한 변경은 실패하며 CANNOT_ASSIGN_TO_OWNER 예외를 반환한다") - void fail_assignSubManager_targetIsOwner() throws Exception { + void fail_updateMemberRole_targetIsOwner() throws Exception { // given PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_MEMBER); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 89ae54686..454fd2318 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -89,7 +89,7 @@ class GetPartyMembers { @Test @DisplayName("성공 - 모임의 멤버들을 역할별로 성공적으로 조회한다.") - void success() { + void success_getPartyMembers() { // given Long partyId = 1L; Long currentMemberId = 10L; @@ -129,7 +129,7 @@ void success() { @Test @DisplayName("성공 - 멤버 목록과 마지막 운동일을 함께 반환한다") - void success_withExerciseHistory() { + void success_getPartyMembers_withExerciseHistory() { // given Long partyId = 1L; Long currentMemberId = 10L; @@ -175,7 +175,7 @@ void success_withExerciseHistory() { @Test @DisplayName("성공 - 운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") - void noExerciseHistory() { + void success_getPartyMembers_noExerciseHistory() { // given Long partyId = 1L; Long currentMemberId = 10L; @@ -203,7 +203,7 @@ void noExerciseHistory() { @Test @DisplayName("실패 - 존재하지 않는 파티면 PartyException을 던진다") - void partyNotFound() { + void fail_getPartyMembers_partyNotFound() { // given given(partyRepository.findById(99L)).willReturn(Optional.empty()); @@ -216,7 +216,7 @@ void partyNotFound() { @Test @DisplayName("실패 - 비활성화된 파티면 PartyException을 던진다") - void partyInactive() { + void fail_getPartyMembers_partyInactive() { // given PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); @@ -239,7 +239,7 @@ class GetMyParties { @Test @DisplayName("성공 - 내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") - void success() { + void success_getMyParties() { // given Long memberId = 10L; Pageable pageable = PageRequest.of(0, 10); @@ -285,7 +285,7 @@ class GetSimpleMyParties { @Test @DisplayName("성공 - 유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") - void success() { + void success_getSimpleMyParties() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -323,7 +323,7 @@ void success() { @Test @DisplayName("실패 - 존재하지 않는 회원일 경우 MemberException을 던진다") - void memberNotFound() { + void fail_getSimpleMyParties_memberNotFound() { // given Long invalidMemberId = 999L; Pageable pageable = PageRequest.of(0, 10); @@ -346,7 +346,7 @@ class GetRecommendedParties { @Test @DisplayName("성공 - Cockple 추천 모드 시 유저 정보(주소, 생년월일, 키워드)를 기반으로 추천 목록을 반환한다") - void success_cockpleRecommend() { + void success_getRecommendedParties_cockpleRecommend() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -385,7 +385,7 @@ void success_cockpleRecommend() { @Test @DisplayName("성공 - 필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") - void success_filterMode() { + void success_getRecommendedParties_filterMode() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -415,7 +415,7 @@ void success_filterMode() { @Test @DisplayName("실패 - 존재하지 않는 회원 ID로 추천 요청 시 MEMBER_NOT_FOUND이 발생한다") - void fail_memberNotFound() { + void fail_getRecommendedParties_memberNotFound() { // given Long memberId = 999L; Pageable pageable = PageRequest.of(0, 10); @@ -435,7 +435,7 @@ void fail_memberNotFound() { @Test @DisplayName("실패 - 대표 주소가 설정되지 않은 회원이 추천 요청 시 MAIN_ADDRESS_NULL이 발생한다") - void fail_mainAddressNotFound() { + void fail_getRecommendedParties_mainAddressNotFound() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -464,7 +464,7 @@ class GetPartyDetails { @Test @DisplayName("성공 - 모임 상세 정보를 정상적으로 조회한다 (비회원, 신청 전)") - void success_nonMember() { + void success_getPartyDetails_nonMember() { // given Long partyId = 1L; Long memberId = 10L; @@ -502,7 +502,7 @@ void success_nonMember() { @Test @DisplayName("성공 - 모임원인 경우 memberStatus가 MEMBER로 반환된다") - void success_member() { + void success_getPartyDetails_member() { // given Long partyId = 1L; Long memberId = 10L; @@ -535,7 +535,7 @@ void success_member() { @Test @DisplayName("실패 - 존재하지 않는 모임 조회 시 PARTY_NOT_FOUND이 발생한다") - void fail_partyNotFound() { + void fail_getPartyDetails_partyNotFound() { // given given(partyRepository.findById(999L)).willReturn(Optional.empty()); @@ -548,7 +548,7 @@ void fail_partyNotFound() { @Test @DisplayName("실패 - 삭제된 모임 조회 시 PARTY_IS_DELETED이 발생한다") - void fail_partyDeleted() { + void fail_getPartyDetails_partyDeleted() { // given PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Party party = PartyFixture.createParty("삭제된 모임", 11L, addr); From b7b3ad400bc2591b404d25b9d8ec09877c8f8787 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sat, 28 Mar 2026 23:28:04 +0900 Subject: [PATCH 45/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20(/api/parties/{partyId}/status)=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 48 ++++++++++ .../service/PartyCommandServiceTest.java | 93 +++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 80c268763..de1a810e8 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -572,6 +572,54 @@ void fail_updateParty_notOwner() throws Exception { } } + @Nested + @DisplayName("PATCH /api/parties/{partyId}/status - 모임 삭제") + class DeleteParty { + + @Test + @DisplayName("200 - 모임장이 모임을 성공적으로 삭제(비활성화)한다") + void success_deleteParty() throws Exception { + // given + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/status", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + Party deletedParty = partyRepository.findById(party.getId()).orElseThrow(); + assertThat(deletedParty.getStatus()).isEqualTo(umc.cockple.demo.domain.party.enums.PartyStatus.INACTIVE); + } + + @Test + @DisplayName("403 - 모임장이 아닌 멤버가 삭제를 시도하면 INSUFFICIENT_PERMISSION 예외를 반환한다") + void fail_deleteParty_notOwner() throws Exception { + // given + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/status", party.getId())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("400 - 이미 삭제된 모임을 다시 삭제 시도하면 PARTY_IS_DELETED 예외를 반환한다") + void fail_deleteParty_partyDeleted() throws Exception { + // given + party.delete(); // 상태 INACTIVE 변경 + partyRepository.save(party); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/status", party.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 0aeb85616..2ccc03a66 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -614,6 +614,99 @@ void fail_updateParty_insufficientPermission() { } } + @Nested + @DisplayName("deleteParty") + class DeleteParty { + + @Test + @DisplayName("성공 - 모임장이 모임을 정상적으로 삭제(비활성화)한다") + void success_deleteParty() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party party = PartyFixture.createParty("삭제할 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + + // when + partyCommandService.deleteParty(partyId, ownerId); + + // then + assertThat(party.getStatus()).isEqualTo(umc.cockple.demo.domain.party.enums.PartyStatus.INACTIVE); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 멤버가 모임 삭제를 시도할 경우 INSUFFICIENT_PERMISSION 발생") + void fail_deleteParty_notOwner() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long notOwnerId = 2L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Member notOwner = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, notOwnerId); + ReflectionTestUtils.setField(notOwner, "id", notOwnerId); + + Party party = PartyFixture.createParty("삭제할 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(notOwnerId)).willReturn(Optional.of(notOwner)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.deleteParty(partyId, notOwnerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 이미 삭제된 모임을 삭제하려고 시도할 경우 PARTY_IS_DELETED 발생") + void fail_deleteParty_partyDeleted() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party party = PartyFixture.createParty("이미 삭제된 모임", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + party.delete(); // 상태를 INACTIVE로 변경 + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.deleteParty(partyId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_IS_DELETED); + } + + @Test + @DisplayName("실패 - 조회된 모임이 존재하지 않을 경우 PARTY_NOT_FOUND 발생") + void fail_deleteParty_partyNotFound() { + // given + Long invalidId = 999L; + given(partyRepository.findById(invalidId)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.deleteParty(invalidId, 1L)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); + } + } + @Nested @DisplayName("updateMemberRole") class UpdateMemberRole { From 91997757ee147fe5f70b29becb9ab0f53d94310b Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 00:35:25 +0900 Subject: [PATCH 46/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=82=AD=EC=A0=9C(/api/parties/{partyId}/members/{?= =?UTF-8?q?memberId})=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 47 +++++++++ .../service/PartyCommandServiceTest.java | 97 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index de1a810e8..511cb0e50 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -620,6 +620,53 @@ void fail_deleteParty_partyDeleted() throws Exception { } } + @Nested + @DisplayName("DELETE /api/parties/{partyId}/members/{memberId} - 모임 멤버 삭제") + class RemoveMember { + + @Test + @DisplayName("200 - 모임장이 일반 멤버를 성공적으로 강퇴한다") + void success_removeMember() throws Exception { + // given + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", party.getId(), normalMember.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + boolean exists = memberPartyRepository.existsByPartyAndMember(party, normalMember); + assertThat(exists).isFalse(); + } + + @Test + @DisplayName("403 - 모임장이 아닌 멤버가 삭제를 시도하면 INSUFFICIENT_PERMISSION 에러를 반환한다") + void fail_removeMember_notOwner() throws Exception { + // given + Member someoneElse = memberRepository.save(MemberFixture.createMember("다른멤버", Gender.MALE, Level.B, 1010L)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, someoneElse, Role.party_MEMBER)); + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", party.getId(), someoneElse.getId())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("400 - 모임장이 자기 자신을 강퇴하려 할 경우 CANNOT_REMOVE_SELF 에러를 반환한다") + void fail_removeMember_selfAsManager() throws Exception { + // given + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", party.getId(), manager.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_REMOVE_SELF.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 2ccc03a66..c34eda4d8 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -811,4 +811,101 @@ void fail_updateMemberRole_notOwner() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } } + + @Nested + @DisplayName("removeMember") + class RemoveMember { + + @Test + @DisplayName("성공 - 모임장이 일반 멤버를 성공적으로 강퇴한다") + void success_removeMember() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long targetMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member targetMember = MemberFixture.createMember("타겟", Gender.MALE, Level.A, targetMemberId); + ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(ownerParty)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + + // when + partyCommandService.removeMember(partyId, targetMemberId, ownerId); + + // then + verify(memberPartyRepository, times(1)).delete(targetMemberParty); + verify(chatRoomService, times(1)).leavePartyChatRoom(partyId, targetMemberId); + } + + @Test + @DisplayName("실패 - 권한이 없는 멤버가 타인을 강퇴하려 하면 INSUFFICIENT_PERMISSION 발생") + void fail_removeMember_insufficientPermission() { + // given + Long partyId = 1L; + Long subManagerId = 2L; + Long targetOwnerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, targetOwnerId); + ReflectionTestUtils.setField(owner, "id", targetOwnerId); + Member subManager = MemberFixture.createMember("부모임장", Gender.MALE, Level.A, subManagerId); + ReflectionTestUtils.setField(subManager, "id", subManagerId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); + given(memberRepository.findById(targetOwnerId)).willReturn(Optional.of(owner)); + given(memberPartyRepository.findByPartyAndMember(party, subManager)).willReturn(Optional.of(subManagerParty)); + given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(ownerParty)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.removeMember(partyId, targetOwnerId, subManagerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 모임장이 자신을 강퇴하려 할 경우 CANNOT_REMOVE_SELF 발생") + void fail_removeMember_cannotRemoveSelf() { + // given + Long partyId = 1L; + Long ownerId = 1L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(ownerParty)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.removeMember(partyId, ownerId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_REMOVE_SELF); + } + } } From 614ddc31695252ff74210d4ddeb9ecaef40888aa Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 03:31:45 +0900 Subject: [PATCH 47/63] =?UTF-8?q?test:=20=EA=B0=80=EC=9E=85=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20(/api/par?= =?UTF-8?q?ties/{partyId}/join-requests)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 57 +++++++++++ .../party/service/PartyQueryServiceTest.java | 99 +++++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 511cb0e50..78411ca37 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -23,6 +23,7 @@ import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; @@ -667,6 +668,62 @@ void fail_removeMember_selfAsManager() throws Exception { } } + @Nested + @DisplayName("GET /api/parties/{partyId}/join-requests - 모임 가입 신청 조회") + class GetJoinRequests { + + @Test + @DisplayName("200 - 모임장이 가입 신청 목록을 정상적으로 조회한다") + void success_getJoinRequests() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("가입희망자", Gender.FEMALE, Level.B, 1010L)); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + partyJoinRequestRepository.save(joinRequest); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/join-requests", party.getId()) + .param("status", "PENDING") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content[0].userId").value(applicant.getId())); + } + + @Test + @DisplayName("403 - 모임장이 아닌 사용자가 조회하면 INSUFFICIENT_PERMISSION 예외가 반환된다") + void fail_getJoinRequests_notOwner() throws Exception { + // given + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/join-requests", party.getId()) + .param("status", "PENDING")) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("400 - 잘못된 상태값을 전달하면 INVALID_REQUEST_STATUS 예외가 반환된다") + void fail_getJoinRequests_invalidStatus() throws Exception { + // given + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/join-requests", party.getId()) + .param("status", "UNKNOWN_STATUS")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_REQUEST_STATUS.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 454fd2318..685f5a092 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -26,6 +26,7 @@ import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; @@ -564,4 +565,102 @@ void fail_getPartyDetails_partyDeleted() { .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); } } + + @Nested + @DisplayName("getJoinRequests") + class GetJoinRequests { + + @Test + @DisplayName("성공 - 모임장이 가입 신청 목록을 정상적으로 조회한다") + void success_getJoinRequests() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Pageable pageable = PageRequest.of(0, 10); + String status = "PENDING"; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", 100L); + + Slice requestSlice = new SliceImpl<>(List.of(joinRequest), pageable, false); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + given(partyJoinRequestRepository.findByPartyAndStatus(party, RequestStatus.PENDING, pageable)) + .willReturn(requestSlice); + + // when + Slice result = partyQueryService.getJoinRequests(partyId, ownerId, status, pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).joinRequestId()).isEqualTo(100L); + verify(partyJoinRequestRepository).findByPartyAndStatus(party, RequestStatus.PENDING, pageable); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 조회하면 INSUFFICIENT_PERMISSION 발생") + void fail_getJoinRequests_notOwner() { + // given + Long partyId = 1L; + Long nonOwnerId = 20L; + Pageable pageable = PageRequest.of(0, 10); + String status = "PENDING"; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member nonOwner = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, nonOwnerId); + ReflectionTestUtils.setField(nonOwner, "id", nonOwnerId); + MemberParty nonOwnerParty = MemberFixture.createMemberParty(party, nonOwner, Role.party_MEMBER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + + // when & then + assertThatThrownBy(() -> partyQueryService.getJoinRequests(partyId, nonOwnerId, status, pageable)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION)); + } + + @Test + @DisplayName("실패 - 잘못된 상태값을 입력하면 INVALID_REQUEST_STATUS 발생") + void fail_getJoinRequests_invalidStatus() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Pageable pageable = PageRequest.of(0, 10); + String invalidStatus = "UNKNOWN"; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + + // when & then + assertThatThrownBy(() -> partyQueryService.getJoinRequests(partyId, ownerId, invalidStatus, pageable)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.INVALID_REQUEST_STATUS)); + } + } } From e980632bfc8ea4a03e4df8078c2af02553f1ce41 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 03:58:11 +0900 Subject: [PATCH 48/63] =?UTF-8?q?test:=20=EA=B0=80=EC=9E=85=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=B2=98=EB=A6=AC=20(/api/parties/{partyId}/join-r?= =?UTF-8?q?equests/{requestId})=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 117 ++++++++++++- .../service/PartyCommandServiceTest.java | 159 ++++++++++++++++++ 2 files changed, 273 insertions(+), 3 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 78411ca37..11c488cf2 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -25,10 +25,12 @@ import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; @@ -45,6 +47,7 @@ import java.time.LocalDate; import java.util.List; +import org.springframework.http.MediaType; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -490,7 +493,6 @@ void fail_createJoinRequest_genderMismatch() throws Exception { .joinPrice(0) .build()); - // 남성 사용자로 신청 시도 SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); mockMvc.perform(post("/api/parties/{partyId}/join-requests", womenParty.getId())) @@ -525,7 +527,7 @@ void success_updateParty() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")); - // DB 검증 + // 검증 Party updatedParty = partyRepository.findById(party.getId()).orElseThrow(); assertThat(updatedParty.getDesignatedCock()).isEqualTo("수정된 콕"); assertThat(updatedParty.getJoinPrice()).isEqualTo(2000); @@ -724,6 +726,115 @@ void fail_getJoinRequests_invalidStatus() throws Exception { } } + @Nested + @DisplayName("PATCH /api/parties/{partyId}/join-requests/{requestId} - 모임 가입 신청 처리") + class ActionJoinRequest { + + @Test + @DisplayName("200 - 모임장이 가입 신청을 성공적으로 승인한다") + void success_actionJoinRequest_approve() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1020L)); + + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", party.getId(), joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + PartyJoinRequest updatedRequest = partyJoinRequestRepository.findById(joinRequest.getId()).orElseThrow(); + assertThat(updatedRequest.getStatus()).isEqualTo(RequestStatus.APPROVED); + boolean isMember = memberPartyRepository.existsByPartyAndMember(party, applicant); + assertThat(isMember).isTrue(); + } + + @Test + @DisplayName("200 - 모임장이 가입 신청을 성공적으로 거절한다") + void success_actionJoinRequest_reject() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("탈락자", Gender.FEMALE, Level.B, 1030L)); + + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.REJECT); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", party.getId(), joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + PartyJoinRequest updatedRequest = partyJoinRequestRepository.findById(joinRequest.getId()).orElseThrow(); + assertThat(updatedRequest.getStatus()).isEqualTo(RequestStatus.REJECTED); + boolean isMember = memberPartyRepository.existsByPartyAndMember(party, applicant); + assertThat(isMember).isFalse(); + } + + @Test + @DisplayName("403 - 모임장이 아닌 사용자가 가입 신청을 처리하려 하면 INSUFFICIENT_PERMISSION 발생") + void fail_actionJoinRequest_notOwner() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1040L)); + + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", party.getId(), joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("409 - 이미 처리된 가입 신청을 다시 처리하려 할 때 JOIN_REQUEST_ALREADY_ACTIONS 상태 반환") + void fail_actionJoinRequest_alreadyHandled() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1050L)); + + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.APPROVED) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", party.getId(), joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS.getCode())); + } + } + @Nested @DisplayName("POST /api/parties - 모임 생성") class CreateParty { @@ -758,7 +869,7 @@ void success_createParty() throws Exception { .andExpect(jsonPath("$.code").value("COMMON201")) .andExpect(jsonPath("$.data.partyId").exists()); - // DB 검증 + // 검증 List parties = partyRepository.findAll(); Party createdParty = parties.stream() .filter(p -> p.getPartyName().equals("새로운 통합 모임")) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index c34eda4d8..fc6f11a20 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -23,6 +23,7 @@ import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; import umc.cockple.demo.domain.party.exception.PartyErrorCode; @@ -47,6 +48,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; @ExtendWith(MockitoExtension.class) class PartyCommandServiceTest { @@ -908,4 +910,161 @@ void fail_removeMember_cannotRemoveSelf() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_REMOVE_SELF); } } + + @Nested + @DisplayName("actionJoinRequest") + class ActionJoinRequest { + + @Test + @DisplayName("성공 - 모임장이 가입 신청을 승인하고 알림과 채팅방 진입, 이벤트를 발생시킨다") + void success_actionJoinRequest_approve() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + given(memberPartyRepository.existsByPartyAndMember(party, applicant)).willReturn(false); + + // when + partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId); + + // then + assertThat(joinRequest.getStatus()).isEqualTo(RequestStatus.APPROVED); + verify(chatRoomService).joinPartyChatRoom(partyId, applicant); + verify(applicationEventPublisher).publishEvent(any(umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent.class)); + verify(notificationCommandService).createNotification(any()); + } + + @Test + @DisplayName("성공 - 모임장이 가입 신청을 거절하면 상태만 REJECTED로 바뀌고 다른 사이드이펙트가 발생하지 않는다") + void success_actionJoinRequest_reject() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.REJECT); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + given(memberPartyRepository.existsByPartyAndMember(party, applicant)).willReturn(false); + + // when + partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId); + + // then + assertThat(joinRequest.getStatus()).isEqualTo(RequestStatus.REJECTED); + verifyNoInteractions(chatRoomService); + verifyNoInteractions(applicationEventPublisher); + verifyNoInteractions(notificationCommandService); + } + + @Test + @DisplayName("실패 - 대상자가 이미 모임 멤버인 경우 ALREADY_MEMBER 검증 에러가 발생한다") + void fail_actionJoinRequest_alreadyMember() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + given(memberPartyRepository.existsByPartyAndMember(party, applicant)).willReturn(true); // 이미 멤버 + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.ALREADY_MEMBER); + } + + @Test + @DisplayName("실패 - 이미 처리된 가입 신청을 다시 처리하려 할 때 JOIN_REQUEST_ALREADY_ACTIONS 발생") + void fail_actionJoinRequest_alreadyActions() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.APPROVED) // 이미 승인됨 + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.REJECT); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + given(memberPartyRepository.existsByPartyAndMember(party, applicant)).willReturn(false); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS); + } + } } From d940c4704883409bf53ca483b469250bd80578d6 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 04:04:29 +0900 Subject: [PATCH 49/63] =?UTF-8?q?test:=20=EA=B0=80=EC=9E=85=20=EC=8A=B9?= =?UTF-8?q?=EC=9D=B8=20=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20(/api/par?= =?UTF-8?q?ties/{partyId}/join-requests)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 25 ++++++++++++ .../party/service/PartyQueryServiceTest.java | 39 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 11c488cf2..29513f935 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -699,6 +699,31 @@ void success_getJoinRequests() throws Exception { .andExpect(jsonPath("$.data.content[0].userId").value(applicant.getId())); } + @Test + @DisplayName("200 - 모임장이 가입 승인된 멤버 목록(APPROVED)을 정상적으로 조회한다") + void success_getJoinRequests_approved() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("승인된멤버", Gender.MALE, Level.C, 1015L)); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.APPROVED) + .build(); + partyJoinRequestRepository.save(joinRequest); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/join-requests", party.getId()) + .param("status", "APPROVED") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content[0].userId").value(applicant.getId())); + } + @Test @DisplayName("403 - 모임장이 아닌 사용자가 조회하면 INSUFFICIENT_PERMISSION 예외가 반환된다") void fail_getJoinRequests_notOwner() throws Exception { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 685f5a092..722f0590e 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -611,6 +611,45 @@ void success_getJoinRequests() { verify(partyJoinRequestRepository).findByPartyAndStatus(party, RequestStatus.PENDING, pageable); } + @Test + @DisplayName("성공 - 모임장이 가입 승인된 멤버 목록(APPROVED)을 정상적으로 조회한다") + void success_getJoinRequests_approved() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Pageable pageable = PageRequest.of(0, 10); + String status = "APPROVED"; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.APPROVED) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", 101L); + + Slice requestSlice = new SliceImpl<>(List.of(joinRequest), pageable, false); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + given(partyJoinRequestRepository.findByPartyAndStatus(party, RequestStatus.APPROVED, pageable)) + .willReturn(requestSlice); + + // when + Slice result = partyQueryService.getJoinRequests(partyId, ownerId, status, pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).joinRequestId()).isEqualTo(101L); + verify(partyJoinRequestRepository).findByPartyAndStatus(party, RequestStatus.APPROVED, pageable); + } + @Test @DisplayName("실패 - 모임장이 아닌 사용자가 조회하면 INSUFFICIENT_PERMISSION 발생") void fail_getJoinRequests_notOwner() { From 205f84e68a2a6b12a4e9e8ae1ab37629f57d126a Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 04:24:42 +0900 Subject: [PATCH 50/63] =?UTF-8?q?test:=20=EC=8B=A0=EA=B7=9C=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=B6=94=EC=B2=9C=EB=B0=9B=EA=B8=B0=20(/api/partie?= =?UTF-8?q?s/{partyId}/members/suggestions)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 42 +++++++++++++++ .../party/service/PartyQueryServiceTest.java | 52 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 29513f935..2dc7b68c8 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -751,6 +751,48 @@ void fail_getJoinRequests_invalidStatus() throws Exception { } } + @Nested + @DisplayName("GET /api/parties/{partyId}/members/suggestions - 신규 멤버 추천받기") + class GetRecommendedMembers { + + @Test + @DisplayName("200 - 신규 멤버 추천 목록을 정상적으로 조회한다") + void success_getRecommendedMembers() throws Exception { + // given + Member suggestedMember = memberRepository.save(MemberFixture.createMember("추천회원", Gender.MALE, Level.B, 1080L)); + + // Note: The logic inside memberRepository.findRecommendedMembers determines the slice to return. + // In integration tests with real DB queries, we insert the member so that they are picked up. + // But since this varies highly depending on algorithms and other data, we just verify the route and response format. + // If the algorithm returns no elements because of specific conditions, testing size==0 or size>0 is fine. + // Let's simply test that the request yields a 200 OK and valid COMMON200 code. + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/members/suggestions", party.getId()) + .param("levelSearch", "B") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()); // 배열 형식인지 확인 + } + + @Test + @DisplayName("404 - 존재하지 않는 모임의 추천 멤버를 조회하면 PARTY_NOT_FOUND 예외 발생") + void fail_getRecommendedMembers_partyNotFound() throws Exception { + // given + Long invalidPartyId = 9999L; + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(get("/api/parties/{partyId}/members/suggestions", invalidPartyId)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + } + @Nested @DisplayName("PATCH /api/parties/{partyId}/join-requests/{requestId} - 모임 가입 신청 처리") class ActionJoinRequest { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 722f0590e..7c82bfaa9 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -46,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; @@ -702,4 +703,55 @@ void fail_getJoinRequests_invalidStatus() { .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.INVALID_REQUEST_STATUS)); } } + + @Nested + @DisplayName("getRecommendedMembers") + class GetRecommendedMembers { + + @Test + @DisplayName("성공 - 조건에 맞는 추천 멤버 목록을 정상적으로 조회한다") + void success_getRecommendedMembers() { + // given + Long partyId = 1L; + String levelSearch = "B"; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", 1L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member suggestedMember = MemberFixture.createMember("추천회원", Gender.MALE, Level.B, 20L); + ReflectionTestUtils.setField(suggestedMember, "id", 20L); + + Slice membersSlice = new SliceImpl<>(List.of(suggestedMember), pageable, false); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findRecommendedMembers(party, levelSearch, pageable)) + .willReturn(membersSlice); + + // when + Slice result = partyQueryService.getRecommendedMembers(partyId, levelSearch, pageable); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).userId()).isEqualTo(20L); + verify(memberRepository).findRecommendedMembers(party, levelSearch, pageable); + } + + @Test + @DisplayName("실패 - 존재하지 않는 모임의 추천 멤버를 조회하면 PARTY_NOT_FOUND 발생") + void fail_getRecommendedMembers_partyNotFound() { + // given + Long partyId = 999L; + String levelSearch = null; + Pageable pageable = PageRequest.of(0, 10); + + given(partyRepository.findById(partyId)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyQueryService.getRecommendedMembers(partyId, levelSearch, pageable)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); + } + } } From df5b3ca3a5d4da04e0db87c6fbae6183635b6b6a Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 06:43:45 +0900 Subject: [PATCH 51/63] =?UTF-8?q?test:=20=EC=8B=A0=EA=B7=9C=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=B4=88=EB=8C=80=20=EB=B3=B4=EB=82=B4=EA=B8=B0(/a?= =?UTF-8?q?pi/parties/{partyId}/invitations)=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 110 ++++++++++++++-- .../service/PartyCommandServiceTest.java | 124 ++++++++++++++++++ 2 files changed, 223 insertions(+), 11 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 2dc7b68c8..75ee8dfcd 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -23,8 +23,10 @@ import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyInviteCreateDTO; import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; @@ -34,6 +36,7 @@ import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; +import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; @@ -81,6 +84,8 @@ class PartyIntegrationTest extends IntegrationTestBase { @Autowired PartyJoinRequestRepository partyJoinRequestRepository; @Autowired + PartyInvitationRepository partyInvitationRepository; + @Autowired ObjectMapper objectMapper; private Member manager; @@ -756,27 +761,39 @@ void fail_getJoinRequests_invalidStatus() throws Exception { class GetRecommendedMembers { @Test - @DisplayName("200 - 신규 멤버 추천 목록을 정상적으로 조회한다") + @DisplayName("200 - 추천 조건(지역/나이/급수)에 맞는 멤버가 추천 목록에 포함된다") void success_getRecommendedMembers() throws Exception { // given - Member suggestedMember = memberRepository.save(MemberFixture.createMember("추천회원", Gender.MALE, Level.B, 1080L)); - - // Note: The logic inside memberRepository.findRecommendedMembers determines the slice to return. - // In integration tests with real DB queries, we insert the member so that they are picked up. - // But since this varies highly depending on algorithms and other data, we just verify the route and response format. - // If the algorithm returns no elements because of specific conditions, testing size==0 or size>0 is fine. - // Let's simply test that the request yields a 200 OK and valid COMMON200 code. - + // party의 추천 조건: addr1=서울특별시, minBirthYear=1990, maxBirthYear=2005 + // party에 남성 A급 레벨 추가 + party.addLevel(Gender.MALE, Level.A); + partyRepository.save(party); + + // 추천 조건을 모두 만족하는 멤버: 남성, A급, 생년 1995, 서울특별시 주소(isMain=true) + Member suggestedMember = memberRepository.save( + MemberFixture.createMember("추천회원", Gender.MALE, Level.A, 1080L, LocalDate.of(1995, 6, 1)) + ); + memberAddrRepository.save(MemberAddr.builder() + .member(suggestedMember) + .addr1("서울특별시") + .addr2("강남구") + .addr3("역삼동") + .streetAddr("테헤란로") + .latitude(37.5) + .longitude(127.0) + .isMain(true) + .build()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); // when & then mockMvc.perform(get("/api/parties/{partyId}/members/suggestions", party.getId()) - .param("levelSearch", "B") .param("page", "0") .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) - .andExpect(jsonPath("$.data.content").isArray()); // 배열 형식인지 확인 + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].userId").value(suggestedMember.getId())); } @Test @@ -793,6 +810,77 @@ void fail_getRecommendedMembers_partyNotFound() throws Exception { } } + @Nested + @DisplayName("POST /api/parties/{partyId}/invitations - 신규 멤버 초대 보내기") + class CreateInvitation { + + @Test + @DisplayName("200 - 모임장이 새로운 멤버를 초대하고 invitationId를 반환한다") + void success_createInvitation() throws Exception { + // given + Member newMember = memberRepository.save(MemberFixture.createMember("새멤버", Gender.FEMALE, Level.B, 1090L)); + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(newMember.getId()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON201")) + .andExpect(jsonPath("$.data.invitationId").exists()); + } + + @Test + @DisplayName("403 - 모임장이 아닌 사용자가 초대하면 INSUFFICIENT_PERMISSION 발생") + void fail_createInvitation_notOwner() throws Exception { + // given + Member newMember = memberRepository.save(MemberFixture.createMember("새멤버", Gender.FEMALE, Level.B, 1091L)); + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(newMember.getId()); + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("409 - 이미 모임 멤버인 사람을 초대하면 ALREADY_MEMBER 발생") + void fail_createInvitation_alreadyMember() throws Exception { + // given - normalMember는 setUp()에서 이미 모임 멤버로 추가된 상태 + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(normalMember.getId()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.ALREADY_MEMBER.getCode())); + } + + @Test + @DisplayName("409 - 이미 대기 중인 초대가 있는 멤버를 중복 초대하면 INVITATION_ALREADY_EXISTS 발생") + void fail_createInvitation_duplicateInvitation() throws Exception { + // given + Member newMember = memberRepository.save(MemberFixture.createMember("새멤버", Gender.FEMALE, Level.B, 1092L)); + partyInvitationRepository.save(PartyInvitation.create(party, manager, newMember)); + + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(newMember.getId()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVITATION_ALREADY_EXISTS.getCode())); + } + } + @Nested @DisplayName("PATCH /api/parties/{partyId}/join-requests/{requestId} - 모임 가입 신청 처리") class ActionJoinRequest { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index fc6f11a20..295730132 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -20,6 +20,7 @@ import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; +import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; @@ -30,6 +31,7 @@ import umc.cockple.demo.domain.party.exception.PartyException; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; +import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -73,6 +75,8 @@ class PartyCommandServiceTest { @Mock private PartyJoinRequestRepository partyJoinRequestRepository; @Mock + private PartyInvitationRepository partyInvitationRepository; + @Mock private FileService fileService; private PartyConverter partyConverter; @@ -1067,4 +1071,124 @@ void fail_actionJoinRequest_alreadyActions() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS); } } + + @Nested + @DisplayName("createInvitation") + class CreateInvitation { + + @Test + @DisplayName("성공 - 모임장이 새로운 멤버를 초대하고 invitationId를 반환한다") + void success_createInvitation() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + given(partyInvitationRepository.existsByPartyAndInviteeAndStatus(party, invitee, RequestStatus.PENDING)).willReturn(false); + + PartyInvitation savedInvitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(savedInvitation, "id", 100L); + given(partyInvitationRepository.save(any())).willReturn(savedInvitation); + + // when + PartyInviteCreateDTO.Response response = partyCommandService.createInvitation(partyId, inviteeId, ownerId); + + // then + assertThat(response.invitationId()).isEqualTo(100L); + verify(notificationCommandService).createNotification(any()); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 초대하려 하면 INSUFFICIENT_PERMISSION 발생") + void fail_createInvitation_notOwner() { + // given + Long partyId = 1L; + Long nonOwnerId = 99L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member nonOwner = MemberFixture.createMember("일반멤버", Gender.MALE, Level.B, nonOwnerId); + ReflectionTestUtils.setField(nonOwner, "id", nonOwnerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", 10L, addr); // ownerId = 10L + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(nonOwnerId)).willReturn(Optional.of(nonOwner)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createInvitation(partyId, inviteeId, nonOwnerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 이미 모임에 가입한 멤버를 초대하면 ALREADY_MEMBER 발생") + void fail_createInvitation_alreadyMember() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("대상멤버", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(true); // 이미 멤버 + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createInvitation(partyId, inviteeId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.ALREADY_MEMBER); + } + + @Test + @DisplayName("실패 - 이미 대기 중인 초대가 있는 멤버를 중복 초대하면 INVITATION_ALREADY_EXISTS 발생") + void fail_createInvitation_duplicateInvitation() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("대상멤버", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + given(partyInvitationRepository.existsByPartyAndInviteeAndStatus(party, invitee, RequestStatus.PENDING)).willReturn(true); // 이미 대기중 초대 + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.createInvitation(partyId, inviteeId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_EXISTS); + } + } } From 7ff49280e12b9ec18a6c80c44d73b3f1e21a247e Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 17:26:33 +0900 Subject: [PATCH 52/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=B4=88?= =?UTF-8?q?=EB=8C=80=20=EC=B2=98=EB=A6=AC(/api/parties/invitations/{invita?= =?UTF-8?q?tionId})=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 104 +++++++++++++ .../service/PartyCommandServiceTest.java | 142 ++++++++++++++++++ 2 files changed, 246 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 75ee8dfcd..87dc686de 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -27,6 +27,7 @@ import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.PartyCreateDTO; import umc.cockple.demo.domain.party.dto.PartyInviteCreateDTO; +import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; @@ -881,6 +882,109 @@ void fail_createInvitation_duplicateInvitation() throws Exception { } } + @Nested + @DisplayName("PATCH /api/parties/invitations/{invitationId} - 모임 초대 처리") + class ActionInvitation { + + @Test + @DisplayName("200 - 초대받은 멤버가 승인하면 모임 멤버로 추가된다") + void success_actionInvitation_approve() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1100L)); + + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(invitee.getId(), invitee.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + PartyInvitation updated = partyInvitationRepository.findById(invitation.getId()).orElseThrow(); + assertThat(updated.getStatus()).isEqualTo(RequestStatus.APPROVED); + assertThat(memberPartyRepository.existsByPartyAndMember(party, invitee)).isTrue(); + } + + @Test + @DisplayName("200 - 초대받은 멤버가 거절하면 상태가 REJECTED로 바뀌고 멤버로 추가되지 않는다") + void success_actionInvitation_reject() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1101L)); + + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.REJECT); + SecurityContextHelper.setAuthentication(invitee.getId(), invitee.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 + PartyInvitation updated = partyInvitationRepository.findById(invitation.getId()).orElseThrow(); + assertThat(updated.getStatus()).isEqualTo(RequestStatus.REJECTED); + assertThat(memberPartyRepository.existsByPartyAndMember(party, invitee)).isFalse(); + } + + @Test + @DisplayName("403 - 초대받은 사람이 아닌 제3자가 처리하면 NOT_YOUR_INVITATION 발생") + void fail_actionInvitation_notYourInvitation() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1102L)); + + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + // normalMember는 초대받은 사람이 아님 + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.NOT_YOUR_INVITATION.getCode())); + } + + @Test + @DisplayName("409 - 이미 처리된 초대를 다시 처리하면 INVITATION_ALREADY_ACTIONS 발생") + void fail_actionInvitation_alreadyActions() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1103L)); + + // 이미 APPROVED 처리된 초대 + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + invitation.updateStatus(RequestStatus.APPROVED); + partyInvitationRepository.save(invitation); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.REJECT); + SecurityContextHelper.setAuthentication(invitee.getId(), invitee.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVITATION_ALREADY_ACTIONS.getCode())); + } + } + @Nested @DisplayName("PATCH /api/parties/{partyId}/join-requests/{requestId} - 모임 가입 신청 처리") class ActionJoinRequest { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 295730132..48b829961 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -23,6 +23,7 @@ import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.dto.*; +import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; @@ -1191,4 +1192,145 @@ void fail_createInvitation_duplicateInvitation() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_EXISTS); } } + + @Nested + @DisplayName("actionInvitation") + class ActionInvitation { + + @Test + @DisplayName("성공 - 초대받은 멤버가 승인하면 모임 멤버로 추가되고 알림이 발생한다") + void success_actionInvitation_approve() { + // given + Long invitationId = 100L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(invitation, "id", invitationId); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + + // when + partyCommandService.actionInvitation(inviteeId, request, invitationId); + + // then + assertThat(invitation.getStatus()).isEqualTo(RequestStatus.APPROVED); + verify(chatRoomService).joinPartyChatRoom(party.getId(), invitee); + verify(applicationEventPublisher).publishEvent(any(PartyMemberJoinedEvent.class)); + verify(notificationCommandService).createNotification(any()); + } + + @Test + @DisplayName("성공 - 초대받은 멤버가 거절하면 상태만 REJECTED로 바뀌고 사이드이펙트가 없다") + void success_actionInvitation_reject() { + // given + Long invitationId = 100L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(invitation, "id", invitationId); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.REJECT); + + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + + // when + partyCommandService.actionInvitation(inviteeId, request, invitationId); + + // then + assertThat(invitation.getStatus()).isEqualTo(RequestStatus.REJECTED); + verifyNoInteractions(chatRoomService); + verifyNoInteractions(applicationEventPublisher); + verifyNoInteractions(notificationCommandService); + } + + @Test + @DisplayName("실패 - 초대받은 사람이 아닌 제3자가 처리하려 하면 NOT_YOUR_INVITATION 발생") + void fail_actionInvitation_notYourInvitation() { + // given + Long invitationId = 100L; + Long ownerId = 10L; + Long inviteeId = 20L; + Long otherId = 99L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Member other = MemberFixture.createMember("제3자", Gender.MALE, Level.C, otherId); + ReflectionTestUtils.setField(other, "id", otherId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(invitation, "id", invitationId); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(otherId)).willReturn(Optional.of(other)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionInvitation(otherId, request, invitationId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.NOT_YOUR_INVITATION); + } + + @Test + @DisplayName("실패 - 이미 처리된 초대를 다시 처리하려 할 때 INVITATION_ALREADY_ACTIONS 발생") + void fail_actionInvitation_alreadyActions() { + // given + Long invitationId = 100L; + Long ownerId = 10L; + Long inviteeId = 20L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, inviteeId); + ReflectionTestUtils.setField(invitee, "id", inviteeId); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", 1L); + + // 이미 승인된 초대 + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + ReflectionTestUtils.setField(invitation, "id", invitationId); + invitation.updateStatus(RequestStatus.APPROVED); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.REJECT); + + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(inviteeId)).willReturn(Optional.of(invitee)); + given(memberPartyRepository.existsByPartyAndMember(party, invitee)).willReturn(false); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionInvitation(inviteeId, request, invitationId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_ACTIONS); + } + } } From 80976bbdbe90795f7d4ad1740623e79b59de0a0f Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 18:55:57 +0900 Subject: [PATCH 53/63] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(/api/parties/{partyId}/keywords)=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 57 ++++++++++++++ .../service/PartyCommandServiceTest.java | 74 ++++++++++++++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 87dc686de..31ba6d088 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -29,6 +29,7 @@ import umc.cockple.demo.domain.party.dto.PartyInviteCreateDTO; import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; +import umc.cockple.demo.domain.party.dto.PartyKeywordDTO; import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; import umc.cockple.demo.domain.party.enums.ActivityTime; @@ -1255,4 +1256,60 @@ void fail_updateMemberRole_targetIsOwner() throws Exception { .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER.getCode())); } } + + @Nested + @DisplayName("POST /api/parties/{partyId}/keywords - 키워드 추가") + class AddKeyword { + + @Test + @DisplayName("200 - 모임장이 유효한 키워드를 정상적으로 추가한다") + void success_addKeyword() throws Exception { + // given + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request( + List.of("친목", "가입비 무료") + ); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/keywords", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")); + + // 검증 - DB에 키워드가 실제로 저장됐는지 확인 + Party updatedParty = partyRepository.findById(party.getId()).orElseThrow(); + assertThat(updatedParty.getKeywords()).hasSize(2); + } + + @Test + @DisplayName("403 - 모임장이 아닌 사용자가 키워드를 추가하면 INSUFFICIENT_PERMISSION 발생") + void fail_addKeyword_notOwner() throws Exception { + // given + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("친목")); + SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/keywords", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); + } + + @Test + @DisplayName("400 - 유효하지 않은 키워드 문자열을 전달하면 INVALID_KEYWORD 발생") + void fail_addKeyword_invalidKeyword() throws Exception { + // given + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("존재하지않는키워드")); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(post("/api/parties/{partyId}/keywords", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_KEYWORD.getCode())); + } + } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 48b829961..b14b77ca6 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -742,9 +742,7 @@ void success_updateMemberRole() { given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(memberParty)); - // 만약 기존 부모임장이 있으면 해제하는 로직에 대한 빈 Optional 반환 given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)).willReturn(Optional.empty()); - // 알림 발송 시 파티 내 전체 멤버를 조회 given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(memberParty)); // when @@ -1333,4 +1331,76 @@ void fail_actionInvitation_alreadyActions() { assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_ACTIONS); } } + + @Nested + @DisplayName("addKeyword") + class AddKeyword { + + @Test + @DisplayName("성공 - 모임장이 유효한 키워드 목록을 모임에 추가한다") + void success_addKeyword() { + // given + Long partyId = 1L; + Long ownerId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request( + List.of("친목", "가입비 무료") + ); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + // when + partyCommandService.addKeyword(partyId, ownerId, request); + + // then + assertThat(party.getKeywords()).hasSize(2); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 키워드를 추가하면 INSUFFICIENT_PERMISSION 발생") + void fail_addKeyword_notOwner() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long nonOwnerId = 99L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("친목")); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.addKeyword(partyId, nonOwnerId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 유효하지 않은 키워드 문자열을 전달하면 INVALID_KEYWORD 발생") + void fail_addKeyword_invalidKeyword() { + // given + Long partyId = 1L; + Long ownerId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", ownerId, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("존재하지않는키워드")); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.addKeyword(partyId, ownerId, request)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVALID_KEYWORD); + } + } } From 6a97a94cabcefc2cc8f884ef466bb306ac31f513 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Sun, 29 Mar 2026 19:52:04 +0900 Subject: [PATCH 54/63] =?UTF-8?q?chore:=20=EB=AA=A8=EC=9E=84=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=20enum=20=EB=8C=80=EB=AC=B8=EC=9E=90=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ExerciseQueryService.java | 2 +- .../exercise/service/ExerciseValidator.java | 4 +- .../domain/member/domain/MemberParty.java | 8 +-- .../party/converter/PartyConverter.java | 6 +- .../party/exception/PartyErrorCode.java | 2 +- .../service/PartyCommandServiceImpl.java | 18 +++--- .../umc/cockple/demo/global/enums/Role.java | 6 +- .../integration/BookmarkIntegrationTest.java | 2 +- .../chat/integration/ChatIntegrationTest.java | 4 +- .../ExerciseCommandIntegrationTest.java | 8 +-- .../ExerciseQueryIntegrationTest.java | 20 +++---- ...ExerciseRecommendationIntegrationTest.java | 4 +- .../service/ExerciseLifecycleServiceTest.java | 24 ++++---- .../ExerciseParticipationServiceTest.java | 8 +-- .../service/ExerciseQueryServiceTest.java | 56 +++++++++---------- .../integration/MemberIntegrationTest.java | 12 ++-- .../service/MemberCommandServiceTest.java | 6 +- .../service/MemberQueryServiceTest.java | 4 +- .../integration/PartyIntegrationTest.java | 26 ++++----- .../service/PartyCommandServiceTest.java | 32 +++++------ .../party/service/PartyQueryServiceTest.java | 24 ++++---- 21 files changed, 137 insertions(+), 139 deletions(-) diff --git a/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryService.java b/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryService.java index 6876cee7a..adf7ae4b5 100644 --- a/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryService.java +++ b/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryService.java @@ -391,7 +391,7 @@ private void validatePartyIsActive(Party party) { private boolean checkManagerPermission(Party party, Member member) { return memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), member.getId(), Role.party_MANAGER); + party.getId(), member.getId(), Role.PARTY_MANAGER); } private ExerciseDetailDTO.ExerciseInfo createExerciseInfo(Exercise exercise) { diff --git a/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java b/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java index a81174b3f..fabda2ec8 100644 --- a/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java +++ b/src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java @@ -90,9 +90,9 @@ private void validatePartyIsActive(Party party) { private void validateSubManagerPermission(Long memberId, Party party) { boolean isOwner = party.getOwnerId().equals(memberId); boolean isManager = memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), memberId, Role.party_MANAGER); + party.getId(), memberId, Role.PARTY_MANAGER); boolean isSubManager = memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), memberId, Role.party_SUBMANAGER); + party.getId(), memberId, Role.PARTY_SUBMANAGER); if (!isOwner && !isManager && !isSubManager) throw new ExerciseException(ExerciseErrorCode.INSUFFICIENT_PERMISSION); diff --git a/src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java b/src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java index 8d3cc6794..cf85ec7cd 100644 --- a/src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java +++ b/src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java @@ -47,7 +47,7 @@ public static MemberParty createOwner(Member member, Party party) { return MemberParty.builder() .member(member) .party(party) - .role(Role.party_MANAGER) + .role(Role.PARTY_MANAGER) .joinedAt(LocalDateTime.now()) .status(ACTIVE) .build(); @@ -57,20 +57,20 @@ public static MemberParty create(Party party, Member member) { return MemberParty.builder() .member(member) .party(party) - .role(Role.party_MEMBER) + .role(Role.PARTY_MEMBER) .joinedAt(LocalDateTime.now()) .status(ACTIVE) .build(); } public boolean isLeader() { - if (this.role == Role.party_MANAGER) return true; + if (this.role == Role.PARTY_MANAGER) return true; return false; } public boolean isViceLeader() { - if (this.role == Role.party_SUBMANAGER) return true; + if (this.role == Role.PARTY_SUBMANAGER) return true; return false; } diff --git a/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java b/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java index 4f87a3a86..e331e6c68 100644 --- a/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java +++ b/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java @@ -211,9 +211,9 @@ public PartyMemberSuggestionDTO.Response toPartyMemberSuggestionDTO(Member membe private int getRolePriority(String role) { return switch (role) { - case "party_MANAGER" -> 0; // 모임장 역할 - case "party_SUBMANAGER" -> 1; // 부모임장 - case "party_MEMBER" -> 2; // 일반 멤버 + case "PARTY_MANAGER" -> 0; // 모임장 역할 + case "PARTY_SUBMANAGER" -> 1; // 부모임장 + case "PARTY_MEMBER" -> 2; // 일반 멤버 default -> 99; }; } diff --git a/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java b/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java index e844ab093..cbba931fd 100644 --- a/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java +++ b/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java @@ -24,7 +24,7 @@ public enum PartyErrorCode implements BaseErrorCode { INVALID_ORDER_TYPE(HttpStatus.BAD_REQUEST, "PARTY106", "유효하지 않은 정렬 기준입니다. (최신순, 오래된 순, 운동 많은 순 중 하나여야 합니다.)"), INVALID_KEYWORD(HttpStatus.BAD_REQUEST, "PARTY107", "유효하지 않은 키워드입니다."), MALE_LEVEL_NOT_NEEDED(HttpStatus.BAD_REQUEST, "PARTY108", "여복 모임은 남자 급수를 설정할 수 없습니다."), - INVALID_ROLE_VALUE(HttpStatus.BAD_REQUEST, "PARTY411", "유효하지 않은 역할 값입니다. (party_SUBMANAGER 또는 party_MEMBER를 입력해주세요.)"), + INVALID_ROLE_VALUE(HttpStatus.BAD_REQUEST, "PARTY411", "유효하지 않은 역할 값입니다. (PARTY_SUBMANAGER 또는 PARTY_MEMBER를 입력해주세요.)"), PARTY_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY201", "존재하지 않는 모임입니다."), JoinRequest_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY202", "존재하지 않는 가입신청입니다."), diff --git a/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java b/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java index f3624188a..948060ea1 100644 --- a/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java +++ b/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java @@ -190,7 +190,7 @@ public void updateMemberRole(Long partyId, Long targetMemberId, Long currentMemb // 모임장 권한 검증 validateOwnerPermission(party, currentMemberId); // 대상이 모임장인 경우 변경 불가 - if (targetMemberParty.getRole() == Role.party_MANAGER) { + if (targetMemberParty.getRole() == Role.PARTY_MANAGER) { throw new PartyException(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER); } // 이미 같은 역할인 경우 @@ -199,10 +199,10 @@ public void updateMemberRole(Long partyId, Long targetMemberId, Long currentMemb } // SUBOWNER 지정 시, 기존 부모임장 자동 해제 - if (newRole == Role.party_SUBMANAGER) { - memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER) + if (newRole == Role.PARTY_SUBMANAGER) { + memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER) .ifPresent(mp -> { - mp.changeRole(Role.party_MEMBER); + mp.changeRole(Role.PARTY_MEMBER); createRoleNotification(partyId, NotificationTarget.PARTY_SUBOWNER_RELEASED, mp.getMember().getNickname()); }); @@ -212,7 +212,7 @@ public void updateMemberRole(Long partyId, Long targetMemberId, Long currentMemb targetMemberParty.changeRole(newRole); // 알림 발송 (전체 멤버 대상) - NotificationTarget notifTarget = (newRole == Role.party_SUBMANAGER) + NotificationTarget notifTarget = (newRole == Role.PARTY_SUBMANAGER) ? NotificationTarget.PARTY_SUBOWNER_ASSIGNED : NotificationTarget.PARTY_SUBOWNER_RELEASED; createRoleNotification(partyId, notifTarget, targetMember.getNickname()); @@ -387,7 +387,7 @@ private void validateIsNotOwner(Party party, Long memberId) { // 부모임장은 권한이 없음을 검증 private void validateIsNotSubOwner(Party party, Long memberId) { - memberPartyRepository.findByPartyIdAndRole(party.getId(), Role.party_SUBMANAGER) + memberPartyRepository.findByPartyIdAndRole(party.getId(), Role.PARTY_SUBMANAGER) .ifPresent(mp -> { if (mp.getMember().getId().equals(memberId)) { throw new PartyException(PartyErrorCode.INVALID_ACTION_FOR_SUBOWNER); @@ -427,7 +427,7 @@ private void validateRemovalPermission(Party party, Member remover, MemberParty if (remover.getId().equals(memberPartyToRemove.getMember().getId())) { //부모임장인 경우에만 가능 MemberParty removerMemberParty = findMemberPartyOrThrow(party, remover); - if (removerMemberParty.getRole() == Role.party_SUBMANAGER) { + if (removerMemberParty.getRole() == Role.PARTY_SUBMANAGER) { return; } else { throw new PartyException(PartyErrorCode.CANNOT_REMOVE_SELF); @@ -439,11 +439,11 @@ private void validateRemovalPermission(Party party, Member remover, MemberParty Role removerRole = removerMemberParty.getRole(); Role targetRole = memberPartyToRemove.getRole(); //모임장은 모두 삭제 가능 - if (removerRole == Role.party_MANAGER) { + if (removerRole == Role.PARTY_MANAGER) { return; } //부모임장은 일반 멤버만 삭제 가능 (모임장을 삭제하려할 경우 권한 없음) - if (removerRole == Role.party_SUBMANAGER && targetRole == Role.party_MEMBER) { + if (removerRole == Role.PARTY_SUBMANAGER && targetRole == Role.PARTY_MEMBER) { return; } //일반 멤버는 권한 없음 diff --git a/src/main/java/umc/cockple/demo/global/enums/Role.java b/src/main/java/umc/cockple/demo/global/enums/Role.java index b347e011f..0daea6101 100644 --- a/src/main/java/umc/cockple/demo/global/enums/Role.java +++ b/src/main/java/umc/cockple/demo/global/enums/Role.java @@ -2,9 +2,9 @@ public enum Role { - party_MEMBER, //일반 멤버 + PARTY_MEMBER, //일반 멤버 - party_MANAGER, //모임장 + PARTY_MANAGER, //모임장 - party_SUBMANAGER //부모임장 + PARTY_SUBMANAGER //부모임장 } diff --git a/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java index 984dfb50f..8ffea1ca0 100644 --- a/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java @@ -63,7 +63,7 @@ void setUp() { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("경기도", "안산시")); bookmarkParty = partyRepository.save(PartyFixture.createParty("테스트 모임", member.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(bookmarkParty, member, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(bookmarkParty, member, Role.PARTY_MANAGER)); bookmarkExercise = exerciseRepository.save(Exercise.builder() .party(bookmarkParty) diff --git a/src/test/java/umc/cockple/demo/domain/chat/integration/ChatIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/chat/integration/ChatIntegrationTest.java index 0671cf4df..4d3643739 100644 --- a/src/test/java/umc/cockple/demo/domain/chat/integration/ChatIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/chat/integration/ChatIntegrationTest.java @@ -58,8 +58,8 @@ void setUp() { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); party = partyRepository.save(PartyFixture.createParty("배드민턴 모임", member.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.party_MANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, otherMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.PARTY_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, otherMember, Role.PARTY_MEMBER)); partyChatRoom = chatRoomRepository.save(ChatFixture.createPartyChatRoom(party)); directChatRoom = chatRoomRepository.save(ChatFixture.createDirectChatRoom()); diff --git a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseCommandIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseCommandIntegrationTest.java index fa378c12c..bcaef277e 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseCommandIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseCommandIntegrationTest.java @@ -70,9 +70,9 @@ void setUp() { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER)); } @AfterEach @@ -606,7 +606,7 @@ void notPartyMember_outsideNotAccepted() throws Exception { void ageNotAllowed() throws Exception { Member youngMember = memberRepository.save( MemberFixture.createMember("어린회원", Gender.MALE, Level.B, 4001L, LocalDate.of(2010, 1, 1))); - memberPartyRepository.save(MemberFixture.createMemberParty(party, youngMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, youngMember, Role.PARTY_MEMBER)); SecurityContextHelper.setAuthentication(youngMember.getId(), youngMember.getNickname()); diff --git a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseQueryIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseQueryIntegrationTest.java index 6e8b1b8e7..de9e789f5 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseQueryIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseQueryIntegrationTest.java @@ -2,7 +2,6 @@ import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import umc.cockple.demo.domain.bookmark.domain.ExerciseBookmark; @@ -39,7 +38,6 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.List; -import java.util.Map; import javax.sql.DataSource; @@ -79,9 +77,9 @@ void setUp() { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER)); } @AfterEach @@ -139,7 +137,7 @@ class Success { .andExpect(jsonPath("$.data.participants.list[0].gender").value("MALE")) .andExpect(jsonPath("$.data.participants.list[0].level").isString()) .andExpect(jsonPath("$.data.participants.list[0].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.participants.list[0].partyPosition").value("party_MEMBER")) + .andExpect(jsonPath("$.data.participants.list[0].partyPosition").value("PARTY_MEMBER")) .andExpect(jsonPath("$.data.participants.list[0].isWithdrawn").value(false)) .andExpect(jsonPath("$.data.waiting.currentWaitingCount").value(1)) .andExpect(jsonPath("$.data.waiting.manCount").value(0)) @@ -147,7 +145,7 @@ class Success { .andExpect(jsonPath("$.data.waiting.list[0].name").value(subManager.getMemberName())) .andExpect(jsonPath("$.data.waiting.list[0].gender").value("FEMALE")) .andExpect(jsonPath("$.data.waiting.list[0].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.waiting.list[0].partyPosition").value("party_SUBMANAGER")) + .andExpect(jsonPath("$.data.waiting.list[0].partyPosition").value("PARTY_SUBMANAGER")) .andExpect(jsonPath("$.data.waiting.list[0].isWithdrawn").value(false)); } @@ -277,13 +275,13 @@ class Success { .andExpect(status().isOk()) .andExpect(jsonPath("$.data.participants.list[0].name").value(manager.getMemberName())) .andExpect(jsonPath("$.data.participants.list[0].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.participants.list[0].partyPosition").value("party_MANAGER")) + .andExpect(jsonPath("$.data.participants.list[0].partyPosition").value("PARTY_MANAGER")) .andExpect(jsonPath("$.data.participants.list[1].name").value(subManager.getMemberName())) .andExpect(jsonPath("$.data.participants.list[1].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.participants.list[1].partyPosition").value("party_SUBMANAGER")) + .andExpect(jsonPath("$.data.participants.list[1].partyPosition").value("PARTY_SUBMANAGER")) .andExpect(jsonPath("$.data.participants.list[2].name").value(normalMember.getMemberName())) .andExpect(jsonPath("$.data.participants.list[2].participantType").value("PARTY_MEMBER")) - .andExpect(jsonPath("$.data.participants.list[2].partyPosition").value("party_MEMBER")) + .andExpect(jsonPath("$.data.participants.list[2].partyPosition").value("PARTY_MEMBER")) .andExpect(jsonPath("$.data.participants.list[3].name").value(outsider.getMemberName())) .andExpect(jsonPath("$.data.participants.list[3].participantType").value("EXTERNAL_PARTICIPANT")) .andExpect(jsonPath("$.data.participants.list[3].partyPosition").value(nullValue())) @@ -1501,7 +1499,7 @@ void setUp() { filteredParty = partyRepository.save(filteredParty); filteredParty.addLevel(Gender.MALE, Level.B); filteredParty = partyRepository.save(filteredParty); - memberPartyRepository.save(MemberFixture.createMemberParty(filteredParty, manager, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(filteredParty, manager, Role.PARTY_MANAGER)); startDate = LocalDate.of(2026, 3, 23); endDate = LocalDate.of(2026, 4, 5); diff --git a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseRecommendationIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseRecommendationIntegrationTest.java index cb51d226d..64e04dced 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseRecommendationIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/integration/ExerciseRecommendationIntegrationTest.java @@ -72,7 +72,7 @@ void setUp() { partyRepository.save(party); // 모임장을 모임 멤버로 등록 - memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER)); } @AfterEach @@ -155,7 +155,7 @@ class Success { void 소속된_모임의_운동은_추천되지_않는다() throws Exception { // given - outsider를 모임에 가입시킴 memberPartyRepository.save( - MemberFixture.createMemberParty(party, outsider, Role.party_MEMBER)); + MemberFixture.createMemberParty(party, outsider, Role.PARTY_MEMBER)); exerciseRepository.save(ExerciseFixture.createRecommendableExercise(party, LocalDate.now().plusDays(3), 37.5, 127.0, "테스트 체육관")); diff --git a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseLifecycleServiceTest.java b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseLifecycleServiceTest.java index af8355184..c317929bd 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseLifecycleServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseLifecycleServiceTest.java @@ -128,9 +128,9 @@ void subManagerCreatesExercise_success() { Member subManager = MemberFixture.createMember("부모임장", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(subManager, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_SUBMANAGER)) .willReturn(true); Exercise savedExercise = Exercise.builder() @@ -175,9 +175,9 @@ void normalMember_throwsException() { Member normalMember = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(normalMember, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_SUBMANAGER)) .willReturn(false); assertThatThrownBy(() -> @@ -278,9 +278,9 @@ void subManagerDeletesExercise_success() { Member subManager = MemberFixture.createMember("부모임장", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(subManager, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_SUBMANAGER)) .willReturn(true); // when @@ -303,9 +303,9 @@ void normalMember_throwsException() { Member normalMember = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(normalMember, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_SUBMANAGER)) .willReturn(false); assertThatThrownBy(() -> @@ -384,9 +384,9 @@ void subManagerUpdatesExercise_success() { Member subManager = MemberFixture.createMember("부모임장", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(subManager, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_SUBMANAGER)) .willReturn(true); Exercise savedExercise = Exercise.builder() @@ -418,9 +418,9 @@ void normalMember_throwsException() { Member normalMember = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L); ReflectionTestUtils.setField(normalMember, "id", 2L); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_SUBMANAGER)) .willReturn(false); assertThatThrownBy(() -> diff --git a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseParticipationServiceTest.java b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseParticipationServiceTest.java index 68ce83bee..62be82d77 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseParticipationServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseParticipationServiceTest.java @@ -341,9 +341,9 @@ void subManagerCancelsMemberParticipation_success() { ExerciseCancelDTO.ByManagerRequest request = new ExerciseCancelDTO.ByManagerRequest(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), subManager.getId(), Role.PARTY_SUBMANAGER)) .willReturn(true); given(memberRepository.findById(participant.getId())).willReturn(Optional.of(participant)); given(memberExerciseRepository.findByExerciseAndMember(exercise, participant)) @@ -392,9 +392,9 @@ void normalMember_throwsException() { ExerciseCancelDTO.ByManagerRequest request = new ExerciseCancelDTO.ByManagerRequest(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_MANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); - given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.party_SUBMANAGER)) + given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole(party.getId(), normalMember.getId(), Role.PARTY_SUBMANAGER)) .willReturn(false); assertThatThrownBy(() -> diff --git a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryServiceTest.java index 117fd8d8f..1a8c53480 100644 --- a/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/exercise/service/ExerciseQueryServiceTest.java @@ -142,7 +142,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); // when @@ -169,7 +169,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), subManager.getId(), Role.party_MANAGER)) + party.getId(), subManager.getId(), Role.PARTY_MANAGER)) .willReturn(false); // when @@ -196,7 +196,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), normalMember.getId(), Role.party_MANAGER)) + party.getId(), normalMember.getId(), Role.PARTY_MANAGER)) .willReturn(false); // when @@ -223,7 +223,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), outsider.getId(), Role.party_MANAGER)) + party.getId(), outsider.getId(), Role.PARTY_MANAGER)) .willReturn(false); // when @@ -253,7 +253,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(withdrawnMember.getId()))) @@ -278,7 +278,7 @@ class Success { MemberExercise memberExercise = MemberFixture.createMemberExercise(activeMember, exercise); - MemberParty memberParty = MemberFixture.createMemberParty(party, activeMember, Role.party_MEMBER); + MemberParty memberParty = MemberFixture.createMemberParty(party, activeMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -289,7 +289,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(activeMember.getId()))) @@ -321,7 +321,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of(guest)); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberRepository.findMemberNamesByIds(any())) .willReturn(Map.of(manager.getId(), "모임장")); @@ -366,9 +366,9 @@ class Success { ReflectionTestUtils.setField(guest, "id", 71L); ReflectionTestUtils.setField(guest, "createdAt", LocalDateTime.now().minusMinutes(1)); - MemberParty managerParty = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); - MemberParty memberParty = MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER); + MemberParty managerParty = MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER); + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); + MemberParty memberParty = MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -379,7 +379,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of(guest)); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(manager.getId(), subManager.getId(), normalMember.getId(), outsider.getId()))) @@ -398,9 +398,9 @@ class Success { ExerciseDetailDTO.ParticipantInfo::participantType, ExerciseDetailDTO.ParticipantInfo::partyPosition) .containsExactly( - tuple("모임장", "PARTY_MEMBER", "party_MANAGER"), - tuple("부모임장", "PARTY_MEMBER", "party_SUBMANAGER"), - tuple("일반멤버", "PARTY_MEMBER", "party_MEMBER"), + tuple("모임장", "PARTY_MEMBER", "PARTY_MANAGER"), + tuple("부모임장", "PARTY_MEMBER", "PARTY_SUBMANAGER"), + tuple("일반멤버", "PARTY_MEMBER", "PARTY_MEMBER"), tuple("외부회원", "EXTERNAL_PARTICIPANT", null), tuple("게스트", "GUEST", null) ); @@ -424,8 +424,8 @@ class Success { MemberExercise second = MemberFixture.createMemberExercise(secondMember, exercise); ReflectionTestUtils.setField(second, "createdAt", LocalDateTime.now()); - MemberParty firstParty = MemberFixture.createMemberParty(party, firstMember, Role.party_MEMBER); - MemberParty secondParty = MemberFixture.createMemberParty(party, secondMember, Role.party_MEMBER); + MemberParty firstParty = MemberFixture.createMemberParty(party, firstMember, Role.PARTY_MEMBER); + MemberParty secondParty = MemberFixture.createMemberParty(party, secondMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -436,7 +436,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(firstMember.getId(), secondMember.getId()))) @@ -468,7 +468,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of(guest)); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberRepository.findMemberNamesByIds(any())) .willReturn(Map.of(manager.getId(), "모임장")); @@ -500,8 +500,8 @@ class Success { MemberExercise second = MemberFixture.createMemberExercise(secondMember, exercise); ReflectionTestUtils.setField(second, "createdAt", LocalDateTime.now()); - MemberParty firstParty = MemberFixture.createMemberParty(party, firstMember, Role.party_MEMBER); - MemberParty secondParty = MemberFixture.createMemberParty(party, secondMember, Role.party_MEMBER); + MemberParty firstParty = MemberFixture.createMemberParty(party, firstMember, Role.PARTY_MEMBER); + MemberParty secondParty = MemberFixture.createMemberParty(party, secondMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -512,7 +512,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(firstMember.getId(), secondMember.getId()))) @@ -549,8 +549,8 @@ class Success { MemberExercise second = MemberFixture.createMemberExercise(femaleMember, exercise); ReflectionTestUtils.setField(second, "createdAt", LocalDateTime.now()); - MemberParty maleParty = MemberFixture.createMemberParty(party, maleMember, Role.party_MEMBER); - MemberParty femaleParty = MemberFixture.createMemberParty(party, femaleMember, Role.party_MEMBER); + MemberParty maleParty = MemberFixture.createMemberParty(party, maleMember, Role.PARTY_MEMBER); + MemberParty femaleParty = MemberFixture.createMemberParty(party, femaleMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -561,7 +561,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(maleMember.getId(), femaleMember.getId()))) @@ -594,8 +594,8 @@ class Success { MemberExercise femaleExercise = MemberFixture.createMemberExercise(femaleMember, exercise); ReflectionTestUtils.setField(femaleExercise, "createdAt", LocalDateTime.now()); - MemberParty maleParty = MemberFixture.createMemberParty(party, maleMember, Role.party_MEMBER); - MemberParty femaleParty = MemberFixture.createMemberParty(party, femaleMember, Role.party_MEMBER); + MemberParty maleParty = MemberFixture.createMemberParty(party, maleMember, Role.PARTY_MEMBER); + MemberParty femaleParty = MemberFixture.createMemberParty(party, femaleMember, Role.PARTY_MEMBER); given(exerciseRepository.findExerciseWithBasicInfo(exercise.getId())) .willReturn(Optional.of(exercise)); @@ -606,7 +606,7 @@ class Success { given(guestRepository.findByExerciseId(exercise.getId())) .willReturn(List.of()); given(memberPartyRepository.existsByPartyIdAndMemberIdAndRole( - party.getId(), manager.getId(), Role.party_MANAGER)) + party.getId(), manager.getId(), Role.PARTY_MANAGER)) .willReturn(true); given(memberPartyRepository.findMemberRolesByPartyAndMembers( party.getId(), List.of(maleMember.getId(), femaleMember.getId()))) diff --git a/src/test/java/umc/cockple/demo/domain/member/integration/MemberIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/member/integration/MemberIntegrationTest.java index bba271356..28d577edc 100644 --- a/src/test/java/umc/cockple/demo/domain/member/integration/MemberIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/member/integration/MemberIntegrationTest.java @@ -112,7 +112,7 @@ class Failure { void manager_cannotWithdraw() throws Exception { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); Party party = partyRepository.save(PartyFixture.createParty("테스트 모임", member.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.PARTY_MANAGER)); SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); @@ -127,7 +127,7 @@ void manager_cannotWithdraw() throws Exception { void subManager_cannotWithdraw() throws Exception { PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); Party party = partyRepository.save(PartyFixture.createParty("테스트 모임", member.getId(), addr)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.party_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, member, Role.PARTY_SUBMANAGER)); SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); @@ -204,8 +204,8 @@ class Success { .build()); // 모임 2개 - memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.party_MEMBER)); - memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.PARTY_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.PARTY_MEMBER)); SecurityContextHelper.setAuthentication(freshMember.getId(), freshMember.getNickname()); @@ -322,8 +322,8 @@ void getMyProfile_success() throws Exception { .build()); // 모임 2개, 운동 2개, 키워드 2개 - memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.party_MEMBER)); - memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.PARTY_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(null, freshMember, Role.PARTY_MEMBER)); memberExerciseRepository.save(MemberExercise.builder() .member(freshMember) diff --git a/src/test/java/umc/cockple/demo/domain/member/service/MemberCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/member/service/MemberCommandServiceTest.java index 290c391a4..f74620c03 100644 --- a/src/test/java/umc/cockple/demo/domain/member/service/MemberCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/member/service/MemberCommandServiceTest.java @@ -703,7 +703,7 @@ class Failure { void 활성_모임의_모임장이면_MANAGER_CANNOT_LEAVE_예외를_던진다() { // given MemberParty leaderParty = MemberParty.builder() - .role(Role.party_MANAGER) + .role(Role.PARTY_MANAGER) .status(MemberPartyStatus.ACTIVE) .joinedAt(LocalDateTime.now()) .build(); @@ -723,7 +723,7 @@ class Failure { void 활성_모임의_부모임장이면_SUBMANAGER_CANNOT_LEAVE_예외를_던진다() { // given MemberParty subManagerParty = MemberParty.builder() - .role(Role.party_SUBMANAGER) + .role(Role.PARTY_SUBMANAGER) .status(MemberPartyStatus.ACTIVE) .joinedAt(LocalDateTime.now()) .build(); @@ -743,7 +743,7 @@ class Failure { void 비활성_모임의_모임장이면_탈퇴가_가능하다() { // given: BANNED 상태의 모임이라면 탈퇴 검증을 통과해야 한다 MemberParty bannedParty = MemberParty.builder() - .role(Role.party_MANAGER) + .role(Role.PARTY_MANAGER) .status(MemberPartyStatus.BANNED) .joinedAt(LocalDateTime.now()) .build(); diff --git a/src/test/java/umc/cockple/demo/domain/member/service/MemberQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/member/service/MemberQueryServiceTest.java index b7c521268..9c1eb0a16 100644 --- a/src/test/java/umc/cockple/demo/domain/member/service/MemberQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/member/service/MemberQueryServiceTest.java @@ -125,8 +125,8 @@ class Success { @DisplayName("참여한_모임_수가_올바르게_반환된다") void 참여한_모임_수가_올바르게_반환된다() { // given - member.getMemberParties().add(MemberFixture.createMemberParty(null, member, umc.cockple.demo.global.enums.Role.party_MEMBER)); - member.getMemberParties().add(MemberFixture.createMemberParty(null, member, umc.cockple.demo.global.enums.Role.party_MEMBER)); + member.getMemberParties().add(MemberFixture.createMemberParty(null, member, umc.cockple.demo.global.enums.Role.PARTY_MEMBER)); + member.getMemberParties().add(MemberFixture.createMemberParty(null, member, umc.cockple.demo.global.enums.Role.PARTY_MEMBER)); given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 31ba6d088..d4e920fa9 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -117,8 +117,8 @@ void setUp() { party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); // 모임 멤버 생성 - memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER)); // 채팅방 생성 ChatRoom chatRoom = chatRoomRepository.save(ChatRoom.createPartyChatRoom(party)); @@ -143,7 +143,7 @@ class GetPartyMembers { void success_getPartyMembers() throws Exception { // 부모임장 추가 Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 1003L)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER)); // 운동 기록 추가 Exercise exercise = exerciseRepository.save(ExerciseFixture.createExercise(party, LocalDate.of(2025, 1, 10))); @@ -154,10 +154,10 @@ void success_getPartyMembers() throws Exception { .andExpect(jsonPath("$.data.summary.totalCount").value(3)) .andExpect(jsonPath("$.data.summary.maleCount").value(2)) .andExpect(jsonPath("$.data.summary.femaleCount").value(1)) - .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) + .andExpect(jsonPath("$.data.members[0].role").value("PARTY_MANAGER")) .andExpect(jsonPath("$.data.members[0].isMe").value(true)) - .andExpect(jsonPath("$.data.members[1].role").value("party_SUBMANAGER")) - .andExpect(jsonPath("$.data.members[2].role").value("party_MEMBER")) + .andExpect(jsonPath("$.data.members[1].role").value("PARTY_SUBMANAGER")) + .andExpect(jsonPath("$.data.members[2].role").value("PARTY_MEMBER")) .andExpect(jsonPath("$.data.members[2].lastExerciseDate").value("2025-01-10")); } @@ -220,7 +220,7 @@ void fail_leaveParty_owner() throws Exception { void fail_leaveParty_subOwner() throws Exception { // 부모임장 생성 및 가입 Member subManager = memberRepository.save(MemberFixture.createMember("부매니저", Gender.MALE, Level.A, 3001L)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER)); // 부모임장 세션으로 설정 SecurityContextHelper.setAuthentication(subManager.getId(), subManager.getNickname()); @@ -425,7 +425,7 @@ void success_getPartyDetails_member() throws Exception { mockMvc.perform(get("/api/parties/{partyId}", party.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.memberStatus").value("MEMBER")) - .andExpect(jsonPath("$.data.memberRole").value("party_MANAGER")); + .andExpect(jsonPath("$.data.memberRole").value("PARTY_MANAGER")); } @Test @@ -655,7 +655,7 @@ void success_removeMember() throws Exception { void fail_removeMember_notOwner() throws Exception { // given Member someoneElse = memberRepository.save(MemberFixture.createMember("다른멤버", Gender.MALE, Level.B, 1010L)); - memberPartyRepository.save(MemberFixture.createMemberParty(party, someoneElse, Role.party_MEMBER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, someoneElse, Role.PARTY_MEMBER)); SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); // when & then @@ -1210,7 +1210,7 @@ class UpdateMemberRole { @DisplayName("200 - 모임장이 일반 멤버를 부모임장으로 성공적으로 임명한다") void success_updateMemberRole() throws Exception { // given - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); // when & then @@ -1222,14 +1222,14 @@ void success_updateMemberRole() throws Exception { // 검증 MemberParty targetMemberParty = memberPartyRepository.findByPartyAndMember(party, normalMember).orElseThrow(); - assertThat(targetMemberParty.getRole()).isEqualTo(Role.party_SUBMANAGER); + assertThat(targetMemberParty.getRole()).isEqualTo(Role.PARTY_SUBMANAGER); } @Test @DisplayName("403 - 모임장이 아닌 멤버가 역할 수정을 시도하면 INSUFFICIENT_PERMISSION 예외를 반환한다") void fail_updateMemberRole_notOwner() throws Exception { // given - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); // 일반 멤버가 권한 변경 시도 SecurityContextHelper.setAuthentication(normalMember.getId(), normalMember.getNickname()); @@ -1245,7 +1245,7 @@ void fail_updateMemberRole_notOwner() throws Exception { @DisplayName("403 - 대상자가 모임장인 경우 권한 변경은 실패하며 CANNOT_ASSIGN_TO_OWNER 예외를 반환한다") void fail_updateMemberRole_targetIsOwner() throws Exception { // given - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_MEMBER); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); // when & then diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index b14b77ca6..df54ebbe9 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -108,7 +108,7 @@ void success_leaveParty() { Member member = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 10L); ReflectionTestUtils.setField(member, "id", memberId); - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.PARTY_MEMBER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); @@ -197,11 +197,11 @@ void fail_leaveParty_isSubOwner() { Member subManager = MemberFixture.createMember("부모임장", Gender.MALE, Level.A, 2L); ReflectionTestUtils.setField(subManager, "id", subManagerId); - MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); - given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)) + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER)) .willReturn(Optional.of(subManagerParty)); // when & then @@ -736,20 +736,20 @@ void success_updateMemberRole() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty memberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + MemberParty memberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(memberParty)); - given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.party_SUBMANAGER)).willReturn(Optional.empty()); + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER)).willReturn(Optional.empty()); given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(memberParty)); // when partyCommandService.updateMemberRole(partyId, targetMemberId, currentOwnerId, request); // then - assertThat(memberParty.getRole()).isEqualTo(Role.party_SUBMANAGER); + assertThat(memberParty.getRole()).isEqualTo(Role.PARTY_SUBMANAGER); verify(notificationCommandService, times(1)).createNotification(any()); } @@ -768,8 +768,8 @@ void fail_updateMemberRole_targetIsOwner() { ReflectionTestUtils.setField(party, "id", partyId); // 타겟이 이미 모임장 권한을 가짐 - MemberParty memberParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + MemberParty memberParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); @@ -803,8 +803,8 @@ void fail_updateMemberRole_notOwner() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); - PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.party_SUBMANAGER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(targetId)).willReturn(Optional.of(targetMember)); @@ -838,8 +838,8 @@ void success_removeMember() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); - MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.party_MEMBER); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); @@ -872,8 +872,8 @@ void fail_removeMember_insufficientPermission() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); - MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); @@ -901,7 +901,7 @@ void fail_removeMember_cannotRemoveSelf() { Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 7c82bfaa9..9813ea1ba 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -108,9 +108,9 @@ void success_getPartyMembers() { ReflectionTestUtils.setField(subManager, "id", 20L); ReflectionTestUtils.setField(normalMember, "id", 30L); - MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty mp2 = MemberFixture.createMemberParty(party, subManager, Role.party_SUBMANAGER); - MemberParty mp3 = MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER); + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); + MemberParty mp3 = MemberFixture.createMemberParty(party, normalMember, Role.PARTY_MEMBER); List memberParties = List.of(mp1, mp2, mp3); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); @@ -144,8 +144,8 @@ void success_getPartyMembers_withExerciseHistory() { ReflectionTestUtils.setField(manager, "id", 10L); ReflectionTestUtils.setField(member1, "id", 20L); - MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); - MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.PARTY_MEMBER); List memberParties = List.of(mp1, mp2); LocalDate lastDate = LocalDate.of(2025, 1, 10); @@ -187,7 +187,7 @@ void success_getPartyMembers_noExerciseHistory() { ReflectionTestUtils.setField(party, "id", partyId); Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); ReflectionTestUtils.setField(manager, "id", 10L); - MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.PARTY_MANAGER); List memberParties = List.of(mp); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); @@ -299,7 +299,7 @@ void success_getSimpleMyParties() { Party party = PartyFixture.createParty("테스트 모임", 10L, addr); ReflectionTestUtils.setField(party, "id", 10L); - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.PARTY_MEMBER); Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); @@ -515,11 +515,11 @@ void success_getPartyDetails_member() { Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1000L); ReflectionTestUtils.setField(member, "id", memberId); - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.party_MEMBER); + MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.PARTY_MEMBER); PartyDetailDTO.Response expected = PartyDetailDTO.Response.builder() .partyId(partyId) .memberStatus("MEMBER") - .memberRole("party_MEMBER") + .memberRole("PARTY_MEMBER") .build(); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); @@ -532,7 +532,7 @@ void success_getPartyDetails_member() { // then assertThat(result.memberStatus()).isEqualTo("MEMBER"); - assertThat(result.memberRole()).isEqualTo("party_MEMBER"); + assertThat(result.memberRole()).isEqualTo("PARTY_MEMBER"); } @Test @@ -666,7 +666,7 @@ void fail_getJoinRequests_notOwner() { Member nonOwner = MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, nonOwnerId); ReflectionTestUtils.setField(nonOwner, "id", nonOwnerId); - MemberParty nonOwnerParty = MemberFixture.createMemberParty(party, nonOwner, Role.party_MEMBER); + MemberParty nonOwnerParty = MemberFixture.createMemberParty(party, nonOwner, Role.PARTY_MEMBER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); @@ -692,7 +692,7 @@ void fail_getJoinRequests_invalidStatus() { Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); ReflectionTestUtils.setField(owner, "id", ownerId); - MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.party_MANAGER); + MemberParty ownerParty = MemberFixture.createMemberParty(party, owner, Role.PARTY_MANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); From cbe2c7806dbc09aca92c6c0c1ec9ab299081f869 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 10:52:13 +0900 Subject: [PATCH 55/63] =?UTF-8?q?chore:=20import=EB=AC=B8=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EC=A4=84=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 24 +++++++----------- .../service/PartyCommandServiceTest.java | 25 ++++++++----------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index d4e920fa9..f52ad5915 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; import umc.cockple.demo.domain.chat.domain.ChatRoom; @@ -25,13 +26,7 @@ import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; -import umc.cockple.demo.domain.party.dto.PartyCreateDTO; -import umc.cockple.demo.domain.party.dto.PartyInviteCreateDTO; -import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; -import umc.cockple.demo.domain.party.dto.PartyJoinActionDTO; -import umc.cockple.demo.domain.party.dto.PartyKeywordDTO; -import umc.cockple.demo.domain.party.dto.PartyMemberRoleDTO; -import umc.cockple.demo.domain.party.dto.PartyUpdateDTO; +import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ActivityTime; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestAction; @@ -52,7 +47,6 @@ import java.time.LocalDate; import java.util.List; -import org.springframework.http.MediaType; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -127,7 +121,7 @@ void setUp() { // 추천 조회용 모임 (manager의 조건에 맞춤) Party suggestedParty = PartyFixture.createParty("추천 모임", normalMember.getId(), addr); - suggestedParty.addLevel(Gender.MALE, Level.A); + suggestedParty.addLevel(Gender.MALE, Level.A); partyRepository.save(suggestedParty); SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); @@ -686,7 +680,7 @@ class GetJoinRequests { void success_getJoinRequests() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("가입희망자", Gender.FEMALE, Level.B, 1010L)); - + PartyJoinRequest joinRequest = PartyJoinRequest.builder() .party(party) .member(applicant) @@ -711,7 +705,7 @@ void success_getJoinRequests() throws Exception { void success_getJoinRequests_approved() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("승인된멤버", Gender.MALE, Level.C, 1015L)); - + PartyJoinRequest joinRequest = PartyJoinRequest.builder() .party(party) .member(applicant) @@ -995,7 +989,7 @@ class ActionJoinRequest { void success_actionJoinRequest_approve() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1020L)); - + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() .party(party) .member(applicant) @@ -1024,7 +1018,7 @@ void success_actionJoinRequest_approve() throws Exception { void success_actionJoinRequest_reject() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("탈락자", Gender.FEMALE, Level.B, 1030L)); - + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() .party(party) .member(applicant) @@ -1053,7 +1047,7 @@ void success_actionJoinRequest_reject() throws Exception { void fail_actionJoinRequest_notOwner() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1040L)); - + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() .party(party) .member(applicant) @@ -1076,7 +1070,7 @@ void fail_actionJoinRequest_notOwner() throws Exception { void fail_actionJoinRequest_alreadyHandled() throws Exception { // given Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1050L)); - + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() .party(party) .member(applicant) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index df54ebbe9..2b92cfc11 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -16,14 +16,13 @@ import umc.cockple.demo.domain.member.domain.MemberParty; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; +import umc.cockple.demo.domain.notification.service.NotificationCommandService; import umc.cockple.demo.domain.party.converter.PartyConverter; import umc.cockple.demo.domain.party.domain.Party; import umc.cockple.demo.domain.party.domain.PartyAddr; -import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.domain.PartyInvitation; -import umc.cockple.demo.domain.notification.service.NotificationCommandService; +import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; -import umc.cockple.demo.domain.party.dto.PartyInviteActionDTO; import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; @@ -31,8 +30,8 @@ import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; -import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; +import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; @@ -49,9 +48,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class PartyCommandServiceTest { @@ -571,7 +568,7 @@ void success_updateParty() { assertThat(party.getJoinPrice()).isEqualTo(0); assertThat(party.getPrice()).isEqualTo(10000); assertThat(party.getContent()).isEqualTo("새로운 내용"); - + verify(notificationCommandService, times(1)).createNotification(any()); } @@ -586,7 +583,7 @@ void fail_updateParty_partyNotFound() { given(partyRepository.findById(partyId)).willReturn(Optional.empty()); // when & then - PartyException exception = assertThrows(PartyException.class, + PartyException exception = assertThrows(PartyException.class, () -> partyCommandService.updateParty(partyId, memberId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); } @@ -601,7 +598,7 @@ void fail_updateParty_insufficientPermission() { PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); ReflectionTestUtils.setField(owner, "id", 1L); - + Member normalMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, 2L); ReflectionTestUtils.setField(normalMember, "id", memberId); @@ -615,7 +612,7 @@ void fail_updateParty_insufficientPermission() { given(memberRepository.findById(memberId)).willReturn(Optional.of(normalMember)); // when & then - PartyException exception = assertThrows(PartyException.class, + PartyException exception = assertThrows(PartyException.class, () -> partyCommandService.updateParty(partyId, memberId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } @@ -776,7 +773,7 @@ void fail_updateMemberRole_targetIsOwner() { given(memberPartyRepository.findByPartyAndMember(party, owner)).willReturn(Optional.of(memberParty)); // when & then - PartyException exception = assertThrows(PartyException.class, + PartyException exception = assertThrows(PartyException.class, () -> partyCommandService.updateMemberRole(partyId, ownerId, ownerId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER); } @@ -793,7 +790,7 @@ void fail_updateMemberRole_notOwner() { PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); ReflectionTestUtils.setField(owner, "id", ownerId); - + Member notOwner = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, notOwnerId); ReflectionTestUtils.setField(notOwner, "id", notOwnerId); @@ -811,7 +808,7 @@ void fail_updateMemberRole_notOwner() { given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); // when & then (notOwnerId를 currentMemberId로 전달하여 실행) - PartyException exception = assertThrows(PartyException.class, + PartyException exception = assertThrows(PartyException.class, () -> partyCommandService.updateMemberRole(partyId, targetId, notOwnerId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } From 00d6944f6277be5aa3f9dcc74d15c36c45078b56 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 11:11:42 +0900 Subject: [PATCH 56/63] =?UTF-8?q?TEST:=20=EB=82=B4=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 31 ++++++- .../party/service/PartyQueryServiceTest.java | 85 +++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index f52ad5915..e99f10057 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -36,6 +36,7 @@ import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; +import umc.cockple.demo.domain.party.enums.PartyOrderType; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; import umc.cockple.demo.global.enums.Role; @@ -242,7 +243,7 @@ void fail_leaveParty_notMember() throws Exception { class GetMyParties { @Test - @DisplayName("200 - 사용자가 가입한 모임 목록을 페이징하여 반환한다") + @DisplayName("200 - 사용자가 가입한 모임 목록을 최신순으로 페이징하여 반환한다") void success_getMyParties() throws Exception { mockMvc.perform(get("/api/my/parties") .param("created", "false") @@ -260,6 +261,34 @@ void success_getMyParties() throws Exception { .andExpect(jsonPath("$.data.last").value(true)); } + @Test + @DisplayName("200 - 사용자가 가입한 모임 목록을 오래된 순으로 페이징하여 반환한다") + void success_getMyParties_oldest() throws Exception { + mockMvc.perform(get("/api/my/parties") + .param("created", "false") + .param("sort", "오래된 순") + .param("size", "10") + .param("page", "0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())); + } + + @Test + @DisplayName("200 - 사용자가 가입한 모임 목록을 운동 많은 순으로 페이징하여 반환한다") + void success_getMyParties_exerciseCount() throws Exception { + mockMvc.perform(get("/api/my/parties") + .param("created", "false") + .param("sort", "운동 많은 순") + .param("size", "10") + .param("page", "0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())); + } + @Test @DisplayName("200 - 가입한 모임이 없을 경우 빈 목록을 반환한다") void success_emptyMyParties() throws Exception { diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 9813ea1ba..79052ab58 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -28,6 +28,7 @@ import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; +import umc.cockple.demo.domain.party.enums.PartyOrderType; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; @@ -279,6 +280,90 @@ void success_getMyParties() { verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); } + + @Test + @DisplayName("성공 - 사용자가 가입한 모임 목록을 오래된 순으로 페이징하여 반환한다") + void success_getMyParties_oldest() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + + Party party1 = PartyFixture.createParty("테스트 모임1", 10L, addr); + ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("테스트 모임2", 10L, addr); + ReflectionTestUtils.setField(party2, "id", 2L); + + // 오래된 순: party1, party2 순서 + Slice partySlice = new SliceImpl<>(List.of(party1, party2), pageable, false); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L, 2L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L, 2L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of()); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, + "오래된 순", pageable); + + // then + assertThat(result.getContent()).hasSize(2); + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + } + + @Test + @DisplayName("성공 - 사용자가 가입한 모임 목록을 운동 많은 순으로 페이징하여 반환한다") + void success_getMyParties_exerciseCount() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + + Party party1 = PartyFixture.createParty("운동많은모임", 10L, addr); + ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("운동적은모임", 10L, addr); + ReflectionTestUtils.setField(party2, "id", 2L); + + // 운동 많은 순: party1, party2 순서 + Slice partySlice = new SliceImpl<>(List.of(party1, party2), pageable, false); + + given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) + .willReturn(partySlice); + given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L, 2L))) + .willReturn(List.of()); + given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L, 2L))) + .willReturn(List.of()); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) + .willReturn(Set.of()); + + // when + Slice result = partyQueryService.getMyParties(memberId, false, + "운동 많은 순", pageable); + + // then + assertThat(result.getContent()).hasSize(2); + verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + } + + @Test + @DisplayName("실패 - 유효하지 않은 정렬 기준을 전달하면 INVALID_ORDER_TYPE 발생") + void fail_getMyParties_invalidSort() { + // given + Long memberId = 10L; + Pageable pageable = PageRequest.of(0, 10); + + // when & then + assertThatThrownBy(() -> partyQueryService.getMyParties(memberId, false, "존재하지않는정렬", pageable)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.INVALID_ORDER_TYPE)); + } } @Nested From 64dddf8582b56f97033df539553ed3a753ae0ae4 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 12:17:02 +0900 Subject: [PATCH 57/63] =?UTF-8?q?TEST:=20=EB=A9=A4=EB=B2=84=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=20=EC=84=A4=EC=A0=95=20API=20=EC=8B=A4=ED=8C=A8,=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PartyCommandServiceTest.java | 54 ++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 2b92cfc11..2e7e0aa24 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -716,12 +716,13 @@ void fail_deleteParty_partyNotFound() { class UpdateMemberRole { @Test - @DisplayName("성공 - 모임장이 일반 멤버를 부모임장으로 지정하고 알림을 발생시킨다") + @DisplayName("성공 - 모임장이 일반 멤버를 부모임장으로 지정하면 기존 부모임장은 일반 멤버로 강등되고 새 부모임장이 지정된다") void success_updateMemberRole() { // given Long partyId = 1L; Long currentOwnerId = 1L; Long targetMemberId = 10L; + Long oldSubManagerId = 20L; PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, currentOwnerId); @@ -730,24 +731,63 @@ void success_updateMemberRole() { Member targetMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, targetMemberId); ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + Member oldSubManager = MemberFixture.createMember("기존부모임장", Gender.MALE, Level.A, oldSubManagerId); + ReflectionTestUtils.setField(oldSubManager, "id", oldSubManagerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); ReflectionTestUtils.setField(party, "id", partyId); - MemberParty memberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + MemberParty oldSubManagerParty = MemberFixture.createMemberParty(party, oldSubManager, Role.PARTY_SUBMANAGER); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); - given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(memberParty)); - given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER)).willReturn(Optional.empty()); - given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(memberParty)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + given(memberPartyRepository.findByPartyIdAndRole(partyId, Role.PARTY_SUBMANAGER)).willReturn(Optional.of(oldSubManagerParty)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(List.of(targetMemberParty, oldSubManagerParty)); // when partyCommandService.updateMemberRole(partyId, targetMemberId, currentOwnerId, request); // then - assertThat(memberParty.getRole()).isEqualTo(Role.PARTY_SUBMANAGER); - verify(notificationCommandService, times(1)).createNotification(any()); + assertThat(targetMemberParty.getRole()).isEqualTo(Role.PARTY_SUBMANAGER); + assertThat(oldSubManagerParty.getRole()).isEqualTo(Role.PARTY_MEMBER); + verify(notificationCommandService, times(4)).createNotification(any()); + } + + @Test + @DisplayName("실패 - 이미 요청한 역할과 같은 역할인 경우 변경 없이 반환된다") + void fail_updateMemberRole_sameRole() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long targetId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Member targetMember = MemberFixture.createMember("타겟", Gender.MALE, Level.A, targetId); + ReflectionTestUtils.setField(targetMember, "id", targetId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty targetMemberParty = spy(MemberFixture.createMemberParty(party, targetMember, Role.PARTY_SUBMANAGER)); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(targetId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + + // when + partyCommandService.updateMemberRole(partyId, targetId, ownerId, request); + + // then + verify(targetMemberParty, never()).changeRole(any()); + verify(notificationCommandService, never()).createNotification(any()); } @Test From 875388555cbf9240fe9c74dc6d6a0893bda15d57 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 12:43:37 +0900 Subject: [PATCH 58/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=A0=EC=B2=AD=20=EC=B2=98=EB=A6=AC=20API=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20enum=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../party/exception/PartyErrorCode.java | 2 +- .../service/PartyCommandServiceImpl.java | 2 +- .../integration/PartyIntegrationTest.java | 45 ++++++++ .../service/PartyCommandServiceTest.java | 102 ++++++++++++++++++ 4 files changed, 149 insertions(+), 2 deletions(-) diff --git a/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java b/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java index cbba931fd..abc159c4d 100644 --- a/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java +++ b/src/main/java/umc/cockple/demo/domain/party/exception/PartyErrorCode.java @@ -27,7 +27,7 @@ public enum PartyErrorCode implements BaseErrorCode { INVALID_ROLE_VALUE(HttpStatus.BAD_REQUEST, "PARTY411", "유효하지 않은 역할 값입니다. (PARTY_SUBMANAGER 또는 PARTY_MEMBER를 입력해주세요.)"), PARTY_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY201", "존재하지 않는 모임입니다."), - JoinRequest_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY202", "존재하지 않는 가입신청입니다."), + JOIN_REQUEST_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY202", "존재하지 않는 가입신청입니다."), JOIN_REQUEST_PARTY_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY203", "해당 모임에서 존재하지 않는 가입신청입니다."), NOT_MEMBER(HttpStatus.BAD_REQUEST, "PARTY204", "해당 모임의 멤버가 아닙니다."), INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "PARTY205", "존재하지 않는 모임 초대입니다."), diff --git a/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java b/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java index 948060ea1..9616b587a 100644 --- a/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java +++ b/src/main/java/umc/cockple/demo/domain/party/service/PartyCommandServiceImpl.java @@ -335,7 +335,7 @@ public void addKeyword(Long partyId, Long memberId, PartyKeywordDTO.Request requ //가입신청 조회 private PartyJoinRequest findJoinRequestOrThrow(Long requestId) { return partyJoinRequestRepository.findById(requestId) - .orElseThrow(() -> new PartyException(PartyErrorCode.JoinRequest_NOT_FOUND)); + .orElseThrow(() -> new PartyException(PartyErrorCode.JOIN_REQUEST_NOT_FOUND)); } private PartyInvitation findInvitationOrThrow(Long invitationId) { diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index e99f10057..9a62fb068 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -529,6 +529,51 @@ void fail_createJoinRequest_genderMismatch() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.GENDER_NOT_MATCH.getCode())); } + + @Test + @DisplayName("409 - 이미 대기중인 가입 신청이 있는 상태에서 다시 신청하면 JOIN_REQUEST_ALREADY_EXISTS 에러를 반환한다") + void fail_createJoinRequest_alreadyExists() throws Exception { + // 가입하지 않은 멤버 생성 + Member applicant = memberRepository.save(MemberFixture.createMember("신청자2", Gender.MALE, Level.A, 5002L, LocalDate.of(1995, 1, 1))); + + // 기존 가입 신청 추가 + PartyJoinRequest joinRequest = PartyJoinRequest.create(applicant, party); + partyJoinRequestRepository.save(joinRequest); + + SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", party.getId())) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.JOIN_REQUEST_ALREADY_EXISTS.getCode())); + } + + @Test + @DisplayName("400 - 삭제된(비활성화된) 모임에 가입 신청하면 PARTY_IS_DELETED 에러를 반환한다") + void fail_createJoinRequest_partyDeleted() throws Exception { + // 파티 생성 후 삭제 + PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울", "금천")); + Party deletedParty = partyRepository.save(PartyFixture.createParty("삭제된 모임", manager.getId(), addr)); + deletedParty.delete(); + partyRepository.save(deletedParty); + + Member applicant = memberRepository.save(MemberFixture.createMember("신청자3", Gender.MALE, Level.A, 5003L)); + SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", deletedParty.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); + } + + @Test + @DisplayName("404 - 존재하지 않는 파티에 가입 신청하면 PARTY_NOT_FOUND 에러를 반환한다") + void fail_createJoinRequest_partyNotFound() throws Exception { + Member applicant = memberRepository.save(MemberFixture.createMember("신청자4", Gender.MALE, Level.A, 5004L)); + SecurityContextHelper.setAuthentication(applicant.getId(), applicant.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/join-requests", 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 2e7e0aa24..f10983082 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -1106,6 +1106,108 @@ void fail_actionJoinRequest_alreadyActions() { () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS); } + + @Test + @DisplayName("실패 - 해당 가입 요청을 찾을 수 없는 경우 JOIN_REQUEST_NOT_FOUND 발생") + void fail_actionJoinRequest_notFound() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long requestId = 999L; // 존재하지 않는 ID + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_NOT_FOUND); + } + + @Test + @DisplayName("실패 - 모임장이 아닌 사용자가 가입 신청을 처리하려 할 때 INSUFFICIENT_PERMISSION 발생") + void fail_actionJoinRequest_insufficientPermission() { + // given + Long partyId = 1L; + Long ownerId = 10L; + Long notOwnerId = 99L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); // 실제 모임장 + ReflectionTestUtils.setField(owner, "id", ownerId); + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, notOwnerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); + } + + @Test + @DisplayName("실패 - 처리하려는 가입 신청이 해당 모임의 것이 아닌 경우 JOIN_REQUEST_PARTY_NOT_FOUND 발생") + void fail_actionJoinRequest_partyNotFound() { + // given + Long partyId = 1L; + Long wrongPartyId = 2L; + Long ownerId = 10L; + Long requestId = 100L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + + Party targetParty = PartyFixture.createParty("대상 모임", owner.getId(), addr); + ReflectionTestUtils.setField(targetParty, "id", partyId); + + Party wrongParty = PartyFixture.createParty("다른 모임", owner.getId(), addr); + ReflectionTestUtils.setField(wrongParty, "id", wrongPartyId); + + Member applicant = MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 20L); + ReflectionTestUtils.setField(applicant, "id", 20L); + + // 다른 모임으로 가입신청 + PartyJoinRequest joinRequest = PartyJoinRequest.builder() + .party(wrongParty) + .member(applicant) + .status(RequestStatus.PENDING) + .build(); + ReflectionTestUtils.setField(joinRequest, "id", requestId); + + PartyJoinActionDTO.Request requestDTO = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(targetParty)); + given(partyJoinRequestRepository.findById(requestId)).willReturn(Optional.of(joinRequest)); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_PARTY_NOT_FOUND); + } } @Nested From ecf40b4c88b73c35d6a23b6f08a29e016d422278 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 13:35:38 +0900 Subject: [PATCH 59/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EC=A1=B0=ED=9A=8C=20API=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo/domain/party/service/PartyQueryServiceTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 79052ab58..95f5bb67f 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -584,6 +584,7 @@ void success_getPartyDetails_nonMember() { assertThat(result.partyName()).isEqualTo("상세 모임"); assertThat(result.memberStatus()).isEqualTo("NOT_MEMBER"); assertThat(result.hasPendingJoinRequest()).isFalse(); + assertThat(result.isBookmarked()).isFalse(); verify(partyRepository).findById(partyId); } @@ -611,6 +612,7 @@ void success_getPartyDetails_member() { given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); given(memberPartyRepository.findByPartyAndMember(party, member)) .willReturn(Optional.of(memberParty)); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(true); // when PartyDetailDTO.Response result = partyQueryService.getPartyDetails(partyId, memberId); @@ -618,6 +620,8 @@ void success_getPartyDetails_member() { // then assertThat(result.memberStatus()).isEqualTo("MEMBER"); assertThat(result.memberRole()).isEqualTo("PARTY_MEMBER"); + assertThat(result.hasPendingJoinRequest()).isNull(); // 멤버이므로 null 반환 + assertThat(result.isBookmarked()).isTrue(); } @Test From d8f18ea721087ff82dbe903859ab4c7e688c1b29 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Tue, 31 Mar 2026 14:52:14 +0900 Subject: [PATCH 60/63] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=82=AD=EC=A0=9C=20API=20=EC=84=B1=EA=B3=B5,=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PartyCommandServiceTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index f10983082..1265256e0 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -892,6 +892,40 @@ void success_removeMember() { verify(chatRoomService, times(1)).leavePartyChatRoom(partyId, targetMemberId); } + @Test + @DisplayName("성공 - 부모임장이 일반 멤버를 성공적으로 강퇴한다") + void success_removeMember_bySubManager() { + // given + Long partyId = 1L; + Long subManagerId = 2L; + Long targetMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member subManager = MemberFixture.createMember("부모임장", Gender.MALE, Level.A, subManagerId); + ReflectionTestUtils.setField(subManager, "id", subManagerId); + Member targetMember = MemberFixture.createMember("일반멤버", Gender.MALE, Level.A, targetMemberId); + ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + + Party party = PartyFixture.createParty("모임명", 1L, addr); // ownerId = 1L + ReflectionTestUtils.setField(party, "id", partyId); + + MemberParty subManagerParty = MemberFixture.createMemberParty(party, subManager, Role.PARTY_SUBMANAGER); + MemberParty targetMemberParty = MemberFixture.createMemberParty(party, targetMember, Role.PARTY_MEMBER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(subManagerId)).willReturn(Optional.of(subManager)); + given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); + given(memberPartyRepository.findByPartyAndMember(party, subManager)).willReturn(Optional.of(subManagerParty)); + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.of(targetMemberParty)); + + // when + partyCommandService.removeMember(partyId, targetMemberId, subManagerId); + + // then + verify(memberPartyRepository, times(1)).delete(targetMemberParty); + verify(chatRoomService, times(1)).leavePartyChatRoom(partyId, targetMemberId); + } + @Test @DisplayName("실패 - 권한이 없는 멤버가 타인을 강퇴하려 하면 INSUFFICIENT_PERMISSION 발생") void fail_removeMember_insufficientPermission() { @@ -949,6 +983,36 @@ void fail_removeMember_cannotRemoveSelf() { () -> partyCommandService.removeMember(partyId, ownerId, ownerId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.CANNOT_REMOVE_SELF); } + + @Test + @DisplayName("실패 - 대상 멤버가 모임 소속이 아닐 경우 NOT_MEMBER 발생") + void fail_removeMember_notMember() { + // given + Long partyId = 1L; + Long ownerId = 1L; + Long targetMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); + ReflectionTestUtils.setField(owner, "id", ownerId); + Member targetMember = MemberFixture.createMember("타겟", Gender.MALE, Level.A, targetMemberId); + ReflectionTestUtils.setField(targetMember, "id", targetMemberId); + + Party party = PartyFixture.createParty("모임명", owner.getId(), addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); + given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); + + // 타겟 멤버가 모임 소속이 아님 -> findMemberPartyOrThrow 에서 NOT_MEMBER 발생 + given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.empty()); + + // when & then + PartyException exception = assertThrows(PartyException.class, + () -> partyCommandService.removeMember(partyId, targetMemberId, ownerId)); + assertThat(exception.getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER); + } } @Nested From 44bf0400c70b2e73aa8ac6f6e7fdb9342687b8c6 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Wed, 1 Apr 2026 00:27:34 +0900 Subject: [PATCH 61/63] =?UTF-8?q?test:=20MEMBER=5FNOT=5FFOUND,=20PARTY=5FN?= =?UTF-8?q?OT=5FFOUND=20=EC=8B=A4=ED=8C=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 170 +++++++++++- .../service/PartyCommandServiceTest.java | 246 +++++++++++++++++- .../party/service/PartyQueryServiceTest.java | 33 ++- 3 files changed, 443 insertions(+), 6 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index 9a62fb068..e9d54e156 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -18,6 +18,7 @@ import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.exception.MemberErrorCode; import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; @@ -36,7 +37,6 @@ import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; import umc.cockple.demo.domain.party.repository.PartyJoinRequestRepository; import umc.cockple.demo.domain.party.repository.PartyRepository; -import umc.cockple.demo.domain.party.enums.PartyOrderType; import umc.cockple.demo.global.enums.Gender; import umc.cockple.demo.global.enums.Level; import umc.cockple.demo.global.enums.Role; @@ -236,6 +236,14 @@ void fail_leaveParty_notMember() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.NOT_MEMBER.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티에서 탈퇴 시도 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_leaveParty_partyNotFound() throws Exception { + mockMvc.perform(delete("/api/parties/{partyId}/members/my", 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -535,7 +543,7 @@ void fail_createJoinRequest_genderMismatch() throws Exception { void fail_createJoinRequest_alreadyExists() throws Exception { // 가입하지 않은 멤버 생성 Member applicant = memberRepository.save(MemberFixture.createMember("신청자2", Gender.MALE, Level.A, 5002L, LocalDate.of(1995, 1, 1))); - + // 기존 가입 신청 추가 PartyJoinRequest joinRequest = PartyJoinRequest.create(applicant, party); partyJoinRequestRepository.save(joinRequest); @@ -648,6 +656,23 @@ void fail_updateParty_notOwner() throws Exception { .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INSUFFICIENT_PERMISSION.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티 수정 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_updateParty_partyNotFound() throws Exception { + // given + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityDay(List.of("월")) + .activityTime("오전") + .build(); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}", 9999L) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -696,6 +721,14 @@ void fail_deleteParty_partyDeleted() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티 삭제 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_deleteParty_partyNotFound() throws Exception { + mockMvc.perform(patch("/api/parties/{partyId}/status", 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -743,6 +776,22 @@ void fail_removeMember_selfAsManager() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_REMOVE_SELF.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티의 멤버 삭제 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_removeMember_partyNotFound() throws Exception { + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", 9999L, normalMember.getId())) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + + @Test + @DisplayName("404 - 존재하지 않는 멤버를 강퇴하려 할 때 MEMBER_NOT_FOUND 에러를 반환한다") + void fail_removeMember_memberNotFound() throws Exception { + mockMvc.perform(delete("/api/parties/{partyId}/members/{memberId}", party.getId(), 9999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(MemberErrorCode.MEMBER_NOT_FOUND.getCode())); + } } @Nested @@ -824,6 +873,15 @@ void fail_getJoinRequests_invalidStatus() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_REQUEST_STATUS.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티의 가입 신청 목록 조회 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_getJoinRequests_partyNotFound() throws Exception { + mockMvc.perform(get("/api/parties/{partyId}/join-requests", 9999L) + .param("status", "PENDING")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -949,6 +1007,32 @@ void fail_createInvitation_duplicateInvitation() throws Exception { .andExpect(status().isConflict()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVITATION_ALREADY_EXISTS.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티에서 멤버 초대 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_createInvitation_partyNotFound() throws Exception { + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(normalMember.getId()); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/invitations", 9999L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + + @Test + @DisplayName("404 - 존재하지 않는 회원을 모임에 초대하면 MEMBER_NOT_FOUND 에러를 반환한다") + void fail_createInvitation_memberNotFound() throws Exception { + PartyInviteCreateDTO.Request request = new PartyInviteCreateDTO.Request(9999L); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/invitations", party.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(MemberErrorCode.MEMBER_NOT_FOUND.getCode())); + } } @Nested @@ -1052,6 +1136,27 @@ void fail_actionInvitation_alreadyActions() throws Exception { .andExpect(status().isConflict()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVITATION_ALREADY_ACTIONS.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 회원이 초대를 처리하려 할 때 MEMBER_NOT_FOUND 에러를 반환한다") + void fail_actionInvitation_memberNotFound() throws Exception { + // given + Member invitee = memberRepository.save(MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 1104L)); + PartyInvitation invitation = partyInvitationRepository.save( + PartyInvitation.create(party, manager, invitee) + ); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + // 인증 정보에는 유효하지만 DB에는 없는 ID 설정 + SecurityContextHelper.setAuthentication(9999L, "ghost"); + + // when & then + mockMvc.perform(patch("/api/parties/invitations/{invitationId}", invitation.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(MemberErrorCode.MEMBER_NOT_FOUND.getCode())); + } } @Nested @@ -1161,6 +1266,28 @@ void fail_actionJoinRequest_alreadyHandled() throws Exception { .andExpect(status().isConflict()) .andExpect(jsonPath("$.code").value(PartyErrorCode.JOIN_REQUEST_ALREADY_ACTIONS.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티의 가입 신청 요청 처리 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_actionJoinRequest_partyNotFound() throws Exception { + // given + Member applicant = memberRepository.save(MemberFixture.createMember("지원자", Gender.FEMALE, Level.B, 1060L)); + PartyJoinRequest joinRequest = partyJoinRequestRepository.save(PartyJoinRequest.builder() + .party(party) + .member(applicant) + .status(RequestStatus.PENDING) + .build()); + + PartyJoinActionDTO.Request actionRequest = new PartyJoinActionDTO.Request(RequestAction.APPROVE); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + // when & then + mockMvc.perform(patch("/api/parties/{partyId}/join-requests/{requestId}", 9999L, joinRequest.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(actionRequest))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } @Nested @@ -1323,6 +1450,32 @@ void fail_updateMemberRole_targetIsOwner() throws Exception { .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(PartyErrorCode.CANNOT_ASSIGN_TO_OWNER.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티의 멤버 역할 수정 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_updateMemberRole_partyNotFound() throws Exception { + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", 9999L, normalMember.getId()) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } + + @Test + @DisplayName("404 - 존재하지 않는 멤버의 역할 수정 시 MEMBER_NOT_FOUND 에러를 반환한다") + void fail_updateMemberRole_memberNotFound() throws Exception { + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(patch("/api/parties/{partyId}/members/{memberId}/role", party.getId(), 9999L) + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(MemberErrorCode.MEMBER_NOT_FOUND.getCode())); + } } @Nested @@ -1379,5 +1532,18 @@ void fail_addKeyword_invalidKeyword() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(PartyErrorCode.INVALID_KEYWORD.getCode())); } + + @Test + @DisplayName("404 - 존재하지 않는 파티에 키워드 추가 시 PARTY_NOT_FOUND 에러를 반환한다") + void fail_addKeyword_partyNotFound() throws Exception { + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("새키워드")); + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/keywords", 9999L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())); + } } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 1265256e0..48739c70a 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -230,6 +230,24 @@ void fail_leaveParty_notMember() { .isInstanceOf(PartyException.class) .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER)); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외가 발생한다") + void fail_leaveParty_memberNotFound() { + // given + Long partyId = 1L; + PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); + Party party = PartyFixture.createParty("모임명", 1L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(10L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, 10L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -358,6 +376,33 @@ void fail_createJoinRequest_ageMismatch() { .isInstanceOf(PartyException.class) .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH)); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_createJoinRequest_partyNotFound() { + // given + Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.B, 1L); + ReflectionTestUtils.setField(member, "id", 1L); + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(999L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_createJoinRequest_memberNotFound() { + // given + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createJoinRequest(1L, 1L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -527,6 +572,33 @@ void fail_createParty_ageMismatch() { () -> partyCommandService.createParty(memberId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.AGE_NOT_MATCH); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_createParty_memberNotFound() { + // given + Long memberId = 1L; + PartyCreateDTO.Request request = PartyCreateDTO.Request.builder() + .partyName("테스트 모임") + .partyType("혼복") + .activityTime("오전") + .addr1("서울") + .addr2("강남") + .activityDay(List.of("월", "수")) + .price(10000) + .joinPrice(5000) + .designatedCock("테스트콕") + .maleLevel(List.of("A조")) + .femaleLevel(List.of("B조")) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createParty(memberId, request)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -616,6 +688,26 @@ void fail_updateParty_insufficientPermission() { () -> partyCommandService.updateParty(partyId, memberId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_updateParty_memberNotFound() { + // given + Long partyId = 1L; + Long memberId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + PartyUpdateDTO.Request request = PartyUpdateDTO.Request.builder() + .activityTime("오전") + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.updateParty(partyId, memberId, request)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -709,6 +801,23 @@ void fail_deleteParty_partyNotFound() { () -> partyCommandService.deleteParty(invalidId, 1L)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_deleteParty_memberNotFound() { + // given + Long partyId = 1L; + Long memberId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.deleteParty(partyId, memberId)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -852,6 +961,36 @@ void fail_updateMemberRole_notOwner() { () -> partyCommandService.updateMemberRole(partyId, targetId, notOwnerId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INSUFFICIENT_PERMISSION); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_updateMemberRole_partyNotFound() { + // given + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.updateMemberRole(999L, 1L, 1L, request)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_updateMemberRole_memberNotFound() { + // given + Long partyId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + PartyMemberRoleDTO.Request request = new PartyMemberRoleDTO.Request(Role.PARTY_SUBMANAGER); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.updateMemberRole(partyId, 1L, 1L, request)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -1004,7 +1143,7 @@ void fail_removeMember_notMember() { given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); given(memberRepository.findById(ownerId)).willReturn(Optional.of(owner)); given(memberRepository.findById(targetMemberId)).willReturn(Optional.of(targetMember)); - + // 타겟 멤버가 모임 소속이 아님 -> findMemberPartyOrThrow 에서 NOT_MEMBER 발생 given(memberPartyRepository.findByPartyAndMember(party, targetMember)).willReturn(Optional.empty()); @@ -1013,6 +1152,34 @@ void fail_removeMember_notMember() { () -> partyCommandService.removeMember(partyId, targetMemberId, ownerId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.NOT_MEMBER); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_removeMember_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.removeMember(999L, 1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_removeMember_memberNotFound() { + // given + Long partyId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.removeMember(partyId, 10L, 1L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -1234,7 +1401,7 @@ void fail_actionJoinRequest_insufficientPermission() { @Test @DisplayName("실패 - 처리하려는 가입 신청이 해당 모임의 것이 아닌 경우 JOIN_REQUEST_PARTY_NOT_FOUND 발생") - void fail_actionJoinRequest_partyNotFound() { + void fail_actionJoinRequest_joinRequestPartyNotFound() { // given Long partyId = 1L; Long wrongPartyId = 2L; @@ -1244,7 +1411,7 @@ void fail_actionJoinRequest_partyNotFound() { PartyAddr addr = PartyFixture.createPartyAddr("서울", "강남"); Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, ownerId); ReflectionTestUtils.setField(owner, "id", ownerId); - + Party targetParty = PartyFixture.createParty("대상 모임", owner.getId(), addr); ReflectionTestUtils.setField(targetParty, "id", partyId); @@ -1272,6 +1439,18 @@ void fail_actionJoinRequest_partyNotFound() { () -> partyCommandService.actionJoinRequest(partyId, ownerId, requestDTO, requestId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.JOIN_REQUEST_PARTY_NOT_FOUND); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_actionJoinRequest_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.actionJoinRequest(999L, 1L, new PartyJoinActionDTO.Request(RequestAction.APPROVE), 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } } @Nested @@ -1392,6 +1571,34 @@ void fail_createInvitation_duplicateInvitation() { () -> partyCommandService.createInvitation(partyId, inviteeId, ownerId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_EXISTS); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_createInvitation_partyNotFound() { + // given + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createInvitation(999L, 1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_createInvitation_memberNotFound() { + // given + Long partyId = 1L; + Party party = PartyFixture.createParty("모임명", 1L, null); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.createInvitation(partyId, 1L, 1L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -1533,6 +1740,26 @@ void fail_actionInvitation_alreadyActions() { () -> partyCommandService.actionInvitation(inviteeId, request, invitationId)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVITATION_ALREADY_ACTIONS); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_actionInvitation_memberNotFound() { + // given + Long invitationId = 100L; + Party party = PartyFixture.createParty("모임명", 1L, null); + Member owner = MemberFixture.createMember("모임장", Gender.MALE, Level.A, 1L); + Member invitee = MemberFixture.createMember("초대대상", Gender.FEMALE, Level.B, 20L); + PartyInvitation invitation = PartyInvitation.create(party, owner, invitee); + + PartyInviteActionDTO.Request request = new PartyInviteActionDTO.Request(RequestAction.APPROVE); + given(partyInvitationRepository.findById(invitationId)).willReturn(Optional.of(invitation)); + given(memberRepository.findById(20L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.actionInvitation(20L, request, invitationId)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -1605,5 +1832,18 @@ void fail_addKeyword_invalidKeyword() { () -> partyCommandService.addKeyword(partyId, ownerId, request)); assertThat(exception.getCode()).isEqualTo(PartyErrorCode.INVALID_KEYWORD); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_addKeyword_partyNotFound() { + // given + PartyKeywordDTO.Request request = new PartyKeywordDTO.Request(List.of("새키워드")); + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyCommandService.addKeyword(999L, 10L, request)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 95f5bb67f..098e07411 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -28,7 +28,6 @@ import umc.cockple.demo.domain.party.domain.PartyAddr; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; -import umc.cockple.demo.domain.party.enums.PartyOrderType; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.exception.PartyException; @@ -654,6 +653,24 @@ void fail_getPartyDetails_partyDeleted() { .satisfies(e -> assertThat(((PartyException) e).getCode()) .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); } + + @Test + @DisplayName("실패 - 존재하지 않는 회원인 경우 MEMBER_NOT_FOUND 예외 발생") + void fail_getPartyDetails_memberNotFound() { + // given + Long partyId = 1L; + Party party = PartyFixture.createParty("상세 모임", 11L, null); + ReflectionTestUtils.setField(party, "id", partyId); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyDetails(partyId, 1L)) + .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()) + .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + } } @Nested @@ -791,6 +808,20 @@ void fail_getJoinRequests_invalidStatus() { .isInstanceOf(PartyException.class) .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.INVALID_REQUEST_STATUS)); } + + @Test + @DisplayName("실패 - 존재하지 않는 파티인 경우 PARTY_NOT_FOUND 예외 발생") + void fail_getJoinRequests_partyNotFound() { + // given + Long invalidId = 999L; + given(partyRepository.findById(invalidId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getJoinRequests(invalidId, 1L, "PENDING", PageRequest.of(0, 10))) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } } @Nested From 9f61942fd2713f62a0e49aef75f446fdbfbc503d Mon Sep 17 00:00:00 2001 From: dbalsk Date: Wed, 1 Apr 2026 20:19:37 +0900 Subject: [PATCH 62/63] =?UTF-8?q?test:=20=EB=82=B4=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=20=EA=B2=80=EC=A6=9D=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EB=AA=A8=EC=9E=84=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0=EC=A4=80=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 158 ++++++++++-- .../party/service/PartyQueryServiceTest.java | 244 ++++++++++++------ 2 files changed, 301 insertions(+), 101 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index e9d54e156..fcbbfb520 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; import umc.cockple.demo.domain.chat.domain.ChatRoom; @@ -250,8 +251,32 @@ void fail_leaveParty_partyNotFound() throws Exception { @DisplayName("GET /api/my/parties - 내 모임 조회") class GetMyParties { + Party party2; + Party party3; + + @BeforeEach + void setUpMyParties() { + PartyAddr addr = partyAddrRepository.findAll().get(0); + + // party: 1번째 생성, 운동 횟수 10 + ReflectionTestUtils.setField(party, "exerciseCount", 10); + partyRepository.save(party); + + // party2: 2번째 생성, 운동 횟수 20 + party2 = partyRepository.save(PartyFixture.createParty("테스트 모임 2", manager.getId(), addr)); + memberPartyRepository.save(MemberFixture.createMemberParty(party2, manager, Role.PARTY_MANAGER)); + ReflectionTestUtils.setField(party2, "exerciseCount", 20); + partyRepository.save(party2); + + // party3: 3번째 생성, 운동 횟수 5 + party3 = partyRepository.save(PartyFixture.createParty("테스트 모임 3", manager.getId(), addr)); + memberPartyRepository.save(MemberFixture.createMemberParty(party3, manager, Role.PARTY_MANAGER)); + ReflectionTestUtils.setField(party3, "exerciseCount", 5); + partyRepository.save(party3); + } + @Test - @DisplayName("200 - 사용자가 가입한 모임 목록을 최신순으로 페이징하여 반환한다") + @DisplayName("200 - 사용자가 가입한 모임 목록을 최신순(기본)으로 페이징하여 반환한다") void success_getMyParties() throws Exception { mockMvc.perform(get("/api/my/parties") .param("created", "false") @@ -260,13 +285,11 @@ void success_getMyParties() throws Exception { .param("page", "0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) - .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) .andExpect(jsonPath("$.data.content").isArray()) - .andExpect(jsonPath("$.data.content.length()").value(1)) - .andExpect(jsonPath("$.data.content[0].partyName").value("테스트 모임")) - .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) - .andExpect(jsonPath("$.data.pageable.pageNumber").value(0)) - .andExpect(jsonPath("$.data.last").value(true)); + .andExpect(jsonPath("$.data.content.length()").value(3)) + .andExpect(jsonPath("$.data.content[0].partyId").value(party3.getId())) + .andExpect(jsonPath("$.data.content[1].partyId").value(party2.getId())) + .andExpect(jsonPath("$.data.content[2].partyId").value(party.getId())); } @Test @@ -280,7 +303,10 @@ void success_getMyParties_oldest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) - .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())); + .andExpect(jsonPath("$.data.content.length()").value(3)) + .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) + .andExpect(jsonPath("$.data.content[1].partyId").value(party2.getId())) + .andExpect(jsonPath("$.data.content[2].partyId").value(party3.getId())); } @Test @@ -294,7 +320,10 @@ void success_getMyParties_exerciseCount() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) - .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())); + .andExpect(jsonPath("$.data.content.length()").value(3)) + .andExpect(jsonPath("$.data.content[0].partyId").value(party2.getId())) // 20회 + .andExpect(jsonPath("$.data.content[1].partyId").value(party.getId())) // 10회 + .andExpect(jsonPath("$.data.content[2].partyId").value(party3.getId())); // 5회 } @Test @@ -311,7 +340,6 @@ void success_emptyMyParties() throws Exception { .param("page", "0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) - .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) .andExpect(jsonPath("$.data.content").isArray()) .andExpect(jsonPath("$.data.content").isEmpty()) .andExpect(jsonPath("$.data.empty").value(true)); @@ -322,6 +350,22 @@ void success_emptyMyParties() throws Exception { @DisplayName("GET /api/my/parties/simple - 내 모임 간략화 조회") class GetSimpleMyParties { + Party party2; + Party party3; + + @BeforeEach + void setUpSimpleMyParties() { + PartyAddr addr = partyAddrRepository.findAll().get(0); + + // party2 + party2 = partyRepository.save(PartyFixture.createParty("간략 모임 2", manager.getId(), addr)); + memberPartyRepository.save(MemberFixture.createMemberParty(party2, manager, Role.PARTY_MANAGER)); + + // party3 + party3 = partyRepository.save(PartyFixture.createParty("간략 모임 3", manager.getId(), addr)); + memberPartyRepository.save(MemberFixture.createMemberParty(party3, manager, Role.PARTY_MANAGER)); + } + @Test @DisplayName("200 - 사용자가 가입한 모임의 간략화된 목록을 페이징하여 반환한다") void success_getSimpleMyParties() throws Exception { @@ -331,13 +375,11 @@ void success_getSimpleMyParties() throws Exception { .param("sort", "createdAt,DESC")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) - .andExpect(jsonPath("$.message").value("요청에 성공했습니다.")) .andExpect(jsonPath("$.data.content").isArray()) - .andExpect(jsonPath("$.data.content.length()").value(1)) - .andExpect(jsonPath("$.data.content[0].partyName").value("테스트 모임")) - .andExpect(jsonPath("$.data.content[0].partyId").value(party.getId())) - .andExpect(jsonPath("$.data.pageable.pageNumber").value(0)) - .andExpect(jsonPath("$.data.last").value(true)); + .andExpect(jsonPath("$.data.content.length()").value(3)) + .andExpect(jsonPath("$.data.content[0].partyId").value(party3.getId())) + .andExpect(jsonPath("$.data.content[1].partyId").value(party2.getId())) + .andExpect(jsonPath("$.data.content[2].partyId").value(party.getId())); } @Test @@ -364,23 +406,67 @@ void success_emptySimpleMyParties() throws Exception { @DisplayName("GET /api/my/parties/suggestions - 모임 추천 조회") class GetRecommendedParties { + Party recParty1; + Party recParty2; + Party recParty3; + + @BeforeEach + void setUpRecommends() { + PartyAddr addr = partyAddrRepository.findAll().get(0); + + // recParty1: 1번째 생성, 운동 횟수 10 + recParty1 = partyRepository.findAll().stream() + .filter(p -> p.getPartyName().equals("추천 모임")) + .findFirst().orElseThrow(); + ReflectionTestUtils.setField(recParty1, "exerciseCount", 10); + partyRepository.save(recParty1); + + // recParty2: 1번째 생성, 운동 횟수 20 + recParty2 = partyRepository.save(PartyFixture.createParty("추천 모임 2", normalMember.getId(), addr)); + recParty2.addLevel(Gender.MALE, Level.A); // manager 조건에 맞도록 + ReflectionTestUtils.setField(recParty2, "exerciseCount", 20); + partyRepository.save(recParty2); + + // recParty3: 3번째 생성, 운동 횟수 5 + recParty3 = partyRepository.save(PartyFixture.createParty("추천 모임 3", normalMember.getId(), addr)); + recParty3.addLevel(Gender.MALE, Level.A); // manager 조건에 맞도록 + ReflectionTestUtils.setField(recParty3, "exerciseCount", 5); + partyRepository.save(recParty3); + } + @Test - @DisplayName("200 - Cockple 추천 모드 시 추천된 모임 목록을 반환한다") + @DisplayName("200 - Cockple 추천 모드 시 추천된 모임 목록 3개를 반환한다") void success_getRecommendedParties_cockpleRecommend() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "true") - .param("sort", "최신순") .param("page", "0") .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) - .andExpect(jsonPath("$.data.content[0].partyName").value("추천 모임")); + .andExpect(jsonPath("$.data.content.length()").value(3)); + } + + @Test + @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 운동 많은 순으로 반환한다") + void success_getRecommendedParties_exerciseCount() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "false") + .param("sort", "운동 많은 순") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content.length()").value(3)) + .andExpect(jsonPath("$.data.content[0].partyId").value(recParty2.getId())) // 20회 + .andExpect(jsonPath("$.data.content[1].partyId").value(recParty1.getId())) // 10회 + .andExpect(jsonPath("$.data.content[2].partyId").value(recParty3.getId())); // 5회 } @Test - @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 반환한다") - void success_getRecommendedParties_filterMode() throws Exception { + @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 최신순으로 반환한다") + void success_getRecommendedParties_latest() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") .param("isCockpleRecommend", "false") .param("addr1", "서울특별시") @@ -391,22 +477,42 @@ void success_getRecommendedParties_filterMode() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) - .andExpect(jsonPath("$.data.content[0].addr1").value("서울특별시")) - .andExpect(jsonPath("$.data.content[0].addr2").value("강남구")); + .andExpect(jsonPath("$.data.content.length()").value(3)) + .andExpect(jsonPath("$.data.content[0].partyId").value(recParty3.getId())) + .andExpect(jsonPath("$.data.content[1].partyId").value(recParty2.getId())) + .andExpect(jsonPath("$.data.content[2].partyId").value(recParty1.getId())); + } + + @Test + @DisplayName("200 - 필터 모드 시 조건에 맞는 모임 목록을 오래된 순으로 반환한다") + void success_getRecommendedParties_oldest() throws Exception { + mockMvc.perform(get("/api/my/parties/suggestions") + .param("isCockpleRecommend", "false") + .param("sort", "오래된 순") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content.length()").value(3)) + .andExpect(jsonPath("$.data.content[0].partyId").value(recParty1.getId())) + .andExpect(jsonPath("$.data.content[1].partyId").value(recParty2.getId())) + .andExpect(jsonPath("$.data.content[2].partyId").value(recParty3.getId())); } @Test @DisplayName("200 - 검색 모드 시 모임명으로 검색된 결과를 반환한다") - void success_getRecommendedParties_searchMode() throws Exception { + void success_getRecommendedParties_search() throws Exception { mockMvc.perform(get("/api/my/parties/suggestions") - .param("search", "추천") + .param("search", "추천 모임 2") .param("isCockpleRecommend", "false") .param("page", "0") .param("size", "10")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("COMMON200")) .andExpect(jsonPath("$.data.content").isArray()) - .andExpect(jsonPath("$.data.content[0].partyName", containsString("추천"))); + .andExpect(jsonPath("$.data.content.length()").value(1)) + .andExpect(jsonPath("$.data.content[0].partyName").value("추천 모임 2")); } @Test diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 098e07411..75843abaf 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -240,48 +240,45 @@ void fail_getPartyMembers_partyInactive() { class GetMyParties { @Test - @DisplayName("성공 - 내 모임 목록과 부가 정보(운동 횟수, 다음 운동 정보, 북마크 여부)를 조합하여 반환한다") + @DisplayName("성공 - 내 모임 목록을 최신순(기본값)으로 페이징하여 반환한다") void success_getMyParties() { // given Long memberId = 10L; Pageable pageable = PageRequest.of(0, 10); PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 1L); + Party party1 = PartyFixture.createParty("모임1", 10L, addr); ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("모임2", 10L, addr); ReflectionTestUtils.setField(party2, "id", 2L); + Party party3 = PartyFixture.createParty("모임3", 10L, addr); ReflectionTestUtils.setField(party3, "id", 3L); - Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); - - PartyDTO.Response expectedResponse = PartyDTO.Response.builder() - .partyId(1L) - .partyName("테스트 모임") - .totalExerciseCount(5) - .nextExerciseInfo("05.01 오전 운동") - .isBookmarked(true) - .build(); + Slice partySlice = new SliceImpl<>(List.of(party3, party2, party1), pageable, false); given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) .willReturn(partySlice); - given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L))) + given(exerciseRepository.findTotalExerciseCountsByPartyIds(anyList())) .willReturn(List.of()); - given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L))) + given(exerciseRepository.findUpcomingExercisesByPartyIds(anyList())) .willReturn(List.of()); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) - .willReturn(Set.of(1L)); + .willReturn(Set.of(1L, 2L, 3L)); + // when - Slice result = partyQueryService.getMyParties(memberId, false, "최신순", - pageable); + Slice result = partyQueryService.getMyParties(memberId, false, "최신순", pageable); // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); - assertThat(result.getContent().get(0).isBookmarked()).isTrue(); - - verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + assertThat(result.getContent()).hasSize(3); + assertThat(result.getContent().get(0).partyId()).isEqualTo(3L); + assertThat(result.getContent().get(1).partyId()).isEqualTo(2L); + assertThat(result.getContent().get(2).partyId()).isEqualTo(1L); + + org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(Pageable.class); + verify(partyRepository).findMyParty(eq(memberId), eq(false), captor.capture()); + assertThat(captor.getValue().getSort().getOrderFor("createdAt").getDirection()) + .isEqualTo(org.springframework.data.domain.Sort.Direction.DESC); } @Test - @DisplayName("성공 - 사용자가 가입한 모임 목록을 오래된 순으로 페이징하여 반환한다") + @DisplayName("성공 - 내 모임 목록을 오래된 순으로 페이징하여 반환한다") void success_getMyParties_oldest() { // given Long memberId = 10L; @@ -289,34 +286,37 @@ void success_getMyParties_oldest() { PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party1 = PartyFixture.createParty("테스트 모임1", 10L, addr); - ReflectionTestUtils.setField(party1, "id", 1L); - Party party2 = PartyFixture.createParty("테스트 모임2", 10L, addr); - ReflectionTestUtils.setField(party2, "id", 2L); + Party party1 = PartyFixture.createParty("모임1", 10L, addr); ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("모임2", 10L, addr); ReflectionTestUtils.setField(party2, "id", 2L); + Party party3 = PartyFixture.createParty("모임3", 10L, addr); ReflectionTestUtils.setField(party3, "id", 3L); - // 오래된 순: party1, party2 순서 - Slice partySlice = new SliceImpl<>(List.of(party1, party2), pageable, false); + // 오래된 순 응답 가정 + Slice partySlice = new SliceImpl<>(List.of(party1, party2, party3), pageable, false); given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) .willReturn(partySlice); - given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L, 2L))) + given(exerciseRepository.findTotalExerciseCountsByPartyIds(anyList())) .willReturn(List.of()); - given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L, 2L))) + given(exerciseRepository.findUpcomingExercisesByPartyIds(anyList())) .willReturn(List.of()); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) .willReturn(Set.of()); // when - Slice result = partyQueryService.getMyParties(memberId, false, - "오래된 순", pageable); + Slice result = partyQueryService.getMyParties(memberId, false, "오래된 순", pageable); // then - assertThat(result.getContent()).hasSize(2); - verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + assertThat(result.getContent()).hasSize(3); + assertThat(result.getContent().get(0).partyId()).isEqualTo(1L); + + org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(Pageable.class); + verify(partyRepository).findMyParty(eq(memberId), eq(false), captor.capture()); + assertThat(captor.getValue().getSort().getOrderFor("createdAt").getDirection()) + .isEqualTo(org.springframework.data.domain.Sort.Direction.ASC); } @Test - @DisplayName("성공 - 사용자가 가입한 모임 목록을 운동 많은 순으로 페이징하여 반환한다") + @DisplayName("성공 - 내 모임 목록을 운동 많은 순으로 페이징하여 반환한다") void success_getMyParties_exerciseCount() { // given Long memberId = 10L; @@ -324,30 +324,33 @@ void success_getMyParties_exerciseCount() { PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party1 = PartyFixture.createParty("운동많은모임", 10L, addr); - ReflectionTestUtils.setField(party1, "id", 1L); - Party party2 = PartyFixture.createParty("운동적은모임", 10L, addr); - ReflectionTestUtils.setField(party2, "id", 2L); + Party party1 = PartyFixture.createParty("모임1", 10L, addr); ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("모임2", 10L, addr); ReflectionTestUtils.setField(party2, "id", 2L); + Party party3 = PartyFixture.createParty("모임3", 10L, addr); ReflectionTestUtils.setField(party3, "id", 3L); - // 운동 많은 순: party1, party2 순서 - Slice partySlice = new SliceImpl<>(List.of(party1, party2), pageable, false); + // 운동 많은 순 응답 가정 (20회, 10회, 5회) + Slice partySlice = new SliceImpl<>(List.of(party2, party1, party3), pageable, false); given(partyRepository.findMyParty(eq(memberId), eq(false), any(Pageable.class))) .willReturn(partySlice); - given(exerciseRepository.findTotalExerciseCountsByPartyIds(List.of(1L, 2L))) + given(exerciseRepository.findTotalExerciseCountsByPartyIds(anyList())) .willReturn(List.of()); - given(exerciseRepository.findUpcomingExercisesByPartyIds(List.of(1L, 2L))) + given(exerciseRepository.findUpcomingExercisesByPartyIds(anyList())) .willReturn(List.of()); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)) .willReturn(Set.of()); // when - Slice result = partyQueryService.getMyParties(memberId, false, - "운동 많은 순", pageable); + Slice result = partyQueryService.getMyParties(memberId, false, "운동 많은 순", pageable); // then - assertThat(result.getContent()).hasSize(2); - verify(partyRepository).findMyParty(eq(memberId), eq(false), any(Pageable.class)); + assertThat(result.getContent()).hasSize(3); + assertThat(result.getContent().get(0).partyId()).isEqualTo(2L); + + org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(Pageable.class); + verify(partyRepository).findMyParty(eq(memberId), eq(false), captor.capture()); + assertThat(captor.getValue().getSort().getOrderFor("exerciseCount").getDirection()) + .isEqualTo(org.springframework.data.domain.Sort.Direction.DESC); } @Test @@ -370,7 +373,7 @@ void fail_getMyParties_invalidSort() { class GetSimpleMyParties { @Test - @DisplayName("성공 - 유효한 회원 ID가 주어지면 가입한 모임의 간략화된 목록을 반환한다") + @DisplayName("성공 - 유효한 회원 ID가 주어지면 가입한 모임 3개의 간략화된 목록을 반환한다") void success_getSimpleMyParties() { // given Long memberId = 1L; @@ -378,30 +381,29 @@ void success_getSimpleMyParties() { Member member = MemberFixture.createMember("사용자", Gender.MALE, Level.A, 1001L); ReflectionTestUtils.setField(member, "id", memberId); - PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party = PartyFixture.createParty("테스트 모임", 10L, addr); - ReflectionTestUtils.setField(party, "id", 10L); - MemberParty memberParty = MemberFixture.createMemberParty(party, member, Role.PARTY_MEMBER); + Party party1 = PartyFixture.createParty("모임1", 10L, addr); ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("모임2", 10L, addr); ReflectionTestUtils.setField(party2, "id", 2L); + Party party3 = PartyFixture.createParty("모임3", 10L, addr); ReflectionTestUtils.setField(party3, "id", 3L); - Slice memberPartySlice = new SliceImpl<>(List.of(memberParty), pageable, false); + MemberParty mp1 = MemberFixture.createMemberParty(party1, member, Role.PARTY_MEMBER); + MemberParty mp2 = MemberFixture.createMemberParty(party2, member, Role.PARTY_MEMBER); + MemberParty mp3 = MemberFixture.createMemberParty(party3, member, Role.PARTY_MEMBER); - PartySimpleDTO.Response expectedResponse = PartySimpleDTO.Response.builder() - .partyId(10L) - .partyName("테스트 모임") - .build(); + Slice memberPartySlice = new SliceImpl<>(List.of(mp1, mp2, mp3), pageable, false); given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); given(memberPartyRepository.findByMember(member, pageable)).willReturn(memberPartySlice); // when - Slice result = partyQueryService.getSimpleMyParties(memberId, - pageable); + Slice result = partyQueryService.getSimpleMyParties(memberId, pageable); // then - assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("테스트 모임"); + assertThat(result.getContent()).hasSize(3); + assertThat(result.getContent().get(0).partyId()).isEqualTo(1L); + assertThat(result.getContent().get(1).partyId()).isEqualTo(2L); + assertThat(result.getContent().get(2).partyId()).isEqualTo(3L); verify(memberRepository).findById(memberId); verify(memberPartyRepository).findByMember(member, pageable); @@ -470,32 +472,124 @@ void success_getRecommendedParties_cockpleRecommend() { } @Test - @DisplayName("성공 - 필터 모드 시 설정한 필터 조건(addr1, addr2 등)에 맞는 모임 목록을 반환한다") - void success_getRecommendedParties_filterMode() { + @DisplayName("성공 - 필터 모드 시 조건에 맞는 모임 목록을 최신순으로 반환한다") + void success_getRecommendedParties_latest() { // given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); - PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder() - .addr1("서울특별시") - .addr2("강남구") - .build(); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().addr1("서울특별시").build(); - Party filteredParty = PartyFixture.createParty("필터 모임", 2L, - PartyFixture.createPartyAddr("서울특별시", "강남구")); - ReflectionTestUtils.setField(filteredParty, "id", 200L); - Slice partySlice = new SliceImpl<>(List.of(filteredParty), pageable, false); + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party p1 = PartyFixture.createParty("모임1", 2L, addr); ReflectionTestUtils.setField(p1, "id", 1L); + Party p2 = PartyFixture.createParty("모임2", 2L, addr); ReflectionTestUtils.setField(p2, "id", 2L); + Party p3 = PartyFixture.createParty("모임3", 2L, addr); ReflectionTestUtils.setField(p3, "id", 3L); + + Slice partySlice = new SliceImpl<>(List.of(p3, p2, p1), pageable, false); given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) .willReturn(partySlice); given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); // when - Slice result = partyQueryService.getRecommendedParties(memberId, false, - filter, "최신순", pageable); + Slice result = partyQueryService.getRecommendedParties(memberId, false, filter, "최신순", pageable); + + // then + assertThat(result.getContent()).hasSize(3); + assertThat(result.getContent().get(0).partyId()).isEqualTo(3L); + + org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(Pageable.class); + verify(partyRepository).searchParties(eq(memberId), eq(filter), captor.capture()); + assertThat(captor.getValue().getSort().getOrderFor("createdAt").getDirection()) + .isEqualTo(org.springframework.data.domain.Sort.Direction.DESC); + } + + @Test + @DisplayName("성공 - 필터 모드 시 조건에 맞는 모임 목록을 오래된 순으로 반환한다") + void success_getRecommendedParties_oldest() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party p1 = PartyFixture.createParty("모임1", 2L, addr); ReflectionTestUtils.setField(p1, "id", 1L); + Party p2 = PartyFixture.createParty("모임2", 2L, addr); ReflectionTestUtils.setField(p2, "id", 2L); + Party p3 = PartyFixture.createParty("모임3", 2L, addr); ReflectionTestUtils.setField(p3, "id", 3L); + + Slice partySlice = new SliceImpl<>(List.of(p1, p2, p3), pageable, false); + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, filter, "오래된 순", pageable); + + // then + assertThat(result.getContent()).hasSize(3); + assertThat(result.getContent().get(0).partyId()).isEqualTo(1L); + + org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(Pageable.class); + verify(partyRepository).searchParties(eq(memberId), eq(filter), captor.capture()); + assertThat(captor.getValue().getSort().getOrderFor("createdAt").getDirection()) + .isEqualTo(org.springframework.data.domain.Sort.Direction.ASC); + } + + @Test + @DisplayName("성공 - 필터 모드 시 조건에 맞는 모임 목록을 운동 많은 순으로 반환한다") + void success_getRecommendedParties_exerciseCount() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party p1 = PartyFixture.createParty("모임1", 2L, addr); ReflectionTestUtils.setField(p1, "id", 1L); + Party p2 = PartyFixture.createParty("모임2", 2L, addr); ReflectionTestUtils.setField(p2, "id", 2L); + Party p3 = PartyFixture.createParty("모임3", 2L, addr); ReflectionTestUtils.setField(p3, "id", 3L); + + Slice partySlice = new SliceImpl<>(List.of(p2, p1, p3), pageable, false); // 20회, 10회, 5회 가정 + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, filter, "운동 많은 순", pageable); + + // then + assertThat(result.getContent()).hasSize(3); + assertThat(result.getContent().get(0).partyId()).isEqualTo(2L); + + org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(Pageable.class); + verify(partyRepository).searchParties(eq(memberId), eq(filter), captor.capture()); + assertThat(captor.getValue().getSort().getOrderFor("exerciseCount").getDirection()) + .isEqualTo(org.springframework.data.domain.Sort.Direction.DESC); + } + + @Test + @DisplayName("성공 - 검색 모드 시 검색 키워드에 맞는 모임 목록을 반환한다") + void success_getRecommendedParties_search() { + // given + Long memberId = 1L; + Pageable pageable = PageRequest.of(0, 10); + PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().search("검색값").build(); + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("검색결과모임", 2L, addr); + ReflectionTestUtils.setField(party, "id", 100L); + Slice partySlice = new SliceImpl<>(List.of(party), pageable, false); + + given(partyRepository.searchParties(eq(memberId), eq(filter), any(Pageable.class))) + .willReturn(partySlice); + given(partyBookmarkRepository.findAllPartyIdsByMemberId(memberId)).willReturn(Set.of()); + + // when + Slice result = partyQueryService.getRecommendedParties(memberId, false, filter, "최신순", pageable); // then assertThat(result.getContent()).hasSize(1); - assertThat(result.getContent().get(0).partyName()).isEqualTo("필터 모임"); + assertThat(result.getContent().get(0).partyName()).isEqualTo("검색결과모임"); verify(partyRepository).searchParties(eq(memberId), eq(filter), any(Pageable.class)); } From 7e05c851e2cb3c7aad888ed22e530829babbc541 Mon Sep 17 00:00:00 2001 From: dbalsk Date: Wed, 1 Apr 2026 20:42:46 +0900 Subject: [PATCH 63/63] =?UTF-8?q?test:=20import=20=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/PartyIntegrationTest.java | 20 ++--- .../service/PartyCommandServiceTest.java | 47 +++++----- .../party/service/PartyQueryServiceTest.java | 89 ++++++++++++------- 3 files changed, 89 insertions(+), 67 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java index fcbbfb520..25fe51839 100644 --- a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -29,10 +29,7 @@ import umc.cockple.demo.domain.party.domain.PartyInvitation; import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; -import umc.cockple.demo.domain.party.enums.ActivityTime; -import umc.cockple.demo.domain.party.enums.ParticipationType; -import umc.cockple.demo.domain.party.enums.RequestAction; -import umc.cockple.demo.domain.party.enums.RequestStatus; +import umc.cockple.demo.domain.party.enums.*; import umc.cockple.demo.domain.party.exception.PartyErrorCode; import umc.cockple.demo.domain.party.repository.PartyAddrRepository; import umc.cockple.demo.domain.party.repository.PartyInvitationRepository; @@ -51,7 +48,6 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -329,8 +325,8 @@ void success_getMyParties_exerciseCount() throws Exception { @Test @DisplayName("200 - 가입한 모임이 없을 경우 빈 목록을 반환한다") void success_emptyMyParties() throws Exception { - Member newMember = memberRepository.save(umc.cockple.demo.support.fixture.MemberFixture.createMember("뉴비", - umc.cockple.demo.global.enums.Gender.MALE, umc.cockple.demo.global.enums.Level.BEGINNER, 3003L)); + Member newMember = memberRepository.save(MemberFixture.createMember("뉴비", + Gender.MALE, Level.BEGINNER, 3003L)); SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); mockMvc.perform(get("/api/my/parties") @@ -360,7 +356,7 @@ void setUpSimpleMyParties() { // party2 party2 = partyRepository.save(PartyFixture.createParty("간략 모임 2", manager.getId(), addr)); memberPartyRepository.save(MemberFixture.createMemberParty(party2, manager, Role.PARTY_MANAGER)); - + // party3 party3 = partyRepository.save(PartyFixture.createParty("간략 모임 3", manager.getId(), addr)); memberPartyRepository.save(MemberFixture.createMemberParty(party3, manager, Role.PARTY_MANAGER)); @@ -385,8 +381,8 @@ void success_getSimpleMyParties() throws Exception { @Test @DisplayName("200 - 가입한 모임이 없을 경우 빈 목록을 반환한다") void success_emptySimpleMyParties() throws Exception { - Member newMember = memberRepository.save(umc.cockple.demo.support.fixture.MemberFixture.createMember("뉴비", - umc.cockple.demo.global.enums.Gender.MALE, umc.cockple.demo.global.enums.Level.BEGINNER, 3003L)); + Member newMember = memberRepository.save(MemberFixture.createMember("뉴비", + Gender.MALE, Level.BEGINNER, 3003L)); SecurityContextHelper.setAuthentication(newMember.getId(), newMember.getNickname()); mockMvc.perform(get("/api/my/parties/simple") @@ -625,7 +621,7 @@ void fail_createJoinRequest_genderMismatch() throws Exception { Party womenParty = partyRepository.save(Party.builder() .partyName("여복 전용 모임") .partyType(ParticipationType.WOMEN_DOUBLES) - .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .status(PartyStatus.ACTIVE) .ownerId(manager.getId()) .partyAddr(addr) .minBirthYear(1900) @@ -798,7 +794,7 @@ void success_deleteParty() throws Exception { // 검증 Party deletedParty = partyRepository.findById(party.getId()).orElseThrow(); - assertThat(deletedParty.getStatus()).isEqualTo(umc.cockple.demo.domain.party.enums.PartyStatus.INACTIVE); + assertThat(deletedParty.getStatus()).isEqualTo(PartyStatus.INACTIVE); } @Test diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java index 48739c70a..e90511e4c 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyCommandServiceTest.java @@ -14,6 +14,8 @@ import umc.cockple.demo.domain.file.service.FileService; import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.exception.MemberErrorCode; +import umc.cockple.demo.domain.member.exception.MemberException; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.notification.service.NotificationCommandService; @@ -24,6 +26,7 @@ import umc.cockple.demo.domain.party.domain.PartyJoinRequest; import umc.cockple.demo.domain.party.dto.*; import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.PartyStatus; import umc.cockple.demo.domain.party.enums.RequestAction; import umc.cockple.demo.domain.party.enums.RequestStatus; import umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent; @@ -245,8 +248,8 @@ void fail_leaveParty_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.leaveParty(partyId, 10L)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -332,7 +335,7 @@ void fail_createJoinRequest_genderMismatch() { Party party = Party.builder() .partyName("여복 모임") .partyType(ParticipationType.WOMEN_DOUBLES) - .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .status(PartyStatus.ACTIVE) .ownerId(10L) .build(); Member member = MemberFixture.createMember("남자지원자", Gender.MALE, Level.B, 1L); // 남성 지원 @@ -360,7 +363,7 @@ void fail_createJoinRequest_ageMismatch() { .partyName("나이 제한 모임") .minBirthYear(1990) .maxBirthYear(2000) - .status(umc.cockple.demo.domain.party.enums.PartyStatus.ACTIVE) + .status(PartyStatus.ACTIVE) .ownerId(10L) .build(); // 1980년생 지원자 (범위 밖) @@ -400,8 +403,8 @@ void fail_createJoinRequest_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.createJoinRequest(1L, 1L)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -596,8 +599,8 @@ void fail_createParty_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.createParty(memberId, request)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -705,8 +708,8 @@ void fail_updateParty_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.updateParty(partyId, memberId, request)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -735,7 +738,7 @@ void success_deleteParty() { partyCommandService.deleteParty(partyId, ownerId); // then - assertThat(party.getStatus()).isEqualTo(umc.cockple.demo.domain.party.enums.PartyStatus.INACTIVE); + assertThat(party.getStatus()).isEqualTo(PartyStatus.INACTIVE); } @Test @@ -815,8 +818,8 @@ void fail_deleteParty_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.deleteParty(partyId, memberId)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -988,8 +991,8 @@ void fail_updateMemberRole_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.updateMemberRole(partyId, 1L, 1L, request)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -1177,8 +1180,8 @@ void fail_removeMember_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.removeMember(partyId, 10L, 1L)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -1222,7 +1225,7 @@ void success_actionJoinRequest_approve() { // then assertThat(joinRequest.getStatus()).isEqualTo(RequestStatus.APPROVED); verify(chatRoomService).joinPartyChatRoom(partyId, applicant); - verify(applicationEventPublisher).publishEvent(any(umc.cockple.demo.domain.party.events.PartyMemberJoinedEvent.class)); + verify(applicationEventPublisher).publishEvent(any(PartyMemberJoinedEvent.class)); verify(notificationCommandService).createNotification(any()); } @@ -1596,8 +1599,8 @@ void fail_createInvitation_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.createInvitation(partyId, 1L, 1L)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -1757,8 +1760,8 @@ void fail_actionInvitation_memberNotFound() { // when & then assertThatThrownBy(() -> partyCommandService.actionInvitation(20L, request, invitationId)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()).isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()).isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java index 75843abaf..e9427a595 100644 --- a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -19,6 +19,8 @@ import umc.cockple.demo.domain.member.domain.Member; import umc.cockple.demo.domain.member.domain.MemberAddr; import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.exception.MemberErrorCode; +import umc.cockple.demo.domain.member.exception.MemberException; import umc.cockple.demo.domain.member.repository.MemberAddrRepository; import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; @@ -247,9 +249,12 @@ void success_getMyParties() { Pageable pageable = PageRequest.of(0, 10); PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party1 = PartyFixture.createParty("모임1", 10L, addr); ReflectionTestUtils.setField(party1, "id", 1L); - Party party2 = PartyFixture.createParty("모임2", 10L, addr); ReflectionTestUtils.setField(party2, "id", 2L); - Party party3 = PartyFixture.createParty("모임3", 10L, addr); ReflectionTestUtils.setField(party3, "id", 3L); + Party party1 = PartyFixture.createParty("모임1", 10L, addr); + ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("모임2", 10L, addr); + ReflectionTestUtils.setField(party2, "id", 2L); + Party party3 = PartyFixture.createParty("모임3", 10L, addr); + ReflectionTestUtils.setField(party3, "id", 3L); Slice partySlice = new SliceImpl<>(List.of(party3, party2, party1), pageable, false); @@ -286,9 +291,12 @@ void success_getMyParties_oldest() { PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party1 = PartyFixture.createParty("모임1", 10L, addr); ReflectionTestUtils.setField(party1, "id", 1L); - Party party2 = PartyFixture.createParty("모임2", 10L, addr); ReflectionTestUtils.setField(party2, "id", 2L); - Party party3 = PartyFixture.createParty("모임3", 10L, addr); ReflectionTestUtils.setField(party3, "id", 3L); + Party party1 = PartyFixture.createParty("모임1", 10L, addr); + ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("모임2", 10L, addr); + ReflectionTestUtils.setField(party2, "id", 2L); + Party party3 = PartyFixture.createParty("모임3", 10L, addr); + ReflectionTestUtils.setField(party3, "id", 3L); // 오래된 순 응답 가정 Slice partySlice = new SliceImpl<>(List.of(party1, party2, party3), pageable, false); @@ -324,9 +332,12 @@ void success_getMyParties_exerciseCount() { PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party1 = PartyFixture.createParty("모임1", 10L, addr); ReflectionTestUtils.setField(party1, "id", 1L); - Party party2 = PartyFixture.createParty("모임2", 10L, addr); ReflectionTestUtils.setField(party2, "id", 2L); - Party party3 = PartyFixture.createParty("모임3", 10L, addr); ReflectionTestUtils.setField(party3, "id", 3L); + Party party1 = PartyFixture.createParty("모임1", 10L, addr); + ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("모임2", 10L, addr); + ReflectionTestUtils.setField(party2, "id", 2L); + Party party3 = PartyFixture.createParty("모임3", 10L, addr); + ReflectionTestUtils.setField(party3, "id", 3L); // 운동 많은 순 응답 가정 (20회, 10회, 5회) Slice partySlice = new SliceImpl<>(List.of(party2, party1, party3), pageable, false); @@ -383,9 +394,12 @@ void success_getSimpleMyParties() { ReflectionTestUtils.setField(member, "id", memberId); PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party party1 = PartyFixture.createParty("모임1", 10L, addr); ReflectionTestUtils.setField(party1, "id", 1L); - Party party2 = PartyFixture.createParty("모임2", 10L, addr); ReflectionTestUtils.setField(party2, "id", 2L); - Party party3 = PartyFixture.createParty("모임3", 10L, addr); ReflectionTestUtils.setField(party3, "id", 3L); + Party party1 = PartyFixture.createParty("모임1", 10L, addr); + ReflectionTestUtils.setField(party1, "id", 1L); + Party party2 = PartyFixture.createParty("모임2", 10L, addr); + ReflectionTestUtils.setField(party2, "id", 2L); + Party party3 = PartyFixture.createParty("모임3", 10L, addr); + ReflectionTestUtils.setField(party3, "id", 3L); MemberParty mp1 = MemberFixture.createMemberParty(party1, member, Role.PARTY_MEMBER); MemberParty mp2 = MemberFixture.createMemberParty(party2, member, Role.PARTY_MEMBER); @@ -420,11 +434,11 @@ void fail_getSimpleMyParties_memberNotFound() { // when & then assertThatThrownBy(() -> partyQueryService.getSimpleMyParties(invalidMemberId, pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .isInstanceOf(MemberException.class) .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) + ((MemberException) e) .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } } @@ -480,9 +494,12 @@ void success_getRecommendedParties_latest() { PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().addr1("서울특별시").build(); PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party p1 = PartyFixture.createParty("모임1", 2L, addr); ReflectionTestUtils.setField(p1, "id", 1L); - Party p2 = PartyFixture.createParty("모임2", 2L, addr); ReflectionTestUtils.setField(p2, "id", 2L); - Party p3 = PartyFixture.createParty("모임3", 2L, addr); ReflectionTestUtils.setField(p3, "id", 3L); + Party p1 = PartyFixture.createParty("모임1", 2L, addr); + ReflectionTestUtils.setField(p1, "id", 1L); + Party p2 = PartyFixture.createParty("모임2", 2L, addr); + ReflectionTestUtils.setField(p2, "id", 2L); + Party p3 = PartyFixture.createParty("모임3", 2L, addr); + ReflectionTestUtils.setField(p3, "id", 3L); Slice partySlice = new SliceImpl<>(List.of(p3, p2, p1), pageable, false); @@ -512,9 +529,12 @@ void success_getRecommendedParties_oldest() { PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party p1 = PartyFixture.createParty("모임1", 2L, addr); ReflectionTestUtils.setField(p1, "id", 1L); - Party p2 = PartyFixture.createParty("모임2", 2L, addr); ReflectionTestUtils.setField(p2, "id", 2L); - Party p3 = PartyFixture.createParty("모임3", 2L, addr); ReflectionTestUtils.setField(p3, "id", 3L); + Party p1 = PartyFixture.createParty("모임1", 2L, addr); + ReflectionTestUtils.setField(p1, "id", 1L); + Party p2 = PartyFixture.createParty("모임2", 2L, addr); + ReflectionTestUtils.setField(p2, "id", 2L); + Party p3 = PartyFixture.createParty("모임3", 2L, addr); + ReflectionTestUtils.setField(p3, "id", 3L); Slice partySlice = new SliceImpl<>(List.of(p1, p2, p3), pageable, false); @@ -544,9 +564,12 @@ void success_getRecommendedParties_exerciseCount() { PartyFilterDTO.Request filter = PartyFilterDTO.Request.builder().build(); PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); - Party p1 = PartyFixture.createParty("모임1", 2L, addr); ReflectionTestUtils.setField(p1, "id", 1L); - Party p2 = PartyFixture.createParty("모임2", 2L, addr); ReflectionTestUtils.setField(p2, "id", 2L); - Party p3 = PartyFixture.createParty("모임3", 2L, addr); ReflectionTestUtils.setField(p3, "id", 3L); + Party p1 = PartyFixture.createParty("모임1", 2L, addr); + ReflectionTestUtils.setField(p1, "id", 1L); + Party p2 = PartyFixture.createParty("모임2", 2L, addr); + ReflectionTestUtils.setField(p2, "id", 2L); + Party p3 = PartyFixture.createParty("모임3", 2L, addr); + ReflectionTestUtils.setField(p3, "id", 3L); Slice partySlice = new SliceImpl<>(List.of(p2, p1, p3), pageable, false); // 20회, 10회, 5회 가정 @@ -606,11 +629,11 @@ void fail_getRecommendedParties_memberNotFound() { // when & then assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .isInstanceOf(MemberException.class) .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) + ((MemberException) e) .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } @Test @@ -630,11 +653,11 @@ void fail_getRecommendedParties_mainAddressNotFound() { // when & then assertThatThrownBy(() -> partyQueryService.getRecommendedParties(memberId, true, filter, "최신순", pageable)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) + .isInstanceOf(MemberException.class) .satisfies(e -> assertThat( - ((umc.cockple.demo.domain.member.exception.MemberException) e) + ((MemberException) e) .getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MAIN_ADDRESS_NULL)); + .isEqualTo(MemberErrorCode.MAIN_ADDRESS_NULL)); } } @@ -761,9 +784,9 @@ void fail_getPartyDetails_memberNotFound() { // when & then assertThatThrownBy(() -> partyQueryService.getPartyDetails(partyId, 1L)) - .isInstanceOf(umc.cockple.demo.domain.member.exception.MemberException.class) - .satisfies(e -> assertThat(((umc.cockple.demo.domain.member.exception.MemberException) e).getCode()) - .isEqualTo(umc.cockple.demo.domain.member.exception.MemberErrorCode.MEMBER_NOT_FOUND)); + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()) + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); } }