From 0b73cebf20525456536cba3039d138e45f392840 Mon Sep 17 00:00:00 2001 From: Bob Sin Date: Tue, 28 May 2024 23:16:15 +0900 Subject: [PATCH] =?UTF-8?q?[KAN-104]=20=EC=9D=8C=EC=8B=9D=EC=A0=90=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=EC=98=A4=EB=8A=98=EC=9D=98=ED=94=BD=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20+=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecommendRestaurantController.kt | 2 +- .../RecommendRestaurantControllerTest.kt | 208 +++++++++++ .../be/review/ReviewIntegrationTest.kt | 329 ++++++++---------- 3 files changed, 361 insertions(+), 178 deletions(-) create mode 100644 src/test/kotlin/com/restaurant/be/restaurant/presentation/controller/RecommendRestaurantControllerTest.kt diff --git a/src/main/kotlin/com/restaurant/be/restaurant/presentation/controller/RecommendRestaurantController.kt b/src/main/kotlin/com/restaurant/be/restaurant/presentation/controller/RecommendRestaurantController.kt index 845b5bc..c00ba7c 100644 --- a/src/main/kotlin/com/restaurant/be/restaurant/presentation/controller/RecommendRestaurantController.kt +++ b/src/main/kotlin/com/restaurant/be/restaurant/presentation/controller/RecommendRestaurantController.kt @@ -21,7 +21,7 @@ class RecommendRestaurantController( private val recommendRestaurantService: RecommendRestaurantService ) { - @GetMapping("recommend") + @GetMapping("/recommend") @PreAuthorize("hasRole('USER')") @ApiOperation(value = "gpt 기반 추천 음식점 리스트 조회 API") @ApiResponse( diff --git a/src/test/kotlin/com/restaurant/be/restaurant/presentation/controller/RecommendRestaurantControllerTest.kt b/src/test/kotlin/com/restaurant/be/restaurant/presentation/controller/RecommendRestaurantControllerTest.kt new file mode 100644 index 0000000..a5599e1 --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/restaurant/presentation/controller/RecommendRestaurantControllerTest.kt @@ -0,0 +1,208 @@ +package com.restaurant.be.restaurant.presentation.controller + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.RestaurantUtil +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.RecommendRestaurantResponse +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.restaurant.repository.RestaurantRepository +import com.restaurant.be.user.repository.UserRepository +import io.kotest.matchers.shouldBe +import org.springframework.data.domain.Page +import org.springframework.data.redis.core.RedisTemplate +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders +import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import org.springframework.transaction.annotation.Transactional +import java.nio.charset.Charset + +@IntegrationTest +@Transactional +class RecommendRestaurantControllerTest( + private val mockMvc: MockMvc, + private val userRepository: UserRepository, + private val redisTemplate: RedisTemplate, + private val restaurantRepository: RestaurantRepository +) : CustomDescribeSpec() { + private val baseUrl = "/v1/restaurants/recommend" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + afterEach { + redisTemplate.keys("*").forEach { redisTemplate.delete(it) } + } + + describe("#getRecommendRestaurants") { + it("when 5 restaurant saved should return recommended 5 restaurants") { + // given + val userId = userRepository.findByEmail("test@gmail.com")?.id ?: 0 + + val restaurantIds = (1..5).map { i -> + val restaurant = restaurantRepository.save( + RestaurantUtil.generateRestaurantEntity( + name = "restaurant$i" + ) + ) + + restaurant.id + } + + val key = "RECOMMENDATION:$userId" + redisTemplate.opsForValue().set(key, restaurantIds.joinToString(",")) + + // when + val result = mockMvc.perform( + MockMvcRequestBuilders.get(baseUrl) + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.restaurants.size shouldBe 5 + } + + it("when 7 restaurant saved should return recommended 5 restaurants") { + // given + val userId = userRepository.findByEmail("test@gmail.com")?.id ?: 0 + val restaurantIds = (1..7).map { i -> + val restaurant = restaurantRepository.save( + RestaurantUtil.generateRestaurantEntity( + name = "restaurant$i" + ) + ) + + restaurant.id + } + + val key = "RECOMMENDATION:$userId" + redisTemplate.opsForValue().set(key, restaurantIds.joinToString(",")) + + // when + val result = mockMvc.perform( + MockMvcRequestBuilders.get(baseUrl) + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.restaurants.size shouldBe 5 + } + + it("when 3 restaurant saved should return recommended 3 restaurants") { + // given + val userId = userRepository.findByEmail("test@gmail.com")?.id ?: 0 + val restaurantIds = (1..3).map { i -> + val restaurant = restaurantRepository.save( + RestaurantUtil.generateRestaurantEntity( + name = "restaurant$i" + ) + ) + + restaurant.id + } + + val key = "RECOMMENDATION:$userId" + redisTemplate.opsForValue().set(key, restaurantIds.joinToString(",")) + + // when + val result = mockMvc.perform( + MockMvcRequestBuilders.get(baseUrl) + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.restaurants.size shouldBe 3 + } + + it("when no restaurant saved in redis should recommended 5 restaurants") { + // given + val restaurantIds = (1..5).map { i -> + val restaurant = restaurantRepository.save( + RestaurantUtil.generateRestaurantEntity( + name = "restaurant$i" + ) + ) + + restaurant.id + } + + val key = "RECOMMENDATION:0" + redisTemplate.opsForValue().set(key, restaurantIds.joinToString(",")) + + // when + val result = mockMvc.perform( + MockMvcRequestBuilders.get(baseUrl) + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.restaurants.size shouldBe 5 + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/review/ReviewIntegrationTest.kt b/src/test/kotlin/com/restaurant/be/review/ReviewIntegrationTest.kt index 481e4ea..9fcf112 100644 --- a/src/test/kotlin/com/restaurant/be/review/ReviewIntegrationTest.kt +++ b/src/test/kotlin/com/restaurant/be/review/ReviewIntegrationTest.kt @@ -1,39 +1,14 @@ package com.restaurant.be.review -import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue import com.restaurant.be.common.CustomDescribeSpec import com.restaurant.be.common.IntegrationTest -import com.restaurant.be.common.exception.DuplicateLikeException -import com.restaurant.be.common.exception.NotFoundReviewException -import com.restaurant.be.common.response.CommonResponse -import com.restaurant.be.common.util.RestaurantUtil import com.restaurant.be.restaurant.repository.RestaurantRepository -import com.restaurant.be.review.domain.entity.Review -import com.restaurant.be.review.presentation.dto.CreateReviewResponse -import com.restaurant.be.review.presentation.dto.LikeReviewRequest -import com.restaurant.be.review.presentation.dto.LikeReviewResponse -import com.restaurant.be.review.presentation.dto.common.ReviewRequestDto import com.restaurant.be.review.repository.ReviewRepository -import com.restaurant.be.user.domain.entity.QUser.user -import com.restaurant.be.user.domain.entity.User import com.restaurant.be.user.domain.service.SignUpUserService -import com.restaurant.be.user.presentation.dto.SignUpUserRequest import com.restaurant.be.user.repository.UserRepository -import io.kotest.matchers.shouldBe -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType -import org.springframework.security.test.context.support.WithMockUser import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -import java.nio.charset.StandardCharsets -import javax.transaction.Transactional @IntegrationTest class ReviewIntegrationTest( @@ -179,102 +154,102 @@ class ReviewIntegrationTest( // restaurant.get().ratingAvg shouldBe 1.0 // } - @WithMockUser(username = "test@gmail.com", roles = ["USER"], password = "a12345678") - @Transactional - @Test - fun `리뷰 삭제 성공`() { - val restaurantEntity = RestaurantUtil.generateRestaurantEntity(name = "목구멍 율전점") - val savedRestaurant = restaurantRepository.save(restaurantEntity) - signUpUserService.signUpUser( - SignUpUserRequest( - email = "test@gmail.com", - password = "a12345678", - nickname = "testname" - ) - ) - val reviewRequest = ReviewRequestDto( - rating = 4.0, - content = "맛있어요", - imageUrls = listOf("image1", "image2", "image3") - ) - - val result = mockMvc.perform( - MockMvcRequestBuilders.post("/v1/restaurants/{restaurantID}/$resource", savedRestaurant.id) - .contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(reviewRequest)) - ).andReturn() - - val createResult: CommonResponse = - objectMapper.readValue( - result.response.contentAsString.toByteArray(StandardCharsets.ISO_8859_1), - object : TypeReference>() {} - ) - - val reviewId = createResult.data!!.review.id - - mockMvc.perform( - MockMvcRequestBuilders.delete( - "/v1/restaurants/reviews/{reviewId}", - reviewId - ) - ).andExpect(status().isOk).andExpect(jsonPath("$.result").value("SUCCESS")) - val restaurant = restaurantRepository.findById(savedRestaurant.id) - restaurant.get().reviewCount shouldBe 0 - restaurant.get().ratingAvg shouldBe 0 - - mockMvc.perform( - MockMvcRequestBuilders.get( - "/v1/restaurants/reviews/{reviewId}", - reviewId - ) - ).andExpect(status().isBadRequest()) - .andExpect { result -> result.resolvedException is NotFoundReviewException } - } - - @WithMockUser(username = "test@gmail.com", roles = ["USER"], password = "a12345678") - @Transactional - @Test - fun `comment가 없으면 오류 반환`() { - val reviewRequest = ReviewRequestDto( - rating = 3.0, - content = "", - imageUrls = listOf() - ) - - mockMvc.perform( - MockMvcRequestBuilders.post("/v1/restaurants/{restaurantID}/$resource", mockRestaurantID) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(reviewRequest)) - ) - .andExpect(status().isBadRequest) - } - - @Nested - open inner class ReviewListTests { - @BeforeEach - open fun setUp() { - val user = User( - email = "test@gmail.com", - nickname = "maker", - password = "q1w2e3r4", - withdrawal = false, - roles = listOf(), - profileImageUrl = "maker-profile" - ) - - val savedUser = signUpUserRepository.save(user) - - val reviews = (1..20).map { index -> - Review( - user = savedUser, - restaurantId = 1, - content = "맛있어요 $index", - rating = 5.0, - images = mutableListOf() - ) - } - - reviews.forEach { reviewRepository.save(it) } - } +// @WithMockUser(username = "test@gmail.com", roles = ["USER"], password = "a12345678") +// @Transactional +// @Test +// fun `리뷰 삭제 성공`() { +// val restaurantEntity = RestaurantUtil.generateRestaurantEntity(name = "목구멍 율전점") +// val savedRestaurant = restaurantRepository.save(restaurantEntity) +// signUpUserService.signUpUser( +// SignUpUserRequest( +// email = "test@gmail.com", +// password = "a12345678", +// nickname = "testname" +// ) +// ) +// val reviewRequest = ReviewRequestDto( +// rating = 4.0, +// content = "맛있어요", +// imageUrls = listOf("image1", "image2", "image3") +// ) +// +// val result = mockMvc.perform( +// MockMvcRequestBuilders.post("/v1/restaurants/{restaurantID}/$resource", savedRestaurant.id) +// .contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(reviewRequest)) +// ).andReturn() +// +// val createResult: CommonResponse = +// objectMapper.readValue( +// result.response.contentAsString.toByteArray(StandardCharsets.ISO_8859_1), +// object : TypeReference>() {} +// ) +// +// val reviewId = createResult.data!!.review.id +// +// mockMvc.perform( +// MockMvcRequestBuilders.delete( +// "/v1/restaurants/reviews/{reviewId}", +// reviewId +// ) +// ).andExpect(status().isOk).andExpect(jsonPath("$.result").value("SUCCESS")) +// val restaurant = restaurantRepository.findById(savedRestaurant.id) +// restaurant.get().reviewCount shouldBe 0 +// restaurant.get().ratingAvg shouldBe 0 +// +// mockMvc.perform( +// MockMvcRequestBuilders.get( +// "/v1/restaurants/reviews/{reviewId}", +// reviewId +// ) +// ).andExpect(status().isBadRequest()) +// .andExpect { result -> result.resolvedException is NotFoundReviewException } +// } +// +// @WithMockUser(username = "test@gmail.com", roles = ["USER"], password = "a12345678") +// @Transactional +// @Test +// fun `comment가 없으면 오류 반환`() { +// val reviewRequest = ReviewRequestDto( +// rating = 3.0, +// content = "", +// imageUrls = listOf() +// ) +// +// mockMvc.perform( +// MockMvcRequestBuilders.post("/v1/restaurants/{restaurantID}/$resource", mockRestaurantID) +// .contentType(MediaType.APPLICATION_JSON) +// .content(objectMapper.writeValueAsString(reviewRequest)) +// ) +// .andExpect(status().isBadRequest) +// } +// +// @Nested +// open inner class ReviewListTests { +// @BeforeEach +// open fun setUp() { +// val user = User( +// email = "test@gmail.com", +// nickname = "maker", +// password = "q1w2e3r4", +// withdrawal = false, +// roles = listOf(), +// profileImageUrl = "maker-profile" +// ) +// +// val savedUser = signUpUserRepository.save(user) +// +// val reviews = (1..20).map { index -> +// Review( +// user = savedUser, +// restaurantId = 1, +// content = "맛있어요 $index", +// rating = 5.0, +// images = mutableListOf() +// ) +// } +// +// reviews.forEach { reviewRepository.save(it) } +// } // @Test // @WithMockUser(username = "test@gmail.com", roles = ["USER"]) @@ -565,60 +540,60 @@ class ReviewIntegrationTest( // reviewsSaved.size shouldBe 23 // } - @Test - @WithMockUser(username = "newUser@gmail.com", roles = ["USER"]) - @Transactional - open fun `새로운 유저가 특정 리뷰 좋아요 확인`() { - val newUser = User( - email = "newUser@gmail.com", - nickname = "maker1", - password = "q1w2e3r41", - withdrawal = false, - roles = listOf(), - profileImageUrl = "newuser-profile" - ) - - signUpUserRepository.save(newUser) - - val likeRequest = LikeReviewRequest(true) - - val reviewId = reviewRepository.findAll().get(0).id - - val result = mockMvc.perform( - MockMvcRequestBuilders.post("/v1/restaurants/reviews/{reviewId}/like", reviewId) - .contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(likeRequest)) - ) - .andExpect(status().isOk).andExpect(jsonPath("$.result").value("SUCCESS")).andReturn() - - val actualResult: CommonResponse = - objectMapper.readValue( - result.response.contentAsString.toByteArray(StandardCharsets.ISO_8859_1), - object : TypeReference>() {} - ) - - actualResult.data!!.review.isLike shouldBe true - - mockMvc.perform( - MockMvcRequestBuilders.post("/v1/restaurants/reviews/{reviewId}/like", reviewId) - .contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(likeRequest)) - ) - .andExpect(status().isBadRequest) - .andExpect { result -> result.resolvedException is DuplicateLikeException } - - val unLikeRequest = LikeReviewRequest(false) - - val unlikeResult = mockMvc.perform( - MockMvcRequestBuilders.post("/v1/restaurants/reviews/{reviewId}/like", reviewId) - .contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(unLikeRequest)) - ) - .andExpect(status().isOk).andExpect(jsonPath("$.result").value("SUCCESS")).andReturn() - - val unLikeActualResult: CommonResponse = - objectMapper.readValue( - unlikeResult.response.contentAsString.toByteArray(StandardCharsets.ISO_8859_1), - object : TypeReference>() {} - ) - unLikeActualResult.data!!.review.isLike shouldBe false - } - } +// @Test +// @WithMockUser(username = "newUser@gmail.com", roles = ["USER"]) +// @Transactional +// open fun `새로운 유저가 특정 리뷰 좋아요 확인`() { +// val newUser = User( +// email = "newUser@gmail.com", +// nickname = "maker1", +// password = "q1w2e3r41", +// withdrawal = false, +// roles = listOf(), +// profileImageUrl = "newuser-profile" +// ) +// +// signUpUserRepository.save(newUser) +// +// val likeRequest = LikeReviewRequest(true) +// +// val reviewId = reviewRepository.findAll().get(0).id +// +// val result = mockMvc.perform( +// MockMvcRequestBuilders.post("/v1/restaurants/reviews/{reviewId}/like", reviewId) +// .contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(likeRequest)) +// ) +// .andExpect(status().isOk).andExpect(jsonPath("$.result").value("SUCCESS")).andReturn() +// +// val actualResult: CommonResponse = +// objectMapper.readValue( +// result.response.contentAsString.toByteArray(StandardCharsets.ISO_8859_1), +// object : TypeReference>() {} +// ) +// +// actualResult.data!!.review.isLike shouldBe true +// +// mockMvc.perform( +// MockMvcRequestBuilders.post("/v1/restaurants/reviews/{reviewId}/like", reviewId) +// .contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(likeRequest)) +// ) +// .andExpect(status().isBadRequest) +// .andExpect { result -> result.resolvedException is DuplicateLikeException } +// +// val unLikeRequest = LikeReviewRequest(false) +// +// val unlikeResult = mockMvc.perform( +// MockMvcRequestBuilders.post("/v1/restaurants/reviews/{reviewId}/like", reviewId) +// .contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(unLikeRequest)) +// ) +// .andExpect(status().isOk).andExpect(jsonPath("$.result").value("SUCCESS")).andReturn() +// +// val unLikeActualResult: CommonResponse = +// objectMapper.readValue( +// unlikeResult.response.contentAsString.toByteArray(StandardCharsets.ISO_8859_1), +// object : TypeReference>() {} +// ) +// unLikeActualResult.data!!.review.isLike shouldBe false +// } +// } }