From 0335f3f5552c6c449db34b6e7ebe10ab026e6e1b Mon Sep 17 00:00:00 2001 From: psy Date: Tue, 28 Oct 2025 15:58:44 +0900 Subject: [PATCH] =?UTF-8?q?test=20#201:=20ProductImageControllerTest=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EB=B0=8F=20REST=20Docs=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상품 이미지 업로드, 조회, 삭제, 대표 이미지 변경 API 테스트 구현 - REST Docs 스니펫(path-parameters, request-parts, response-fields) 생성 - DiscountControllerTest 스타일에 맞춰 응답 데이터 없는 API는 요청만 문서화 - TestConfig, SecurityTestConfig 적용하여 MockMvc 기반 테스트 환경 구성 --- src/docs/asciidoc/_product_image.adoc | 46 ++++++ src/docs/asciidoc/index.adoc | 1 + .../product/ProductImageControllerTest.java | 150 ++++++++++++++++++ .../come2us/global/config/TestConfig.java | 6 + 4 files changed, 203 insertions(+) create mode 100644 src/docs/asciidoc/_product_image.adoc create mode 100644 src/test/java/com/irum/come2us/domain/product/ProductImageControllerTest.java diff --git a/src/docs/asciidoc/_product_image.adoc b/src/docs/asciidoc/_product_image.adoc new file mode 100644 index 00000000..6363d658 --- /dev/null +++ b/src/docs/asciidoc/_product_image.adoc @@ -0,0 +1,46 @@ +== 상품 이미지 (Product Image) API + +=== 1. 상품 이미지 업로드 +`POST /products/{productId}/images` + +요청 경로 파라미터: +include::{snippets}/product-image-upload/path-parameters.adoc[] + +요청 본문 (Multipart): +include::{snippets}/product-image-upload/request-parts.adoc[] + +응답: +include::{snippets}/product-image-upload/http-response.adoc[] + + +=== 2. 상품 이미지 조회 +`GET /products/{productId}/images` + +요청 경로 파라미터: +include::{snippets}/product-image-list-get/path-parameters.adoc[] + +응답 본문: +include::{snippets}/product-image-list-get/response-fields.adoc[] + +응답: +include::{snippets}/product-image-list-get/http-response.adoc[] + + +=== 3. 대표 이미지 변경 +`PATCH /products/{productId}/images/{imageId}/default` + +요청 경로 파라미터: +include::{snippets}/product-image-change-default/path-parameters.adoc[] + +응답: +include::{snippets}/product-image-change-default/http-response.adoc[] + + +=== 4. 상품 이미지 삭제 +`DELETE /products/{productId}/images/{imageId}` + +요청 경로 파라미터: +include::{snippets}/product-image-delete/path-parameters.adoc[] + +응답: +include::{snippets}/product-image-delete/http-response.adoc[] diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index c84c9618..cc5f49dc 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -15,4 +15,5 @@ include::_discount.adoc[] include::_deliverypolicy.adoc[] // include::_product.adoc[] +include::_product_image.adoc[] diff --git a/src/test/java/com/irum/come2us/domain/product/ProductImageControllerTest.java b/src/test/java/com/irum/come2us/domain/product/ProductImageControllerTest.java new file mode 100644 index 00000000..fecfb6f2 --- /dev/null +++ b/src/test/java/com/irum/come2us/domain/product/ProductImageControllerTest.java @@ -0,0 +1,150 @@ +package com.irum.come2us.domain.product; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.irum.come2us.domain.product.application.service.ProductImageService; +import com.irum.come2us.domain.product.presentation.controller.ProductImageController; +import com.irum.come2us.domain.product.presentation.dto.response.ProductImageResponse; +import com.irum.come2us.global.config.SecurityTestConfig; +import com.irum.come2us.global.config.TestConfig; +import java.util.List; +import java.util.UUID; +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.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(ProductImageController.class) +@AutoConfigureRestDocs +@Import({SecurityTestConfig.class, TestConfig.class}) +public class ProductImageControllerTest { + + @Autowired private MockMvc mockMvc; + @Autowired private ProductImageService productImageService; + @Autowired private ObjectMapper objectMapper; + + private final UUID mockProductId = UUID.randomUUID(); + private final UUID mockImageId = UUID.randomUUID(); + + @Test + @DisplayName("상품 이미지 업로드 API (사장님 권한)") + void uploadProductImagesApiTest() throws Exception { + MockMultipartFile file = + new MockMultipartFile( + "images", "test.jpg", MediaType.IMAGE_JPEG_VALUE, "dummy".getBytes()); + + doNothing().when(productImageService).uploadProductImages(eq(mockProductId), any()); + + mockMvc.perform( + multipart("/products/{productId}/images", mockProductId) + .file(file) + .with(csrf()) + .with(user("100").roles("OWNER"))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.CREATED.value())) + .andDo( + document( + "product-image-upload", + pathParameters(parameterWithName("productId").description("상품 ID")), + requestParts( + partWithName("images").description("업로드할 상품 이미지 파일들")))); + } + + @Test + @DisplayName("대표 이미지 변경 API (사장님 권한)") + void changeDefaultImageApiTest() throws Exception { + doNothing() + .when(productImageService) + .changeDefaultImage(eq(mockProductId), eq(mockImageId)); + + mockMvc.perform( + patch( + "/products/{productId}/images/{imageId}/default", + mockProductId, + mockImageId) + .with(csrf()) + .with(user("100").roles("OWNER"))) + .andExpect(status().isNoContent()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.NO_CONTENT.value())) + .andDo( + document( + "product-image-change-default", + pathParameters( + parameterWithName("productId").description("상품 ID"), + parameterWithName("imageId") + .description("대표로 변경할 이미지 ID")))); + } + + @Test + @DisplayName("상품 이미지 삭제 API (사장님 권한)") + void deleteProductImageApiTest() throws Exception { + doNothing() + .when(productImageService) + .deleteProductImage(eq(mockProductId), eq(mockImageId)); + + mockMvc.perform( + delete("/products/{productId}/images/{imageId}", mockProductId, mockImageId) + .with(csrf()) + .with(user("100").roles("OWNER"))) + .andExpect(status().isNoContent()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.NO_CONTENT.value())) + .andDo( + document( + "product-image-delete", + pathParameters( + parameterWithName("productId").description("상품 ID"), + parameterWithName("imageId").description("삭제할 이미지 ID")))); + } + + @Test + @DisplayName("상품 이미지 목록 조회 API") + void getProductImagesApiTest() throws Exception { + List responseList = + List.of( + new ProductImageResponse( + mockImageId, "https://cdn.example.com/img.jpg", true)); + + when(productImageService.getProductImages(eq(mockProductId))).thenReturn(responseList); + + mockMvc.perform( + get("/products/{productId}/images", mockProductId) + .with(csrf()) + .with(user("1").roles("CUSTOMER"))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data[0].id").value(mockImageId.toString())) + .andExpect(jsonPath("$.data[0].isDefault").value(true)) + .andDo( + document( + "product-image-list-get", + pathParameters(parameterWithName("productId").description("상품 ID")), + responseFields( + fieldWithPath("success").description("요청 성공 여부"), + fieldWithPath("status").description("HTTP 상태 코드"), + fieldWithPath("data[].id").description("상품 이미지 식별 ID"), + fieldWithPath("data[].imageUrl").description("이미지 URL"), + fieldWithPath("data[].isDefault").description("대표 이미지 여부"), + fieldWithPath("timestamp").description("응답 시간")))); + } +} diff --git a/src/test/java/com/irum/come2us/global/config/TestConfig.java b/src/test/java/com/irum/come2us/global/config/TestConfig.java index ce1ba3e1..dc989d20 100644 --- a/src/test/java/com/irum/come2us/global/config/TestConfig.java +++ b/src/test/java/com/irum/come2us/global/config/TestConfig.java @@ -6,6 +6,7 @@ import com.irum.come2us.domain.discount.application.service.DiscountService; import com.irum.come2us.domain.member.application.service.ManagerService; import com.irum.come2us.domain.member.application.service.MemberService; +import com.irum.come2us.domain.product.application.service.ProductImageService; import com.irum.come2us.domain.refund.application.service.RefundService; import com.irum.come2us.global.util.CookieUtil; import org.mockito.Mockito; @@ -53,4 +54,9 @@ public RefundService refundService() { public DiscountService discountService() { return Mockito.mock(DiscountService.class); } + + @Bean + public ProductImageService productImageService() { + return Mockito.mock(ProductImageService.class); + } }