From 720e4bc11f02d7db80d77bb0c0148765018ec23d Mon Sep 17 00:00:00 2001 From: kanghana1 Date: Thu, 26 Mar 2026 23:11:42 +0900 Subject: [PATCH 1/4] =?UTF-8?q?test:=20=EC=B0=9C=20=ED=86=B5=ED=95=A9?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?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/BookmarkIntegrationTest.java | 467 ++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java 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 new file mode 100644 index 000000000..e4c1ef026 --- /dev/null +++ b/src/test/java/umc/cockple/demo/domain/bookmark/integration/BookmarkIntegrationTest.java @@ -0,0 +1,467 @@ +package umc.cockple.demo.domain.bookmark.integration; + +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MockMvc; +import umc.cockple.demo.domain.bookmark.domain.ExerciseBookmark; +import umc.cockple.demo.domain.bookmark.domain.PartyBookmark; +import umc.cockple.demo.domain.bookmark.enums.BookmarkedExerciseOrderType; +import umc.cockple.demo.domain.bookmark.exception.BookmarkErrorCode; +import umc.cockple.demo.domain.bookmark.repository.ExerciseBookmarkRepository; +import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; +import umc.cockple.demo.domain.exercise.domain.Exercise; +import umc.cockple.demo.domain.exercise.domain.ExerciseAddr; +import umc.cockple.demo.domain.exercise.exception.ExerciseErrorCode; +import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; +import umc.cockple.demo.domain.member.domain.Member; +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.domain.Party; +import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.enums.PartyOrderType; +import umc.cockple.demo.domain.party.exception.PartyErrorCode; +import umc.cockple.demo.domain.party.repository.PartyAddrRepository; +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.IntegrationTestBase; +import umc.cockple.demo.support.SecurityContextHelper; +import umc.cockple.demo.support.fixture.MemberFixture; +import umc.cockple.demo.support.fixture.PartyFixture; + +import java.time.LocalDate; +import java.time.LocalTime; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.nullValue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +class BookmarkIntegrationTest extends IntegrationTestBase { + + @Autowired MockMvc mockMvc; + @Autowired MemberRepository memberRepository; + @Autowired PartyRepository partyRepository; + @Autowired PartyAddrRepository partyAddrRepository; + @Autowired ExerciseRepository exerciseRepository; + @Autowired ExerciseBookmarkRepository exerciseBookmarkRepository; + @Autowired PartyBookmarkRepository partyBookmarkRepository; + @Autowired MemberPartyRepository memberPartyRepository; + @Autowired MemberExerciseRepository memberExerciseRepository; + + + private Member member; + private Party bookmarkParty; + private Exercise bookmarkExercise; + + @BeforeEach + void setUp() { + member = memberRepository.save(MemberFixture.createMember("테스터", Gender.MALE, Level.A, 1001L)); + + PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("경기도", "안산시")); + bookmarkParty = partyRepository.save(PartyFixture.createParty("테스트 모임", member.getId(), addr)); + + memberPartyRepository.save(MemberFixture.createMemberParty(bookmarkParty, member, Role.party_MANAGER)); + + bookmarkExercise = exerciseRepository.save(Exercise.builder() + .party(bookmarkParty) + .date(LocalDate.of(2026, 12, 31)) + .startTime(LocalTime.of(10, 0)) + .endTime(LocalTime.of(12, 0)) + .maxCapacity(10) + .partyGuestAccept(true) + .outsideGuestAccept(false) + .exerciseAddr(ExerciseAddr.builder() + .addr1("경기도") + .addr2("안산시") + .streetAddr("경기도 안산시 한양대학로 1") + .buildingName("테스트 체육관") + .latitude(37.5) + .longitude(127.0) + .build()) + .build()); + } + + @AfterEach + void tearDown() { + exerciseBookmarkRepository.deleteAll(); + partyBookmarkRepository.deleteAll(); + memberExerciseRepository.deleteAll(); + exerciseRepository.deleteAll(); + memberPartyRepository.deleteAll(); + partyRepository.deleteAll(); + partyAddrRepository.deleteAll(); + memberRepository.deleteAll(); + SecurityContextHelper.clearAuthentication(); + } + + + @Nested + @DisplayName("POST /api/parties/{partyId}/bookmark - 모임 찜하기") + class PartyBookmarkCreate { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("200 - 모임을 찜하면 partyBookmarkId를 반환한다") + void partyBookmark_success() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/bookmark", bookmarkParty.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").isNumber()); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("404 - 존재하지 않는 모임이면 에러를 반환한다") + void partyNotFound() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(post("/api/parties/{partyId}/bookmark", 999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_NOT_FOUND.getMessage())); + } + + @Test + @DisplayName("400 - 이미 찜한 모임이면 에러를 반환한다") + void alreadyBookmarked() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + partyBookmarkRepository.save(PartyBookmark.builder() + .party(bookmarkParty) + .member(member) + .orderType(PartyOrderType.LATEST) + .build()); + + mockMvc.perform(post("/api/parties/{partyId}/bookmark", bookmarkParty.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(BookmarkErrorCode.ALREADY_BOOKMARK.getCode())) + .andExpect(jsonPath("$.message").value(BookmarkErrorCode.ALREADY_BOOKMARK.getMessage())); + } + + @Test + @DisplayName("400 - 삭제된 모임은 찜할 수 없다") + void partyIsDeleted() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + bookmarkParty.delete(); + partyRepository.save(bookmarkParty); + + mockMvc.perform(post("/api/parties/{partyId}/bookmark", bookmarkParty.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())) + .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_IS_DELETED.getMessage())); + } + } + } + + + @Nested + @DisplayName("DELETE /api/parties/{partyId}/bookmark - 모임 찜 해제") + class PartyBookmarkRelease { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("200 - 찜한 모임을 해제하면 200 응답을 반환한다") + void releasePartyBookmark_success() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + partyBookmarkRepository.save(PartyBookmark.builder() + .party(bookmarkParty) + .member(member) + .orderType(PartyOrderType.LATEST) + .build()); + + mockMvc.perform(delete("/api/parties/{partyId}/bookmark", bookmarkParty.getId())) + .andExpect(status().isOk()); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("404 - 존재하지 않는 모임이면 에러를 반환한다") + void partyNotFound() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/bookmark", 999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_NOT_FOUND.getMessage())); + } + + @Test + @DisplayName("400 - 찜하지 않은 모임을 해제하려 하면 에러를 반환한다") + void notBookmarked() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(delete("/api/parties/{partyId}/bookmark", bookmarkParty.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(BookmarkErrorCode.ALREADY_RELEASE_BOOKMARK.getCode())) + .andExpect(jsonPath("$.message").value(BookmarkErrorCode.ALREADY_RELEASE_BOOKMARK.getMessage())); + } + } + } + + + @Nested + @DisplayName("POST /api/exercises/{exerciseId}/bookmark - 운동 찜하기") + class ExerciseBookmarkCreate { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("200 - 운동을 찜하면 exerciseBookmarkId를 반환한다") + void exerciseBookmark_success() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(post("/api/exercises/{exerciseId}/bookmark", bookmarkExercise.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").isNumber()); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("404 - 존재하지 않는 운동이면 에러를 반환한다") + void exerciseNotFound() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(post("/api/exercises/{exerciseId}/bookmark", 999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ExerciseErrorCode.EXERCISE_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message").value(ExerciseErrorCode.EXERCISE_NOT_FOUND.getMessage())); + } + + @Test + @DisplayName("400 - 이미 찜한 운동이면 에러를 반환한다") + void alreadyBookmarked() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + exerciseBookmarkRepository.save(ExerciseBookmark.builder() + .member(member) + .exercise(bookmarkExercise) + .build()); + + mockMvc.perform(post("/api/exercises/{exerciseId}/bookmark", bookmarkExercise.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(BookmarkErrorCode.ALREADY_BOOKMARK.getCode())) + .andExpect(jsonPath("$.message").value(BookmarkErrorCode.ALREADY_BOOKMARK.getMessage())); + } + } + } + + + @Nested + @DisplayName("DELETE /api/exercises/{exerciseId}/bookmark - 운동 찜 해제") + class ExerciseBookmarkRelease { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("200 - 찜한 운동을 해제하면 200 응답을 반환한다") + void releaseExerciseBookmark_success() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + exerciseBookmarkRepository.save(ExerciseBookmark.builder() + .member(member) + .exercise(bookmarkExercise) + .build()); + + mockMvc.perform(delete("/api/exercises/{exerciseId}/bookmark", bookmarkExercise.getId())) + .andExpect(status().isOk()); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("404 - 존재하지 않는 운동이면 에러를 반환한다") + void exerciseNotFound() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(delete("/api/exercises/{exerciseId}/bookmark", 999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ExerciseErrorCode.EXERCISE_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message").value(ExerciseErrorCode.EXERCISE_NOT_FOUND.getMessage())); + } + + @Test + @DisplayName("400 - 찜하지 않은 운동을 해제하려 하면 에러를 반환한다") + void notBookmarked() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(delete("/api/exercises/{exerciseId}/bookmark", bookmarkExercise.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(BookmarkErrorCode.ALREADY_RELEASE_BOOKMARK.getCode())) + .andExpect(jsonPath("$.message").value(BookmarkErrorCode.ALREADY_RELEASE_BOOKMARK.getMessage())); + } + } + } + + + @Nested + @DisplayName("GET /api/exercises/bookmarks - 찜한 운동 전체 조회") + class GetAllExerciseBookmarks { + + @BeforeEach + void setUp() { + memberExerciseRepository.save(MemberFixture.createMemberExercise(member, bookmarkExercise)); + exerciseBookmarkRepository.save(ExerciseBookmark.builder() + .member(member) + .exercise(bookmarkExercise) + .build()); + } + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("200 - LATEST 정렬로 찜한 운동을 전체 조회하면 모든 필드를 반환한다") + void getAllExerciseBookmarks_latest_allFields() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(get("/api/exercises/bookmarks") + .param("orderType", BookmarkedExerciseOrderType.LATEST.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(1))) + .andExpect(jsonPath("$.data[0].exerciseId").isNumber()) + .andExpect(jsonPath("$.data[0].partyName").value("테스트 모임")) + .andExpect(jsonPath("$.data[0].buildingName").value("테스트 체육관")) + .andExpect(jsonPath("$.data[0].streetAddr").value("경기도 안산시 한양대학로 1")) + .andExpect(jsonPath("$.data[0].femaleLevel").isArray()) + .andExpect(jsonPath("$.data[0].maleLevel").isArray()) + .andExpect(jsonPath("$.data[0].date").value("2026-12-31")) + .andExpect(jsonPath("$.data[0].startExerciseTime").value("10:00:00")) + .andExpect(jsonPath("$.data[0].endExerciseTime").value("12:00:00")) + .andExpect(jsonPath("$.data[0].maxMemberCnt").value(10)) + .andExpect(jsonPath("$.data[0].nowMemberCnt").isNumber()) + .andExpect(jsonPath("$.data[0].includeParty").value(true)) + .andExpect(jsonPath("$.data[0].includeExercise").value(true)); + } + + @Test + @DisplayName("200 - EARLIEST 정렬로 찜한 운동 전체 조회 시 성공한다") + void getAllExerciseBookmarks_earliest() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(get("/api/exercises/bookmarks") + .param("orderType", BookmarkedExerciseOrderType.EARLIEST.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(1))); + } + + @Test + @DisplayName("200 - 찜한 운동이 없으면 빈 리스트를 반환한다") + void noBookmarks_returnsEmptyList() throws Exception { + Member otherMember = memberRepository.save( + MemberFixture.createMember("다른멤버", Gender.FEMALE, Level.B, 2002L)); + SecurityContextHelper.setAuthentication(otherMember.getId(), otherMember.getNickname()); + + mockMvc.perform(get("/api/exercises/bookmarks") + .param("orderType", BookmarkedExerciseOrderType.LATEST.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(0))); + } + } + } + + // ============================================================ + // GET /api/parties/bookmarks - 찜한 모임 전체 조회 + // ============================================================ + + @Nested + @DisplayName("GET /api/parties/bookmarks - 찜한 모임 전체 조회") + class GetAllPartyBookmarks { + + @BeforeEach + void setUp() { + partyBookmarkRepository.save(PartyBookmark.builder() + .party(bookmarkParty) + .member(member) + .orderType(PartyOrderType.LATEST) + .build()); + } + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("200 - LATEST 정렬로 찜한 모임을 전체 조회하면 모든 필드를 반환한다") + void getAllPartyBookmarks_latest_allFields() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(get("/api/parties/bookmarks") + .param("orderType", PartyOrderType.LATEST.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(1))) + .andExpect(jsonPath("$.data[0].partyId").isNumber()) + .andExpect(jsonPath("$.data[0].partyName").value("테스트 모임")) + .andExpect(jsonPath("$.data[0].addr1").value("경기도")) + .andExpect(jsonPath("$.data[0].addr2").value("안산시")) + .andExpect(jsonPath("$.data[0].maleLevel").isArray()) + .andExpect(jsonPath("$.data[0].femaleLevel").isArray()) + .andExpect(jsonPath("$.data[0].latestExerciseDate").value("2026-12-31")) + .andExpect(jsonPath("$.data[0].latestExerciseTime").value("MORNING")) + .andExpect(jsonPath("$.data[0].exerciseCnt").isNumber()) + .andExpect(jsonPath("$.data[0].profileImgUrl").value(nullValue())); + } + + @Test + @DisplayName("200 - EXERCISE_COUNT 정렬로 찜한 모임 전체 조회 시 성공한다") + void getAllPartyBookmarks_exerciseCount() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(get("/api/parties/bookmarks") + .param("orderType", PartyOrderType.EXERCISE_COUNT.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(1))); + } + + @Test + @DisplayName("200 - OLDEST 정렬로 찜한 모임 전체 조회 시 성공한다") + void getAllPartyBookmarks_oldest() throws Exception { + SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); + + mockMvc.perform(get("/api/parties/bookmarks") + .param("orderType", PartyOrderType.OLDEST.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(1))); + } + + @Test + @DisplayName("200 - 찜한 모임이 없으면 빈 리스트를 반환한다") + void noBookmarks_returnsEmptyList() throws Exception { + Member otherMember = memberRepository.save( + MemberFixture.createMember("다른멤버", Gender.FEMALE, Level.B, 2002L)); + SecurityContextHelper.setAuthentication(otherMember.getId(), otherMember.getNickname()); + + mockMvc.perform(get("/api/parties/bookmarks") + .param("orderType", PartyOrderType.LATEST.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(0))); + } + } + } +} From 8bba2bef8b2a7954a209d9636612e0c126325bfd Mon Sep 17 00:00:00 2001 From: kanghana1 Date: Fri, 27 Mar 2026 14:58:44 +0900 Subject: [PATCH 2/4] =?UTF-8?q?test:=20=EC=B0=9C=20=EB=8B=A8=EC=9C=84?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/BookmarkCommandServiceTest.java | 498 ++++++++++++++++++ .../service/BookmarkQueryServiceTest.java | 458 ++++++++++++++++ 2 files changed, 956 insertions(+) create mode 100644 src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkCommandServiceTest.java create mode 100644 src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkQueryServiceTest.java diff --git a/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkCommandServiceTest.java new file mode 100644 index 000000000..8ea43c1d3 --- /dev/null +++ b/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkCommandServiceTest.java @@ -0,0 +1,498 @@ +package umc.cockple.demo.domain.bookmark.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; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import umc.cockple.demo.domain.bookmark.domain.ExerciseBookmark; +import umc.cockple.demo.domain.bookmark.domain.PartyBookmark; +import umc.cockple.demo.domain.bookmark.exception.BookmarkErrorCode; +import umc.cockple.demo.domain.bookmark.exception.BookmarkException; +import umc.cockple.demo.domain.bookmark.repository.ExerciseBookmarkRepository; +import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; +import umc.cockple.demo.domain.exercise.domain.Exercise; +import umc.cockple.demo.domain.exercise.exception.ExerciseErrorCode; +import umc.cockple.demo.domain.exercise.exception.ExerciseException; +import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; +import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.exception.MemberErrorCode; +import umc.cockple.demo.domain.member.exception.MemberException; +import umc.cockple.demo.domain.member.repository.MemberRepository; +import umc.cockple.demo.domain.party.domain.Party; +import umc.cockple.demo.domain.party.enums.PartyOrderType; +import umc.cockple.demo.domain.party.enums.PartyStatus; +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.support.fixture.ExerciseFixture; +import umc.cockple.demo.support.fixture.MemberFixture; +import umc.cockple.demo.support.fixture.PartyFixture; + +import java.time.LocalDate; +import java.util.ArrayList; +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.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.never; + +@ExtendWith(MockitoExtension.class) +@DisplayName("BookmarkCommandService") +class BookmarkCommandServiceTest { + + @InjectMocks + private BookmarkCommandService bookmarkCommandService; + + @Mock private PartyBookmarkRepository partyBookmarkRepository; + @Mock private ExerciseBookmarkRepository exerciseBookmarkRepository; + @Mock private MemberRepository memberRepository; + @Mock private PartyRepository partyRepository; + @Mock private ExerciseRepository exerciseRepository; + + private Member member; + private Party party; + private Exercise exercise; + + @BeforeEach + void setUp() { + member = MemberFixture.createMember("테스트 유저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", 1L); + + party = PartyFixture.createParty("테스트 모임", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(party, "id", 10L); + + exercise = ExerciseFixture.createExercise(party, LocalDate.of(2099, 12, 31)); + ReflectionTestUtils.setField(exercise, "id", 100L); + } + + @Nested + @DisplayName("partyBookmark - 모임 찜하기") + class PartyBookmarks { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("모임을 찜하면 저장된 북마크 id를 반환한다") + void createPartyBookmark_returnsBookmarkId() { + // given + umc.cockple.demo.domain.bookmark.domain.PartyBookmark savedBookmark = umc.cockple.demo.domain.bookmark.domain.PartyBookmark.builder() + .member(member) + .party(party) + .orderType(PartyOrderType.LATEST) + .build(); + ReflectionTestUtils.setField(savedBookmark, "id", 50L); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyRepository.findById(party.getId())).willReturn(Optional.of(party)); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); + given(partyBookmarkRepository.findAllByMember(member)).willReturn(new ArrayList<>()); + given(partyBookmarkRepository.save(any(PartyBookmark.class))).willReturn(savedBookmark); + + // when + Long result = bookmarkCommandService.partyBookmark(member.getId(), party.getId()); + + // then + assertThat(result).isEqualTo(50L); + then(partyBookmarkRepository).should().save(any(PartyBookmark.class)); + } + + @Test + @DisplayName("찜 목록이 15개 이상이면 가장 오래된 북마크를 삭제하고 새로 저장한다") + void createPartyBookmark_deletesOldestWhenLimitExceeded() { + // given + List existingBookmarks = new ArrayList<>(); + for (int i = 0; i < 15; i++) { + existingBookmarks.add(PartyBookmark.builder() + .member(member) + .party(party) + .orderType(PartyOrderType.LATEST) + .build()); + } + + PartyBookmark oldestBookmark = PartyBookmark.builder() + .member(member) + .party(party) + .orderType(PartyOrderType.LATEST) + .build(); + + PartyBookmark savedBookmark = PartyBookmark.builder() + .member(member) + .party(party) + .orderType(PartyOrderType.LATEST) + .build(); + ReflectionTestUtils.setField(savedBookmark, "id", 99L); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyRepository.findById(party.getId())).willReturn(Optional.of(party)); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(false); + given(partyBookmarkRepository.findAllByMember(member)).willReturn(existingBookmarks); + given(partyBookmarkRepository.findFirstByMemberOrderByCreatedAtAsc(member)) + .willReturn(Optional.of(oldestBookmark)); + given(partyBookmarkRepository.save(any(PartyBookmark.class))).willReturn(savedBookmark); + + // when + Long result = bookmarkCommandService.partyBookmark(member.getId(), party.getId()); + + // then + assertThat(result).isEqualTo(99L); + then(partyBookmarkRepository).should().delete(oldestBookmark); + then(partyBookmarkRepository).should().save(any(PartyBookmark.class)); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("존재하지 않는 회원이면 MemberException(MEMBER_NOT_FOUND)을 던진다") + void memberNotFound_throwsMemberException() { + given(memberRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.partyBookmark(999L, party.getId())) + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()) + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("존재하지 않는 모임이면 PartyException(PARTY_NOT_FOUND)을 던진다") + void partyNotFound_throwsPartyException() { + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.partyBookmark(member.getId(), 999L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("비활성화된 모임이면 PartyException(PARTY_IS_DELETED)을 던진다") + void partyIsInactive_throwsPartyException() { + Party inactiveParty = PartyFixture.createParty("삭제된 모임", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(inactiveParty, "id", 20L); + ReflectionTestUtils.setField(inactiveParty, "status", PartyStatus.INACTIVE); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyRepository.findById(20L)).willReturn(Optional.of(inactiveParty)); + + assertThatThrownBy(() -> + bookmarkCommandService.partyBookmark(member.getId(), 20L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } + + @Test + @DisplayName("이미 찜한 모임이면 BookmarkException(ALREADY_BOOKMARK)을 던진다") + void alreadyBookmarked_throwsBookmarkException() { + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyRepository.findById(party.getId())).willReturn(Optional.of(party)); + given(partyBookmarkRepository.existsByMemberAndParty(member, party)).willReturn(true); + + assertThatThrownBy(() -> + bookmarkCommandService.partyBookmark(member.getId(), party.getId())) + .isInstanceOf(BookmarkException.class) + .satisfies(e -> assertThat(((BookmarkException) e).getCode()) + .isEqualTo(BookmarkErrorCode.ALREADY_BOOKMARK)); + + then(partyBookmarkRepository).should(never()).save(any()); + } + } + } + + @Nested + @DisplayName("releasePartyBookmark - 모임 찜 해제") + class ReleasePartyBookmarks { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("찜한 모임을 해제하면 북마크를 삭제한다") + void releasePartyBookmark_deletesBookmark() { + // given + PartyBookmark bookmark = PartyBookmark.builder() + .member(member) + .party(party) + .orderType(PartyOrderType.LATEST) + .build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyRepository.findById(party.getId())).willReturn(Optional.of(party)); + given(partyBookmarkRepository.findByMemberAndParty(member, party)) + .willReturn(Optional.of(bookmark)); + + // when + bookmarkCommandService.releasePartyBookmark(member.getId(), party.getId()); + + // then + then(partyBookmarkRepository).should().delete(bookmark); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("존재하지 않는 회원이면 MemberException(MEMBER_NOT_FOUND)을 던진다") + void memberNotFound_throwsMemberException() { + given(memberRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.releasePartyBookmark(999L, party.getId())) + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()) + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("존재하지 않는 모임이면 PartyException(PARTY_NOT_FOUND)을 던진다") + void partyNotFound_throwsPartyException() { + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.releasePartyBookmark(member.getId(), 999L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()) + .isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("찜하지 않은 모임이면 BookmarkException(ALREADY_RELEASE_BOOKMARK)을 던진다") + void bookmarkNotFound_throwsBookmarkException() { + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyRepository.findById(party.getId())).willReturn(Optional.of(party)); + given(partyBookmarkRepository.findByMemberAndParty(member, party)) + .willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.releasePartyBookmark(member.getId(), party.getId())) + .isInstanceOf(BookmarkException.class) + .satisfies(e -> assertThat(((BookmarkException) e).getCode()) + .isEqualTo(BookmarkErrorCode.ALREADY_RELEASE_BOOKMARK)); + + then(partyBookmarkRepository).should(never()).delete(any()); + } + } + } + + @Nested + @DisplayName("exerciseBookmark - 운동 찜하기") + class ExerciseBookmarkCreate { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("운동을 찜하면 저장된 북마크 id를 반환한다") + void createExerciseBookmark_returnsBookmarkId() { + // given + ExerciseBookmark savedBookmark = ExerciseBookmark.builder() + .member(member) + .exercise(exercise) + .build(); + ReflectionTestUtils.setField(savedBookmark, "id", 200L); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseRepository.findById(exercise.getId())).willReturn(Optional.of(exercise)); + given(exerciseBookmarkRepository.existsByMemberAndExercise(member, exercise)).willReturn(false); + given(exerciseBookmarkRepository.findAllByMember(member)).willReturn(new ArrayList<>()); + given(exerciseBookmarkRepository.save(any(ExerciseBookmark.class))).willReturn(savedBookmark); + + // when + Long result = bookmarkCommandService.exerciseBookmark(member.getId(), exercise.getId()); + + // then + assertThat(result).isEqualTo(200L); + then(exerciseBookmarkRepository).should().save(any(ExerciseBookmark.class)); + } + + @Test + @DisplayName("찜 목록이 50개 이상이면 가장 오래된 북마크를 삭제하고 새로 저장한다") + void createExerciseBookmark_deletesOldestWhenLimitExceeded() { + // given + List existingBookmarks = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + existingBookmarks.add(ExerciseBookmark.builder() + .member(member) + .exercise(exercise) + .build()); + } + + ExerciseBookmark oldestBookmark = ExerciseBookmark.builder() + .member(member) + .exercise(exercise) + .build(); + + ExerciseBookmark savedBookmark = ExerciseBookmark.builder() + .member(member) + .exercise(exercise) + .build(); + ReflectionTestUtils.setField(savedBookmark, "id", 300L); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseRepository.findById(exercise.getId())).willReturn(Optional.of(exercise)); + given(exerciseBookmarkRepository.existsByMemberAndExercise(member, exercise)).willReturn(false); + given(exerciseBookmarkRepository.findAllByMember(member)).willReturn(existingBookmarks); + given(exerciseBookmarkRepository.findFirstByMemberOrderByCreatedAtAsc(member)) + .willReturn(Optional.of(oldestBookmark)); + given(exerciseBookmarkRepository.save(any(ExerciseBookmark.class))).willReturn(savedBookmark); + + // when + Long result = bookmarkCommandService.exerciseBookmark(member.getId(), exercise.getId()); + + // then + assertThat(result).isEqualTo(300L); + then(exerciseBookmarkRepository).should().delete(oldestBookmark); + then(exerciseBookmarkRepository).should().save(any(ExerciseBookmark.class)); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("존재하지 않는 회원이면 MemberException(MEMBER_NOT_FOUND)을 던진다") + void memberNotFound_throwsMemberException() { + given(memberRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.exerciseBookmark(999L, exercise.getId())) + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()) + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("존재하지 않는 운동이면 ExerciseException(EXERCISE_NOT_FOUND)을 던진다") + void exerciseNotFound_throwsExerciseException() { + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.exerciseBookmark(member.getId(), 999L)) + .isInstanceOf(ExerciseException.class) + .satisfies(e -> assertThat(((ExerciseException) e).getCode()) + .isEqualTo(ExerciseErrorCode.EXERCISE_NOT_FOUND)); + } + + @Test + @DisplayName("이미 찜한 운동이면 BookmarkException(ALREADY_BOOKMARK)을 던진다") + void alreadyBookmarked_throwsBookmarkException() { + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseRepository.findById(exercise.getId())).willReturn(Optional.of(exercise)); + given(exerciseBookmarkRepository.existsByMemberAndExercise(member, exercise)).willReturn(true); + + assertThatThrownBy(() -> + bookmarkCommandService.exerciseBookmark(member.getId(), exercise.getId())) + .isInstanceOf(BookmarkException.class) + .satisfies(e -> assertThat(((BookmarkException) e).getCode()) + .isEqualTo(BookmarkErrorCode.ALREADY_BOOKMARK)); + + then(exerciseBookmarkRepository).should(never()).save(any()); + } + } + } + + @Nested + @DisplayName("releaseExerciseBookmark - 운동 찜 해제") + class ReleaseExerciseBookmark { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("찜한 운동을 해제하면 북마크를 삭제한다") + void releaseExerciseBookmark_deletesBookmark() { + // given + ExerciseBookmark bookmark = ExerciseBookmark.builder() + .member(member) + .exercise(exercise) + .build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseRepository.findById(exercise.getId())).willReturn(Optional.of(exercise)); + given(exerciseBookmarkRepository.findByMemberAndExercise(member, exercise)) + .willReturn(Optional.of(bookmark)); + + // when + bookmarkCommandService.releaseExerciseBookmark(member.getId(), exercise.getId()); + + // then + then(exerciseBookmarkRepository).should().delete(bookmark); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("존재하지 않는 회원이면 MemberException(MEMBER_NOT_FOUND)을 던진다") + void memberNotFound_throwsMemberException() { + given(memberRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.releaseExerciseBookmark(999L, exercise.getId())) + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()) + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("존재하지 않는 운동이면 ExerciseException(EXERCISE_NOT_FOUND)을 던진다") + void exerciseNotFound_throwsExerciseException() { + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.releaseExerciseBookmark(member.getId(), 999L)) + .isInstanceOf(ExerciseException.class) + .satisfies(e -> assertThat(((ExerciseException) e).getCode()) + .isEqualTo(ExerciseErrorCode.EXERCISE_NOT_FOUND)); + } + + @Test + @DisplayName("찜하지 않은 운동이면 BookmarkException(ALREADY_RELEASE_BOOKMARK)을 던진다") + void bookmarkNotFound_throwsBookmarkException() { + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseRepository.findById(exercise.getId())).willReturn(Optional.of(exercise)); + given(exerciseBookmarkRepository.findByMemberAndExercise(member, exercise)) + .willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkCommandService.releaseExerciseBookmark(member.getId(), exercise.getId())) + .isInstanceOf(BookmarkException.class) + .satisfies(e -> assertThat(((BookmarkException) e).getCode()) + .isEqualTo(BookmarkErrorCode.ALREADY_RELEASE_BOOKMARK)); + + then(exerciseBookmarkRepository).should(never()).delete(any()); + } + } + } +} diff --git a/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkQueryServiceTest.java new file mode 100644 index 000000000..dfbf80477 --- /dev/null +++ b/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkQueryServiceTest.java @@ -0,0 +1,458 @@ +package umc.cockple.demo.domain.bookmark.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; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import umc.cockple.demo.domain.bookmark.converter.BookmarkConverter; +import umc.cockple.demo.domain.bookmark.domain.ExerciseBookmark; +import umc.cockple.demo.domain.bookmark.domain.PartyBookmark; +import umc.cockple.demo.domain.bookmark.dto.GetAllExerciseBookmarksResponseDTO; +import umc.cockple.demo.domain.bookmark.dto.GetAllPartyBookmarkResponseDTO; +import umc.cockple.demo.domain.bookmark.enums.BookmarkedExerciseOrderType; +import umc.cockple.demo.domain.bookmark.repository.ExerciseBookmarkRepository; +import umc.cockple.demo.domain.bookmark.repository.PartyBookmarkRepository; +import umc.cockple.demo.domain.exercise.domain.Exercise; +import umc.cockple.demo.domain.file.service.FileService; +import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.exception.MemberErrorCode; +import umc.cockple.demo.domain.member.exception.MemberException; +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.domain.Party; +import umc.cockple.demo.domain.party.enums.ActivityTime; +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.support.fixture.ExerciseFixture; +import umc.cockple.demo.support.fixture.MemberFixture; +import umc.cockple.demo.support.fixture.PartyFixture; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +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.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +@DisplayName("BookmarkQueryService") +class BookmarkQueryServiceTest { + + @InjectMocks + private BookmarkQueryService bookmarkQueryService; + + @Mock private ExerciseBookmarkRepository exerciseBookmarkRepository; + @Mock private PartyBookmarkRepository partyBookmarkRepository; + @Mock private MemberPartyRepository memberPartyRepository; + @Mock private MemberExerciseRepository memberExerciseRepository; + @Mock private MemberRepository memberRepository; + @Mock private BookmarkConverter bookmarkConverter; + + private Member member; + private Party party; + + @BeforeEach + void setUp() { + member = MemberFixture.createMember("테스트 유저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(member, "id", 1L); + + party = PartyFixture.createParty("테스트 모임", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(party, "id", 10L); + } + + @Nested + @DisplayName("getAllExerciseBookmarks - 찜한 운동 목록 조회") + class GetAllExerciseBookmarks { + + private Exercise oldExercise; + private Exercise newExercise; + + @BeforeEach + void setUp() { + oldExercise = ExerciseFixture.createExerciseWithAddr(party, LocalDate.of(2099, 6, 1)); + ReflectionTestUtils.setField(oldExercise, "id", 101L); + + newExercise = ExerciseFixture.createExerciseWithAddr(party, LocalDate.of(2099, 12, 31)); + ReflectionTestUtils.setField(newExercise, "id", 102L); + } + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("찜한 운동이 없으면 빈 목록을 반환한다") + void noBookmarks_returnsEmptyList() { + // given + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseBookmarkRepository.findAllByMember(member)).willReturn(new ArrayList<>()); + given(memberPartyRepository.findAllPartyIdsByMemberAndPartyIds(anyLong(), anyList())) + .willReturn(new ArrayList<>()); + given(memberExerciseRepository.findAllExerciseIdsByMemberAndExerciseIds(anyLong(), anyList())) + .willReturn(new ArrayList<>()); + + // when + List result = + bookmarkQueryService.getAllExerciseBookmarks(member.getId(), BookmarkedExerciseOrderType.LATEST); + + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("LATEST 정렬 시 최신순으로 반환한다") + void latestOrder_returnsNewestFirst() { + // given + ExerciseBookmark bookmarkOld = ExerciseBookmark.builder() + .member(member).exercise(oldExercise).build(); + ReflectionTestUtils.setField(bookmarkOld, "createdAt", LocalDateTime.now().minusDays(2)); + + ExerciseBookmark bookmarkNew = ExerciseBookmark.builder() + .member(member).exercise(newExercise).build(); + ReflectionTestUtils.setField(bookmarkNew, "createdAt", LocalDateTime.now().minusDays(1)); + + // 레포지토리에서 오래된 순서로 반환 + List bookmarks = new ArrayList<>(List.of(bookmarkOld, bookmarkNew)); + + GetAllExerciseBookmarksResponseDTO dtoOld = GetAllExerciseBookmarksResponseDTO.builder() + .exerciseId(101L).partyName("테스트 모임").build(); + GetAllExerciseBookmarksResponseDTO dtoNew = GetAllExerciseBookmarksResponseDTO.builder() + .exerciseId(102L).partyName("테스트 모임").build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseBookmarkRepository.findAllByMember(member)).willReturn(bookmarks); + given(memberPartyRepository.findAllPartyIdsByMemberAndPartyIds(anyLong(), anyList())) + .willReturn(List.of(party.getId())); + given(memberExerciseRepository.findAllExerciseIdsByMemberAndExerciseIds(anyLong(), anyList())) + .willReturn(List.of(oldExercise.getId(), newExercise.getId())); + given(bookmarkConverter.exerciseBookmarkToDTO(eq(bookmarkNew), any(Boolean.class), any(Boolean.class))) + .willReturn(dtoNew); + given(bookmarkConverter.exerciseBookmarkToDTO(eq(bookmarkOld), any(Boolean.class), any(Boolean.class))) + .willReturn(dtoOld); + + // when + List result = + bookmarkQueryService.getAllExerciseBookmarks(member.getId(), BookmarkedExerciseOrderType.LATEST); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).exerciseId()).isEqualTo(102L); // 최신 것이 먼저 + assertThat(result.get(1).exerciseId()).isEqualTo(101L); + } + + @Test + @DisplayName("EARLIEST 정렬 시 오래된 순으로 반환한다") + void earliestOrder_returnsOldestFirst() { + // given + ExerciseBookmark bookmarkOld = ExerciseBookmark.builder() + .member(member).exercise(oldExercise).build(); + ReflectionTestUtils.setField(bookmarkOld, "createdAt", LocalDateTime.now().minusDays(2)); + + ExerciseBookmark bookmarkNew = ExerciseBookmark.builder() + .member(member).exercise(newExercise).build(); + ReflectionTestUtils.setField(bookmarkNew, "createdAt", LocalDateTime.now().minusDays(1)); + + // 레포지토리에서 최신 순서로 반환 + List bookmarks = new ArrayList<>(List.of(bookmarkNew, bookmarkOld)); + + GetAllExerciseBookmarksResponseDTO dtoOld = GetAllExerciseBookmarksResponseDTO.builder() + .exerciseId(101L).partyName("테스트 모임").build(); + GetAllExerciseBookmarksResponseDTO dtoNew = GetAllExerciseBookmarksResponseDTO.builder() + .exerciseId(102L).partyName("테스트 모임").build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseBookmarkRepository.findAllByMember(member)).willReturn(bookmarks); + given(memberPartyRepository.findAllPartyIdsByMemberAndPartyIds(anyLong(), anyList())) + .willReturn(List.of(party.getId())); + given(memberExerciseRepository.findAllExerciseIdsByMemberAndExerciseIds(anyLong(), anyList())) + .willReturn(List.of(oldExercise.getId(), newExercise.getId())); + given(bookmarkConverter.exerciseBookmarkToDTO(eq(bookmarkOld), any(Boolean.class), any(Boolean.class))) + .willReturn(dtoOld); + given(bookmarkConverter.exerciseBookmarkToDTO(eq(bookmarkNew), any(Boolean.class), any(Boolean.class))) + .willReturn(dtoNew); + + // when + List result = + bookmarkQueryService.getAllExerciseBookmarks(member.getId(), BookmarkedExerciseOrderType.EARLIEST); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).exerciseId()).isEqualTo(101L); // 오래된 것이 먼저 + assertThat(result.get(1).exerciseId()).isEqualTo(102L); + } + + @Test + @DisplayName("includeParty, includeExercise 정보를 정확히 반영하여 변환한다") + void convertsBookmarkWithCorrectIncludeFlags() { + // given + Exercise exercise = ExerciseFixture.createExerciseWithAddr(party, LocalDate.of(2099, 12, 31)); + ReflectionTestUtils.setField(exercise, "id", 101L); + + ExerciseBookmark bookmark = ExerciseBookmark.builder() + .member(member).exercise(exercise).build(); + ReflectionTestUtils.setField(bookmark, "createdAt", LocalDateTime.now()); + + GetAllExerciseBookmarksResponseDTO dto = GetAllExerciseBookmarksResponseDTO.builder() + .exerciseId(101L).includeParty(true).includeExercise(false).build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(exerciseBookmarkRepository.findAllByMember(member)).willReturn(new ArrayList<>(List.of(bookmark))); + given(memberPartyRepository.findAllPartyIdsByMemberAndPartyIds(eq(member.getId()), anyList())) + .willReturn(List.of(party.getId())); // 모임 멤버 + given(memberExerciseRepository.findAllExerciseIdsByMemberAndExerciseIds(eq(member.getId()), anyList())) + .willReturn(new ArrayList<>()); // 운동 미참여 + given(bookmarkConverter.exerciseBookmarkToDTO(bookmark, true, false)).willReturn(dto); + + // when + List result = + bookmarkQueryService.getAllExerciseBookmarks(member.getId(), BookmarkedExerciseOrderType.LATEST); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).includeParty()).isTrue(); + assertThat(result.get(0).includeExercise()).isFalse(); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("존재하지 않는 회원이면 MemberException(MEMBER_NOT_FOUND)을 던진다") + void memberNotFound_throwsMemberException() { + given(memberRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkQueryService.getAllExerciseBookmarks(999L, BookmarkedExerciseOrderType.LATEST)) + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()) + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); + } + } + } + + @Nested + @DisplayName("getAllPartyBookmarks - 찜한 모임 목록 조회") + class GetAllPartyBookmarks { + + @Nested + @DisplayName("성공 케이스") + class Success { + + @Test + @DisplayName("찜한 모임이 없으면 빈 목록을 반환한다") + void noBookmarks_returnsEmptyList() { + // given + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyBookmarkRepository.findAllByMemberWithParty(member)).willReturn(new ArrayList<>()); + + // when + List result = + bookmarkQueryService.getAllPartyBookmarks(member.getId(), PartyOrderType.LATEST); + + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("LATEST 정렬 시 최신순으로 반환한다") + void latestOrder_returnsNewestFirst() { + // given + Party partyA = PartyFixture.createParty("모임A", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(partyA, "id", 11L); + + Party partyB = PartyFixture.createParty("모임B", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "종로구")); + ReflectionTestUtils.setField(partyB, "id", 12L); + + PartyBookmark bookmarkOld = PartyBookmark.builder() + .member(member).party(partyA) + .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.LATEST).build(); + ReflectionTestUtils.setField(bookmarkOld, "createdAt", LocalDateTime.now().minusDays(2)); + + PartyBookmark bookmarkNew = PartyBookmark.builder() + .member(member).party(partyB) + .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.LATEST).build(); + ReflectionTestUtils.setField(bookmarkNew, "createdAt", LocalDateTime.now().minusDays(1)); + + GetAllPartyBookmarkResponseDTO dtoA = GetAllPartyBookmarkResponseDTO.builder() + .partyId(11L).partyName("모임A").build(); + GetAllPartyBookmarkResponseDTO dtoB = GetAllPartyBookmarkResponseDTO.builder() + .partyId(12L).partyName("모임B").build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyBookmarkRepository.findAllByMemberWithParty(member)) + .willReturn(new ArrayList<>(List.of(bookmarkOld, bookmarkNew))); + given(bookmarkConverter.partyBookmarkToDTO(eq(bookmarkNew), any(), any(), any())) + .willReturn(dtoB); + given(bookmarkConverter.partyBookmarkToDTO(eq(bookmarkOld), any(), any(), any())) + .willReturn(dtoA); + + // when + List result = + bookmarkQueryService.getAllPartyBookmarks(member.getId(), PartyOrderType.LATEST); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).partyId()).isEqualTo(12L); // 최신 것이 먼저 + assertThat(result.get(1).partyId()).isEqualTo(11L); + } + + @Test + @DisplayName("EXERCISE_COUNT 정렬 시 운동 횟수 많은 순으로 반환한다") + void exerciseCountOrder_returnsMostExercisedFirst() { + // given + Party partyLow = PartyFixture.createParty("운동 적은 모임", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(partyLow, "id", 11L); + ReflectionTestUtils.setField(partyLow, "exerciseCount", 2); + + Party partyHigh = PartyFixture.createParty("운동 많은 모임", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "종로구")); + ReflectionTestUtils.setField(partyHigh, "id", 12L); + ReflectionTestUtils.setField(partyHigh, "exerciseCount", 10); + + PartyBookmark bookmarkLow = PartyBookmark.builder() + .member(member).party(partyLow) + .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.EXERCISE_COUNT).build(); + ReflectionTestUtils.setField(bookmarkLow, "createdAt", LocalDateTime.now().minusDays(1)); + + PartyBookmark bookmarkHigh = PartyBookmark.builder() + .member(member).party(partyHigh) + .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.EXERCISE_COUNT).build(); + ReflectionTestUtils.setField(bookmarkHigh, "createdAt", LocalDateTime.now().minusDays(2)); + + GetAllPartyBookmarkResponseDTO dtoLow = GetAllPartyBookmarkResponseDTO.builder() + .partyId(11L).partyName("운동 적은 모임").exerciseCnt(2).build(); + GetAllPartyBookmarkResponseDTO dtoHigh = GetAllPartyBookmarkResponseDTO.builder() + .partyId(12L).partyName("운동 많은 모임").exerciseCnt(10).build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyBookmarkRepository.findAllByMemberWithParty(member)) + .willReturn(new ArrayList<>(List.of(bookmarkLow, bookmarkHigh))); + given(bookmarkConverter.partyBookmarkToDTO(eq(bookmarkHigh), any(), any(), any())) + .willReturn(dtoHigh); + given(bookmarkConverter.partyBookmarkToDTO(eq(bookmarkLow), any(), any(), any())) + .willReturn(dtoLow); + + // when + List result = + bookmarkQueryService.getAllPartyBookmarks(member.getId(), PartyOrderType.EXERCISE_COUNT); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).partyId()).isEqualTo(12L); // 운동 많은 모임이 먼저 + assertThat(result.get(1).partyId()).isEqualTo(11L); + } + + @Test + @DisplayName("파티에 미래 운동이 있을 때 가장 가까운 운동 정보를 함께 반환한다") + void partyWithFutureExercise_returnsLatestExerciseInfo() { + // given + Exercise futureExercise = ExerciseFixture.createExercise(party, + LocalDate.now().plusDays(7), LocalTime.of(10, 0), true, false); + party.addExercise(futureExercise); + + PartyBookmark bookmark = PartyBookmark.builder() + .member(member).party(party) + .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.LATEST).build(); + ReflectionTestUtils.setField(bookmark, "createdAt", LocalDateTime.now()); + + GetAllPartyBookmarkResponseDTO dto = GetAllPartyBookmarkResponseDTO.builder() + .partyId(party.getId()) + .latestExerciseDate(LocalDate.now().plusDays(7)) + .latestExerciseTime(ActivityTime.MORNING) + .build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyBookmarkRepository.findAllByMemberWithParty(member)) + .willReturn(new ArrayList<>(List.of(bookmark))); + given(bookmarkConverter.partyBookmarkToDTO(eq(bookmark), + eq(futureExercise), eq(ActivityTime.MORNING), any())) + .willReturn(dto); + + // when + List result = + bookmarkQueryService.getAllPartyBookmarks(member.getId(), PartyOrderType.LATEST); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).latestExerciseDate()).isEqualTo(LocalDate.now().plusDays(7)); + assertThat(result.get(0).latestExerciseTime()).isEqualTo(ActivityTime.MORNING); + } + + @Test + @DisplayName("파티에 미래 운동이 없을 때 exercise는 null로 변환된다") + void partyWithNoFutureExercise_passesNullExercise() { + // given - 과거 운동만 있는 파티 + Exercise pastExercise = ExerciseFixture.createExercise(party, + LocalDate.now().minusDays(1), LocalTime.of(10, 0), true, false); + party.addExercise(pastExercise); + + PartyBookmark bookmark = PartyBookmark.builder() + .member(member).party(party) + .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.LATEST).build(); + ReflectionTestUtils.setField(bookmark, "createdAt", LocalDateTime.now()); + + GetAllPartyBookmarkResponseDTO dto = GetAllPartyBookmarkResponseDTO.builder() + .partyId(party.getId()) + .latestExerciseDate(null) + .latestExerciseTime(null) + .build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyBookmarkRepository.findAllByMemberWithParty(member)) + .willReturn(new ArrayList<>(List.of(bookmark))); + given(bookmarkConverter.partyBookmarkToDTO(eq(bookmark), eq(null), eq(null), any())) + .willReturn(dto); + + // when + List result = + bookmarkQueryService.getAllPartyBookmarks(member.getId(), PartyOrderType.LATEST); + + // then + assertThat(result).hasSize(1); + // null exercise 로 converter가 호출되었는지 검증 + verify(bookmarkConverter, times(1)) + .partyBookmarkToDTO(eq(bookmark), eq(null), eq(null), any()); + } + } + + @Nested + @DisplayName("실패 케이스") + class Failure { + + @Test + @DisplayName("존재하지 않는 회원이면 MemberException(MEMBER_NOT_FOUND)을 던진다") + void memberNotFound_throwsMemberException() { + given(memberRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> + bookmarkQueryService.getAllPartyBookmarks(999L, PartyOrderType.LATEST)) + .isInstanceOf(MemberException.class) + .satisfies(e -> assertThat(((MemberException) e).getCode()) + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); + } + } + } +} From 48a7e61d39ef44663798492d494367f0bc8c3f48 Mon Sep 17 00:00:00 2001 From: kanghana1 Date: Sat, 28 Mar 2026 00:15:03 +0900 Subject: [PATCH 3/4] =?UTF-8?q?test:=20=EC=B0=9C=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=88=84=EB=9D=BD=EB=90=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/BookmarkCommandServiceTest.java | 2 +- .../service/BookmarkQueryServiceTest.java | 53 +++++++++++++++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkCommandServiceTest.java b/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkCommandServiceTest.java index 8ea43c1d3..f4ae228d0 100644 --- a/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkCommandServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkCommandServiceTest.java @@ -89,7 +89,7 @@ class Success { @DisplayName("모임을 찜하면 저장된 북마크 id를 반환한다") void createPartyBookmark_returnsBookmarkId() { // given - umc.cockple.demo.domain.bookmark.domain.PartyBookmark savedBookmark = umc.cockple.demo.domain.bookmark.domain.PartyBookmark.builder() + PartyBookmark savedBookmark = PartyBookmark.builder() .member(member) .party(party) .orderType(PartyOrderType.LATEST) diff --git a/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkQueryServiceTest.java index dfbf80477..75602159e 100644 --- a/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkQueryServiceTest.java +++ b/src/test/java/umc/cockple/demo/domain/bookmark/service/BookmarkQueryServiceTest.java @@ -287,12 +287,12 @@ void latestOrder_returnsNewestFirst() { PartyBookmark bookmarkOld = PartyBookmark.builder() .member(member).party(partyA) - .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.LATEST).build(); + .orderType(PartyOrderType.LATEST).build(); ReflectionTestUtils.setField(bookmarkOld, "createdAt", LocalDateTime.now().minusDays(2)); PartyBookmark bookmarkNew = PartyBookmark.builder() .member(member).party(partyB) - .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.LATEST).build(); + .orderType(PartyOrderType.LATEST).build(); ReflectionTestUtils.setField(bookmarkNew, "createdAt", LocalDateTime.now().minusDays(1)); GetAllPartyBookmarkResponseDTO dtoA = GetAllPartyBookmarkResponseDTO.builder() @@ -318,6 +318,51 @@ void latestOrder_returnsNewestFirst() { assertThat(result.get(1).partyId()).isEqualTo(11L); } + @Test + @DisplayName("OLDEST 정렬 시 오래된 순으로 반환한다") + void oldestOrder_returnsOldestFirst() { + // given + Party partyA = PartyFixture.createParty("모임A", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "강남구")); + ReflectionTestUtils.setField(partyA, "id", 11L); + + Party partyB = PartyFixture.createParty("모임B", member.getId(), + PartyFixture.createPartyAddr("서울특별시", "종로구")); + ReflectionTestUtils.setField(partyB, "id", 12L); + + PartyBookmark bookmarkOld = PartyBookmark.builder() + .member(member).party(partyA) + .orderType(PartyOrderType.OLDEST).build(); + ReflectionTestUtils.setField(bookmarkOld, "createdAt", LocalDateTime.now().minusDays(2)); + + PartyBookmark bookmarkNew = PartyBookmark.builder() + .member(member).party(partyB) + .orderType(PartyOrderType.OLDEST).build(); + ReflectionTestUtils.setField(bookmarkNew, "createdAt", LocalDateTime.now().minusDays(1)); + + GetAllPartyBookmarkResponseDTO dtoA = GetAllPartyBookmarkResponseDTO.builder() + .partyId(11L).partyName("모임A").build(); + GetAllPartyBookmarkResponseDTO dtoB = GetAllPartyBookmarkResponseDTO.builder() + .partyId(12L).partyName("모임B").build(); + + given(memberRepository.findById(member.getId())).willReturn(Optional.of(member)); + given(partyBookmarkRepository.findAllByMemberWithParty(member)) + .willReturn(new ArrayList<>(List.of(bookmarkNew, bookmarkOld))); + given(bookmarkConverter.partyBookmarkToDTO(eq(bookmarkOld), any(), any(), any())) + .willReturn(dtoA); + given(bookmarkConverter.partyBookmarkToDTO(eq(bookmarkNew), any(), any(), any())) + .willReturn(dtoB); + + // when + List result = + bookmarkQueryService.getAllPartyBookmarks(member.getId(), PartyOrderType.OLDEST); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).partyId()).isEqualTo(11L); // 오래된 것이 먼저 + assertThat(result.get(1).partyId()).isEqualTo(12L); + } + @Test @DisplayName("EXERCISE_COUNT 정렬 시 운동 횟수 많은 순으로 반환한다") void exerciseCountOrder_returnsMostExercisedFirst() { @@ -334,12 +379,12 @@ void exerciseCountOrder_returnsMostExercisedFirst() { PartyBookmark bookmarkLow = PartyBookmark.builder() .member(member).party(partyLow) - .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.EXERCISE_COUNT).build(); + .orderType(PartyOrderType.EXERCISE_COUNT).build(); ReflectionTestUtils.setField(bookmarkLow, "createdAt", LocalDateTime.now().minusDays(1)); PartyBookmark bookmarkHigh = PartyBookmark.builder() .member(member).party(partyHigh) - .orderType(umc.cockple.demo.domain.party.enums.PartyOrderType.EXERCISE_COUNT).build(); + .orderType(PartyOrderType.EXERCISE_COUNT).build(); ReflectionTestUtils.setField(bookmarkHigh, "createdAt", LocalDateTime.now().minusDays(2)); GetAllPartyBookmarkResponseDTO dtoLow = GetAllPartyBookmarkResponseDTO.builder() From 55bd59bd644fa39c3b0e5eaffb36a6fb98a4177d Mon Sep 17 00:00:00 2001 From: kanghana1 Date: Sat, 28 Mar 2026 00:33:03 +0900 Subject: [PATCH 4/4] =?UTF-8?q?test:=20order=EB=8F=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/BookmarkIntegrationTest.java | 95 ++++++++++++++----- 1 file changed, 71 insertions(+), 24 deletions(-) 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 e4c1ef026..984dfb50f 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 @@ -323,13 +323,41 @@ void notBookmarked() throws Exception { @DisplayName("GET /api/exercises/bookmarks - 찜한 운동 전체 조회") class GetAllExerciseBookmarks { + private Exercise newExercise; + @BeforeEach void setUp() { + // 먼저 저장 = 오래된 북마크 memberExerciseRepository.save(MemberFixture.createMemberExercise(member, bookmarkExercise)); exerciseBookmarkRepository.save(ExerciseBookmark.builder() .member(member) .exercise(bookmarkExercise) .build()); + + // 나중에 저장 = 최신 북마크 + newExercise = exerciseRepository.save(Exercise.builder() + .party(bookmarkParty) + .date(LocalDate.of(2027, 6, 30)) + .startTime(LocalTime.of(14, 0)) + .endTime(LocalTime.of(16, 0)) + .maxCapacity(8) + .partyGuestAccept(true) + .outsideGuestAccept(false) + .exerciseAddr(ExerciseAddr.builder() + .addr1("경기도") + .addr2("안산시") + .streetAddr("경기도 안산시 한양대학로 1") + .buildingName("테스트 체육관") + .latitude(37.5) + .longitude(127.0) + .build()) + .build()); + + memberExerciseRepository.save(MemberFixture.createMemberExercise(member, newExercise)); + exerciseBookmarkRepository.save(ExerciseBookmark.builder() + .member(member) + .exercise(newExercise) + .build()); } @Nested @@ -337,38 +365,41 @@ void setUp() { class Success { @Test - @DisplayName("200 - LATEST 정렬로 찜한 운동을 전체 조회하면 모든 필드를 반환한다") + @DisplayName("200 - LATEST 정렬로 찜한 운동을 전체 조회하면 최신순으로 반환한다") void getAllExerciseBookmarks_latest_allFields() throws Exception { SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); mockMvc.perform(get("/api/exercises/bookmarks") .param("orderType", BookmarkedExerciseOrderType.LATEST.name())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(1))) - .andExpect(jsonPath("$.data[0].exerciseId").isNumber()) + .andExpect(jsonPath("$.data", hasSize(2))) + .andExpect(jsonPath("$.data[0].exerciseId").value(newExercise.getId())) + .andExpect(jsonPath("$.data[1].exerciseId").value(bookmarkExercise.getId())) .andExpect(jsonPath("$.data[0].partyName").value("테스트 모임")) .andExpect(jsonPath("$.data[0].buildingName").value("테스트 체육관")) .andExpect(jsonPath("$.data[0].streetAddr").value("경기도 안산시 한양대학로 1")) .andExpect(jsonPath("$.data[0].femaleLevel").isArray()) .andExpect(jsonPath("$.data[0].maleLevel").isArray()) - .andExpect(jsonPath("$.data[0].date").value("2026-12-31")) - .andExpect(jsonPath("$.data[0].startExerciseTime").value("10:00:00")) - .andExpect(jsonPath("$.data[0].endExerciseTime").value("12:00:00")) - .andExpect(jsonPath("$.data[0].maxMemberCnt").value(10)) + .andExpect(jsonPath("$.data[0].date").value("2027-06-30")) + .andExpect(jsonPath("$.data[0].startExerciseTime").value("14:00:00")) + .andExpect(jsonPath("$.data[0].endExerciseTime").value("16:00:00")) + .andExpect(jsonPath("$.data[0].maxMemberCnt").value(8)) .andExpect(jsonPath("$.data[0].nowMemberCnt").isNumber()) .andExpect(jsonPath("$.data[0].includeParty").value(true)) .andExpect(jsonPath("$.data[0].includeExercise").value(true)); } @Test - @DisplayName("200 - EARLIEST 정렬로 찜한 운동 전체 조회 시 성공한다") + @DisplayName("200 - EARLIEST 정렬로 찜한 운동을 전체 조회하면 오래된순으로 반환한다") void getAllExerciseBookmarks_earliest() throws Exception { SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); mockMvc.perform(get("/api/exercises/bookmarks") .param("orderType", BookmarkedExerciseOrderType.EARLIEST.name())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(1))); + .andExpect(jsonPath("$.data", hasSize(2))) + .andExpect(jsonPath("$.data[0].exerciseId").value(bookmarkExercise.getId())) + .andExpect(jsonPath("$.data[1].exerciseId").value(newExercise.getId())); } @Test @@ -394,13 +425,26 @@ void noBookmarks_returnsEmptyList() throws Exception { @DisplayName("GET /api/parties/bookmarks - 찜한 모임 전체 조회") class GetAllPartyBookmarks { + private Party newParty; + @BeforeEach void setUp() { + // 먼저 저장 = 오래된 북마크 partyBookmarkRepository.save(PartyBookmark.builder() .party(bookmarkParty) .member(member) .orderType(PartyOrderType.LATEST) .build()); + + // 나중에 저장 = 최신 북마크 + PartyAddr newAddr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); + newParty = partyRepository.save(PartyFixture.createParty("새 테스트 모임", member.getId(), newAddr)); + + partyBookmarkRepository.save(PartyBookmark.builder() + .party(newParty) + .member(member) + .orderType(PartyOrderType.LATEST) + .build()); } @Nested @@ -408,24 +452,25 @@ void setUp() { class Success { @Test - @DisplayName("200 - LATEST 정렬로 찜한 모임을 전체 조회하면 모든 필드를 반환한다") + @DisplayName("200 - LATEST 정렬로 찜한 모임을 전체 조회하면 최신순으로 반환한다") void getAllPartyBookmarks_latest_allFields() throws Exception { SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); mockMvc.perform(get("/api/parties/bookmarks") .param("orderType", PartyOrderType.LATEST.name())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(1))) - .andExpect(jsonPath("$.data[0].partyId").isNumber()) - .andExpect(jsonPath("$.data[0].partyName").value("테스트 모임")) - .andExpect(jsonPath("$.data[0].addr1").value("경기도")) - .andExpect(jsonPath("$.data[0].addr2").value("안산시")) - .andExpect(jsonPath("$.data[0].maleLevel").isArray()) - .andExpect(jsonPath("$.data[0].femaleLevel").isArray()) - .andExpect(jsonPath("$.data[0].latestExerciseDate").value("2026-12-31")) - .andExpect(jsonPath("$.data[0].latestExerciseTime").value("MORNING")) - .andExpect(jsonPath("$.data[0].exerciseCnt").isNumber()) - .andExpect(jsonPath("$.data[0].profileImgUrl").value(nullValue())); + .andExpect(jsonPath("$.data", hasSize(2))) + .andExpect(jsonPath("$.data[0].partyId").value(newParty.getId())) + .andExpect(jsonPath("$.data[1].partyId").value(bookmarkParty.getId())) + .andExpect(jsonPath("$.data[1].partyName").value("테스트 모임")) + .andExpect(jsonPath("$.data[1].addr1").value("경기도")) + .andExpect(jsonPath("$.data[1].addr2").value("안산시")) + .andExpect(jsonPath("$.data[1].maleLevel").isArray()) + .andExpect(jsonPath("$.data[1].femaleLevel").isArray()) + .andExpect(jsonPath("$.data[1].latestExerciseDate").value("2026-12-31")) + .andExpect(jsonPath("$.data[1].latestExerciseTime").value("MORNING")) + .andExpect(jsonPath("$.data[1].exerciseCnt").isNumber()) + .andExpect(jsonPath("$.data[1].profileImgUrl").value(nullValue())); } @Test @@ -436,18 +481,20 @@ void getAllPartyBookmarks_exerciseCount() throws Exception { mockMvc.perform(get("/api/parties/bookmarks") .param("orderType", PartyOrderType.EXERCISE_COUNT.name())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(1))); + .andExpect(jsonPath("$.data", hasSize(2))); } @Test - @DisplayName("200 - OLDEST 정렬로 찜한 모임 전체 조회 시 성공한다") + @DisplayName("200 - OLDEST 정렬로 찜한 모임을 전체 조회하면 오래된순으로 반환한다") void getAllPartyBookmarks_oldest() throws Exception { SecurityContextHelper.setAuthentication(member.getId(), member.getNickname()); mockMvc.perform(get("/api/parties/bookmarks") .param("orderType", PartyOrderType.OLDEST.name())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(1))); + .andExpect(jsonPath("$.data", hasSize(2))) + .andExpect(jsonPath("$.data[0].partyId").value(bookmarkParty.getId())) + .andExpect(jsonPath("$.data[1].partyId").value(newParty.getId())); } @Test