From 654cab76de2863cb291b52f15a5b30d3d1b1dd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 00:07:19 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[refactor]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=20tearDown()=EC=97=90=EC=84=9C=20@=20Tran?= =?UTF-8?q?sactional=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=95=88?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20=ED=95=A8=EC=88=98,=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EA=B4=80=EA=B3=84,import=EB=AC=B8=20=EC=82=AD=EC=A0=9C(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/BookChangeSavedApiTest.java | 10 ++----- .../in/web/BookDetailSearchApiTest.java | 19 +++++------- .../in/web/BookGetSelectableListApiTest.java | 12 ++------ .../in/web/BookMostSearchedBooksApiTest.java | 8 ++--- .../adapter/in/web/BookSearchApiTest.java | 11 ++----- .../in/web/BookShowSavedListApiTest.java | 12 ++------ .../in/web/CommentCreateControllerTest.java | 2 -- .../adapter/in/web/CommentDeleteApiTest.java | 28 ++++++++--------- .../adapter/in/web/CommentShowAllApiTest.java | 11 ++----- .../CommentCommandPersistenceAdapterTest.java | 6 ---- .../thip/comment/domain/CommentTest.java | 16 ---------- .../common/persistence/StatusFilterTest.java | 9 +----- .../in/web/BasicFeedShowAllApiTest.java | 13 ++------ .../adapter/in/web/FeedShowMineApiTest.java | 25 ++-------------- .../adapter/in/web/FeedShowSingleApiTest.java | 18 ++--------- .../in/web/FeedShowSpecificUserApiTest.java | 16 ++-------- .../in/web/FeedShowUserInfoApiTest.java | 25 ++++++++++------ .../in/web/FeedUpdateControllerTest.java | 2 -- .../FollowingPriorityFeedShowAllApiTest.java | 13 ++------ .../konkuk/thip/feed/domain/FeedTest.java | 24 --------------- .../adapter/in/web/RoomCreateApiTest.java | 11 +------ .../in/web/RoomGetHomeJoinedRoomsApiTest.java | 21 +++++-------- .../in/web/RoomGetMemberListApiTest.java | 11 ++----- .../room/adapter/in/web/RoomJoinApiTest.java | 19 +++++------- .../web/RoomRecruitingDetailViewApiTest.java | 15 ++-------- .../adapter/in/web/RoomShowMineApiTest.java | 12 ++------ .../in/web/RoomVerifyPasswordApiTest.java | 9 ++---- .../in/web/AttendanceCheckCreateApiTest.java | 12 ++------ .../in/web/AttendanceCheckDeleteApiTest.java | 19 +++++------- .../in/web/AttendanceCheckShowApiTest.java | 12 ++------ .../adapter/in/web/VoteCreateApiTest.java | 18 ++--------- .../service/VoteCreateServiceTest.java | 26 ++-------------- .../adapter/in/web/UserDeleteApiTest.java | 30 ++++--------------- .../adapter/in/web/UserFollowApiTest.java | 12 ++------ .../in/web/UserIsFollowingApiTest.java | 9 ++---- .../UserShowFollowingsInFeedViewApiTest.java | 11 ++----- .../in/web/UserSignupControllerTest.java | 8 ++--- .../adapter/in/web/UserUpdateApiTest.java | 8 ++--- .../web/UserVerifyNicknameControllerTest.java | 8 ++--- 39 files changed, 119 insertions(+), 432 deletions(-) diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookChangeSavedApiTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookChangeSavedApiTest.java index fe5aa6855..45dd85b8f 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookChangeSavedApiTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookChangeSavedApiTest.java @@ -13,7 +13,6 @@ import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -24,6 +23,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.Optional; @@ -36,6 +36,7 @@ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") +@Transactional @DisplayName("[통합] 방 저장상태 변경 api 통합 테스트") class BookChangeSavedApiTest { @@ -90,13 +91,6 @@ void setUp() { } - @AfterEach - void tearDown() { - savedBookJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - } - @Test @DisplayName("DB에 책이 존재하고 해당 책을 저장하려고 할때 [책 저장 성공]") void saveBook_success() throws Exception { diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchApiTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchApiTest.java index 28fecf312..608f77ece 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchApiTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchApiTest.java @@ -1,5 +1,6 @@ package konkuk.thip.book.adapter.in.web; +import jakarta.persistence.EntityManager; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.book.application.service.BookSearchService; @@ -19,7 +20,6 @@ import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -27,6 +27,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -35,6 +36,7 @@ @SpringBootTest @AutoConfigureMockMvc(addFilters = false) @ActiveProfiles("test") +@Transactional @DisplayName("[통합] 책 상세보기 api 통합 테스트") class BookDetailSearchApiTest { @@ -52,6 +54,8 @@ class BookDetailSearchApiTest { private FeedJpaRepository feedJpaRepository; @Autowired private SavedBookJpaRepository savedBookJpaRepository; + @Autowired + EntityManager em; @BeforeEach void setup() { @@ -90,6 +94,9 @@ void setup() { .category(category) .build()); + em.flush(); + em.clear(); + roomParticipantJpaRepository.save(RoomParticipantJpaEntity.builder() .currentPage(10) .userPercentage(10.0) @@ -112,16 +119,6 @@ void setup() { .build()); } - @AfterEach - void tearDown() { - savedBookJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("책 상세 검색 결과를 정상적으로 반환.") void searchDetailBooks_ReturnsCorrectResult() { diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookGetSelectableListApiTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookGetSelectableListApiTest.java index 030332ce1..64694759a 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookGetSelectableListApiTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookGetSelectableListApiTest.java @@ -15,7 +15,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; import org.hamcrest.Matchers; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +24,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDate; @@ -39,6 +39,7 @@ @SpringBootTest @AutoConfigureMockMvc(addFilters = false) @ActiveProfiles("test") +@Transactional @DisplayName("[통합] 저장한 책 및 참여 중 책 리스트 조회 API 통합 테스트") class BookGetSelectableListApiTest { @@ -52,15 +53,6 @@ class BookGetSelectableListApiTest { @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - savedBookJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - private RoomJpaEntity saveScienceRoomWithBookIsbn(String isbn, String roomName, double roomPercentage) { BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() .title("책이름") diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookMostSearchedBooksApiTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookMostSearchedBooksApiTest.java index a1cec3e9d..8069449ab 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookMostSearchedBooksApiTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookMostSearchedBooksApiTest.java @@ -8,7 +8,6 @@ import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -21,6 +20,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -34,6 +34,7 @@ @SpringBootTest @AutoConfigureMockMvc(addFilters = false) @ActiveProfiles("test") +@Transactional @DisplayName("[통합] 가장 많이 검색된 책 조회 API 통합 테스트") class BookMostSearchedBooksApiTest { @@ -72,11 +73,6 @@ void setUp() { .build()); } - @AfterEach - void tearDown() { - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("어제 랭킹 Top 5를 정상적으로 조회한다") void getMostSearchedBooks_returnsRankList() throws Exception { diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookSearchApiTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookSearchApiTest.java index a2f307559..f480dc4ad 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookSearchApiTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookSearchApiTest.java @@ -10,7 +10,6 @@ import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,6 +19,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -32,7 +32,8 @@ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") -@DisplayName("[통합] 방 검색 api 통합 테스트") +@DisplayName("[통합] 책 검색 api 통합 테스트") +@Transactional class BookSearchApiTest { @Autowired @@ -70,12 +71,6 @@ void setUp() { testToken = jwtUtil.createAccessToken(user.getUserId()); } - @AfterEach - void tearDown() { - recentSearchJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("책 검색 API 정상 호출 - 키워드와 페이지 번호가 주어졌을 때") void searchBooks_success() throws Exception { diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookShowSavedListApiTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookShowSavedListApiTest.java index f8a49f643..559bfd07d 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookShowSavedListApiTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookShowSavedListApiTest.java @@ -11,7 +11,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; import org.hamcrest.Matchers; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -21,6 +20,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -34,6 +34,7 @@ @SpringBootTest @AutoConfigureMockMvc(addFilters = false) @ActiveProfiles("test") +@Transactional @DisplayName("[통합] 저장한 책 조회 API 통합 테스트") class BookShowSavedListApiTest { @@ -47,15 +48,6 @@ class BookShowSavedListApiTest { @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAll(); - savedBookJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - } - @Test @DisplayName("저장된 책 조회 시 책 정보를 저장한 최신순으로 정렬해서 반환한다.") void getSavedBooks_success() throws Exception { diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateControllerTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateControllerTest.java index 7f9346f20..4eb90f0d1 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateControllerTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentCreateControllerTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; -import konkuk.thip.comment.adapter.out.persistence.repository.CommentJpaRepository; import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; @@ -58,7 +57,6 @@ class CommentCreateControllerTest { @Autowired private FeedJpaRepository feedJpaRepository; @Autowired private VoteJpaRepository voteJpaRepository; @Autowired private RecordJpaRepository recordJpaRepository; - @Autowired private CommentJpaRepository commentJpaRepository; @Autowired private RoomJpaRepository roomJpaRepository; @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteApiTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteApiTest.java index e0bdbd54a..b33bc4bd8 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteApiTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteApiTest.java @@ -1,5 +1,6 @@ package konkuk.thip.comment.adapter.in.web; +import jakarta.persistence.EntityManager; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; @@ -20,7 +21,6 @@ import konkuk.thip.roompost.adapter.out.jpa.VoteJpaEntity; import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -29,6 +29,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import static konkuk.thip.common.entity.StatusType.INACTIVE; import static konkuk.thip.post.domain.PostType.FEED; @@ -38,6 +39,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 댓글 삭제 api 통합 테스트") class CommentDeleteApiTest { @@ -55,6 +57,8 @@ class CommentDeleteApiTest { @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; @Autowired private CommentLikeJpaRepository commentLikeJpaRepository; + @Autowired private EntityManager em; + private Alias alias; private UserJpaEntity user; private Category category; @@ -77,19 +81,6 @@ record = recordJpaRepository.save(TestEntityFactory.createRecord(user,room)); roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, user, RoomParticipantRole.HOST, 0.0)); } - @AfterEach - void tearDown() { - recordJpaRepository.deleteAllInBatch(); - voteJpaRepository.deleteAllInBatch(); - commentLikeJpaRepository.deleteAll(); - commentJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("루트댓글을 삭제하면 [soft delete 처리]된다") void deleteRootComment_success() throws Exception { @@ -105,6 +96,9 @@ void deleteRootComment_success() throws Exception { .requestAttr("userId", user.getUserId())) .andExpect(status().isOk()); + em.flush(); + em.clear(); + // then CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(commentId).orElse(null); assertThat(commentJpaEntity.getStatus()).isEqualTo(INACTIVE); @@ -126,6 +120,9 @@ void deleteReplyComment_success() throws Exception { .requestAttr("userId", user.getUserId())) .andExpect(status().isOk()); + em.flush(); + em.clear(); + // then CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(replyId).orElse(null); assertThat(commentJpaEntity.getStatus()).isEqualTo(INACTIVE); @@ -149,6 +146,9 @@ void deleteComment_success() throws Exception { .requestAttr("userId", user.getUserId())) .andExpect(status().isOk()); + em.flush(); + em.clear(); + // then CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(commentId).orElse(null); assertThat(commentJpaEntity.getStatus()).isEqualTo(INACTIVE); diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentShowAllApiTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentShowAllApiTest.java index 9f0b13de4..89e52e4a6 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentShowAllApiTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentShowAllApiTest.java @@ -23,6 +23,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -37,6 +38,7 @@ @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 댓글 조회 api 통합 테스트") +@Transactional class CommentShowAllApiTest { @Autowired private MockMvc mockMvc; @@ -49,15 +51,6 @@ class CommentShowAllApiTest { private static final String FEED_POST_TYPE = PostType.FEED.getType(); - @AfterEach - void tearDown() { - commentLikeJpaRepository.deleteAllInBatch(); - commentJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("댓글 조회 요청에 대하여, 특정 게시글(= 피드, 기록, 투표)의 루트 댓글, 루트 댓글의 모든 자식 댓글의 데이터를 구분하여 반환한다.") void comment_show_all_test() throws Exception { diff --git a/src/test/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapterTest.java b/src/test/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapterTest.java index 875360766..3e7fdcf17 100644 --- a/src/test/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapterTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapterTest.java @@ -6,15 +6,12 @@ import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; import konkuk.thip.comment.adapter.out.mapper.CommentMapper; import konkuk.thip.comment.adapter.out.persistence.repository.CommentJpaRepository; -import konkuk.thip.comment.adapter.out.persistence.repository.CommentLikeJpaRepository; import konkuk.thip.common.entity.StatusType; import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.config.TestQuerydslConfig; import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; import konkuk.thip.post.domain.PostType; -import konkuk.thip.roompost.adapter.out.persistence.repository.record.RecordJpaRepository; -import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteJpaRepository; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; @@ -37,12 +34,9 @@ class CommentCommandPersistenceAdapterTest { @Autowired CommentCommandPersistenceAdapter adapter; // repository 인터페이스가 아니므로 자동 스캔 X -> import 해줘야함 @Autowired CommentJpaRepository commentJpaRepository; - @Autowired CommentLikeJpaRepository commentLikeJpaRepository; @Autowired BookJpaRepository bookJpaRepository; @Autowired FeedJpaRepository feedJpaRepository; @Autowired UserJpaRepository userJpaRepository; - @Autowired RecordJpaRepository recordJpaRepository; - @Autowired VoteJpaRepository voteJpaRepository; @MockitoBean CommentMapper commentMapper; // Mock bean 으로 설정 diff --git a/src/test/java/konkuk/thip/comment/domain/CommentTest.java b/src/test/java/konkuk/thip/comment/domain/CommentTest.java index a33a2b877..c608f474e 100644 --- a/src/test/java/konkuk/thip/comment/domain/CommentTest.java +++ b/src/test/java/konkuk/thip/comment/domain/CommentTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Test; import static konkuk.thip.common.entity.StatusType.ACTIVE; -import static konkuk.thip.common.entity.StatusType.INACTIVE; import static konkuk.thip.common.exception.code.ErrorCode.*; import static konkuk.thip.post.domain.PostType.FEED; import static org.junit.jupiter.api.Assertions.*; @@ -32,21 +31,6 @@ private Comment createParentComment(Long postId) { .build(); } - - private Comment createInactiveComment(Long postId) { - return Comment.builder() - .id(124L) //ID 임의 주입 - .content(CONTENT) - .targetPostId(postId) - .creatorId(CREATOR_ID) - .postType(FEED) - .parentCommentId(null) - .reportCount(0) - .likeCount(0) - .status(INACTIVE) - .build(); - } - @Test @DisplayName("createComment: 일반 댓글 생성 시 parentId는 null이면 정상적으로 Comment가 생성된다.") void createRootComment_valid() { diff --git a/src/test/java/konkuk/thip/common/persistence/StatusFilterTest.java b/src/test/java/konkuk/thip/common/persistence/StatusFilterTest.java index 238491f34..8277ec669 100644 --- a/src/test/java/konkuk/thip/common/persistence/StatusFilterTest.java +++ b/src/test/java/konkuk/thip/common/persistence/StatusFilterTest.java @@ -8,7 +8,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +23,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional public class StatusFilterTest { @Autowired private UserJpaRepository userJpaRepository; @@ -37,13 +37,6 @@ public class StatusFilterTest { @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - public void tearDown() { - savedBookJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - private void saveActiveUser(int count) { for (int i = 1; i <= count; i++) { UserJpaEntity user = TestEntityFactory.createUser(Alias.WRITER, "activeUser" + i); diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/BasicFeedShowAllApiTest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/BasicFeedShowAllApiTest.java index cd6c59e9c..b9760a385 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/BasicFeedShowAllApiTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/BasicFeedShowAllApiTest.java @@ -13,7 +13,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +21,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -38,6 +38,7 @@ ) @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 피드 전체 조회(최신순 조회) api 통합 테스트") class BasicFeedShowAllApiTest { @Autowired @@ -64,16 +65,6 @@ class BasicFeedShowAllApiTest { @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - postLikeJpaRepository.deleteAllInBatch(); - savedFeedJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("피드 조회를 요청할 경우, [feedId, 작성자 닉네임, ,,] 의 피드 정보를 최신순으로 정렬해서 반환한다.") void feed_show_all_test() throws Exception { diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowMineApiTest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowMineApiTest.java index 7636df682..9bfb3623f 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowMineApiTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowMineApiTest.java @@ -5,13 +5,9 @@ import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.feed.adapter.out.jpa.FeedJpaEntity; import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; -import konkuk.thip.post.adapter.out.persistence.repository.PostLikeJpaRepository; -import konkuk.thip.feed.adapter.out.persistence.repository.SavedFeedJpaRepository; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; -import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -20,6 +16,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -35,6 +32,7 @@ @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 내 피드 조회 api 통합 테스트") +@Transactional class FeedShowMineApiTest { @Autowired @@ -46,31 +44,12 @@ class FeedShowMineApiTest { @Autowired private FeedJpaRepository feedJpaRepository; - @Autowired - private FollowingJpaRepository followingJpaRepository; - @Autowired private BookJpaRepository bookJpaRepository; - @Autowired - private SavedFeedJpaRepository savedFeedJpaRepository; - - @Autowired - private PostLikeJpaRepository postLikeJpaRepository; - @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - postLikeJpaRepository.deleteAllInBatch(); - savedFeedJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("내 피드 조회를 요청할 경우, [feedId, 작성일, 책정보, ,,] 의 피드 정보를 최신순으로 정렬해서 반환한다.") void feed_show_mine_test() throws Exception { diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowSingleApiTest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowSingleApiTest.java index 450798708..782eafc71 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowSingleApiTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowSingleApiTest.java @@ -8,13 +8,10 @@ import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; import konkuk.thip.feed.adapter.out.persistence.repository.SavedFeedJpaRepository; import konkuk.thip.feed.domain.value.Tag; -import konkuk.thip.post.adapter.out.persistence.repository.PostLikeJpaRepository; import konkuk.thip.room.domain.value.Category; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; -import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +19,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -35,6 +33,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 단일 피드 조회 api 통합 테스트") class FeedShowSingleApiTest { @@ -42,23 +41,10 @@ class FeedShowSingleApiTest { private MockMvc mockMvc; @Autowired private UserJpaRepository userJpaRepository; @Autowired private FeedJpaRepository feedJpaRepository; - @Autowired private FollowingJpaRepository followingJpaRepository; @Autowired private BookJpaRepository bookJpaRepository; @Autowired private SavedFeedJpaRepository savedFeedJpaRepository; - @Autowired private PostLikeJpaRepository postLikeJpaRepository; - private List tags; - @AfterEach - void tearDown() { - postLikeJpaRepository.deleteAllInBatch(); - savedFeedJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("단일 피드 조회를 요청할 경우, [피드 정보, 피드 작성자 정보, 피드와 연관된 태그들] 등의 정보를 반환한다.") void feed_show_single_test() throws Exception { diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowSpecificUserApiTest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowSpecificUserApiTest.java index 6701bfb82..726aa5c50 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowSpecificUserApiTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowSpecificUserApiTest.java @@ -11,9 +11,7 @@ import konkuk.thip.feed.adapter.out.persistence.repository.SavedFeedJpaRepository; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; -import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +20,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -36,6 +35,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 특정 유저 피드 조회 api 통합 테스트") class FeedShowSpecificUserApiTest { @@ -48,9 +48,6 @@ class FeedShowSpecificUserApiTest { @Autowired private FeedJpaRepository feedJpaRepository; - @Autowired - private FollowingJpaRepository followingJpaRepository; - @Autowired private BookJpaRepository bookJpaRepository; @@ -63,15 +60,6 @@ class FeedShowSpecificUserApiTest { @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - postLikeJpaRepository.deleteAllInBatch(); - savedFeedJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - } @Test @DisplayName("특정 유저의 피드 조회를 요청할 경우, [feedId, 작성일, 책정보, ,,] 의 피드 정보를 최신순으로 정렬해서 반환한다.") diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowUserInfoApiTest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowUserInfoApiTest.java index 13e017cf0..46999902d 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowUserInfoApiTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedShowUserInfoApiTest.java @@ -1,5 +1,6 @@ package konkuk.thip.feed.adapter.in.web; +import jakarta.persistence.EntityManager; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.common.util.TestEntityFactory; @@ -9,7 +10,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +18,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -31,6 +32,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 피드 화면에서의 유저 정보 조회 api 통합 테스트") class FeedShowUserInfoApiTest { @@ -40,14 +42,7 @@ class FeedShowUserInfoApiTest { @Autowired private FollowingJpaRepository followingJpaRepository; @Autowired private BookJpaRepository bookJpaRepository; @Autowired private JdbcTemplate jdbcTemplate; - - @AfterEach - void tearDown() { - feedJpaRepository.deleteAllInBatch(); - followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - } + @Autowired private EntityManager em; @Test @DisplayName("내 피드에서의 유저 정보를 조회할 경우, 내 개인 정보, 나의 팔로워 정보, 내가 작성한 모든 피드 개수 를 반환한다.") @@ -74,6 +69,9 @@ void feed_show_mine_info_test() throws Exception { "UPDATE users SET follower_count = ? WHERE user_id = ?", 2, me.getUserId()); // me 의 followerCount 값을 2로 update + em.flush(); + em.clear(); + // 피드 생성 및 생성일 직접 설정 BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBook()); // 공통 Book feedJpaRepository.save(TestEntityFactory.createFeed(me, book, true)); // 공개글 @@ -144,6 +142,9 @@ void feed_show_mine_info_follower_many_test() throws Exception { "UPDATE users SET follower_count = ? WHERE user_id = ?", 7, me.getUserId()); // me 의 followerCount 값을 7로 update + em.flush(); + em.clear(); + //when //then mockMvc.perform(get("/feeds/mine/info") .requestAttr("userId", me.getUserId())) @@ -191,6 +192,9 @@ void feed_show_user_info_test() throws Exception { feedJpaRepository.save(TestEntityFactory.createFeed(anotherUser, book, false)); // 비공개글 feedJpaRepository.save(TestEntityFactory.createFeed(anotherUser, book, false)); // 비공개글 + em.flush(); + em.clear(); + //when //then mockMvc.perform(get("/feeds/users/{userId}/info", anotherUser.getUserId()) .requestAttr("userId", me.getUserId())) @@ -284,6 +288,9 @@ void feed_show_user_info_follower_many_test() throws Exception { "UPDATE users SET follower_count = ? WHERE user_id = ?", 7, anotherUser.getUserId()); // anotherUser 의 followerCount 값을 7로 update + em.flush(); + em.clear(); + //when //then mockMvc.perform(get("/feeds/users/{userId}/info", anotherUser.getUserId()) .requestAttr("userId", me.getUserId())) diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedUpdateControllerTest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedUpdateControllerTest.java index 97816072e..3fd5aa97e 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedUpdateControllerTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedUpdateControllerTest.java @@ -5,7 +5,6 @@ import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.feed.adapter.out.persistence.repository.FeedJpaRepository; -import konkuk.thip.room.domain.value.Category; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; @@ -52,7 +51,6 @@ class FeedUpdateControllerTest { @BeforeEach void setUp() { - Alias alias = TestEntityFactory.createLiteratureAlias(); UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(alias)); BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBookWithISBN("9788954682152")); diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FollowingPriorityFeedShowAllApiTest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FollowingPriorityFeedShowAllApiTest.java index 4c4e1b3fb..e489e42a4 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/FollowingPriorityFeedShowAllApiTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FollowingPriorityFeedShowAllApiTest.java @@ -13,7 +13,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +21,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -37,6 +37,7 @@ ) @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 피드 전체 조회(내가 팔로잉한 유저 우선순 조회) api 통합 테스트") class FollowingPriorityFeedShowAllApiTest { @@ -64,16 +65,6 @@ class FollowingPriorityFeedShowAllApiTest { @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - postLikeJpaRepository.deleteAllInBatch(); - savedFeedJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("피드 조회를 요청할 경우, [feedId, 작성자 닉네임, ,,] 의 피드 정보를 최신순으로 정렬해서 반환한다.") void feed_show_all_test() throws Exception { diff --git a/src/test/java/konkuk/thip/feed/domain/FeedTest.java b/src/test/java/konkuk/thip/feed/domain/FeedTest.java index 746e5be96..e4aa33d34 100644 --- a/src/test/java/konkuk/thip/feed/domain/FeedTest.java +++ b/src/test/java/konkuk/thip/feed/domain/FeedTest.java @@ -63,30 +63,6 @@ private Feed createPrivateFeed() { .build(); } -// @Test -// @DisplayName("validateTags: 태그가 5개 초과 시 InvalidStateException이 발생한다.") -// void validateTags_exceedsMax_throws() { -// List tags = List.of("a", "b", "c", "d", "e", "f"); -// -// InvalidStateException ex = assertThrows(InvalidStateException.class, -// () -> Feed.validateTags(tags)); -// -// assertEquals(INVALID_FEED_COMMAND, ex.getErrorCode()); -// assertTrue(ex.getCause().getMessage().contains("최대 5개")); -// } -// -// @Test -// @DisplayName("validateTags: 중복 태그 있을 경우 InvalidStateException이 발생한다.") -// void validateTags_withDuplicates_throws() { -// List tags = List.of("a", "b", "a"); -// -// InvalidStateException ex = assertThrows(InvalidStateException.class, -// () -> Feed.validateTags(tags)); -// -// assertEquals(INVALID_FEED_COMMAND, ex.getErrorCode()); -// assertTrue(ex.getCause().getMessage().contains("중복")); -// } - @Test @DisplayName("validateCreateComment: 공개 피드면 누구나 댓글을 작성 할 수 있다") void validateCreateComment_publicFeed_passes() { diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java index 829039b1b..78306cd96 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java @@ -40,6 +40,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 방 생성 api 통합 테스트") class RoomCreateApiTest { @@ -50,14 +51,6 @@ class RoomCreateApiTest { @Autowired private RoomJpaRepository roomJpaRepository; @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; - @AfterEach - void tearDown() { - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - private void saveUserAndLiteratureCategory() { Alias alias = TestEntityFactory.createLiteratureAlias(); @@ -68,8 +61,6 @@ private void saveUserAndLiteratureCategory() { .role(UserRole.USER) .alias(alias) .build()); - - Category category = TestEntityFactory.createLiteratureCategory(); } private void saveBookWithPageCount() { diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java index 3ac9526f8..acce4237f 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetHomeJoinedRoomsApiTest.java @@ -14,7 +14,6 @@ import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; import konkuk.thip.user.domain.value.Alias; import org.hamcrest.Matchers; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -24,6 +23,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -36,6 +36,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 모임 홈 참여중인 내 모임방 조회 api 통합 테스트") class RoomGetHomeJoinedRoomsApiTest { @@ -79,14 +80,6 @@ void setUp() { roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room2,user1, RoomParticipantRole.HOST,60.0)); } - @AfterEach - void tearDown() { - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - } - private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, LocalDate endDate, int recruitCount) { BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() .title(bookTitle) @@ -205,8 +198,8 @@ void getHomeJoinedRooms_sortByStartDateWhenUserPercentageEquals() throws Excepti } @Test - @DisplayName("사용자가 참여중인 방 목록 중 모집중(시작 전)인 방은 참여중 목록에 포함되지 않는다.") - void getHomeJoinedRooms_excludeRecruitingRooms() throws Exception { + @DisplayName("사용자가 참여중인 방 목록 중 모집중(시작 전)인 방도 참여중 목록에 포함된다.") + void getHomeJoinedRooms_includeRecruitingRooms() throws Exception { // given Alias alias = TestEntityFactory.createLiteratureAlias(); @@ -231,11 +224,13 @@ void getHomeJoinedRooms_excludeRecruitingRooms() throws Exception { // then result.andExpect(status().isOk()) - .andExpect(jsonPath("$.data.roomList", hasSize(1))) + .andExpect(jsonPath("$.data.roomList", hasSize(2))) .andExpect(jsonPath("$.data.nextCursor").value(Matchers.nullValue())) .andExpect(jsonPath("$.data.isLast", is(true))) .andExpect(jsonPath("$.data.roomList[0].roomId", is(activeRoom.getRoomId().intValue()))) - .andExpect(jsonPath("$.data.roomList[0].userPercentage", is(50))); + .andExpect(jsonPath("$.data.roomList[0].userPercentage", is(50))) + .andExpect(jsonPath("$.data.roomList[1].roomId", is(recruitRoom.getRoomId().intValue()))) + .andExpect(jsonPath("$.data.roomList[1].userPercentage", is(-1))); } diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java index 480bba84d..5132ad08d 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java @@ -23,6 +23,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -34,6 +35,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 독서 메이트(방 멤버) 조회 api 통합 테스트") class RoomGetMemberListApiTest { @@ -103,15 +105,6 @@ void setUp() { followingJpaRepository.save(TestEntityFactory.createFollowing(user3, user1)); } - @AfterEach - void tearDown() { - followingJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - } - @Test @DisplayName("방 멤버 리스트(독서메이트)가 userId, nickname, imageUrl, alias, subscriberCount로 조회된다.") void getRoomMemberList_success() throws Exception { diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java index ce1c0ff9d..cf6913a3c 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomJoinApiTest.java @@ -1,10 +1,10 @@ package konkuk.thip.room.adapter.in.web; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityManager; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.common.util.TestEntityFactory; -import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomParticipantJpaEntity; import konkuk.thip.room.domain.value.RoomParticipantRole; @@ -15,7 +15,6 @@ import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +24,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.HashMap; @@ -39,6 +39,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 방 참여/취소 API 통합 테스트") class RoomJoinApiTest { @@ -49,7 +50,7 @@ class RoomJoinApiTest { @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; @Autowired private BookJpaRepository bookJpaRepository; @Autowired private UserJpaRepository userJpaRepository; - @Autowired private NotificationJpaRepository notificationJpaRepository; + @Autowired private EntityManager em; private RoomJpaEntity room; private UserJpaEntity host; @@ -109,15 +110,6 @@ private void createUsers(Alias alias) { .build()); } - @AfterEach - void tearDown() { - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - notificationJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("방 참여 성공 - 참여자 저장 및 인원수 증가 확인") void joinRoom_success() throws Exception { @@ -175,6 +167,9 @@ void cancelJoin_success() throws Exception { .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()); + em.flush(); + em.clear(); + // 참여자 삭제 확인 RoomParticipantJpaEntity member = roomParticipantJpaRepository.findById(memberParticipation.getRoomParticipantId()).orElse(null); assertThat(member.getStatus()).isEqualTo(INACTIVE); diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java index ee37cbf18..f2959bfe7 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomRecruitingDetailViewApiTest.java @@ -15,7 +15,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; import konkuk.thip.user.domain.value.UserRole; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +23,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.List; @@ -36,6 +36,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 모집 중인 방 상세조회 api 통합 테스트") class RoomRecruitingDetailViewApiTest { @@ -46,17 +47,7 @@ class RoomRecruitingDetailViewApiTest { @Autowired private RoomJpaRepository roomJpaRepository; @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; - @AfterEach - void tearDown() { - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - } - private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount, RoomStatus roomStatus) { - Alias alias = TestEntityFactory.createScienceAlias(); - BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() .title(bookTitle) .isbn(isbn) @@ -85,8 +76,6 @@ private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String room } private RoomJpaEntity saveLiteratureRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount, RoomStatus roomStatus) { - Alias alias = TestEntityFactory.createLiteratureAlias(); - BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() .title(bookTitle) .isbn(isbn) diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java index c75278393..3bf245393 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java @@ -24,6 +24,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -35,6 +36,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 내 방 목록 조회 api 통합 테스트") class RoomShowMineApiTest { @@ -46,17 +48,7 @@ class RoomShowMineApiTest { @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, LocalDate endDate, int recruitCount, RoomStatus roomStatus) { - Alias alias = TestEntityFactory.createScienceAlias(); - BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() .title(bookTitle) .isbn(isbn) diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java index dde07a0b3..2c62ebd2e 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java @@ -24,6 +24,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -34,6 +35,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 비공개 방 비밀번호 입력 검증 api 통합 테스트") class RoomVerifyPasswordApiTest { @@ -100,13 +102,6 @@ void setUp() { publicRoomId = publicRoom.getRoomId(); } - @AfterEach - void tearDown() { - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - } - @Test @DisplayName("모집기간이 만료되지 않은 비공개 방의 비밀번호 입력 검증에 [성공]한다") void verifyRoomPassword_success() throws Exception { diff --git a/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckCreateApiTest.java b/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckCreateApiTest.java index a3117747d..8cd93fb60 100644 --- a/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckCreateApiTest.java +++ b/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckCreateApiTest.java @@ -14,7 +14,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +23,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.util.Map; @@ -37,6 +37,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 오늘의 한마디 생성 api 통합 테스트") class AttendanceCheckCreateApiTest { @@ -51,15 +52,6 @@ class AttendanceCheckCreateApiTest { @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; @Autowired private AttendanceCheckJpaRepository attendanceCheckJpaRepository; - @AfterEach - void tearDown() { - attendanceCheckJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("방의 참석자는 출석체크(= 오늘의 한마디) 를 작성할 수 있다.") void attendance_check_create_test() throws Exception { diff --git a/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java b/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java index d9274100a..9777942a5 100644 --- a/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java +++ b/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckDeleteApiTest.java @@ -1,6 +1,6 @@ package konkuk.thip.roompost.adapter.in.web; -import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityManager; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.common.util.TestEntityFactory; @@ -14,7 +14,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,6 +22,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import static konkuk.thip.common.entity.StatusType.INACTIVE; import static konkuk.thip.common.exception.code.ErrorCode.ATTENDANCE_CHECK_CAN_NOT_DELETE; @@ -35,6 +35,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 오늘의 한마디 삭제 api 통합 테스트") class AttendanceCheckDeleteApiTest { @@ -42,21 +43,12 @@ class AttendanceCheckDeleteApiTest { @Autowired private MockMvc mockMvc; - @Autowired private ObjectMapper objectMapper; @Autowired private UserJpaRepository userJpaRepository; @Autowired private BookJpaRepository bookJpaRepository; @Autowired private RoomJpaRepository roomJpaRepository; @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; @Autowired private AttendanceCheckJpaRepository attendanceCheckJpaRepository; - - @AfterEach - void tearDown() { - attendanceCheckJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } + @Autowired private EntityManager em; @Test @DisplayName("오늘의 한마디 작성자는 본인이 작성한 오늘의 한마디를 삭제(= soft delete) 할 수 있다.") @@ -81,6 +73,9 @@ void attendance_check_delete_test() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.data.roomId", is(room.getRoomId().intValue()))); + em.flush(); + em.clear(); + AttendanceCheckJpaEntity deleted = attendanceCheckJpaRepository.findById(ac1.getAttendanceCheckId()).orElse(null); Assertions.assertNotNull(deleted); assertThat(deleted.getStatus()).isEqualTo(INACTIVE); diff --git a/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckShowApiTest.java b/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckShowApiTest.java index 949369b07..a7e57badd 100644 --- a/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckShowApiTest.java +++ b/src/test/java/konkuk/thip/roompost/adapter/in/web/AttendanceCheckShowApiTest.java @@ -14,7 +14,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +23,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -36,6 +36,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 오늘의 한마디 조회 api 통합 테스트") class AttendanceCheckShowApiTest { @@ -50,15 +51,6 @@ class AttendanceCheckShowApiTest { @Autowired private AttendanceCheckJpaRepository attendanceCheckJpaRepository; @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - attendanceCheckJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("방의 출석체크(= 오늘의 한마디) 조회 요청하면, [오늘의 한마디 작성자 정보, 오늘의 한마디 정보] 등을 최신순으로 반환한다.") void attendance_check_show_test() throws Exception { diff --git a/src/test/java/konkuk/thip/roompost/adapter/in/web/VoteCreateApiTest.java b/src/test/java/konkuk/thip/roompost/adapter/in/web/VoteCreateApiTest.java index 3275f9b77..9c1a2054b 100644 --- a/src/test/java/konkuk/thip/roompost/adapter/in/web/VoteCreateApiTest.java +++ b/src/test/java/konkuk/thip/roompost/adapter/in/web/VoteCreateApiTest.java @@ -19,17 +19,16 @@ import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteItemJpaRepository; import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -41,6 +40,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 투표 생성 api 통합 테스트") class VoteCreateApiTest { @Autowired @@ -70,20 +70,6 @@ class VoteCreateApiTest { @Autowired private NotificationJpaRepository notificationJpaRepository; - @Autowired - private JdbcTemplate jdbcTemplate; - - @AfterEach - void tearDown() { - voteItemJpaRepository.deleteAllInBatch(); - voteJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - notificationJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - private void saveUserAndRoom() { Alias alias = TestEntityFactory.createScienceAlias(); UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(alias, "user")); diff --git a/src/test/java/konkuk/thip/roompost/application/service/VoteCreateServiceTest.java b/src/test/java/konkuk/thip/roompost/application/service/VoteCreateServiceTest.java index 2491a9723..debdbc06e 100644 --- a/src/test/java/konkuk/thip/roompost/application/service/VoteCreateServiceTest.java +++ b/src/test/java/konkuk/thip/roompost/application/service/VoteCreateServiceTest.java @@ -3,26 +3,23 @@ import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; import konkuk.thip.common.util.TestEntityFactory; -import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomParticipantJpaEntity; import konkuk.thip.room.domain.value.RoomParticipantRole; import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; import konkuk.thip.room.domain.value.Category; -import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteItemJpaRepository; -import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteJpaRepository; import konkuk.thip.roompost.application.port.in.dto.vote.VoteCreateCommand; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -31,6 +28,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 투표 생성 service 통합 테스트") class VoteCreateServiceTest { @@ -47,29 +45,9 @@ class VoteCreateServiceTest { @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; - @Autowired - private VoteJpaRepository voteJpaRepository; - - @Autowired - private VoteItemJpaRepository voteItemJpaRepository; - @Autowired private VoteCreateService voteCreateService; - @Autowired - private NotificationJpaRepository notificationJpaRepository; - - @AfterEach - void tearDown() { - voteItemJpaRepository.deleteAllInBatch(); - voteJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - notificationJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("유저가 투표를 생성하면, 해당 유저의 [RoomParticipant의 currentPage, userPercentage]와 해당 방의 [Room의 roomPercentage] 값이 변경된다.") void vote_create_room_participant_and_room_percentage_update() throws Exception { diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserDeleteApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserDeleteApiTest.java index 0a513adcd..016874078 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserDeleteApiTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserDeleteApiTest.java @@ -34,7 +34,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.application.port.UserTokenBlacklistQueryPort; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -43,6 +42,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -61,6 +61,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc @DisplayName("[통합] 회원탈퇴 api 테스트") public class UserDeleteApiTest { @@ -86,28 +87,7 @@ public class UserDeleteApiTest { @Autowired private UserTokenBlacklistQueryPort userTokenBlacklistQueryPort; @Autowired private JwtUtil jwtUtil; - @Autowired private EntityManager entityManager; - - @AfterEach - void tearDown() { - followingJpaRepository.deleteAllInBatch(); - recentSearchJpaRepository.deleteAllInBatch(); - savedFeedJpaRepository.deleteAllInBatch(); - savedBookJpaRepository.deleteAllInBatch(); - attendanceCheckJpaRepository.deleteAllInBatch(); - voteParticipantJpaRepository.deleteAllInBatch(); - commentLikeJpaRepository.deleteAllInBatch(); - commentJpaRepository.deleteAllInBatch(); - postLikeJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - recordJpaRepository.deleteAllInBatch(); - voteItemJpaRepository.deleteAllInBatch(); - voteJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - } + @Autowired private EntityManager em; @Test @DisplayName("회원탈퇴 성공시 모든 연관 엔티티가 각 엔티티 삭제 전략에 맞게 삭제되고 탈퇴한 회원의 토큰이 블랙리스트에 등록된다.") @@ -260,6 +240,9 @@ void deleteUser_success() throws Exception { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); + em.flush(); + em.clear(); + // then: 1) 유저 팔로잉/팔로워 관계 삭제 // 탈퇴한 유저1의 팔로잉/팔로워 관계는 모두 삭제되어야하고, 관련 없는 유저3->유저2 팔로우관계만 남아있어야함 // 유저2의 팔로워 수가 1이어야함 @@ -390,7 +373,6 @@ void deleteUser_success() throws Exception { // 12) 유저 soft delete (status=INACTIVE) // 탈퇴한 유저의 oauth2Id는 deleted:로 시작해야함 - entityManager.clear(); UserJpaEntity deletedUser = userJpaRepository.findById(testUser1.getUserId()).orElse(null); assertThat(deletedUser.getStatus()).isEqualTo(INACTIVE); assertThat(deletedUser.getOauth2Id()).startsWith("deleted:"); diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java index 0344345fa..1f68b4c99 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java @@ -1,14 +1,12 @@ package konkuk.thip.user.adapter.in.web; import konkuk.thip.common.util.TestEntityFactory; -import konkuk.thip.notification.adapter.out.persistence.repository.NotificationJpaRepository; import konkuk.thip.user.adapter.out.jpa.FollowingJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -17,6 +15,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.Optional; @@ -27,6 +26,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest +@Transactional @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 팔로잉 상태 변경 API 통합 테스트") @@ -37,14 +37,6 @@ class UserFollowApiTest { @Autowired private UserJpaRepository userJpaRepository; @Autowired private FollowingJpaRepository followingJpaRepository; - @Autowired private NotificationJpaRepository notificationJpaRepository; - - @AfterEach - void tearDown() { - followingJpaRepository.deleteAllInBatch(); - notificationJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } @Test @DisplayName("팔로우 요청 후 언팔로우 요청 시 엔티티가 삭제되었는지 확인한다.") diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserIsFollowingApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserIsFollowingApiTest.java index 118d5d2b0..40eb8722c 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserIsFollowingApiTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserIsFollowingApiTest.java @@ -6,7 +6,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -15,6 +14,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -22,6 +22,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 팔로잉 여부 조회 API 통합 테스트") class UserIsFollowingApiTest { @@ -35,12 +36,6 @@ class UserIsFollowingApiTest { @Autowired private FollowingJpaRepository followingJpaRepository; - @AfterEach - void tearDown() { - followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAll(); - } - @Test @DisplayName("팔로우 관계가 존재하면 true를 반환한다.") void isFollowing_true() throws Exception { diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserShowFollowingsInFeedViewApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserShowFollowingsInFeedViewApiTest.java index 95531b453..13ebd6e35 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserShowFollowingsInFeedViewApiTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserShowFollowingsInFeedViewApiTest.java @@ -10,7 +10,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,6 +18,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -32,6 +32,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 피드 조회 화면에서, 내 띱 목록 조회 api 통합 테스트") class UserShowFollowingsInFeedViewApiTest { @@ -43,14 +44,6 @@ class UserShowFollowingsInFeedViewApiTest { @Autowired private BookJpaRepository bookJpaRepository; @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - feedJpaRepository.deleteAllInBatch(); - followingJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("전체 피드 조회 화면에서, 내가 팔로잉 하는 사람들의 [userId, 닉네임, 프로필 이미지] 정보를 1.최근 공개 피드를 작성한 사람 -> 2.최근 팔로잉 맺은 사람 순으로 반환합니다.") void show_my_following_recent_writers_test() throws Exception { diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java index 77b38e3be..b2235efb4 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java @@ -8,7 +8,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +17,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import static konkuk.thip.common.exception.code.ErrorCode.API_INVALID_PARAM; import static konkuk.thip.common.exception.code.ErrorCode.AUTH_TOKEN_NOT_FOUND; @@ -30,6 +30,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc +@Transactional @DisplayName("[통합] 회원가입 api 테스트") class UserSignupControllerTest { @@ -45,11 +46,6 @@ class UserSignupControllerTest { @Autowired private JwtUtil jwtUtil; - @AfterEach - void tearDown() { - userJpaRepository.deleteAll(); - } - @Test @DisplayName("[칭호id, 닉네임] 정보를 바탕으로 회원가입을 진행한다.") void signup_success() throws Exception { diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserUpdateApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserUpdateApiTest.java index 9a929dc52..4386ab7ee 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserUpdateApiTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserUpdateApiTest.java @@ -7,7 +7,6 @@ import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -16,6 +15,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -26,6 +26,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 사용자 정보 수정 API 통합 테스트") class UserUpdateApiTest { @@ -39,11 +40,6 @@ class UserUpdateApiTest { @Autowired private UserJpaRepository userJpaRepository; - @AfterEach - void tearDown() { - userJpaRepository.deleteAll(); - } - @Test @DisplayName("사용자 닉네임과 별칭이 정상적으로 업데이트된다.") void updateUser_success() throws Exception { diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java index 99901b3d6..a6042d8f6 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java @@ -8,7 +8,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,6 +18,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -32,6 +32,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 닉네임 중복 검증 api 테스트") class UserVerifyNicknameControllerTest { @@ -45,11 +46,6 @@ class UserVerifyNicknameControllerTest { @Autowired private UserJpaRepository userJpaRepository; @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - userJpaRepository.deleteAllInBatch(); - } - @Test @DisplayName("[닉네임]값이 unique 할 경우, true를 반환한다.") void verify_nickname_true() throws Exception { From 58d9d0fa9197c975320b7341c743dfec4286afc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 00:07:48 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[refactor]=203.jwt=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=EA=B8=B0=EA=B0=84=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=B6=94=EC=B6=9C=20(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/security/util/JwtUtil.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/common/security/util/JwtUtil.java b/src/main/java/konkuk/thip/common/security/util/JwtUtil.java index df2073145..0aea6d718 100644 --- a/src/main/java/konkuk/thip/common/security/util/JwtUtil.java +++ b/src/main/java/konkuk/thip/common/security/util/JwtUtil.java @@ -25,9 +25,10 @@ public class JwtUtil { private final SecretKey secretKey; - //todo 확정 후 환경변수로 변경 - private final long tokenExpiredMs = 2592000000L; // 30일 - private final long signupTokenExpiredMs = 2592000000L; // 30일 + @Value("${jwt.access-token-expiration}") + private long tokenExpiredMs; + @Value("${jwt.signup-token-expiration}") + private long signupTokenExpiredMs; public JwtUtil(@Value("${jwt.secret}") String secret) { secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); From 604523b6a2072b73614eea7652afb3487a8d35bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 00:11:23 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[refactor]=201.=EB=A7=8C=EB=A3=8C?= =?UTF-8?q?=EB=90=9C=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?api=EC=B6=94=EA=B0=80=20=20=EC=A7=84=ED=96=89=EC=A4=91=EC=9D=B8?= =?UTF-8?q?=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20=EC=99=80?= =?UTF-8?q?=20response=EA=B0=80=20=EA=B0=99=EA=B8=B0=EB=95=8C=EB=AC=B8?= =?UTF-8?q?=EC=97=90=20=EA=B8=B0=EC=A1=B4=EC=9D=98=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=EB=B0=A9=20=EC=83=81=EC=84=B8=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EB=A5=BC=20=EB=A7=8C=EB=A3=8C=EB=90=9C=20=EB=B0=A9=20?= =?UTF-8?q?+=20=EC=A7=84=ED=96=89=EC=A4=91=EC=9D=B8=20=EB=B0=A9=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20=ED=95=98=EB=8A=94=20api?= =?UTF-8?q?=20=ED=95=98=EB=82=98=EB=A1=9C=20=ED=95=A9=EC=B9=A8=20=20(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/util/DateUtil.java | 23 +++++ .../RoomGetHomeJoinedListResponse.java | 7 +- ...omPlayingOrExpiredDetailViewResponse.java} | 2 +- .../mapper/RoomParticipantQueryMapper.java | 26 ++++- .../in/RoomShowPlayingDetailViewUseCase.java | 8 -- ...ShowPlayingOrExpiredDetailViewUseCase.java | 8 ++ .../port/out/dto/RoomParticipantQueryDto.java | 7 +- ...howPlayingOrExpiredDetailViewService.java} | 31 +++--- .../VoteQueryPersistenceAdapter.java | 4 +- .../repository/vote/VoteQueryRepository.java | 4 +- .../vote/VoteQueryRepositoryImpl.java | 10 +- .../application/port/out/VoteQueryPort.java | 4 +- ...oomPlayingOrExpiredDetailViewApiTest.java} | 95 ++++++++++++++----- 13 files changed, 160 insertions(+), 69 deletions(-) rename src/main/java/konkuk/thip/room/adapter/in/web/response/{RoomPlayingDetailViewResponse.java => RoomPlayingOrExpiredDetailViewResponse.java} (94%) delete mode 100644 src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingDetailViewUseCase.java create mode 100644 src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingOrExpiredDetailViewUseCase.java rename src/main/java/konkuk/thip/room/application/service/{RoomShowPlayingDetailViewService.java => RoomShowPlayingOrExpiredDetailViewService.java} (62%) rename src/test/java/konkuk/thip/room/adapter/in/web/{RoomPlayingDetailViewApiTest.java => RoomPlayingOrExpiredDetailViewApiTest.java} (82%) diff --git a/src/main/java/konkuk/thip/common/util/DateUtil.java b/src/main/java/konkuk/thip/common/util/DateUtil.java index 7e4f6f373..414b0a05b 100644 --- a/src/main/java/konkuk/thip/common/util/DateUtil.java +++ b/src/main/java/konkuk/thip/common/util/DateUtil.java @@ -68,6 +68,29 @@ public static String RecruitingRoomFormatAfterTime(LocalDate date) { return "마감 임박"; } + public static String RecruitingRoomFormatAfterTimeSimple(LocalDate date) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime dateTime = date.atStartOfDay(); + Duration d = Duration.between(now, dateTime); + + if (d.isNegative() || d.isZero()) { + return "??"; + } + + long days = d.toDays(); + if (days > 0) { + return days + "일"; + } + + long hours = d.toHours(); + if (hours >= 1) { + return hours + "시간"; + } + + long minutes = d.toMinutes(); + return minutes + "분"; + } + public static String formatDate(LocalDate date) { return date.format(DATE_FORMATTER); diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetHomeJoinedListResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetHomeJoinedListResponse.java index c67c97888..516ac114e 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetHomeJoinedListResponse.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetHomeJoinedListResponse.java @@ -1,5 +1,7 @@ package konkuk.thip.room.adapter.in.web.response; +import io.swagger.v3.oas.annotations.media.Schema; + import java.util.List; public record RoomGetHomeJoinedListResponse( @@ -13,7 +15,10 @@ public record JoinedRoomInfo( String bookImageUrl, String roomTitle, int memberCount, - int userPercentage + @Schema(description = "진행중인 방에서 유저의 방 진행도(ex. \"35\"), 모집중인 방은 쓰레기값이 넘어갑니다. 무시해주세요.") + int userPercentage, + @Schema(description = "모집중인 방에서 방 모집 마감일까지 남은 시간 (ex. \"3일\"), 진행중인 방은 쓰레기값이 넘어갑니다. 무시해주세요.") + String deadlineDate // 방 모집 마감일 (~일/시 형식) ) {} public static RoomGetHomeJoinedListResponse of(List roomList, String nickname, String nextCursor, boolean isLast){ diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPlayingDetailViewResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPlayingOrExpiredDetailViewResponse.java similarity index 94% rename from src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPlayingDetailViewResponse.java rename to src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPlayingOrExpiredDetailViewResponse.java index e68bc4010..aeff43eae 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPlayingDetailViewResponse.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPlayingOrExpiredDetailViewResponse.java @@ -5,7 +5,7 @@ import java.util.List; @Builder -public record RoomPlayingDetailViewResponse( +public record RoomPlayingOrExpiredDetailViewResponse( boolean isHost, Long roomId, String roomName, diff --git a/src/main/java/konkuk/thip/room/application/mapper/RoomParticipantQueryMapper.java b/src/main/java/konkuk/thip/room/application/mapper/RoomParticipantQueryMapper.java index 08fb5c95b..035059f37 100644 --- a/src/main/java/konkuk/thip/room/application/mapper/RoomParticipantQueryMapper.java +++ b/src/main/java/konkuk/thip/room/application/mapper/RoomParticipantQueryMapper.java @@ -4,11 +4,13 @@ import konkuk.thip.room.adapter.in.web.response.RoomGetHomeJoinedListResponse; import konkuk.thip.room.application.port.out.dto.RoomParticipantQueryDto; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.ReportingPolicy; import java.util.List; +import static konkuk.thip.room.domain.value.RoomStatus.IN_PROGRESS; +import static konkuk.thip.room.domain.value.RoomStatus.RECRUITING; + @Mapper( componentModel = "spring", imports = DateUtil.class, @@ -18,6 +20,24 @@ public interface RoomParticipantQueryMapper { List toHomeJoinedRoomResponse(List dtos); - @Mapping(target = "userPercentage", expression = "java(dto.userPercentage().intValue())") - RoomGetHomeJoinedListResponse.JoinedRoomInfo toJoinedRoomInfo(RoomParticipantQueryDto dto); + default RoomGetHomeJoinedListResponse.JoinedRoomInfo toJoinedRoomInfo(RoomParticipantQueryDto dto) { + int userPercentage = -1; + String deadlineDate = null; + + if (IN_PROGRESS.equals(dto.roomStatus())) { + userPercentage = dto.userPercentage().intValue(); + } else if (RECRUITING.equals(dto.roomStatus())) { + deadlineDate = DateUtil.RecruitingRoomFormatAfterTimeSimple(dto.startDate()); + } + + return new RoomGetHomeJoinedListResponse.JoinedRoomInfo( + dto.roomId(), + dto.bookImageUrl(), + dto.roomTitle(), + dto.memberCount(), + userPercentage, + deadlineDate + ); + } + } diff --git a/src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingDetailViewUseCase.java b/src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingDetailViewUseCase.java deleted file mode 100644 index abc8f2624..000000000 --- a/src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingDetailViewUseCase.java +++ /dev/null @@ -1,8 +0,0 @@ -package konkuk.thip.room.application.port.in; - -import konkuk.thip.room.adapter.in.web.response.RoomPlayingDetailViewResponse; - -public interface RoomShowPlayingDetailViewUseCase { - - RoomPlayingDetailViewResponse getPlayingRoomDetailView(Long userId, Long roomId); -} diff --git a/src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingOrExpiredDetailViewUseCase.java b/src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingOrExpiredDetailViewUseCase.java new file mode 100644 index 000000000..bea7cd0b6 --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingOrExpiredDetailViewUseCase.java @@ -0,0 +1,8 @@ +package konkuk.thip.room.application.port.in; + +import konkuk.thip.room.adapter.in.web.response.RoomPlayingOrExpiredDetailViewResponse; + +public interface RoomShowPlayingOrExpiredDetailViewUseCase { + + RoomPlayingOrExpiredDetailViewResponse getPlayingOrExpiredRoomDetailView(Long userId, Long roomId); +} diff --git a/src/main/java/konkuk/thip/room/application/port/out/dto/RoomParticipantQueryDto.java b/src/main/java/konkuk/thip/room/application/port/out/dto/RoomParticipantQueryDto.java index 052fdea03..51a8048eb 100644 --- a/src/main/java/konkuk/thip/room/application/port/out/dto/RoomParticipantQueryDto.java +++ b/src/main/java/konkuk/thip/room/application/port/out/dto/RoomParticipantQueryDto.java @@ -1,6 +1,7 @@ package konkuk.thip.room.application.port.out.dto; import com.querydsl.core.annotations.QueryProjection; +import konkuk.thip.room.domain.value.RoomStatus; import lombok.Builder; import org.springframework.util.Assert; @@ -13,8 +14,9 @@ public record RoomParticipantQueryDto( String roomTitle, Integer memberCount, Double userPercentage, - LocalDate startDate // 방 진행 시작일 -) { + LocalDate startDate, // 방 진행 시작일 + RoomStatus roomStatus + ) { @QueryProjection public RoomParticipantQueryDto { Assert.notNull(roomId, "roomId must not be null"); @@ -23,5 +25,6 @@ public record RoomParticipantQueryDto( Assert.notNull(memberCount, "memberCount must not be null"); Assert.notNull(userPercentage, "userPercentage must not be null"); Assert.notNull(startDate, "startDate must not be null"); + Assert.notNull(roomStatus, "roomStatus must not be null"); } } diff --git a/src/main/java/konkuk/thip/room/application/service/RoomShowPlayingDetailViewService.java b/src/main/java/konkuk/thip/room/application/service/RoomShowPlayingOrExpiredDetailViewService.java similarity index 62% rename from src/main/java/konkuk/thip/room/application/service/RoomShowPlayingDetailViewService.java rename to src/main/java/konkuk/thip/room/application/service/RoomShowPlayingOrExpiredDetailViewService.java index fc3ba2f07..c0351ef55 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomShowPlayingDetailViewService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomShowPlayingOrExpiredDetailViewService.java @@ -3,14 +3,13 @@ import konkuk.thip.book.application.port.out.BookCommandPort; import konkuk.thip.book.domain.Book; import konkuk.thip.common.util.DateUtil; -import konkuk.thip.room.adapter.in.web.response.RoomPlayingDetailViewResponse; -import konkuk.thip.room.application.port.in.RoomShowPlayingDetailViewUseCase; +import konkuk.thip.room.adapter.in.web.response.RoomPlayingOrExpiredDetailViewResponse; +import konkuk.thip.room.application.port.in.RoomShowPlayingOrExpiredDetailViewUseCase; import konkuk.thip.room.application.port.out.RoomCommandPort; import konkuk.thip.room.application.port.out.RoomQueryPort; import konkuk.thip.room.application.service.validator.RoomParticipantValidator; import konkuk.thip.room.domain.Room; import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; -import konkuk.thip.room.domain.RoomParticipants; import konkuk.thip.room.domain.RoomParticipant; import konkuk.thip.roompost.application.port.out.VoteQueryPort; import lombok.RequiredArgsConstructor; @@ -21,7 +20,7 @@ @Service @RequiredArgsConstructor -public class RoomShowPlayingDetailViewService implements RoomShowPlayingDetailViewUseCase { +public class RoomShowPlayingOrExpiredDetailViewService implements RoomShowPlayingOrExpiredDetailViewUseCase { private static final int TOP_PARTICIPATION_VOTES_COUNT = 3; @@ -35,7 +34,7 @@ public class RoomShowPlayingDetailViewService implements RoomShowPlayingDetailVi @Override @Transactional(readOnly = true) - public RoomPlayingDetailViewResponse getPlayingRoomDetailView(Long userId, Long roomId) { + public RoomPlayingOrExpiredDetailViewResponse getPlayingOrExpiredRoomDetailView(Long userId, Long roomId) { // 1. 해당 방의 참여자인지 조회 roomParticipantValidator.validateUserIsRoomMember(roomId, userId); @@ -44,21 +43,19 @@ public RoomPlayingDetailViewResponse getPlayingRoomDetailView(Long userId, Long Room room = roomCommandPort.getByIdOrThrow(roomId); Book book = bookCommandPort.findById(room.getBookId()); - // 2. Room과 연관된 UserRoom 조회, RoomParticipants 일급 컬렉션 생성 - // TODO. Room 도메인에 memberCount 값 추가된 후 리펙토링 - List findByRoomId = roomParticipantCommandPort.findAllByRoomId(roomId); - RoomParticipants roomParticipants = RoomParticipants.from(findByRoomId); + // 2. Room과 연관된 RoomParticipant 조회 + RoomParticipant roomParticipant = roomParticipantCommandPort.getByUserIdAndRoomIdOrThrow(userId, roomId); // 3. 투표 참여율이 가장 높은 투표 조회 - List topParticipationVotes = voteQueryPort.findTopParticipationVotesByRoom(room, TOP_PARTICIPATION_VOTES_COUNT); + List topParticipationVotes = voteQueryPort.findTopParticipationVotesByRoom(room, TOP_PARTICIPATION_VOTES_COUNT); // 4. response 구성 - return buildResponse(userId, room, book, roomParticipants, topParticipationVotes); + return buildResponse(room, book, roomParticipant, topParticipationVotes); } - private RoomPlayingDetailViewResponse buildResponse(Long userId, Room room, Book book, RoomParticipants roomParticipants, List topParticipationVotes) { - return RoomPlayingDetailViewResponse.builder() - .isHost(roomParticipants.isHostOfRoom(userId)) + private RoomPlayingOrExpiredDetailViewResponse buildResponse(Room room, Book book, RoomParticipant roomParticipant, List topParticipationVotes) { + return RoomPlayingOrExpiredDetailViewResponse.builder() + .isHost(roomParticipant.isHost()) .roomId(room.getId()) .roomName(room.getTitle()) .roomImageUrl(room.getCategory().getImageUrl()) @@ -67,13 +64,13 @@ private RoomPlayingDetailViewResponse buildResponse(Long userId, Room room, Book .progressEndDate(DateUtil.formatDate(room.getEndDate())) .category(room.getCategory().getValue()) .roomDescription(room.getDescription()) - .memberCount(roomParticipants.calculateMemberCount()) + .memberCount(room.getMemberCount()) .recruitCount(room.getRecruitCount()) .isbn(book.getIsbn()) .bookTitle(book.getTitle()) .authorName(book.getAuthorName()) - .currentPage(roomParticipants.getCurrentPageOfUser(userId)) - .userPercentage((int) roomParticipants.getUserPercentageOfUser(userId)) + .currentPage(roomParticipant.getCurrentPage()) + .userPercentage((int) roomParticipant.getUserPercentage()) .currentVotes(topParticipationVotes) .categoryColor(roomQueryPort.findAliasColorOfCategory(room.getCategory())) // TODO : 리펙토링 대상 .build(); diff --git a/src/main/java/konkuk/thip/roompost/adapter/out/persistence/VoteQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/roompost/adapter/out/persistence/VoteQueryPersistenceAdapter.java index ac86e9552..97ab89043 100644 --- a/src/main/java/konkuk/thip/roompost/adapter/out/persistence/VoteQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/roompost/adapter/out/persistence/VoteQueryPersistenceAdapter.java @@ -1,6 +1,6 @@ package konkuk.thip.roompost.adapter.out.persistence; -import konkuk.thip.room.adapter.in.web.response.RoomPlayingDetailViewResponse; +import konkuk.thip.room.adapter.in.web.response.RoomPlayingOrExpiredDetailViewResponse; import konkuk.thip.room.domain.Room; import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteJpaRepository; import konkuk.thip.roompost.application.port.out.VoteQueryPort; @@ -23,7 +23,7 @@ public class VoteQueryPersistenceAdapter implements VoteQueryPort { private final VoteJpaRepository voteJpaRepository; @Override - public List findTopParticipationVotesByRoom(Room room, int count) { + public List findTopParticipationVotesByRoom(Room room, int count) { return voteJpaRepository.findTopParticipationVotesByRoom(room.getId(), count); } diff --git a/src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/vote/VoteQueryRepository.java b/src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/vote/VoteQueryRepository.java index b659d7029..d7033890b 100644 --- a/src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/vote/VoteQueryRepository.java +++ b/src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/vote/VoteQueryRepository.java @@ -1,6 +1,6 @@ package konkuk.thip.roompost.adapter.out.persistence.repository.vote; -import konkuk.thip.room.adapter.in.web.response.RoomPlayingDetailViewResponse; +import konkuk.thip.room.adapter.in.web.response.RoomPlayingOrExpiredDetailViewResponse; import konkuk.thip.roompost.adapter.out.jpa.VoteJpaEntity; import konkuk.thip.roompost.application.port.out.dto.VoteItemQueryDto; @@ -11,7 +11,7 @@ public interface VoteQueryRepository { List findVotesByRoom(Long roomId, String type, Integer pageStart, Integer pageEnd, Long userId); - List findTopParticipationVotesByRoom(Long roomId, int count); + List findTopParticipationVotesByRoom(Long roomId, int count); List mapVoteItemsByVoteIds(Set voteIds, Long userId); diff --git a/src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/vote/VoteQueryRepositoryImpl.java b/src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/vote/VoteQueryRepositoryImpl.java index adbbeca2b..4071ba234 100644 --- a/src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/vote/VoteQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/vote/VoteQueryRepositoryImpl.java @@ -3,7 +3,7 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; -import konkuk.thip.room.adapter.in.web.response.RoomPlayingDetailViewResponse; +import konkuk.thip.room.adapter.in.web.response.RoomPlayingOrExpiredDetailViewResponse; import konkuk.thip.roompost.adapter.out.jpa.QVoteItemJpaEntity; import konkuk.thip.roompost.adapter.out.jpa.QVoteJpaEntity; import konkuk.thip.roompost.adapter.out.jpa.QVoteParticipantJpaEntity; @@ -54,7 +54,7 @@ private BooleanExpression filterByType(String type, QVoteJpaEntity post, Long us } @Override - public List findTopParticipationVotesByRoom(Long roomId, int count) { + public List findTopParticipationVotesByRoom(Long roomId, int count) { // 1. Fetch top votes by total participation count List topVotes = jpaQueryFactory .select(vote) @@ -69,16 +69,16 @@ public List findTopParticipationVotes // 2. Map to DTOs including vote items return topVotes.stream() .map(vote -> { - List voteItems = jpaQueryFactory + List voteItems = jpaQueryFactory .select(voteItem) .from(voteItem) .where(voteItem.voteJpaEntity.eq(vote)) .orderBy(voteItem.count.desc()) .fetch() .stream() - .map(item -> new RoomPlayingDetailViewResponse.CurrentVote.VoteItem(item.getItemName())) + .map(item -> new RoomPlayingOrExpiredDetailViewResponse.CurrentVote.VoteItem(item.getItemName())) .toList(); - return new RoomPlayingDetailViewResponse.CurrentVote( + return new RoomPlayingOrExpiredDetailViewResponse.CurrentVote( vote.getContent(), vote.getPage(), vote.isOverview(), diff --git a/src/main/java/konkuk/thip/roompost/application/port/out/VoteQueryPort.java b/src/main/java/konkuk/thip/roompost/application/port/out/VoteQueryPort.java index 3bb1a416e..ee68f34ec 100644 --- a/src/main/java/konkuk/thip/roompost/application/port/out/VoteQueryPort.java +++ b/src/main/java/konkuk/thip/roompost/application/port/out/VoteQueryPort.java @@ -1,6 +1,6 @@ package konkuk.thip.roompost.application.port.out; -import konkuk.thip.room.adapter.in.web.response.RoomPlayingDetailViewResponse; +import konkuk.thip.room.adapter.in.web.response.RoomPlayingOrExpiredDetailViewResponse; import konkuk.thip.room.domain.Room; import konkuk.thip.roompost.application.port.out.dto.VoteItemQueryDto; @@ -10,7 +10,7 @@ public interface VoteQueryPort { - List findTopParticipationVotesByRoom(Room room, int count); + List findTopParticipationVotesByRoom(Room room, int count); Map> findVoteItemsByVoteIds(Set voteIds, Long userId); diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingOrExpiredDetailViewApiTest.java similarity index 82% rename from src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java rename to src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingOrExpiredDetailViewApiTest.java index 90f6e4a24..86852ca31 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingOrExpiredDetailViewApiTest.java @@ -10,6 +10,7 @@ import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; import konkuk.thip.room.domain.value.Category; +import konkuk.thip.room.domain.value.RoomStatus; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; @@ -18,7 +19,6 @@ import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteItemJpaRepository; import konkuk.thip.roompost.adapter.out.persistence.repository.vote.VoteJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -27,12 +27,15 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.List; import java.util.stream.IntStream; import static konkuk.thip.common.exception.code.ErrorCode.ROOM_ACCESS_FORBIDDEN; +import static konkuk.thip.room.domain.value.RoomStatus.EXPIRED; +import static konkuk.thip.room.domain.value.RoomStatus.IN_PROGRESS; import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -40,9 +43,10 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) -@DisplayName("[통합] 진행 중인 방 상세조회 api 통합 테스트") -class RoomPlayingDetailViewApiTest { +@DisplayName("[통합] 진행 중인/완료된 방 상세조회 api 통합 테스트") +class RoomPlayingOrExpiredDetailViewApiTest { @Autowired private MockMvc mockMvc; @Autowired private UserJpaRepository userJpaRepository; @@ -52,19 +56,8 @@ class RoomPlayingDetailViewApiTest { @Autowired private VoteJpaRepository voteJpaRepository; @Autowired private VoteItemJpaRepository voteItemJpaRepository; - @AfterEach - void tearDown() { - voteItemJpaRepository.deleteAll(); - voteJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - } - - private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, int recruitCount) { - Alias alias = TestEntityFactory.createScienceAlias(); + private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String roomName, LocalDate startDate, RoomStatus roomStatus) { BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() .title(bookTitle) .isbn(isbn) @@ -85,9 +78,11 @@ private RoomJpaEntity saveScienceRoom(String bookTitle, String isbn, String room .roomPercentage(0.0) .startDate(startDate) .endDate(LocalDate.now().plusDays(30)) - .recruitCount(recruitCount) + .recruitCount(10) .bookJpaEntity(book) .category(category) + .memberCount(4) + .roomStatus(roomStatus) .build()); } @@ -145,11 +140,59 @@ private void createVoteToRoom(UserJpaEntity creator, RoomJpaEntity roomJpaEntity } } + @Test + @DisplayName("완료된 모임방 상세조회할 경우, [해당 모임방의 정보, 책 정보, 유저의 현재 활동 정보, 현재 진행중인 투표]를 반환한다.") + void get_expired_room_detail() throws Exception { + //given + RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1),EXPIRED); + saveUsersToRoom(room, 4); + RoomParticipantJpaEntity roomParticipantJpaEntity = roomParticipantJpaRepository.findAllByRoomId(room.getRoomId()).get(0); + roomParticipantJpaRepository.delete(roomParticipantJpaEntity); + RoomParticipantJpaEntity joiningMember = roomParticipantJpaRepository.save(RoomParticipantJpaEntity.builder() + .userJpaEntity(roomParticipantJpaEntity.getUserJpaEntity()) + .roomJpaEntity(roomParticipantJpaEntity.getRoomJpaEntity()) + .roomParticipantRole(RoomParticipantRole.MEMBER) // Member + .currentPage(50) // 현재 member의 마지막 활동 page + .userPercentage(10.6) // 현재 member의 활동 percentage + .build()); + + createVoteToRoom(joiningMember.getUserJpaEntity(), room, 2); // 2개의 투표 생성 + + //when + ResultActions result = mockMvc.perform(get("/rooms/{roomId}", room.getRoomId()) + .requestAttr("userId", joiningMember.getUserJpaEntity().getUserId())); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.isHost", is(false))) + .andExpect(jsonPath("$.data.roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomImageUrl", is(Category.SCIENCE_IT.getImageUrl()))) // 방 대표 이미지 추가 + .andExpect(jsonPath("$.data.progressStartDate", is(DateUtil.formatDate(LocalDate.now().plusDays(1))))) + .andExpect(jsonPath("$.data.memberCount", is(4))) + .andExpect(jsonPath("$.data.recruitCount", is(10))) + .andExpect(jsonPath("$.data.isbn", is("isbn1"))) + .andExpect(jsonPath("$.data.bookTitle", is("과학-책"))) + .andExpect(jsonPath("$.data.currentPage", is(50))) + .andExpect(jsonPath("$.data.userPercentage", is(10))) // 내림 + .andExpect(jsonPath("$.data.currentVotes", hasSize(2))) + /** + * currentVotes 검증 : 현재 모임방의 참여율이 높은 투표와 투표 항목들을 노출 + * <정렬 순서> : 투표 참여율 높은 순 (vote 2 -> vote 1 순) + */ + .andExpect(jsonPath("$.data.currentVotes[0].content", is("vote-content-2"))) + .andExpect(jsonPath("$.data.currentVotes[0].voteItems[0].itemName", is("item-2-1"))) + .andExpect(jsonPath("$.data.currentVotes[0].voteItems[1].itemName", is("item-2-2"))) + + .andExpect(jsonPath("$.data.currentVotes[1].content", is("vote-content-1"))) + .andExpect(jsonPath("$.data.currentVotes[1].voteItems[0].itemName", is("item-1-1"))) + .andExpect(jsonPath("$.data.currentVotes[1].voteItems[1].itemName", is("item-1-2"))); + } + @Test @DisplayName("진행중인 모임방 상세조회할 경우, [해당 모임방의 정보, 책 정보, 유저의 현재 활동 정보, 현재 진행중인 투표]를 반환한다.") void get_playing_room_detail() throws Exception { //given - RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1),IN_PROGRESS); saveUsersToRoom(room, 4); RoomParticipantJpaEntity roomParticipantJpaEntity = roomParticipantJpaRepository.findAllByRoomId(room.getRoomId()).get(0); roomParticipantJpaRepository.delete(roomParticipantJpaEntity); @@ -164,7 +207,7 @@ void get_playing_room_detail() throws Exception { createVoteToRoom(joiningMember.getUserJpaEntity(), room, 2); // 2개의 투표 생성 //when - ResultActions result = mockMvc.perform(get("/rooms/{roomId}/playing", room.getRoomId()) + ResultActions result = mockMvc.perform(get("/rooms/{roomId}", room.getRoomId()) .requestAttr("userId", joiningMember.getUserJpaEntity().getUserId())); //then @@ -197,7 +240,7 @@ void get_playing_room_detail() throws Exception { @DisplayName("모임방의 호스트가 조회할 경우, 유저가 해당 방의 호스트임을 응답값으로 보여준다. (나머지 응답값은 동일)") void get_playing_room_detail_host() throws Exception { //given - RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1),IN_PROGRESS); saveUsersToRoom(room, 4); RoomParticipantJpaEntity roomParticipantJpaEntity = roomParticipantJpaRepository.findAllByRoomId(room.getRoomId()).get(0); roomParticipantJpaRepository.delete(roomParticipantJpaEntity); @@ -212,7 +255,7 @@ void get_playing_room_detail_host() throws Exception { createVoteToRoom(roomHost.getUserJpaEntity(), room, 2); // 2개의 투표 생성 //when - ResultActions result = mockMvc.perform(get("/rooms/{roomId}/playing", room.getRoomId()) + ResultActions result = mockMvc.perform(get("/rooms/{roomId}", room.getRoomId()) .requestAttr("userId", roomHost.getUserJpaEntity().getUserId())); //then @@ -245,7 +288,7 @@ void get_playing_room_detail_host() throws Exception { @DisplayName("모임방에 속하지 않는 유저가 진행중인 모임방 상세조회를 요청한 경우, 400 error 발생한다.") void get_playing_room_detail_not_belong_to_room() throws Exception { //given - RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1),IN_PROGRESS); saveUsersToRoom(room, 4); RoomParticipantJpaEntity roomParticipantJpaEntity = roomParticipantJpaRepository.findAllByRoomId(room.getRoomId()).get(0); roomParticipantJpaRepository.delete(roomParticipantJpaEntity); @@ -260,7 +303,7 @@ void get_playing_room_detail_not_belong_to_room() throws Exception { createVoteToRoom(joiningMember.getUserJpaEntity(), room, 2); // 2개의 투표 생성 //when //then - mockMvc.perform(get("/rooms/{roomId}/playing", room.getRoomId()) + mockMvc.perform(get("/rooms/{roomId}", room.getRoomId()) .requestAttr("userId", 1000L)) // 방에 속하지 않는 유저 .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(ROOM_ACCESS_FORBIDDEN.getCode())) @@ -271,7 +314,7 @@ void get_playing_room_detail_not_belong_to_room() throws Exception { @DisplayName("모임방에서 진행중인 투표가 많을 경우, 참여율이 높은 순으로 최대 3개의 투표만 보여준다.") void get_playing_room_detail_too_many_votes() throws Exception { //given - RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1),IN_PROGRESS); saveUsersToRoom(room, 4); RoomParticipantJpaEntity roomParticipantJpaEntity = roomParticipantJpaRepository.findAllByRoomId(room.getRoomId()).get(0); roomParticipantJpaRepository.delete(roomParticipantJpaEntity); @@ -286,7 +329,7 @@ void get_playing_room_detail_too_many_votes() throws Exception { createVoteToRoom(joiningMember.getUserJpaEntity(), room, 6); // 6개의 투표 생성 //when - ResultActions result = mockMvc.perform(get("/rooms/{roomId}/playing", room.getRoomId()) + ResultActions result = mockMvc.perform(get("/rooms/{roomId}", room.getRoomId()) .requestAttr("userId", joiningMember.getUserJpaEntity().getUserId())); //then @@ -323,7 +366,7 @@ void get_playing_room_detail_too_many_votes() throws Exception { @DisplayName("모임방에서 진행중인 투표가 없을 경우, 빈 리스트를 보여준다.") void get_playing_room_detail_no_votes() throws Exception { //given - RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), 10); + RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1),IN_PROGRESS); saveUsersToRoom(room, 4); RoomParticipantJpaEntity roomParticipantJpaEntity = roomParticipantJpaRepository.findAllByRoomId(room.getRoomId()).get(0); roomParticipantJpaRepository.delete(roomParticipantJpaEntity); @@ -338,7 +381,7 @@ void get_playing_room_detail_no_votes() throws Exception { createVoteToRoom(joiningMember.getUserJpaEntity(), room, 0); // 투표 생성 X //when - ResultActions result = mockMvc.perform(get("/rooms/{roomId}/playing", room.getRoomId()) + ResultActions result = mockMvc.perform(get("/rooms/{roomId}", room.getRoomId()) .requestAttr("userId", joiningMember.getUserJpaEntity().getUserId())); //then From 23eb3d3537d9b125fa7af9b12242afb6ab0f24f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 00:13:27 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[refactor]=205.=20=EC=B5=9C=EA=B7=BC?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EB=90=9C=20=EB=AA=A8=EC=9E=84=EB=B0=A9=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=AA=A8=EC=A7=91=20=EB=A7=88?= =?UTF-8?q?=EA=B0=90=20=EC=9E=85=EB=B0=95=ED=95=9C=20=EB=B0=A9/=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=20=EB=B0=A9/=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EB=B0=A9=20=EC=83=9D=EC=84=B1=EB=90=9C=20=EB=B0=A9=20-->=20?= =?UTF-8?q?=EB=AA=A8=EC=A7=91=EC=A4=91=EC=9D=B8=EB=B0=A9/=EA=B3=B5?= =?UTF-8?q?=EA=B0=9C=EB=B0=A9=20=EC=A1=B0=ED=9A=8C=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=20(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/RoomQueryController.java | 36 ++++----- .../RoomGetDeadlinePopularRecentResponse.java | 25 +++++++ .../RoomGetDeadlinePopularResponse.java | 22 ------ .../RoomQueryPersistenceAdapter.java | 13 +++- .../repository/RoomQueryRepository.java | 6 +- .../repository/RoomQueryRepositoryImpl.java | 75 +++++++++++++------ .../application/mapper/RoomQueryMapper.java | 6 +- .../RoomGetDeadlinePopularRecentUseCase.java | 8 ++ .../in/RoomGetDeadlinePopularUseCase.java | 8 -- .../application/port/out/RoomQueryPort.java | 5 +- ... RoomGetDeadlinePopularRecentService.java} | 21 +++--- ... RoomGetDeadlinePopularRecentApiTest.java} | 38 ++-------- 12 files changed, 144 insertions(+), 119 deletions(-) create mode 100644 src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetDeadlinePopularRecentResponse.java delete mode 100644 src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetDeadlinePopularResponse.java create mode 100644 src/main/java/konkuk/thip/room/application/port/in/RoomGetDeadlinePopularRecentUseCase.java delete mode 100644 src/main/java/konkuk/thip/room/application/port/in/RoomGetDeadlinePopularUseCase.java rename src/main/java/konkuk/thip/room/application/service/{RoomGetDeadlinePopularService.java => RoomGetDeadlinePopularRecentService.java} (63%) rename src/test/java/konkuk/thip/room/adapter/in/web/{RoomGetDeadlinePopularApiTest.java => RoomGetDeadlinePopularRecentApiTest.java} (80%) diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java index 12c04b8ba..da9c13f2f 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java @@ -27,10 +27,10 @@ public class RoomQueryController { private final RoomVerifyPasswordUseCase roomVerifyPasswordUseCase; private final RoomShowRecruitingDetailViewUseCase roomShowRecruitingDetailViewUseCase; private final RoomGetMemberListUseCase roomGetMemberListUseCase; - private final RoomShowPlayingDetailViewUseCase roomShowPlayingDetailViewUseCase; + private final RoomShowPlayingOrExpiredDetailViewUseCase roomShowPlayingOrExpiredDetailViewUseCase; private final RoomShowMineUseCase roomShowMineUseCase; private final RoomGetBookPageUseCase roomGetBookPageUseCase; - private final RoomGetDeadlinePopularUseCase roomGetDeadlinePopularUsecase; + private final RoomGetDeadlinePopularRecentUseCase roomGetDeadlinePopularRecentUseCase; @Operation( summary = "모집중인 방 검색", @@ -41,6 +41,7 @@ public class RoomQueryController { public BaseResponse searchRecruitingRooms( @Parameter(description = "검색 키워드 (책 이름 or 방 이름)", example = "해리") @RequestParam(value = "keyword", required = false, defaultValue = "") final String keyword, @Parameter(description = "모임방 카테고리", example = "문학") @RequestParam(value = "category", required = false, defaultValue = "") final String category, + @Parameter(description = "전체검색여부 (전체 검색에 해당할때만 true로 보내주세요) ", example = "true") @RequestParam(value = "isAllCategory", required = false, defaultValue = "false") final boolean isAllCategory, @Parameter(description = "정렬 방식 (마감 임박 : deadline, 신청 인원 : memberCount)", example = "deadline") @RequestParam("sort") final String sort, @Parameter(description = "사용자가 검색어 입력을 '확정'했는지 여부 (입력 중: false, 입력 확정: true)", example = "false") @RequestParam(name = "isFinalized") final boolean isFinalized, @Parameter(description = "커서 (첫번째 요청시 : null, 다음 요청시 : 이전 요청에서 반환받은 nextCursor 값)") @@ -48,7 +49,7 @@ public BaseResponse searchRecruitingRooms( @Parameter(hidden = true) @UserId final Long userId ) { return BaseResponse.ok(roomSearchUseCase.searchRecruitingRooms( - RoomSearchQuery.of(keyword, category, sort, isFinalized, cursor, userId) + RoomSearchQuery.of(keyword, category, sort, isFinalized, cursor, userId,isAllCategory) )); } @@ -79,7 +80,7 @@ public BaseResponse getRecruitingRoomDetailVie @Operation( summary = "[모임 홈] 참여중인 내 모임방 조회", - description = "사용자가 참여중인 모임방 목록을 조회합니다." + description = "사용자가 참여중인 (모집중/진행중인 방) 모임방 목록을 조회합니다." ) @ExceptionDescription(ROOM_GET_HOME_JOINED_LIST) @GetMapping("/rooms/home/joined") @@ -105,18 +106,18 @@ public BaseResponse getRoomMemberList( return BaseResponse.ok(roomGetMemberListUseCase.getRoomMemberList(userId, roomId)); } - // 진행중인 방 상세보기 + // 진행중인/완료된 방 상세보기 @Operation( - summary = "진행중인 방 상세보기", - description = "진행중인 방의 상세 정보를 조회합니다." + summary = "진행중인/완료된 방 상세보기", + description = "진행중인/완료된 방의 상세 정보를 조회합니다." ) - @ExceptionDescription(ROOM_PLAYING_DETAIL) - @GetMapping("/rooms/{roomId}/playing") - public BaseResponse getPlayingRoomDetailView( + @ExceptionDescription(ROOM_PLAYING_OR_EXPIRED_DETAIL) + @GetMapping("/rooms/{roomId}") + public BaseResponse getPlayingOrExpiredRoomDetailView( @Parameter(hidden = true) @UserId final Long userId, @PathVariable("roomId") final Long roomId ) { - return BaseResponse.ok(roomShowPlayingDetailViewUseCase.getPlayingRoomDetailView(userId, roomId)); + return BaseResponse.ok(roomShowPlayingOrExpiredDetailViewUseCase.getPlayingOrExpiredRoomDetailView(userId, roomId)); } // 내 모임방 리스트 조회 @@ -148,16 +149,15 @@ public BaseResponse getBookPage( } @Operation( - summary = "마감 임박 및 인기 방 조회", - description = "카테고리별로 마감 임박 방과 인기 방을 조회합니다." + summary = "마감 임박/인기 방/최근 생성된 방 조회", + description = "카테고리별로 마감 임박 방, 인기 방, 최근 생성된 방을 조회합니다." ) - @ExceptionDescription(ROOM_GET_DEADLINE_POPULAR) + @ExceptionDescription(ROOM_GET_DEADLINE_POPULAR_RECENT) @GetMapping("/rooms") - public BaseResponse getDeadlineAndPopularRoomList( + public BaseResponse getDeadlineAndPopularAndRecentRoomList( @Parameter(description = "카테고리 이름 (default : 문학)", example = "과학/IT") - @RequestParam(value = "category", defaultValue = "문학") final String category, - @Parameter(hidden = true) @UserId final Long userId + @RequestParam(value = "category", defaultValue = "문학") final String category ) { - return BaseResponse.ok(roomGetDeadlinePopularUsecase.getDeadlineAndPopularRoomList(category, userId)); + return BaseResponse.ok(roomGetDeadlinePopularRecentUseCase.getDeadlineAndPopularAndRecentRoomList(category)); } } diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetDeadlinePopularRecentResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetDeadlinePopularRecentResponse.java new file mode 100644 index 000000000..372b990d2 --- /dev/null +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetDeadlinePopularRecentResponse.java @@ -0,0 +1,25 @@ +package konkuk.thip.room.adapter.in.web.response; + +import java.util.List; + +public record RoomGetDeadlinePopularRecentResponse( + List deadlineRoomList, + List popularRoomList, + List recentRoomList +) { + public record RoomGetDeadlinePopularRecentDto( + Long roomId, + String bookImageUrl, + String roomName, + int recruitCount, // 방 최대 인원 수 + int memberCount, + String deadlineDate + ) { + } + + public static RoomGetDeadlinePopularRecentResponse of(List deadlineRoomList, + List popularRoomList, + List recentRoomList) { + return new RoomGetDeadlinePopularRecentResponse(deadlineRoomList, popularRoomList, recentRoomList); + } +} diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetDeadlinePopularResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetDeadlinePopularResponse.java deleted file mode 100644 index 21d75bd2d..000000000 --- a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetDeadlinePopularResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package konkuk.thip.room.adapter.in.web.response; - -import java.util.List; - -public record RoomGetDeadlinePopularResponse( - List deadlineRoomList, - List popularRoomList -) { - public record RoomGetDeadlinePopularDto( - Long roomId, - String bookImageUrl, - String roomName, - int recruitCount, // 방 최대 인원 수 - int memberCount, - String deadlineDate - ) { - } - - public static RoomGetDeadlinePopularResponse of(List deadlineRoomList, List popularRoomList) { - return new RoomGetDeadlinePopularResponse(deadlineRoomList, popularRoomList); - } -} diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java index 237172e33..0847b867f 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java @@ -169,13 +169,18 @@ private CursorBasedList findRoomsByMemberCountCursor(Cursor cursor } @Override - public List findRoomsByCategoryOrderByDeadline(Category category, int limit, Long userId) { - return roomJpaRepository.findRoomsByCategoryOrderByStartDateAsc(category, limit, userId); + public List findRoomsByCategoryOrderByDeadline(Category category, int limit) { + return roomJpaRepository.findRoomsByCategoryOrderByStartDateAsc(category, limit); } @Override - public List findRoomsByCategoryOrderByPopular(Category category, int limit, Long userId) { - return roomJpaRepository.findRoomsByCategoryOrderByMemberCount(category, limit, userId); + public List findRoomsByCategoryOrderByPopular(Category category, int limit) { + return roomJpaRepository.findRoomsByCategoryOrderByMemberCount(category, limit); + } + + @Override + public List findRoomsByCategoryOrderByRecent(Category category, int limit) { + return roomJpaRepository.findRoomsByCategoryOrderByCreatedAtDesc(category, limit); } @Override diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java index 03af9f0b9..453d396ac 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java @@ -31,9 +31,11 @@ public interface RoomQueryRepository { List findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); - List findRoomsByCategoryOrderByStartDateAsc(Category category, int limit, Long userId); + List findRoomsByCategoryOrderByStartDateAsc(Category category, int limit); - List findRoomsByCategoryOrderByMemberCount(Category category, int limit, Long userId); + List findRoomsByCategoryOrderByMemberCount(Category category, int limit); + + List findRoomsByCategoryOrderByCreatedAtDesc(Category category, int limit); List findRoomsByIsbnOrderByStartDateAsc(String isbn, LocalDate dateCursor, Long roomIdCursor, int pageSize); } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java index c791ed28e..2e039bf51 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java @@ -24,8 +24,12 @@ import org.springframework.stereotype.Repository; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; +import static konkuk.thip.room.domain.value.RoomStatus.IN_PROGRESS; +import static konkuk.thip.room.domain.value.RoomStatus.RECRUITING; + @Repository @RequiredArgsConstructor public class RoomQueryRepositoryImpl implements RoomQueryRepository { @@ -38,7 +42,7 @@ public class RoomQueryRepositoryImpl implements RoomQueryRepository { /** 모집중 + ACTIVE 공통 where */ private BooleanBuilder recruitingActiveWhere() { BooleanBuilder where = new BooleanBuilder(); - where.and(room.roomStatus.eq(RoomStatus.RECRUITING)); + where.and(room.roomStatus.eq(RECRUITING)); return where; } @@ -170,7 +174,7 @@ public List findOtherRecruitingR .join(room.bookJpaEntity, book) .where( room.category.eq(category) - .and(room.roomStatus.eq(RoomStatus.RECRUITING)) // 모집 중인 방 + .and(room.roomStatus.eq(RECRUITING)) // 모집 중인 방 .and(room.roomId.ne(roomId))// 현재 방 제외 .and(room.isPublic.isTrue()) // 공개방 만 ) @@ -200,10 +204,12 @@ public List findHomeJoinedRoomsByUserPercentage( // 검색 조건(where) 조립 // 유저가 참여한 방만: userId 조건 - // 활동 기간 중인 방만: IN_PROGRESS 상태 + // 활동/모집 기간 중인 방만: IN_PROGRESS or RECRUITING BooleanBuilder where = new BooleanBuilder(); where.and(participant.userJpaEntity.userId.eq(userId)); - where.and(room.roomStatus.eq(RoomStatus.IN_PROGRESS)); // 활동 기간 중인 방만: IN_PROGRESS 상태 + where.and(room.roomStatus.eq(IN_PROGRESS) + .or(room.roomStatus.eq(RECRUITING))); // 활동: IN_PROGRESS 상태, 모집: RECRUITING + // 커서 기반 추가 조건 if (userPercentageCursor != null && startDateCursor != null && roomIdCursor != null) { @@ -218,6 +224,7 @@ public List findHomeJoinedRoomsByUserPercentage( ); } + //정렬 순서 : 진행중인 모임방 (진행도 높은 순 > 활동시작일 빠른 순 > 방 아이디 오름차순) > 참여한 모집중인 모임방 (활동 시작일 빠른 순 > 방 아이디 오름차순) return queryFactory .select(new QRoomParticipantQueryDto( room.roomId, @@ -225,16 +232,22 @@ public List findHomeJoinedRoomsByUserPercentage( room.title, room.memberCount, participant.userPercentage, - room.startDate + room.startDate, + room.roomStatus )) .from(participant) .join(participant.roomJpaEntity, room) .join(room.bookJpaEntity, book) .where(where) .orderBy( - participant.userPercentage.desc(), // 진행률 높은 순(내림차순) - room.startDate.asc(), // 진행률 같으면 활동 시작일 빠른 순 (오름차순) - room.roomId.asc() // 둘 다 같으면 방 아이디 작은 순 (오름차순) + // 1. 상태가 IN_PROGRESS이면 진행률 높은 순, 모집중이면 0(기본값)로 처리해 후순위로 빠지게 함 + new CaseBuilder() + .when(room.roomStatus.eq(IN_PROGRESS)) + .then(participant.userPercentage) + .otherwise((double) 0) + .desc(), + room.startDate.asc(), // 2. 활동 시작일 빠른 순 + room.roomId.asc() // 3. 방 아이디 작은 순(오름차순) ) .limit(pageSize + 1) .fetch(); @@ -246,7 +259,7 @@ public List findRecruitingRoomsUserParticipated( Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { BooleanExpression base = participant.userJpaEntity.userId.eq(userId) - .and(room.roomStatus.eq(RoomStatus.RECRUITING)); // 유저가 참여한 방 && 모집중인 방 + .and(room.roomStatus.eq(RECRUITING)); // 유저가 참여한 방 && 모집중인 방 DateExpression cursorExpr = room.startDate; // 커서 비교는 startDate(= 모집 마감일 - 1일) OrderSpecifier[] orders = new OrderSpecifier[]{ cursorExpr.asc(), room.roomId.asc() @@ -261,7 +274,7 @@ public List findPlayingRoomsUserParticipated( Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { BooleanExpression base = participant.userJpaEntity.userId.eq(userId) - .and(room.roomStatus.eq(RoomStatus.IN_PROGRESS)); // 유저가 참여한 방 && 현재 진행중인 방 + .and(room.roomStatus.eq(IN_PROGRESS)); // 유저가 참여한 방 && 현재 진행중인 방 DateExpression cursorExpr = room.endDate; // 커서 비교는 endDate(= 진행 마감일) OrderSpecifier[] orders = new OrderSpecifier[]{ cursorExpr.asc(), room.roomId.asc() @@ -275,8 +288,8 @@ public List findPlayingRoomsUserParticipated( public List findPlayingAndRecruitingRoomsUserParticipated( Long userId, Integer priorityCursor, LocalDate dateCursor, Long roomIdCursor, int pageSize ) { - BooleanExpression playing = room.roomStatus.eq(RoomStatus.IN_PROGRESS); - BooleanExpression recruiting = room.roomStatus.eq(RoomStatus.RECRUITING); + BooleanExpression playing = room.roomStatus.eq(IN_PROGRESS); + BooleanExpression recruiting = room.roomStatus.eq(RECRUITING); BooleanExpression base = participant.userJpaEntity.userId.eq(userId) .and(playing.or(recruiting)); // 유저가 참여한 방 && 현재 진행중인 방 + 모집중인 방 @@ -310,7 +323,7 @@ public List findExpiredRoomsUserParticipated( } @Override - public List findRoomsByCategoryOrderByStartDateAsc(Category category, int limit, Long userId) { + public List findRoomsByCategoryOrderByStartDateAsc(Category category, int limit) { return queryFactory .select(new QRoomQueryDto( room.roomId, @@ -323,14 +336,14 @@ public List findRoomsByCategoryOrderByStartDateAsc(Category catego )) .from(room) .join(room.bookJpaEntity, book) - .where(findDeadlinePopularRoomCondition(category, userId)) + .where(findDeadlinePopularRecentRoomCondition(category)) .orderBy(room.startDate.asc(), room.memberCount.desc(), room.roomId.asc()) .limit(limit) .fetch(); } @Override - public List findRoomsByCategoryOrderByMemberCount(Category category, int limit, Long userId) { + public List findRoomsByCategoryOrderByMemberCount(Category category, int limit) { return queryFactory .select(new QRoomQueryDto( room.roomId, @@ -343,17 +356,38 @@ public List findRoomsByCategoryOrderByMemberCount(Category categor )) .from(room) .join(room.bookJpaEntity, book) - .where(findDeadlinePopularRoomCondition(category, userId)) + .where(findDeadlinePopularRecentRoomCondition(category)) .orderBy(room.memberCount.desc(), room.startDate.asc(), room.roomId.asc()) .limit(limit) .fetch(); } + @Override + public List findRoomsByCategoryOrderByCreatedAtDesc(Category category, int limit) { + return queryFactory + .select(new QRoomQueryDto( + room.roomId, + book.imageUrl, + room.title, + room.recruitCount, + room.memberCount, + room.startDate, + room.roomStatus + )) + .from(room) + .join(room.bookJpaEntity, book) + .where(findDeadlinePopularRecentRoomCondition(category) + .and(room.createdAt.goe(LocalDateTime.now().minusHours(72)))) //생성된지 72시간 이내 + .orderBy(room.createdAt.desc(), room.roomId.desc()) + .limit(limit) + .fetch(); + } + @Override public List findRoomsByIsbnOrderByStartDateAsc(String isbn, LocalDate dateCursor, Long roomIdCursor, int pageSize) { DateExpression cursorExpr = room.startDate; // 커서 비교는 startDate(= 모집 마감일 - 1일) BooleanExpression baseCondition = room.bookJpaEntity.isbn.eq(isbn) - .and(room.roomStatus.eq(RoomStatus.RECRUITING)); // 모집중인 방 + .and(room.roomStatus.eq(RECRUITING)); // 모집중인 방 if (dateCursor != null && roomIdCursor != null) { // 첫 페이지가 아닌 경우 @@ -380,11 +414,10 @@ public List findRoomsByIsbnOrderByStartDateAsc(String isbn, LocalD .fetch(); } - private BooleanExpression findDeadlinePopularRoomCondition(Category category, Long userId) { + private BooleanExpression findDeadlinePopularRecentRoomCondition(Category category) { return room.category.eq(category) - .and(room.roomStatus.eq(RoomStatus.RECRUITING)) // 모집중인 방 - .and(room.isPublic.isTrue()) // 공개 방만 조회 - .and(userJoinedRoom(userId).not()); // 유저가 참여하지 않은 방만 조회 + .and(room.roomStatus.eq(RECRUITING)) // 모집중인 방 + .and(room.isPublic.isTrue()); // 공개 방만 조회 } /** diff --git a/src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java b/src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java index ad930e20c..d7b3df6fc 100644 --- a/src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java +++ b/src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java @@ -1,7 +1,7 @@ package konkuk.thip.room.application.mapper; import konkuk.thip.common.util.DateUtil; -import konkuk.thip.room.adapter.in.web.response.RoomGetDeadlinePopularResponse; +import konkuk.thip.room.adapter.in.web.response.RoomGetDeadlinePopularRecentResponse; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; import konkuk.thip.room.adapter.in.web.response.RoomShowMineResponse; import konkuk.thip.room.application.port.out.dto.RoomQueryDto; @@ -29,9 +29,9 @@ public interface RoomQueryMapper { target = "deadlineDate", expression = "java(DateUtil.formatAfterTime(dto.endDate()))" ) - RoomGetDeadlinePopularResponse.RoomGetDeadlinePopularDto toDeadlinePopularRoomDto(RoomQueryDto dto); + RoomGetDeadlinePopularRecentResponse.RoomGetDeadlinePopularRecentDto toDeadlinePopularRecentRoomDto(RoomQueryDto dto); - List toDeadlinePopularRoomDtoList(List roomQueryDtos); + List toDeadlinePopularRecentRoomDtoList(List roomQueryDtos); diff --git a/src/main/java/konkuk/thip/room/application/port/in/RoomGetDeadlinePopularRecentUseCase.java b/src/main/java/konkuk/thip/room/application/port/in/RoomGetDeadlinePopularRecentUseCase.java new file mode 100644 index 000000000..c5a22c283 --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/port/in/RoomGetDeadlinePopularRecentUseCase.java @@ -0,0 +1,8 @@ +package konkuk.thip.room.application.port.in; + +import konkuk.thip.room.adapter.in.web.response.RoomGetDeadlinePopularRecentResponse; + +public interface RoomGetDeadlinePopularRecentUseCase { + + RoomGetDeadlinePopularRecentResponse getDeadlineAndPopularAndRecentRoomList(String category); +} diff --git a/src/main/java/konkuk/thip/room/application/port/in/RoomGetDeadlinePopularUseCase.java b/src/main/java/konkuk/thip/room/application/port/in/RoomGetDeadlinePopularUseCase.java deleted file mode 100644 index ded5d0d12..000000000 --- a/src/main/java/konkuk/thip/room/application/port/in/RoomGetDeadlinePopularUseCase.java +++ /dev/null @@ -1,8 +0,0 @@ -package konkuk.thip.room.application.port.in; - -import konkuk.thip.room.adapter.in.web.response.RoomGetDeadlinePopularResponse; - -public interface RoomGetDeadlinePopularUseCase { - - RoomGetDeadlinePopularResponse getDeadlineAndPopularRoomList(String category, Long userId); -} diff --git a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java index 8c43d2034..c8dcf7b6f 100644 --- a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java +++ b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java @@ -36,10 +36,11 @@ public interface RoomQueryPort { CursorBasedList findRoomsByIsbnOrderByDeadline(String isbn, Cursor cursor); - List findRoomsByCategoryOrderByDeadline(Category category, int limit, Long userId); + List findRoomsByCategoryOrderByDeadline(Category category, int limit); - List findRoomsByCategoryOrderByPopular(Category category, int limit, Long userId); + List findRoomsByCategoryOrderByPopular(Category category, int limit); + List findRoomsByCategoryOrderByRecent(Category category, int limit); /** * 임시 메서드 * TODO 리펙토링 대상 diff --git a/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularService.java b/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java similarity index 63% rename from src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularService.java rename to src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java index ba6bad483..752bac44e 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java @@ -1,8 +1,8 @@ package konkuk.thip.room.application.service; -import konkuk.thip.room.adapter.in.web.response.RoomGetDeadlinePopularResponse; +import konkuk.thip.room.adapter.in.web.response.RoomGetDeadlinePopularRecentResponse; import konkuk.thip.room.application.mapper.RoomQueryMapper; -import konkuk.thip.room.application.port.in.RoomGetDeadlinePopularUseCase; +import konkuk.thip.room.application.port.in.RoomGetDeadlinePopularRecentUseCase; import konkuk.thip.room.application.port.out.RoomQueryPort; import konkuk.thip.room.domain.value.Category; import lombok.RequiredArgsConstructor; @@ -11,7 +11,7 @@ @Service @RequiredArgsConstructor -public class RoomGetDeadlinePopularService implements RoomGetDeadlinePopularUseCase { +public class RoomGetDeadlinePopularRecentService implements RoomGetDeadlinePopularRecentUseCase { private final RoomQueryPort roomQueryPort; private final RoomQueryMapper roomQueryMapper; @@ -20,14 +20,17 @@ public class RoomGetDeadlinePopularService implements RoomGetDeadlinePopularUseC @Override @Transactional(readOnly = true) - public RoomGetDeadlinePopularResponse getDeadlineAndPopularRoomList(String categoryStr, Long userId) { + public RoomGetDeadlinePopularRecentResponse getDeadlineAndPopularAndRecentRoomList(String categoryStr) { Category category = Category.from(categoryStr); - var deadlineRoomList = roomQueryMapper.toDeadlinePopularRoomDtoList( - roomQueryPort.findRoomsByCategoryOrderByDeadline(category, DEFAULT_LIMIT, userId)); - var popularRoomList = roomQueryMapper.toDeadlinePopularRoomDtoList( - roomQueryPort.findRoomsByCategoryOrderByPopular(category, DEFAULT_LIMIT, userId)); + var deadlineRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( + roomQueryPort.findRoomsByCategoryOrderByDeadline(category, DEFAULT_LIMIT)); + var popularRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( + roomQueryPort.findRoomsByCategoryOrderByPopular(category, DEFAULT_LIMIT)); + var recentRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( + roomQueryPort.findRoomsByCategoryOrderByRecent(category, DEFAULT_LIMIT)); - return RoomGetDeadlinePopularResponse.of(deadlineRoomList, popularRoomList); + return RoomGetDeadlinePopularRecentResponse.of(deadlineRoomList, popularRoomList,recentRoomList); } + } diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularRecentApiTest.java similarity index 80% rename from src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularApiTest.java rename to src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularRecentApiTest.java index d097c98b5..59025daac 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularRecentApiTest.java @@ -34,20 +34,18 @@ /** * /rooms?category=문학 API 통합 테스트 - * - 마감 임박, 인기 방 조회 - * - 내가 참여한 방은 제외 + * - 마감 임박, 인기 방, 최근생성된 방 조회 * - 비공개 방은 제외 */ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) -@DisplayName("[통합] 마감 임박 및 인기 방 조회 API 통합 테스트") +@DisplayName("[통합] 마감 임박, 인기 방, 최근 생성된 방 조회 API 통합 테스트") @Transactional -class RoomGetDeadlinePopularApiTest { +class RoomGetDeadlinePopularRecentApiTest { @Autowired private MockMvc mockMvc; @Autowired private RoomJpaRepository roomJpaRepository; - @Autowired private RoomParticipantJpaRepository participantJpaRepository; @Autowired private UserJpaRepository userJpaRepository; @Autowired private BookJpaRepository bookJpaRepository; @@ -61,7 +59,6 @@ class RoomGetDeadlinePopularApiTest { private final LocalDate today = LocalDate.now(); private RoomJpaEntity privateRoom; - private RoomJpaEntity joinedRoom; @BeforeEach void setUp() { @@ -100,19 +97,10 @@ void setUp() { privateRoom.updateRoomStatus(RoomStatus.RECRUITING); privateRoom.updateIsPublic(false); roomJpaRepository.save(privateRoom); - - // 내가 참여한 방 (조건 불만족) - joinedRoom = TestEntityFactory.createRoom(book, category); - joinedRoom.updateStartDate(today.plusDays(5)); - joinedRoom.updateRoomStatus(RoomStatus.RECRUITING); - joinedRoom = roomJpaRepository.save(joinedRoom); - participantJpaRepository.save( - TestEntityFactory.createRoomParticipant(joinedRoom, currentUser, RoomParticipantRole.MEMBER, 0) - ); } @Test - @DisplayName("카테고리별로 마감 임박 방 4개와 인기 방 4개를 조회하고 조건과 정렬을 검증한다") + @DisplayName("카테고리별로 마감 임박 방 4개와 인기 방 4개 최근생성된 방 4개를 조회하고 조건과 정렬을 검증한다") void getDeadlineAndPopularRooms() throws Exception { mockMvc.perform( get("/rooms") @@ -128,20 +116,10 @@ void getDeadlineAndPopularRooms() throws Exception { .andExpect(jsonPath("$.data.deadlineRoomList[0].bookImageUrl").isString()) .andExpect(jsonPath("$.data.deadlineRoomList[0].roomName").isString()) .andExpect(jsonPath("$.data.deadlineRoomList[0].memberCount").isNumber()) - .andExpect(jsonPath("$.data.deadlineRoomList[0].deadlineDate").isString()); - } - - @Test - @DisplayName("내가 참여한 방은 조회 결과에 포함되지 않는다") - void joinedRoomIsExcluded() throws Exception { - mockMvc.perform( - get("/rooms") - .param("category", "문학") - .requestAttr("userId", currentUser.getUserId()) - ) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.deadlineRoomList[*].roomId", not(hasItem(joinedRoom.getRoomId().intValue())))) - .andExpect(jsonPath("$.data.popularRoomList[*].roomId", not(hasItem(joinedRoom.getRoomId().intValue())))); + .andExpect(jsonPath("$.data.deadlineRoomList[0].deadlineDate").isString()) + .andExpect(jsonPath("$.data.recentRoomList", hasSize(4))) + .andExpect(jsonPath("$.data.recentRoomList[0].roomId").value(rooms.get(9).getRoomId())) //최근생성된 방은 생성된 순으로 정렬 + .andExpect(jsonPath("$.data.recentRoomList[1].roomId").value(rooms.get(8).getRoomId())); } @Test From 22e5a808241ba30f949aabc92303f12e3248fa67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 00:13:56 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[refactor]=206.=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EB=B0=A9=20=EA=B2=80=EC=83=89=EC=8B=9C=EC=97=90=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=EB=B0=A9=20=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8A=94=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94=EA=B0=80=20=20?= =?UTF-8?q?(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/dto/RoomSearchQuery.java | 7 +- .../service/RoomSearchService.java | 71 ++++++-- .../adapter/in/web/RoomSearchApiTest.java | 151 ++++++++++++------ 3 files changed, 165 insertions(+), 64 deletions(-) diff --git a/src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchQuery.java b/src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchQuery.java index 7de400e8b..cda18478e 100644 --- a/src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchQuery.java +++ b/src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchQuery.java @@ -6,10 +6,11 @@ public record RoomSearchQuery( String sortStr, boolean isFinalized, String cursorStr, - Long userId + Long userId, + boolean isAllCategory ) { public static RoomSearchQuery of (String keyword, String categoryStr, String sortStr, - boolean isFinalized, String cursorStr, Long userId) { - return new RoomSearchQuery(keyword, categoryStr, sortStr, isFinalized, cursorStr, userId); + boolean isFinalized, String cursorStr, Long userId, boolean isAllCategory) { + return new RoomSearchQuery(keyword, categoryStr, sortStr, isFinalized, cursorStr, userId, isAllCategory); } } diff --git a/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java b/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java index 849a38051..0fb7e22d1 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java @@ -1,5 +1,6 @@ package konkuk.thip.room.application.service; +import konkuk.thip.common.exception.BusinessException; import konkuk.thip.common.util.Cursor; import konkuk.thip.common.util.CursorBasedList; import konkuk.thip.recentSearch.domain.value.RecentSearchType; @@ -16,6 +17,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static konkuk.thip.common.exception.code.ErrorCode.API_INVALID_PARAM; + @Service @RequiredArgsConstructor public class RoomSearchService implements RoomSearchUseCase { @@ -31,6 +34,7 @@ public class RoomSearchService implements RoomSearchUseCase { public RoomSearchResponse searchRecruitingRooms(RoomSearchQuery query) { // 1. validation RoomSearchSortParam sortParam = RoomSearchSortParam.from(query.sortStr()); + validateSearchParams(query.keyword(),query.isAllCategory(),query.categoryStr()); Category category = validateCategory(query.categoryStr()); // 2. Cursor 생성 @@ -51,23 +55,48 @@ public RoomSearchResponse searchRecruitingRooms(RoomSearchQuery query) { } private CursorBasedList executeRecruitingRoomSearch(RoomSearchQuery query, Category category, RoomSearchSortParam sortParam, Cursor cursor) { - CursorBasedList result = null; + boolean isAllCategory = query.isAllCategory(); + String keyword = query.keyword(); + boolean isKeywordEmpty = (keyword == null || keyword.trim().isEmpty()); + + // 빈 키워드이면서 isAllCategory가 true인 경우는 전체 조회를 위해 빈 문자열을 사용하고, 그렇지 않으면 그대로 keyword를 사용 + String effectiveKeyword = isKeywordEmpty && isAllCategory ? "" : keyword; + if (category == null) { - switch (sortParam) { - case DEADLINE: - return roomQueryPort.searchRecruitingRoomsByDeadline(query.keyword(), cursor); - case MEMBER_COUNT: - return roomQueryPort.searchRecruitingRoomsByMemberCount(query.keyword(), cursor); + // 전체 카테고리 중에서 + // 1) 전체검색(isAllCategory=true)이거나 + // 2) 키워드가 비어있지 않은 경우 + // 해당 조건 모두 포함해서 키워드 기반 검색 또는 전체 검색 수행 + if (isAllCategory || !isKeywordEmpty) { + switch (sortParam) { + case DEADLINE: + return roomQueryPort.searchRecruitingRoomsByDeadline(effectiveKeyword, cursor); + case MEMBER_COUNT: + return roomQueryPort.searchRecruitingRoomsByMemberCount(effectiveKeyword, cursor); + } } } else { - switch (sortParam) { - case DEADLINE: - return roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline(query.keyword(), category, cursor); - case MEMBER_COUNT: - return roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount(query.keyword(), category, cursor); + if (isAllCategory && isKeywordEmpty) { + // isAllCategory가 true이고, 키워드가 비어있으면 + // 특정 카테고리 내에서 '전체 조회'를 의미함 즉, 키워드 없이 카테고리 필터만 적용해서 전체 방 조회 + switch (sortParam) { + case DEADLINE: + return roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline("", category, cursor); + case MEMBER_COUNT: + return roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount("", category, cursor); + } + } else if (!isAllCategory) { + // isAllCategory가 false인 경우 (전체검색 아님) + // category가 존재하고 키워드는 있거나 빈 문자열이어도 키워드 기반 조회 수행 + switch (sortParam) { + case DEADLINE: + return roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline(effectiveKeyword, category, cursor); + case MEMBER_COUNT: + return roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount(effectiveKeyword, category, cursor); + } } } - return result; + return null; } private Category validateCategory(String categoryStr) { @@ -77,4 +106,22 @@ private Category validateCategory(String categoryStr) { return Category.from(categoryStr); } + + private void validateSearchParams(String keyword, boolean isAllCategory, String categoryStr) { + boolean isKeywordEmpty = (keyword == null || keyword.trim().isEmpty()); + boolean isCategoryEmpty = (categoryStr == null || categoryStr.trim().isEmpty()); + + // 키워드와 카테고리 둘 다 없을 때 isAllCategory가 true여야 함 + if (isKeywordEmpty && isCategoryEmpty && !isAllCategory) { + throw new BusinessException(API_INVALID_PARAM, + new IllegalArgumentException("검색어와 카테고리가 없을 경우, 전체 검색을 명시하는 isAllCategory=true 옵션이 필요합니다.")); + } + + // 기존 예외 : 키워드 있는데 isAllCategory=true 이면서 특정 카테고리 존재 불가 + if (isAllCategory && !isKeywordEmpty && !isCategoryEmpty) { + throw new BusinessException(API_INVALID_PARAM, + new IllegalArgumentException("키워드가 있을 때 특정 카테고리 검색과 전체검색(isAllCategory=true)을 동시에 사용할 수 없습니다.")); + } + } + } diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java index 0a45f740f..582e60a18 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomSearchApiTest.java @@ -8,13 +8,11 @@ import konkuk.thip.recentSearch.adapter.out.persistence.repository.RecentSearchJpaRepository; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; -import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; import konkuk.thip.room.domain.value.Category; import konkuk.thip.room.domain.value.RoomStatus; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +23,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -36,6 +35,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 방 검색 api 통합 테스트") class RoomSearchApiTest { @@ -44,19 +44,9 @@ class RoomSearchApiTest { @Autowired private UserJpaRepository userJpaRepository; @Autowired private BookJpaRepository bookJpaRepository; @Autowired private RoomJpaRepository roomJpaRepository; - @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; @Autowired private RecentSearchJpaRepository recentSearchJpaRepository; @Autowired private JdbcTemplate jdbcTemplate; - @AfterEach - void tearDown() { - recentSearchJpaRepository.deleteAllInBatch(); - roomParticipantJpaRepository.deleteAllInBatch(); - roomJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - } - private RoomJpaEntity saveScienceRecruitingRoom(String bookTitle, String roomName, LocalDate startDate, LocalDate endDate) { BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBookWithBookTitle(bookTitle)); @@ -92,6 +82,106 @@ private void updateRoomMemberCount(RoomJpaEntity roomJpaEntity, int count) { ); } + + @Test + @DisplayName("isAllCategory=true, keyword 입력 x, 카테고리 선택 X -> 방 전체조회") + void search_allCategory_true_keyword_empty_category_null() throws Exception { + + //given + RoomJpaEntity science_room_1 = saveScienceRecruitingRoom("과학-책", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30)); + updateRoomMemberCount(science_room_1, 4); + + RoomJpaEntity science_room_3 = saveScienceRecruitingRoom("과학-책", "무슨방일까요??", LocalDate.now().plusDays(3), LocalDate.now().plusDays(30)); + updateRoomMemberCount(science_room_3, 2); + + RoomJpaEntity room_3 = saveLiteratureRecruitingRoom("문학-책", "방제목에-과학-포함된-문학방", LocalDate.now().plusDays(10), LocalDate.now().plusDays(30)); + updateRoomMemberCount(room_3, 6); + + RoomJpaEntity recruit_expired_room_4 = saveScienceProgressRoom("과학-책", "모집기한-지난-과학방", LocalDate.now().minusDays(1), LocalDate.now().plusDays(30)); + updateRoomMemberCount(recruit_expired_room_4, 6); + + // when + ResultActions result = mockMvc.perform(get("/rooms/search") + .requestAttr("userId", 1L) + .param("isAllCategory", "true") // 전체 카테고리 포함 + .param("sort", "deadline") + .param("isFinalized", "false")); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roomList", hasSize(3))) // 결과 리스트 크기 확인 + .andExpect(jsonPath("$.data.isLast", is(true))) + /** + * roomList 검증 : 정렬 순서, 방 검색 결과 검증 + * <정렬 순서> + * 1. 정렬 조건 + * 2. 정렬 조건이 같을 경우, roomId 기준 오름차순 정렬 (무한 스크롤 시 누락 & 중복 되는 데이터 발생하지 않도록) + */ + .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[1].roomName", is("무슨방일까요??"))) + .andExpect(jsonPath("$.data.roomList[2].roomName", is("방제목에-과학-포함된-문학방"))); + } + + @Test + @DisplayName("isAllCategory=true, keyword = [과학], 카테고리 선택 X -> 전체 방중에서 키워드 검색") + void search_allCategory_true_keyword_nonEmpty_category_null() throws Exception { + // given + RoomJpaEntity science_room_1 = saveScienceRecruitingRoom("과학-책", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30)); + updateRoomMemberCount(science_room_1, 4); + RoomJpaEntity science_room_2 = saveScienceRecruitingRoom("과학-책", "방이름입니다", LocalDate.now().plusDays(2), LocalDate.now().plusDays(30)); + updateRoomMemberCount(science_room_2, 5); + RoomJpaEntity room_3 = saveLiteratureRecruitingRoom("문학-책", "방제목에-과학-포함된-문학방", LocalDate.now().plusDays(10), LocalDate.now().plusDays(30)); + updateRoomMemberCount(room_3, 6); + + // when + ResultActions result = mockMvc.perform(get("/rooms/search") + .requestAttr("userId", 1L) + .param("keyword", "과학") // 실제 키워드 있음 + .param("isAllCategory", "true") + .param("sort", "deadline") + .param("isFinalized", "false")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roomList", hasSize(3))) + .andExpect(jsonPath("$.data.roomList[0].roomName", containsString("과학"))) + .andExpect(jsonPath("$.data.roomList[1].roomName", containsString("방이름입니다"))) + .andExpect(jsonPath("$.data.roomList[2].roomName", containsString("과학"))) + .andExpect(jsonPath("$.data.isLast", is(true))); + } + + @Test + @DisplayName("isAllCategory=true, keyword 입력 x, 카테고리 = [과학/IT] -> [과학/IT] 카테고리 내 전체 조회") + void search_allCategory_true_keyword_empty_category_given() throws Exception { + // given + RoomJpaEntity science_room_1 = saveScienceRecruitingRoom("과학-책", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30)); + updateRoomMemberCount(science_room_1, 4); + + RoomJpaEntity science_room_3 = saveScienceRecruitingRoom("과학-책", "무슨방일까요??", LocalDate.now().plusDays(3), LocalDate.now().plusDays(30)); + updateRoomMemberCount(science_room_3, 2); + + RoomJpaEntity room_3 = saveLiteratureRecruitingRoom("문학-책", "방제목에-과학-포함된-문학방", LocalDate.now().plusDays(10), LocalDate.now().plusDays(30)); + updateRoomMemberCount(room_3, 6); + + RoomJpaEntity recruit_expired_room_4 = saveScienceProgressRoom("과학-책", "모집기한-지난-과학방", LocalDate.now().minusDays(1), LocalDate.now().plusDays(30)); + updateRoomMemberCount(recruit_expired_room_4, 6); + // when + ResultActions result = mockMvc.perform(get("/rooms/search") + .requestAttr("userId", 1L) + .param("category", Category.SCIENCE_IT.getValue()) // 과학 카테고리 설정 + .param("isAllCategory", "true") + .param("sort", "deadline") + .param("isFinalized", "false")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roomList", hasSize(2))) + .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomList[1].roomName", is("무슨방일까요??"))) + .andExpect(jsonPath("$.data.isLast", is(true))); + } + + @Test @DisplayName("keyword = [과학], 카테고리 선택 X, 정렬 = [마감임박순] 일 경우, 방이름 or 책제목에 '과학'이 포함된 모집중인 방 검색 결과가 마감임박순으로 반환된다.") void search_keyword_and_sort_deadline() throws Exception { @@ -230,43 +320,6 @@ void search_category_and_sort_deadline() throws Exception { .andExpect(jsonPath("$.data.roomList[1].roomName", is("무슨방일까요??"))); } - @Test - @DisplayName("keyword 입력 x, 카테고리 입력 x, 정렬 = [마감임박순] 일 경우, DB에 존재하는 전체 방 검색 결과가 반환된다.") - void search_sort_deadline() throws Exception { - //given - RoomJpaEntity science_room_1 = saveScienceRecruitingRoom("과학-책", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30)); - updateRoomMemberCount(science_room_1, 4); - - RoomJpaEntity science_room_3 = saveScienceRecruitingRoom("과학-책", "무슨방일까요??", LocalDate.now().plusDays(3), LocalDate.now().plusDays(30)); - updateRoomMemberCount(science_room_3, 2); - - RoomJpaEntity room_3 = saveLiteratureRecruitingRoom("문학-책", "방제목에-과학-포함된-문학방", LocalDate.now().plusDays(10), LocalDate.now().plusDays(30)); - updateRoomMemberCount(room_3, 6); - - RoomJpaEntity recruit_expired_room_4 = saveScienceProgressRoom("과학-책", "모집기한-지난-과학방", LocalDate.now().minusDays(1), LocalDate.now().plusDays(30)); - updateRoomMemberCount(recruit_expired_room_4, 6); - - //when - ResultActions result = mockMvc.perform(get("/rooms/search") - .requestAttr("userId", 1L) - .param("sort", "deadline") - .param("isFinalized", String.valueOf(false))); - - //then - result.andExpect(status().isOk()) - .andExpect(jsonPath("$.data.roomList", hasSize(3))) // 결과 리스트 크기 확인 - .andExpect(jsonPath("$.data.isLast", is(true))) - /** - * roomList 검증 : 정렬 순서, 방 검색 결과 검증 - * <정렬 순서> - * 1. 정렬 조건 - * 2. 정렬 조건이 같을 경우, roomId 기준 오름차순 정렬 (무한 스크롤 시 누락 & 중복 되는 데이터 발생하지 않도록) - */ - .andExpect(jsonPath("$.data.roomList[0].roomName", is("과학-방-1일뒤-활동시작"))) - .andExpect(jsonPath("$.data.roomList[1].roomName", is("무슨방일까요??"))) - .andExpect(jsonPath("$.data.roomList[2].roomName", is("방제목에-과학-포함된-문학방"))); - } - @Test @DisplayName("keyword=[과학], category=[과학/IT], 정렬=[마감임박순] 일 경우, keyword, category 조건을 모두 만족하는 방만 반환된다.") void search_keyword_and_category() throws Exception { From 948285f6b896059d3bcd6d2a3d8137616e6c5657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 00:14:10 +0900 Subject: [PATCH 06/12] =?UTF-8?q?[refactor]=207.=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EB=B0=A9=20=EB=B8=94=EB=9D=BC=EC=9D=B8=EB=93=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20-=20=EA=B8=80=EC=9E=90=20=EC=88=98=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B3=B5=EB=B0=B1(=EB=9D=84=EC=96=B4=EC=93=B0=EA=B8=B0)?= =?UTF-8?q?=EC=9D=84=20=EC=9B=90=EB=B3=B8=20=EB=82=B4=EC=9A=A9=EA=B3=BC=20?= =?UTF-8?q?=EB=8F=99=EC=9D=BC=ED=95=98=EA=B2=8C=20=EC=B9=98=ED=99=98=20=20?= =?UTF-8?q?(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../validator/RoomPostAccessValidator.java | 28 +++++++++++++------ .../adapter/in/web/RoomPostSearchApiTest.java | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/konkuk/thip/roompost/application/service/validator/RoomPostAccessValidator.java b/src/main/java/konkuk/thip/roompost/application/service/validator/RoomPostAccessValidator.java index 705b8e8c8..5c2c80d5e 100644 --- a/src/main/java/konkuk/thip/roompost/application/service/validator/RoomPostAccessValidator.java +++ b/src/main/java/konkuk/thip/roompost/application/service/validator/RoomPostAccessValidator.java @@ -7,7 +7,7 @@ @HelperService public class RoomPostAccessValidator { - private static final String BLURRED_STRING = "여긴 못 지나가지롱~~"; + private static final String BLURRED_STRING = "태정태세문단세예성연중인명선광인효현숙경영정순헌철고순"; public void validateGroupRoomPostFilters(Integer pageStart, Integer pageEnd, Boolean isPageFilter, Boolean isOverview, int bookPageSize, double currentPercentage) { if(!isPageFilter && !isOverview) { // 어떤 필터도 적용되지 않는 경우 @@ -52,22 +52,32 @@ public String createBlurredString(String contents) { return contents; } - int originalLength = contents.length(); int blurLen = BLURRED_STRING.length(); + StringBuilder sb = new StringBuilder(contents.length()); - // 필요한 전체 반복 횟수 계산 - int repeat = originalLength / blurLen; + // 블러 문자열 인덱스 + int blurIndex = 0; - StringBuilder sb = new StringBuilder(originalLength); + for (int i = 0; i < contents.length(); i++) { + char ch = contents.charAt(i); - // 몫 만큼 반복 - for (int i = 0; i < repeat + 1; i++) { - sb.append(BLURRED_STRING); + // 특수문자/공백일 경우 그대로 append + if (Character.isWhitespace(ch) || isSpecialCharacter(ch)) { + sb.append(ch); + } else { + // 나머지 문자들은 모두 치환 + sb.append(BLURRED_STRING.charAt(blurIndex)); + blurIndex = (blurIndex + 1) % blurLen; // 순환 + } } - return sb.toString(); } + private boolean isSpecialCharacter(char ch) { + // 아스키 문자 중 문자/숫자만 제외하고 모두 특수문자 처리 예시 + return !Character.isLetterOrDigit(ch); + } + public boolean isLocked(int currentPage, int bookPageSize) { return currentPage < bookPageSize; } diff --git a/src/test/java/konkuk/thip/roompost/adapter/in/web/RoomPostSearchApiTest.java b/src/test/java/konkuk/thip/roompost/adapter/in/web/RoomPostSearchApiTest.java index 7eee4c414..d7e8571be 100644 --- a/src/test/java/konkuk/thip/roompost/adapter/in/web/RoomPostSearchApiTest.java +++ b/src/test/java/konkuk/thip/roompost/adapter/in/web/RoomPostSearchApiTest.java @@ -345,7 +345,7 @@ void searchGroupRecords_locked_content_blurred() throws Exception { String content = post.path("content").asText(); if (isLocked) { - assertThat(content).contains("여긴 못 지나가지롱~~"); + assertThat(content).contains("태정 태세문단세"); } } } From 3c89c83a53e574b5db44bf210ac56d33b577ce89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 00:14:18 +0900 Subject: [PATCH 07/12] =?UTF-8?q?[refactor]=20=EA=B4=80=EB=A0=A8=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/common/swagger/SwaggerResponseDescription.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java index 5d21b174e..223bdbef9 100644 --- a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java @@ -99,7 +99,7 @@ public enum SwaggerResponseDescription { ROOM_GET_MEMBER_LIST(new LinkedHashSet<>(Set.of( ROOM_NOT_FOUND ))), - ROOM_PLAYING_DETAIL(new LinkedHashSet<>(Set.of( + ROOM_PLAYING_OR_EXPIRED_DETAIL(new LinkedHashSet<>(Set.of( BOOK_NOT_FOUND, ROOM_NOT_FOUND, ROOM_ACCESS_FORBIDDEN @@ -109,7 +109,7 @@ public enum SwaggerResponseDescription { BOOK_NOT_FOUND, ROOM_ACCESS_FORBIDDEN ))), - ROOM_GET_DEADLINE_POPULAR(new LinkedHashSet<>(Set.of( + ROOM_GET_DEADLINE_POPULAR_RECENT(new LinkedHashSet<>(Set.of( CATEGORY_NOT_MATCH ))), CHANGE_ROOM_LIKE_STATE(new LinkedHashSet<>(Set.of( From 6eeed8a732d0a1809210c77987daf6e7bfd1272d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 00:17:21 +0900 Subject: [PATCH 08/12] =?UTF-8?q?[refactor]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=20tearDown()=EC=97=90=EC=84=9C=20@=20Tran?= =?UTF-8?q?sactional=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=95=88?= =?UTF-8?q?=EC=93=B0=EB=8A=94=20=ED=95=A8=EC=88=98,=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EA=B4=80=EA=B3=84,import=EB=AC=B8=20=EC=82=AD=EC=A0=9C(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/comment/adapter/in/web/CommentShowAllApiTest.java | 1 - .../java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java | 1 - .../adapter/in/web/RoomGetDeadlinePopularRecentApiTest.java | 2 -- .../thip/room/adapter/in/web/RoomGetMemberListApiTest.java | 1 - .../konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java | 1 - .../thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java | 1 - 6 files changed, 7 deletions(-) diff --git a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentShowAllApiTest.java b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentShowAllApiTest.java index 89e52e4a6..4b7a7637a 100644 --- a/src/test/java/konkuk/thip/comment/adapter/in/web/CommentShowAllApiTest.java +++ b/src/test/java/konkuk/thip/comment/adapter/in/web/CommentShowAllApiTest.java @@ -13,7 +13,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java index 78306cd96..0abe8ac7f 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateApiTest.java @@ -14,7 +14,6 @@ import konkuk.thip.user.domain.value.UserRole; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularRecentApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularRecentApiTest.java index 59025daac..7cfaf062d 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularRecentApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetDeadlinePopularRecentApiTest.java @@ -6,9 +6,7 @@ import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; -import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; import konkuk.thip.room.domain.value.Category; -import konkuk.thip.room.domain.value.RoomParticipantRole; import konkuk.thip.room.domain.value.RoomStatus; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java index 5132ad08d..8de71496d 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java @@ -13,7 +13,6 @@ import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.adapter.out.persistence.repository.following.FollowingJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java index 3bf245393..c700c04e0 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java @@ -14,7 +14,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java index 2c62ebd2e..9ea7461e1 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomVerifyPasswordApiTest.java @@ -12,7 +12,6 @@ import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; import konkuk.thip.user.domain.value.Alias; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From cf038d10cb1f529487567c638c47c08fc8b3dbac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 02:45:43 +0900 Subject: [PATCH 09/12] =?UTF-8?q?[refactor]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/RoomPlayingOrExpiredDetailViewApiTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingOrExpiredDetailViewApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingOrExpiredDetailViewApiTest.java index 86852ca31..4df3fb245 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingOrExpiredDetailViewApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingOrExpiredDetailViewApiTest.java @@ -144,7 +144,7 @@ private void createVoteToRoom(UserJpaEntity creator, RoomJpaEntity roomJpaEntity @DisplayName("완료된 모임방 상세조회할 경우, [해당 모임방의 정보, 책 정보, 유저의 현재 활동 정보, 현재 진행중인 투표]를 반환한다.") void get_expired_room_detail() throws Exception { //given - RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1),EXPIRED); + RoomJpaEntity room = saveScienceRoom("과학-책", "isbn1", "과학-방-지난달-활동시작", LocalDate.now().minusDays(31),EXPIRED); saveUsersToRoom(room, 4); RoomParticipantJpaEntity roomParticipantJpaEntity = roomParticipantJpaRepository.findAllByRoomId(room.getRoomId()).get(0); roomParticipantJpaRepository.delete(roomParticipantJpaEntity); @@ -165,9 +165,9 @@ void get_expired_room_detail() throws Exception { //then result.andExpect(status().isOk()) .andExpect(jsonPath("$.data.isHost", is(false))) - .andExpect(jsonPath("$.data.roomName", is("과학-방-1일뒤-활동시작"))) + .andExpect(jsonPath("$.data.roomName", is("과학-방-지난달-활동시작"))) .andExpect(jsonPath("$.data.roomImageUrl", is(Category.SCIENCE_IT.getImageUrl()))) // 방 대표 이미지 추가 - .andExpect(jsonPath("$.data.progressStartDate", is(DateUtil.formatDate(LocalDate.now().plusDays(1))))) + .andExpect(jsonPath("$.data.progressStartDate", is(DateUtil.formatDate(LocalDate.now().minusDays(31))))) .andExpect(jsonPath("$.data.memberCount", is(4))) .andExpect(jsonPath("$.data.recruitCount", is(10))) .andExpect(jsonPath("$.data.isbn", is("isbn1"))) From ca8feae0be2a556f9629a214312e7c3b0835b4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Mon, 15 Sep 2025 20:03:39 +0900 Subject: [PATCH 10/12] =?UTF-8?q?[refactor]=20=EB=A6=AC=EB=B7=B0=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=EC=88=98=EC=A0=95=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/common/exception/code/ErrorCode.java | 2 +- .../konkuk/thip/common/util/DateUtil.java | 4 +- .../RoomGetHomeJoinedListResponse.java | 6 +- .../RoomQueryPersistenceAdapter.java | 5 +- .../repository/RoomQueryRepository.java | 3 +- .../repository/RoomQueryRepositoryImpl.java | 4 +- .../mapper/RoomParticipantQueryMapper.java | 2 +- .../port/in/dto/RoomSearchMode.java | 47 ++++++++ .../application/port/out/RoomQueryPort.java | 3 +- .../RoomGetDeadlinePopularRecentService.java | 5 +- .../service/RoomSearchService.java | 103 +++++++++--------- .../RoomShowRecruitingDetailViewService.java | 2 +- 12 files changed, 121 insertions(+), 65 deletions(-) create mode 100644 src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchMode.java diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index 50f51b6d7..0628d8a6d 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -99,7 +99,7 @@ public enum ErrorCode implements ResponseCode { INVALID_ROOM_SEARCH_SORT(HttpStatus.BAD_REQUEST, 100005, "방 검색 시 정렬 조건이 잘못되었습니다."), ROOM_MEMBER_COUNT_EXCEEDED(HttpStatus.BAD_REQUEST, 100006, "방의 최대 인원 수를 초과했습니다."), ROOM_MEMBER_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 100007, "방의 인원 수가 1 이하(방장 포함)입니다."), - ROOM_IS_EXPIRED(HttpStatus.BAD_REQUEST, 100008, "방이 만료되었습니다."), + ROOM_IS_EXPIRED(HttpStatus.BAD_REQUEST, 100008, " 완료된 모임방에서는 기존 기록에 대한 조회만 가능해요."), ROOM_POST_TYPE_NOT_MATCH(HttpStatus.BAD_REQUEST, 100009, "일치하는 방 게시물 타입 이름이 없습니다. [RECORD, VOTE] 중 하나여야 합니다."), ROOM_NOT_IN_PROGRESS(HttpStatus.BAD_REQUEST, 100010, "진행 중인 방이 아닙니다."), diff --git a/src/main/java/konkuk/thip/common/util/DateUtil.java b/src/main/java/konkuk/thip/common/util/DateUtil.java index 414b0a05b..b07f47d33 100644 --- a/src/main/java/konkuk/thip/common/util/DateUtil.java +++ b/src/main/java/konkuk/thip/common/util/DateUtil.java @@ -46,7 +46,7 @@ public static String formatAfterTime(LocalDate date) { return minutes + "분 뒤"; } - public static String RecruitingRoomFormatAfterTime(LocalDate date) { + public static String recruitingRoomFormatAfterTime(LocalDate date) { LocalDateTime now = LocalDateTime.now(); LocalDateTime dateTime = date.atStartOfDay(); Duration d = Duration.between(now, dateTime); @@ -68,7 +68,7 @@ public static String RecruitingRoomFormatAfterTime(LocalDate date) { return "마감 임박"; } - public static String RecruitingRoomFormatAfterTimeSimple(LocalDate date) { + public static String recruitingRoomFormatAfterTimeSimple(LocalDate date) { LocalDateTime now = LocalDateTime.now(); LocalDateTime dateTime = date.atStartOfDay(); Duration d = Duration.between(now, dateTime); diff --git a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetHomeJoinedListResponse.java b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetHomeJoinedListResponse.java index 516ac114e..e68404d63 100644 --- a/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetHomeJoinedListResponse.java +++ b/src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetHomeJoinedListResponse.java @@ -15,9 +15,11 @@ public record JoinedRoomInfo( String bookImageUrl, String roomTitle, int memberCount, - @Schema(description = "진행중인 방에서 유저의 방 진행도(ex. \"35\"), 모집중인 방은 쓰레기값이 넘어갑니다. 무시해주세요.") + @Schema(description = "진행중인 방에서 유저의 방 진행도, 모집중인 방은 쓰레기값이 넘어갑니다. 무시해주세요.", + example = "35") int userPercentage, - @Schema(description = "모집중인 방에서 방 모집 마감일까지 남은 시간 (ex. \"3일\"), 진행중인 방은 쓰레기값이 넘어갑니다. 무시해주세요.") + @Schema(description = "모집중인 방에서 방 모집 마감일까지 남은 시간, 진행중인 방은 쓰레기값이 넘어갑니다. 무시해주세요.", + example = "3일") String deadlineDate // 방 모집 마감일 (~일/시 형식) ) {} public static RoomGetHomeJoinedListResponse of(List roomList, diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java index 0847b867f..de59242cd 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java @@ -16,6 +16,7 @@ import org.springframework.stereotype.Repository; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; @Repository @@ -179,8 +180,8 @@ public List findRoomsByCategoryOrderByPopular(Category category, i } @Override - public List findRoomsByCategoryOrderByRecent(Category category, int limit) { - return roomJpaRepository.findRoomsByCategoryOrderByCreatedAtDesc(category, limit); + public List findRoomsByCategoryOrderByRecent(Category category, LocalDateTime now, int limit) { + return roomJpaRepository.findRoomsByCategoryOrderByCreatedAtDesc(category, now, limit); } @Override diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java index 453d396ac..3a8893c07 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java @@ -5,6 +5,7 @@ import konkuk.thip.room.application.port.out.dto.RoomQueryDto; import konkuk.thip.room.domain.value.Category; +import java.time.LocalDateTime; import java.util.List; import java.time.LocalDate; @@ -35,7 +36,7 @@ public interface RoomQueryRepository { List findRoomsByCategoryOrderByMemberCount(Category category, int limit); - List findRoomsByCategoryOrderByCreatedAtDesc(Category category, int limit); + List findRoomsByCategoryOrderByCreatedAtDesc(Category category, LocalDateTime now, int limit); List findRoomsByIsbnOrderByStartDateAsc(String isbn, LocalDate dateCursor, Long roomIdCursor, int pageSize); } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java index 2e039bf51..79b9f1287 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java @@ -363,7 +363,7 @@ public List findRoomsByCategoryOrderByMemberCount(Category categor } @Override - public List findRoomsByCategoryOrderByCreatedAtDesc(Category category, int limit) { + public List findRoomsByCategoryOrderByCreatedAtDesc(Category category, LocalDateTime now, int limit) { return queryFactory .select(new QRoomQueryDto( room.roomId, @@ -377,7 +377,7 @@ public List findRoomsByCategoryOrderByCreatedAtDesc(Category categ .from(room) .join(room.bookJpaEntity, book) .where(findDeadlinePopularRecentRoomCondition(category) - .and(room.createdAt.goe(LocalDateTime.now().minusHours(72)))) //생성된지 72시간 이내 + .and(room.createdAt.goe(now.minusHours(72)))) .orderBy(room.createdAt.desc(), room.roomId.desc()) .limit(limit) .fetch(); diff --git a/src/main/java/konkuk/thip/room/application/mapper/RoomParticipantQueryMapper.java b/src/main/java/konkuk/thip/room/application/mapper/RoomParticipantQueryMapper.java index 035059f37..7a9421d44 100644 --- a/src/main/java/konkuk/thip/room/application/mapper/RoomParticipantQueryMapper.java +++ b/src/main/java/konkuk/thip/room/application/mapper/RoomParticipantQueryMapper.java @@ -27,7 +27,7 @@ default RoomGetHomeJoinedListResponse.JoinedRoomInfo toJoinedRoomInfo(RoomPartic if (IN_PROGRESS.equals(dto.roomStatus())) { userPercentage = dto.userPercentage().intValue(); } else if (RECRUITING.equals(dto.roomStatus())) { - deadlineDate = DateUtil.RecruitingRoomFormatAfterTimeSimple(dto.startDate()); + deadlineDate = DateUtil.recruitingRoomFormatAfterTimeSimple(dto.startDate()); } return new RoomGetHomeJoinedListResponse.JoinedRoomInfo( diff --git a/src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchMode.java b/src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchMode.java new file mode 100644 index 000000000..e66c5c9dd --- /dev/null +++ b/src/main/java/konkuk/thip/room/application/port/in/dto/RoomSearchMode.java @@ -0,0 +1,47 @@ +package konkuk.thip.room.application.port.in.dto; + +import konkuk.thip.room.domain.value.Category; + +public enum RoomSearchMode{ + GLOBAL_BY_KEYWORD_OR_ALL, // 카테고리 없음: 전체 또는 키워드 기반 + CATEGORY_ALL, // 카테고리 있음 + 전체조회 (키워드 비어있고 isAllCategory=true) + CATEGORY_BY_KEYWORD; // 카테고리 있음 + 키워드 기반(또는 전체가 아님) + + public static RoomSearchMode determineSearchMode(Category category, boolean isAllCategory, String keyword) { + final boolean isKeywordEmpty = isEmpty(keyword); + + if (category == null) { + // 카테고리 없음 → 전체 또는 키워드 기반 + // 유효성 검증에서 이미 조합을 보장하므로 그대로 처리 + return RoomSearchMode.GLOBAL_BY_KEYWORD_OR_ALL; + } + + // 카테고리 있음 + if (isAllCategory && isKeywordEmpty) { + return RoomSearchMode.CATEGORY_ALL; + } + return RoomSearchMode.CATEGORY_BY_KEYWORD; + } + + public static String resolveEffectiveKeyword(RoomSearchMode mode, String keyword) { + final boolean isKeywordEmpty = isEmpty(keyword); + return switch (mode) { + case GLOBAL_BY_KEYWORD_OR_ALL -> + // 전체(isAllCategory=true)이며 키워드 비어있을 수 있으므로 빈 문자열 허용 + isKeywordEmpty ? "" : keyword.trim(); + case CATEGORY_ALL -> + // 카테고리 전체 조회: 키워드 강제 빈 문자열 + ""; + case CATEGORY_BY_KEYWORD -> + // 키워드가 비어있을 수도 있지만, 의미적으로 "전체가 아님"이므로 + // 저장 계층에서 빈 문자열이면 전체와 동일해질 수 있음 → 그대로 전달(정책 유지) + isKeywordEmpty ? "" : keyword.trim(); + }; + } + + public static boolean isEmpty(String s) { + return s == null || s.trim().isEmpty(); + } +} + + diff --git a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java index c8dcf7b6f..c3c347f71 100644 --- a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java +++ b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java @@ -8,6 +8,7 @@ import konkuk.thip.room.domain.Room; import konkuk.thip.room.domain.value.Category; +import java.time.LocalDateTime; import java.util.List; public interface RoomQueryPort { @@ -40,7 +41,7 @@ public interface RoomQueryPort { List findRoomsByCategoryOrderByPopular(Category category, int limit); - List findRoomsByCategoryOrderByRecent(Category category, int limit); + List findRoomsByCategoryOrderByRecent(Category category, LocalDateTime now, int limit); /** * 임시 메서드 * TODO 리펙토링 대상 diff --git a/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java b/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java index 752bac44e..2e893d5bd 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java @@ -9,6 +9,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor public class RoomGetDeadlinePopularRecentService implements RoomGetDeadlinePopularRecentUseCase { @@ -22,13 +24,14 @@ public class RoomGetDeadlinePopularRecentService implements RoomGetDeadlinePopul @Transactional(readOnly = true) public RoomGetDeadlinePopularRecentResponse getDeadlineAndPopularAndRecentRoomList(String categoryStr) { Category category = Category.from(categoryStr); + LocalDateTime now = LocalDateTime.now(); var deadlineRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( roomQueryPort.findRoomsByCategoryOrderByDeadline(category, DEFAULT_LIMIT)); var popularRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( roomQueryPort.findRoomsByCategoryOrderByPopular(category, DEFAULT_LIMIT)); var recentRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( - roomQueryPort.findRoomsByCategoryOrderByRecent(category, DEFAULT_LIMIT)); + roomQueryPort.findRoomsByCategoryOrderByRecent(category, now, DEFAULT_LIMIT)); return RoomGetDeadlinePopularRecentResponse.of(deadlineRoomList, popularRoomList,recentRoomList); } diff --git a/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java b/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java index 0fb7e22d1..27bfa01ac 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java @@ -7,6 +7,7 @@ import konkuk.thip.recentSearch.application.service.manager.RecentSearchCreateManager; import konkuk.thip.room.adapter.in.web.response.RoomSearchResponse; import konkuk.thip.room.application.mapper.RoomQueryMapper; +import konkuk.thip.room.application.port.in.dto.RoomSearchMode; import konkuk.thip.room.application.port.in.dto.RoomSearchSortParam; import konkuk.thip.room.application.port.in.RoomSearchUseCase; import konkuk.thip.room.application.port.in.dto.RoomSearchQuery; @@ -30,23 +31,29 @@ public class RoomSearchService implements RoomSearchUseCase { private final RoomQueryMapper roomQueryMapper; @Override - @Transactional // <- 최근 검색 저장으로 인한 트랜잭션 + @Transactional public RoomSearchResponse searchRecruitingRooms(RoomSearchQuery query) { - // 1. validation + // 1) 파라미터 파싱/검증 RoomSearchSortParam sortParam = RoomSearchSortParam.from(query.sortStr()); - validateSearchParams(query.keyword(),query.isAllCategory(),query.categoryStr()); + validateSearchParams(query.keyword(), query.isAllCategory(), query.categoryStr()); Category category = validateCategory(query.categoryStr()); - // 2. Cursor 생성 + // 2) 검색 모드/키워드 결정 + RoomSearchMode mode = RoomSearchMode.determineSearchMode(category, query.isAllCategory(), query.keyword()); + String effectiveKeyword = RoomSearchMode.resolveEffectiveKeyword(mode, query.keyword()); + + // 3) 커서 생성 Cursor cursor = Cursor.from(query.cursorStr(), DEFAULT_PAGE_SIZE); - // 3. 방 검색 - CursorBasedList result = executeRecruitingRoomSearch(query, category, sortParam, cursor); + // 4) 실행 (정렬 기준별 단일 switch) + CursorBasedList result = executeSearchMode(mode, sortParam, effectiveKeyword, category, cursor); - // 4. 검색 완료일 경우, 최근 검색어 저장 - recentSearchCreateManager.saveRecentSearchByUser(query.userId(), query.keyword(), RecentSearchType.ROOM_SEARCH, query.isFinalized()); + // 5) 최근 검색어 저장 + recentSearchCreateManager.saveRecentSearchByUser( + query.userId(), query.keyword(), RecentSearchType.ROOM_SEARCH, query.isFinalized() + ); - // 5. response 구성 + // 6) 응답 매핑 return new RoomSearchResponse( roomQueryMapper.toRoomSearchResponse(result.contents()), result.nextCursor(), @@ -54,49 +61,43 @@ public RoomSearchResponse searchRecruitingRooms(RoomSearchQuery query) { ); } - private CursorBasedList executeRecruitingRoomSearch(RoomSearchQuery query, Category category, RoomSearchSortParam sortParam, Cursor cursor) { - boolean isAllCategory = query.isAllCategory(); - String keyword = query.keyword(); - boolean isKeywordEmpty = (keyword == null || keyword.trim().isEmpty()); + private CursorBasedList executeSearchMode( + RoomSearchMode mode, + RoomSearchSortParam sort, + String keyword, + Category category, + Cursor cursor + ) { + return switch (sort) { + case DEADLINE -> executeByDeadline(mode, keyword, category, cursor); + case MEMBER_COUNT -> executeByMemberCount(mode, keyword, category, cursor); + default -> throw new BusinessException( + API_INVALID_PARAM, + new IllegalArgumentException("지원하지 않는 정렬 기준입니다: " + sort) + ); + }; + } - // 빈 키워드이면서 isAllCategory가 true인 경우는 전체 조회를 위해 빈 문자열을 사용하고, 그렇지 않으면 그대로 keyword를 사용 - String effectiveKeyword = isKeywordEmpty && isAllCategory ? "" : keyword; - - if (category == null) { - // 전체 카테고리 중에서 - // 1) 전체검색(isAllCategory=true)이거나 - // 2) 키워드가 비어있지 않은 경우 - // 해당 조건 모두 포함해서 키워드 기반 검색 또는 전체 검색 수행 - if (isAllCategory || !isKeywordEmpty) { - switch (sortParam) { - case DEADLINE: - return roomQueryPort.searchRecruitingRoomsByDeadline(effectiveKeyword, cursor); - case MEMBER_COUNT: - return roomQueryPort.searchRecruitingRoomsByMemberCount(effectiveKeyword, cursor); - } - } - } else { - if (isAllCategory && isKeywordEmpty) { - // isAllCategory가 true이고, 키워드가 비어있으면 - // 특정 카테고리 내에서 '전체 조회'를 의미함 즉, 키워드 없이 카테고리 필터만 적용해서 전체 방 조회 - switch (sortParam) { - case DEADLINE: - return roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline("", category, cursor); - case MEMBER_COUNT: - return roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount("", category, cursor); - } - } else if (!isAllCategory) { - // isAllCategory가 false인 경우 (전체검색 아님) - // category가 존재하고 키워드는 있거나 빈 문자열이어도 키워드 기반 조회 수행 - switch (sortParam) { - case DEADLINE: - return roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline(effectiveKeyword, category, cursor); - case MEMBER_COUNT: - return roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount(effectiveKeyword, category, cursor); - } - } - } - return null; + private CursorBasedList executeByDeadline( + RoomSearchMode mode, String keyword, Category category, Cursor cursor + ) { + return switch (mode) { + case GLOBAL_BY_KEYWORD_OR_ALL -> roomQueryPort.searchRecruitingRoomsByDeadline(keyword, cursor); + case CATEGORY_ALL -> roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline("", category, cursor); + case CATEGORY_BY_KEYWORD -> + roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline(keyword, category, cursor); + }; + } + + private CursorBasedList executeByMemberCount( + RoomSearchMode mode, String keyword, Category category, Cursor cursor + ) { + return switch (mode) { + case GLOBAL_BY_KEYWORD_OR_ALL -> roomQueryPort.searchRecruitingRoomsByMemberCount(keyword, cursor); + case CATEGORY_ALL -> roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount("", category, cursor); + case CATEGORY_BY_KEYWORD -> + roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount(keyword, category, cursor); + }; } private Category validateCategory(String categoryStr) { diff --git a/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java b/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java index 5600c619b..2897ef2af 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomShowRecruitingDetailViewService.java @@ -64,7 +64,7 @@ private RoomRecruitingDetailViewResponse buildResponse( .isPublic(room.isPublic()) .progressStartDate(DateUtil.formatDate(room.getStartDate())) .progressEndDate(DateUtil.formatDate(room.getEndDate())) - .recruitEndDate(DateUtil.RecruitingRoomFormatAfterTime(room.getStartDate())) + .recruitEndDate(DateUtil.recruitingRoomFormatAfterTime(room.getStartDate())) .category(room.getCategory().getValue()) .categoryColor(roomQueryPort.findAliasColorOfCategory(room.getCategory())) // TODO : 리펙토링 대상 .roomDescription(room.getDescription()) From c987296e7e07712ca6e223eff8e865dae3be913f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Tue, 16 Sep 2025 21:56:04 +0900 Subject: [PATCH 11/12] =?UTF-8?q?[refactor]=20=EB=A6=AC=EB=B7=B0=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=EC=88=98=EC=A0=95=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/RoomQueryPersistenceAdapter.java | 4 ++-- .../out/persistence/repository/RoomQueryRepository.java | 2 +- .../persistence/repository/RoomQueryRepositoryImpl.java | 8 +++++--- .../thip/room/application/port/out/RoomQueryPort.java | 2 +- .../service/RoomGetDeadlinePopularRecentService.java | 4 +++- .../thip/room/application/service/RoomSearchService.java | 8 ++------ 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java index de59242cd..b706a517b 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java @@ -180,8 +180,8 @@ public List findRoomsByCategoryOrderByPopular(Category category, i } @Override - public List findRoomsByCategoryOrderByRecent(Category category, LocalDateTime now, int limit) { - return roomJpaRepository.findRoomsByCategoryOrderByCreatedAtDesc(category, now, limit); + public List findRoomsByCategoryOrderByRecent(Category category, LocalDateTime createdAfter, int limit) { + return roomJpaRepository.findRoomsByCategoryOrderByCreatedAtDesc(category, createdAfter, limit); } @Override diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java index 3a8893c07..cd93198e8 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java @@ -36,7 +36,7 @@ public interface RoomQueryRepository { List findRoomsByCategoryOrderByMemberCount(Category category, int limit); - List findRoomsByCategoryOrderByCreatedAtDesc(Category category, LocalDateTime now, int limit); + List findRoomsByCategoryOrderByCreatedAtDesc(Category category, LocalDateTime createdAfter, int limit); List findRoomsByIsbnOrderByStartDateAsc(String isbn, LocalDate dateCursor, Long roomIdCursor, int pageSize); } diff --git a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java index 79b9f1287..3ae7fe5d3 100644 --- a/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java @@ -363,7 +363,7 @@ public List findRoomsByCategoryOrderByMemberCount(Category categor } @Override - public List findRoomsByCategoryOrderByCreatedAtDesc(Category category, LocalDateTime now, int limit) { + public List findRoomsByCategoryOrderByCreatedAtDesc(Category category, LocalDateTime createdAfter, int limit) { return queryFactory .select(new QRoomQueryDto( room.roomId, @@ -376,8 +376,10 @@ public List findRoomsByCategoryOrderByCreatedAtDesc(Category categ )) .from(room) .join(room.bookJpaEntity, book) - .where(findDeadlinePopularRecentRoomCondition(category) - .and(room.createdAt.goe(now.minusHours(72)))) + .where( + findDeadlinePopularRecentRoomCondition(category) + .and(room.createdAt.goe(createdAfter)) + ) .orderBy(room.createdAt.desc(), room.roomId.desc()) .limit(limit) .fetch(); diff --git a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java index c3c347f71..d0961035f 100644 --- a/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java +++ b/src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java @@ -41,7 +41,7 @@ public interface RoomQueryPort { List findRoomsByCategoryOrderByPopular(Category category, int limit); - List findRoomsByCategoryOrderByRecent(Category category, LocalDateTime now, int limit); + List findRoomsByCategoryOrderByRecent(Category category, LocalDateTime createdAfter, int limit); /** * 임시 메서드 * TODO 리펙토링 대상 diff --git a/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java b/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java index 2e893d5bd..2b7be4ae2 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomGetDeadlinePopularRecentService.java @@ -19,19 +19,21 @@ public class RoomGetDeadlinePopularRecentService implements RoomGetDeadlinePopul private final RoomQueryMapper roomQueryMapper; private static final int DEFAULT_LIMIT = 4; + public static final long RECENT_HOURS = 72; @Override @Transactional(readOnly = true) public RoomGetDeadlinePopularRecentResponse getDeadlineAndPopularAndRecentRoomList(String categoryStr) { Category category = Category.from(categoryStr); LocalDateTime now = LocalDateTime.now(); + LocalDateTime recentCutoff = now.minusHours(RECENT_HOURS); var deadlineRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( roomQueryPort.findRoomsByCategoryOrderByDeadline(category, DEFAULT_LIMIT)); var popularRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( roomQueryPort.findRoomsByCategoryOrderByPopular(category, DEFAULT_LIMIT)); var recentRoomList = roomQueryMapper.toDeadlinePopularRecentRoomDtoList( - roomQueryPort.findRoomsByCategoryOrderByRecent(category, now, DEFAULT_LIMIT)); + roomQueryPort.findRoomsByCategoryOrderByRecent(category, recentCutoff, DEFAULT_LIMIT)); return RoomGetDeadlinePopularRecentResponse.of(deadlineRoomList, popularRoomList,recentRoomList); } diff --git a/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java b/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java index 27bfa01ac..268251e62 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomSearchService.java @@ -83,9 +83,7 @@ private CursorBasedList executeByDeadline( ) { return switch (mode) { case GLOBAL_BY_KEYWORD_OR_ALL -> roomQueryPort.searchRecruitingRoomsByDeadline(keyword, cursor); - case CATEGORY_ALL -> roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline("", category, cursor); - case CATEGORY_BY_KEYWORD -> - roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline(keyword, category, cursor); + case CATEGORY_ALL, CATEGORY_BY_KEYWORD -> roomQueryPort.searchRecruitingRoomsWithCategoryByDeadline(keyword, category, cursor); }; } @@ -94,9 +92,7 @@ private CursorBasedList executeByMemberCount( ) { return switch (mode) { case GLOBAL_BY_KEYWORD_OR_ALL -> roomQueryPort.searchRecruitingRoomsByMemberCount(keyword, cursor); - case CATEGORY_ALL -> roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount("", category, cursor); - case CATEGORY_BY_KEYWORD -> - roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount(keyword, category, cursor); + case CATEGORY_ALL, CATEGORY_BY_KEYWORD -> roomQueryPort.searchRecruitingRoomsWithCategoryByMemberCount(keyword, category, cursor); }; } From 0597611942e3daca82580ac8be6fb427b286474e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Tue, 16 Sep 2025 22:12:36 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[refactor]=20=EA=B3=B5=EB=B0=B1=EC=9D=BC?= =?UTF-8?q?=20=EA=B2=BD=EC=9A=B0(=EC=A0=84=EC=B2=B4=EA=B2=80=EC=83=89)=20?= =?UTF-8?q?=EC=B5=9C=EA=B7=BC=EA=B2=80=EC=83=89=EC=96=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20x=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manager/RecentSearchCreateManager.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java b/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java index 111e3db1a..963d38632 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java +++ b/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java @@ -15,15 +15,15 @@ public class RecentSearchCreateManager { private final RecentSearchQueryPort recentSearchQueryPort; public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type, boolean isFinalized) { - // 검색완료일 경우에 최근검색어 추가 - if (isFinalized) { - // 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인 - recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId, type) - .ifPresentOrElse( - recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신 - () -> recentSearchCommandPort.save(RecentSearch.withoutId(keyword, type, userId)) // 없으면 새로 저장 - ); - } + if (!isFinalized) return; // 검색완료일 경우에 최근검색어 추가 + if (keyword == null || keyword.trim().isEmpty()) return; + String normalized = keyword.trim(); + // 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인 + recentSearchQueryPort.findRecentSearchByKeywordAndUserId(normalized, userId, type) + .ifPresentOrElse( + recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신 + () -> recentSearchCommandPort.save(RecentSearch.withoutId(normalized, type, userId)) // 없으면 새로 저장 + ); } }