diff --git a/src/main/java/com/back/domain/cocktail/comment/dto/CocktailCommentResponseDto.java b/src/main/java/com/back/domain/cocktail/comment/dto/CocktailCommentResponseDto.java index 966a3a3f..6f2b17e2 100644 --- a/src/main/java/com/back/domain/cocktail/comment/dto/CocktailCommentResponseDto.java +++ b/src/main/java/com/back/domain/cocktail/comment/dto/CocktailCommentResponseDto.java @@ -7,7 +7,7 @@ public record CocktailCommentResponseDto( Long commentId, - Long postId, + Long cocktailId, String userNickName, LocalDateTime createdAt, LocalDateTime updatedAt, diff --git a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java index d1080a19..a389f05a 100644 --- a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java +++ b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java @@ -56,7 +56,6 @@ public RsData> searchAndFilter( // 서비스 호출 List searchResults = cocktailService.searchAndFilter(cocktailSearchRequestDto); - // RsData로 통일된 응답 반환 return RsData.successOf(searchResults); } } diff --git a/src/main/java/com/back/domain/cocktail/controller/CocktailShareController.java b/src/main/java/com/back/domain/cocktail/controller/CocktailShareController.java index b14e3fb9..d7918718 100644 --- a/src/main/java/com/back/domain/cocktail/controller/CocktailShareController.java +++ b/src/main/java/com/back/domain/cocktail/controller/CocktailShareController.java @@ -24,7 +24,7 @@ public ResponseEntity>> getShareLink(@PathVariable Lo .map(cocktail -> { Map response = Map.of( // 공유 URL - "url", "https://www.ssoul.o-r.kr/cocktails/" + cocktail.getId(), + "url", "https://www.ssoul.life/cocktails/" + cocktail.getId(), // 공유 제목 "title", cocktail.getCocktailName(), // 공유 이미지 (선택) diff --git a/src/test/java/com/back/domain/cocktail/comment/controller/CocktailCommentControllerTest.java b/src/test/java/com/back/domain/cocktail/comment/controller/CocktailCommentControllerTest.java new file mode 100644 index 00000000..d90d9027 --- /dev/null +++ b/src/test/java/com/back/domain/cocktail/comment/controller/CocktailCommentControllerTest.java @@ -0,0 +1,202 @@ +package com.back.domain.cocktail.comment.controller; + +import com.back.domain.cocktail.comment.dto.CocktailCommentCreateRequestDto; +import com.back.domain.cocktail.comment.dto.CocktailCommentResponseDto; +import com.back.domain.cocktail.comment.dto.CocktailCommentUpdateRequestDto; +import com.back.domain.cocktail.comment.service.CocktailCommentService; +import com.back.domain.post.comment.enums.CommentStatus; +import com.back.global.jwt.JwtUtil; +import com.back.global.rq.Rq; +import com.fasterxml.jackson.databind.ObjectMapper; +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.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(CocktailCommentController.class) +@AutoConfigureMockMvc(addFilters = false) +public class CocktailCommentControllerTest { + + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockitoBean + private CocktailCommentService cocktailCommentService; + @MockitoBean + private JwtUtil jwtUtil; + @MockitoBean + private Rq rq; + + private CocktailCommentResponseDto createSampleResponseDto(Long id) { + return new CocktailCommentResponseDto( + id, + 1L, + "테스트유저" + id, + LocalDateTime.now(), + LocalDateTime.now(), + CommentStatus.PUBLIC, + "테스트 내용" + id + ); + } + + @Test + @DisplayName("칵테일 댓글 작성 API 테스트") + void t1() throws Exception { + // given + CocktailCommentCreateRequestDto requestDto = new CocktailCommentCreateRequestDto( + CommentStatus.PUBLIC, + "테스트 내용1" + ); + CocktailCommentResponseDto responseDto = createSampleResponseDto(1L); + given(cocktailCommentService.createCocktailComment(anyLong(), any(CocktailCommentCreateRequestDto.class))).willReturn(responseDto); + + // when & then + mockMvc.perform(post("/cocktails/{cocktailId}/comments", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.commentId").value(1L)) + .andExpect(jsonPath("$.data.cocktailId").value(1L)) + .andExpect(jsonPath("$.data.userNickName").value("테스트유저1")) + .andExpect(jsonPath("$.data.status").value("PUBLIC")) + .andExpect(jsonPath("$.data.content").value("테스트 내용1")) + .andDo(print()); + } + + @Test + @DisplayName("칵테일 댓글 다건 조회 API 테스트") + void t2() throws Exception { + // given + List firstPage = new ArrayList<>(); + for (long i = 30; i >= 21; i--) { + firstPage.add(createSampleResponseDto(i)); + } + + List secondPage = new ArrayList<>(); + for (long i = 20; i >= 11; i--) { + secondPage.add(createSampleResponseDto(i)); + } + + given(cocktailCommentService.getCocktailComments(1L, null)).willReturn(firstPage); // 첫 호출(lastId 없음) + given(cocktailCommentService.getCocktailComments(1L, 21L)).willReturn(secondPage); + + // when & then + mockMvc.perform(get("/cocktails/{cocktailId}/comments", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(10))) + .andExpect(jsonPath("$.data[0].commentId").value(30)) + .andExpect(jsonPath("$.data[0].cocktailId").value(1L)) + .andExpect(jsonPath("$.data[0].userNickName").value("테스트유저30")) + .andExpect(jsonPath("$.data[0].status").value("PUBLIC")) + .andExpect(jsonPath("$.data[0].content").value("테스트 내용30")) + .andDo(print()); + + mockMvc.perform(get("/cocktails/{cocktailId}/comments", 1L).param("lastId", "21")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(10))) + .andExpect(jsonPath("$.data[0].commentId").value(20)) + .andExpect(jsonPath("$.data[0].cocktailId").value(1L)) + .andExpect(jsonPath("$.data[0].userNickName").value("테스트유저20")) + .andExpect(jsonPath("$.data[0].status").value("PUBLIC")) + .andExpect(jsonPath("$.data[0].content").value("테스트 내용20")) + .andDo(print()); + } + + @Test + @DisplayName("칵테일 댓글 단건 조회 API 테스트") + void t3() throws Exception { + // given + Long cocktailId = 1L; + Long cocktailCommentId = 1L; + CocktailCommentResponseDto responseDto = createSampleResponseDto(cocktailCommentId); + given(cocktailCommentService.getCocktailComment(cocktailId, cocktailCommentId)).willReturn(responseDto); + + // when & then + mockMvc.perform(get("/cocktails/{cocktailId}/comments/{cocktailCommentId}", cocktailId, cocktailCommentId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.commentId").value(cocktailCommentId)) + .andExpect(jsonPath("$.data.cocktailId").value(cocktailId)) + .andExpect(jsonPath("$.data.userNickName").value("테스트유저1")) + .andExpect(jsonPath("$.data.status").value("PUBLIC")) + .andExpect(jsonPath("$.data.content").value("테스트 내용1")) + .andDo(print()); + } + + @Test + @DisplayName("칵테일 댓글 수정 API 테스트") + void t4() throws Exception { + // given + Long cocktailId = 1L; + Long cocktailCommentId = 1L; + + CocktailCommentUpdateRequestDto requestDto = new CocktailCommentUpdateRequestDto( + CommentStatus.PUBLIC, + "수정된 내용" + cocktailCommentId + ); + + CocktailCommentResponseDto responseDto = new CocktailCommentResponseDto( + cocktailCommentId, + cocktailId, + "테스트유저" + cocktailCommentId, + LocalDateTime.now(), + LocalDateTime.now(), + CommentStatus.PUBLIC, + "수정된 내용" + cocktailCommentId + ); + + given(cocktailCommentService.updateCocktailComment(eq(cocktailId), eq(cocktailCommentId), any(CocktailCommentUpdateRequestDto.class))) + .willReturn(responseDto); + + // when & then + mockMvc.perform(patch("/cocktails/{cocktailId}/comments/{cocktailCommentId}", cocktailId, cocktailCommentId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.commentId").value(cocktailCommentId)) + .andExpect(jsonPath("$.data.cocktailId").value(cocktailId)) + .andExpect(jsonPath("$.data.userNickName").value("테스트유저" + cocktailCommentId)) + .andExpect(jsonPath("$.data.status").value("PUBLIC")) + .andExpect(jsonPath("$.data.content").value("수정된 내용" + cocktailCommentId)) + .andDo(print()); + } + + @Test + @DisplayName("칵테일 댓글 삭제 API 테스트") + void t5() throws Exception { + // given + Long cocktailId = 1L; + Long cocktailCommentId = 1L; + + willDoNothing().given(cocktailCommentService).deleteCocktailComment(cocktailId, cocktailCommentId); + + // when & then + mockMvc.perform(delete("/cocktails/{cocktailId}/comments/{cocktailCommentId}", cocktailId, cocktailCommentId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.data").value(nullValue())) // null 이므로 empty 체크 가능 + .andDo(print()); + + // 추가 검증: 서비스 메소드가 정확히 호출됐는지 확인 + verify(cocktailCommentService).deleteCocktailComment(cocktailId, cocktailCommentId); + } +} diff --git a/src/test/java/com/back/domain/cocktail/controller/CocktailControllerTest.java b/src/test/java/com/back/domain/cocktail/controller/CocktailControllerTest.java index 2ac0de1e..0ad94224 100644 --- a/src/test/java/com/back/domain/cocktail/controller/CocktailControllerTest.java +++ b/src/test/java/com/back/domain/cocktail/controller/CocktailControllerTest.java @@ -1,5 +1,6 @@ package com.back.domain.cocktail.controller; +import com.back.domain.cocktail.dto.CocktailSearchRequestDto; import com.back.domain.cocktail.entity.Cocktail; import com.back.domain.cocktail.enums.AlcoholBaseType; import com.back.domain.cocktail.enums.AlcoholStrength; @@ -7,6 +8,7 @@ import com.back.domain.cocktail.repository.CocktailRepository; import com.back.domain.user.service.UserService; import com.back.global.rq.Rq; +import com.back.global.standard.util.Ut; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.DisplayName; @@ -27,6 +29,7 @@ import java.time.LocalDateTime; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -138,6 +141,47 @@ void t4() throws Exception { .andExpect(jsonPath("$.data").isArray()); } + @Test + @DisplayName("칵테일 검색 및 필터링") + void t5() throws Exception { + // given: DB에 칵테일 저장 + Cocktail savedCocktail = cocktailRepository.save( + Cocktail.builder() + .cocktailName("모히토") + .alcoholStrength(AlcoholStrength.WEAK) + .cocktailType(CocktailType.SHORT) + .alcoholBaseType(AlcoholBaseType.RUM) + .cocktailImgUrl("https://example.com/image.jpg") + .cocktailStory("상쾌한 라임과 민트") + .ingredient("라임, 민트, 럼, 설탕, 탄산수") + .recipe("라임과 민트를 섞고 럼을 넣고 탄산수로 완성") + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build() + ); + + // 검색 조건 (키워드: "모히토") + CocktailSearchRequestDto requestDto = new CocktailSearchRequestDto(); + requestDto.setKeyword("모히토"); + + // when: POST 요청 + ResultActions resultActions = mvc.perform( + post("/cocktails/search") + .contentType(MediaType.APPLICATION_JSON) + .content(Ut.json.toString(requestDto)) + ).andDo(print()); + + // then: 상태코드, JSON 구조 검증 + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.data[0].cocktailName").value("모히토")) + .andExpect(jsonPath("$.data[0].alcoholStrength").value("WEAK")) + .andExpect(jsonPath("$.data[0].cocktailType").value("SHORT")) + .andExpect(jsonPath("$.data[0].alcoholBaseType").value("RUM")); + } + @TestConfiguration static class TestConfig { @Bean @@ -149,5 +193,4 @@ public Rq rq() { return new Rq(req, resp, userService); } } - }