-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 책 검색 시 사용자 최근 검색어 로직 추가 #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
932624d
b6b33cc
7dfc39b
a13f097
9c24c97
66ba6d4
8ffc765
110f487
27f5dfd
993d800
80fc5e9
1e3263b
b63518d
239815b
c2bb000
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,35 @@ | ||
| package konkuk.thip.recentSearch.adapter.out.persistence; | ||
|
|
||
| import konkuk.thip.common.exception.EntityNotFoundException; | ||
| import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; | ||
| import konkuk.thip.recentSearch.adapter.out.mapper.RecentSearchMapper; | ||
| import konkuk.thip.recentSearch.application.port.out.RecentSearchCommandPort; | ||
| import konkuk.thip.recentSearch.domain.RecentSearch; | ||
| import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; | ||
| import konkuk.thip.user.adapter.out.persistence.UserJpaRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import static konkuk.thip.common.exception.code.ErrorCode.USER_NOT_FOUND; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class RecentSearchCommandPersistenceAdapter implements RecentSearchCommandPort { | ||
|
|
||
| private final RecentSearchJpaRepository recentSearchJpaRepository; | ||
| private final UserJpaRepository userJpaRepository; | ||
|
|
||
| private final RecentSearchMapper recentSearchMapper; | ||
|
|
||
| @Override | ||
| public void save(Long userId,RecentSearch recentSearch) { | ||
|
|
||
| UserJpaEntity userJpaEntity = userJpaRepository.findById(userId) | ||
| .orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND)); | ||
|
|
||
| RecentSearchJpaEntity recentSearchJpaEntity = | ||
| recentSearchMapper.toJpaEntity(recentSearch, userJpaEntity); | ||
|
|
||
| recentSearchJpaRepository.save(recentSearchJpaEntity); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| package konkuk.thip.recentSearch.application.port.out; | ||
|
|
||
|
|
||
| public interface RecentSearchCommandPort { | ||
| import konkuk.thip.recentSearch.domain.RecentSearch; | ||
|
|
||
| public interface RecentSearchCommandPort { | ||
| void save(Long userId, RecentSearch recentSearch); | ||
|
Comment on lines
+6
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain메서드 시그니처 개선으로 도메인 객체 활용 이전의 개별 파라미터들(
다만, 다음 스크립트로 🏁 Script executed: #!/bin/bash
# RecentSearch 도메인 객체의 구조 확인
ast-grep --pattern $'class RecentSearch {
$$$
}'Length of output: 51 🏁 Script executed: #!/bin/bash
# RecentSearch 도메인 객체 정의 위치 및 구조 확인
file=$(find . -type f -name "RecentSearch.java" | head -n 1)
if [ -z "$file" ]; then
echo "RecentSearch.java 파일을 찾을 수 없습니다."
else
echo "==> $file <=="
sed -n '1,200p' "$file"
fiLength of output: 785 메서드 시그니처 간소화 제안
🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,17 @@ | ||
| package konkuk.thip.book.adapter.in.web; | ||
|
|
||
| import konkuk.thip.common.exception.code.ErrorCode; | ||
| import konkuk.thip.common.security.util.JwtUtil; | ||
| import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; | ||
| import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; | ||
| import konkuk.thip.recentSearch.adapter.out.persistence.RecentSearchJpaRepository; | ||
| import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; | ||
| import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; | ||
| import konkuk.thip.user.adapter.out.jpa.UserRole; | ||
| import konkuk.thip.user.adapter.out.persistence.AliasJpaRepository; | ||
| import konkuk.thip.user.adapter.out.persistence.UserJpaRepository; | ||
| import org.junit.jupiter.api.AfterEach; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
|
|
@@ -10,25 +21,79 @@ | |
| import org.springframework.test.web.servlet.MockMvc; | ||
| import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||
|
|
||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThat; | ||
| import static org.hamcrest.Matchers.*; | ||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; | ||
|
|
||
| @SpringBootTest | ||
| @AutoConfigureMockMvc(addFilters = false) | ||
| @AutoConfigureMockMvc | ||
| @ActiveProfiles("test") | ||
| class BookQueryControllerTest { | ||
|
|
||
| @Autowired | ||
| private MockMvc mockMvc; | ||
|
|
||
| @Autowired | ||
| private UserJpaRepository userJpaRepository; | ||
|
|
||
| @Autowired | ||
| private AliasJpaRepository aliasJpaRepository; | ||
|
|
||
| @Autowired | ||
| private RecentSearchJpaRepository recentSearchJpaRepository; | ||
|
|
||
| @Autowired | ||
| private JwtUtil jwtUtil; | ||
|
|
||
| private String testToken; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
|
|
||
| AliasJpaEntity alias = aliasJpaRepository.save(AliasJpaEntity.builder() | ||
| .value("책벌레") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📖🐜 |
||
| .color("blue") | ||
| .imageUrl("http://image.url") | ||
| .build()); | ||
|
|
||
| UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder() | ||
| .oauth2Id("kakao_432708231") | ||
| .nickname("User1") | ||
| .imageUrl("https://avatar1.jpg") | ||
| .role(UserRole.USER) | ||
| .aliasForUserJpaEntity(alias) | ||
| .build()); | ||
|
|
||
| RecentSearchJpaEntity recentSearch = recentSearchJpaRepository.save(RecentSearchJpaEntity.builder() | ||
| .searchTerm("테스트검색어") | ||
| .type(SearchType.BOOK_SEARCH) | ||
| .userJpaEntity(user) | ||
| .build()); | ||
|
|
||
| testToken = jwtUtil.createAccessToken(user.getUserId()); | ||
| } | ||
|
|
||
| @AfterEach | ||
| void tearDown() { | ||
| recentSearchJpaRepository.deleteAll(); | ||
| userJpaRepository.deleteAll(); | ||
| aliasJpaRepository.deleteAll(); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("책 검색 API 정상 호출 - 키워드와 페이지 번호가 주어졌을 때") | ||
| void searchBooks_success() throws Exception { | ||
|
|
||
| // given | ||
| String keyword = "테스트"; | ||
| int page = 1; | ||
|
|
||
| // when & then | ||
| mockMvc.perform(get("/books") | ||
| .param("keyword", "테스트") | ||
| .param("page", "1") | ||
| .header("Authorization", "Bearer " + testToken) | ||
| .param("keyword", keyword) | ||
| .param("page", String.valueOf(page)) | ||
| .contentType(MediaType.APPLICATION_JSON)) | ||
| .andExpect(status().isOk()) | ||
| .andExpect(jsonPath("$.isSuccess").value(true)) | ||
|
|
@@ -40,9 +105,16 @@ void searchBooks_success() throws Exception { | |
| @Test | ||
| @DisplayName("책 검색 API 실패 - 페이지가 범위를 벗어났을 때 400 에러 발생") | ||
| void searchBooks_pageOutOfRange() throws Exception { | ||
|
|
||
| // given | ||
| String keyword = "테스트"; | ||
| int page = 99999; // totalPages보다 큰 값으로 가정 | ||
|
|
||
| // when & then | ||
| mockMvc.perform(get("/books") | ||
| .param("keyword", "테스트") | ||
| .param("page", "99999") // totalPages보다 큰 값으로 가정 | ||
| .header("Authorization", "Bearer " + testToken) | ||
| .param("keyword", keyword) | ||
| .param("page", String.valueOf(page)) | ||
| .contentType(MediaType.APPLICATION_JSON)) | ||
| .andExpect(status().isBadRequest()) | ||
| .andExpect(jsonPath("$.isSuccess").value(false)) | ||
|
|
@@ -54,9 +126,16 @@ void searchBooks_pageOutOfRange() throws Exception { | |
| @Test | ||
| @DisplayName("책 검색 API 실패 - 키워드가 비어서 넘어올 때 400 에러 발생") | ||
| void searchBooks_keywordMissing_badRequest() throws Exception { | ||
|
|
||
| // given | ||
| String keyword = ""; | ||
| int page = 1; | ||
|
|
||
| // when & then | ||
| mockMvc.perform(get("/books") | ||
| .param("page", "1") | ||
| .param("keyword", "") | ||
| .header("Authorization", "Bearer " + testToken) | ||
| .param("keyword", keyword) | ||
| .param("page", String.valueOf(page)) | ||
| .contentType(MediaType.APPLICATION_JSON)) | ||
| .andExpect(status().isBadRequest()) | ||
| .andExpect(jsonPath("$.code").value(ErrorCode.BOOK_KEYWORD_REQUIRED.getCode())) | ||
|
|
@@ -66,12 +145,51 @@ void searchBooks_keywordMissing_badRequest() throws Exception { | |
| @Test | ||
| @DisplayName("책 검색 API 실패 - 페이지 번호가 1 미만일 때 400 에러 발생") | ||
| void searchBooks_pageInvalid_badRequest() throws Exception { | ||
|
|
||
| // given | ||
| String keyword = "테스트"; | ||
| int page = 0; | ||
|
|
||
| // when & then | ||
| mockMvc.perform(get("/books") | ||
| .param("keyword", "테스트") | ||
| .param("page", "0") | ||
| .header("Authorization", "Bearer " + testToken) | ||
| .param("keyword", keyword) | ||
| .param("page", String.valueOf(page)) | ||
| .contentType(MediaType.APPLICATION_JSON)) | ||
| .andExpect(status().isBadRequest()) | ||
| .andExpect(jsonPath("$.code").value(ErrorCode.BOOK_PAGE_NUMBER_INVALID.getCode())) | ||
| .andExpect(jsonPath("$.message", containsString("페이지 번호는 1 이상의 값이어야 합니다"))); | ||
| } | ||
|
|
||
|
|
||
| @Test | ||
| @DisplayName("책 검색 성공 시 최근검색어 저장") | ||
| void searchBooks_savesRecentSearch() throws Exception { | ||
|
Comment on lines
+165
to
+167
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM |
||
|
|
||
| // given | ||
| String keyword = "테스트"; | ||
| int page = 1; | ||
|
|
||
| // when | ||
| mockMvc.perform(get("/books") | ||
| .header("Authorization", "Bearer " + testToken) | ||
| .param("keyword", keyword) | ||
| .param("page", String.valueOf(page)) | ||
| .contentType(MediaType.APPLICATION_JSON)) | ||
| .andExpect(status().isOk()); | ||
|
|
||
| // then | ||
| // 최근검색어가 저장되었는지 확인 | ||
| UserJpaEntity user = userJpaRepository.findAll().get(0); | ||
| RecentSearchJpaEntity recentSearch = recentSearchJpaRepository.findAll().stream() | ||
| .filter(rs -> rs.getUserJpaEntity().getUserId().equals(user.getUserId())) | ||
| .filter(rs -> rs.getSearchTerm().equals(keyword)) | ||
| .findFirst() | ||
| .orElse(null); | ||
|
|
||
| assertThat(recentSearch).isNotNull(); | ||
| assertThat(recentSearch.getSearchTerm()).isEqualTo(keyword); | ||
| assertThat(recentSearch.getType()).isEqualTo(SearchType.BOOK_SEARCH); | ||
| assertThat(recentSearch.getUserJpaEntity().getUserId()).isEqualTo(user.getUserId()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏻