From 704a1bc06c4748dab8ff5b0455185c4b0aa3d223 Mon Sep 17 00:00:00 2001 From: psy Date: Wed, 29 Oct 2025 02:57:10 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat=20#200:=20product=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=A7=A4?= =?UTF-8?q?=ED=95=91,=20CartResponse=EC=97=90=20=EC=83=81=ED=92=88?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A3=BC=EC=84=9D=ED=95=B4?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cart/presentation/dto/response/CartResponse.java | 12 ++++++------ .../domain/product/domain/entity/Product.java | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/irum/come2us/domain/cart/presentation/dto/response/CartResponse.java b/src/main/java/com/irum/come2us/domain/cart/presentation/dto/response/CartResponse.java index 78c60942..cee81acd 100644 --- a/src/main/java/com/irum/come2us/domain/cart/presentation/dto/response/CartResponse.java +++ b/src/main/java/com/irum/come2us/domain/cart/presentation/dto/response/CartResponse.java @@ -10,7 +10,7 @@ public record CartResponse( UUID optionValueId, String productName, String optionValueName, - // String imageUrl, // 상품 이미지 구현 후 주석 해제 + String imageUrl, int quantity, int basePrice, // 상품 기본가 int extraPrice, // 옵션 추가금 @@ -26,17 +26,17 @@ public static CartResponse from(Cart cart) { int unit = base + extra; int total = unit * cart.getQuantity(); - // String imageUrl = (product.getProductImages() != null && - // !product.getProductImages().isEmpty()) - // ? product.getProductImages().get(0).getImageUrl() - // : null; + String imageUrl = + (product.getProductImages() != null && !product.getProductImages().isEmpty()) + ? product.getProductImages().get(0).getImageUrl() + : null; return CartResponse.builder() .cartId(cart.getId()) .optionValueId(optionValue.getId()) .productName(product.getName()) .optionValueName(optionValue.getName()) - // .imageUrl(imageUrl) + .imageUrl(imageUrl) .quantity(cart.getQuantity()) .basePrice(base) .extraPrice(extra) diff --git a/src/main/java/com/irum/come2us/domain/product/domain/entity/Product.java b/src/main/java/com/irum/come2us/domain/product/domain/entity/Product.java index bba92469..290a718a 100644 --- a/src/main/java/com/irum/come2us/domain/product/domain/entity/Product.java +++ b/src/main/java/com/irum/come2us/domain/product/domain/entity/Product.java @@ -132,5 +132,8 @@ public void updateCategory(Category category) { this.category = category; } - // TODO: 이미지 매핑, 리뷰 매핑 + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) + private List productImages = new ArrayList<>(); + + // TODO: 리뷰 매핑 } From 7cde48afb510fc44faaf62c0770b6c2798456013 Mon Sep 17 00:00:00 2001 From: psy Date: Wed, 29 Oct 2025 02:59:46 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat=20#200:=20CartRepository=EC=97=90=20Pr?= =?UTF-8?q?oductImage=20fetch=20join=20=EC=B6=94=EA=B0=80=EB=A1=9C=20N+1?= =?UTF-8?q?=20=EC=BF=BC=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cart → OptionValue → OptionGroup → Product → ProductImage 경로에 LEFT JOIN FETCH 추가 - 장바구니 조회 시 상품 이미지 지연로딩 제거로 성능 개선 --- .../domain/cart/domain/repository/CartRepository.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/irum/come2us/domain/cart/domain/repository/CartRepository.java b/src/main/java/com/irum/come2us/domain/cart/domain/repository/CartRepository.java index 669d8482..4b2aa0d7 100644 --- a/src/main/java/com/irum/come2us/domain/cart/domain/repository/CartRepository.java +++ b/src/main/java/com/irum/come2us/domain/cart/domain/repository/CartRepository.java @@ -23,8 +23,10 @@ public interface CartRepository extends JpaRepository { Cart findByMemberIdAndOptionValueId( @Param("memberId") @NotNull Long memberId, @Param("optionValueId") UUID optionValueId); - // 특정 회원의 장바구니 + 상품/옵션 통합 조회 - // TODO: 이미지 조인 추후 추가 예정 + /** + * 특정 회원의 장바구니 + 상품/옵션/이미지 통합 조회 - Cart → OptionValue → OptionGroup → Product → ProductImage - + * LEFT JOIN FETCH 로 대표 이미지까지 미리 로딩 + */ @Query( """ SELECT DISTINCT c @@ -32,6 +34,7 @@ Cart findByMemberIdAndOptionValueId( JOIN FETCH c.optionValue ov JOIN FETCH ov.optionGroup og JOIN FETCH og.product p + LEFT JOIN FETCH p.productImages pi WHERE c.member.memberId = :memberId """) List findAllWithProductByMemberId(@Param("memberId") Long memberId); From d44ad546ed696b1d9656f1dd4bd7b4d5639a7874 Mon Sep 17 00:00:00 2001 From: psy Date: Wed, 29 Oct 2025 09:59:36 +0900 Subject: [PATCH 3/5] =?UTF-8?q?test=20#200:=20CartController=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=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 - CartController CRUD API 테스트 코드 작성 (MockMvc 기반) - 장바구니 생성, 조회, 수정, 삭제 성공 케이스 검증 - Spring REST Docs 스니펫(cart-create, cart-get-list, cart-update, cart-delete) 생성 - TestConfig 적용으로 Mock 서비스 주입 - CartRepository fetch join 개선 반영 --- src/docs/asciidoc/_cart.adoc | 46 ++++++ src/docs/asciidoc/index.adoc | 2 + .../cart/application/service/CartService.java | 43 +---- .../controller/CartController.java | 23 ++- .../domain/cart/CartControllerTest.java | 153 ++++++++++++++++++ .../come2us/global/config/TestConfig.java | 6 + 6 files changed, 226 insertions(+), 47 deletions(-) create mode 100644 src/docs/asciidoc/_cart.adoc create mode 100644 src/test/java/com/irum/come2us/domain/cart/CartControllerTest.java diff --git a/src/docs/asciidoc/_cart.adoc b/src/docs/asciidoc/_cart.adoc new file mode 100644 index 00000000..a302f9c3 --- /dev/null +++ b/src/docs/asciidoc/_cart.adoc @@ -0,0 +1,46 @@ +== 장바구니 (Cart) API + +=== 1. 장바구니 추가 +`POST /carts` + +요청 본문: +include::{snippets}/cart-create/request-fields.adoc[] + +응답: +include::{snippets}/cart-create/http-response.adoc[] + +--- + +=== 2. 장바구니 목록 조회 +`GET /carts` + +응답 본문: +include::{snippets}/cart-get-list/response-fields.adoc[] + +응답: +include::{snippets}/cart-get-list/http-response.adoc[] + +--- + +=== 3. 장바구니 수량 수정 +`PATCH /carts/{cartId}` + +경로 파라미터 (Path Parameters): +include::{snippets}/cart-update/path-parameters.adoc[] + +요청 본문: +include::{snippets}/cart-update/request-fields.adoc[] + +응답: +include::{snippets}/cart-update/http-response.adoc[] + +--- + +=== 4. 장바구니 삭제 +`DELETE /carts/{cartId}` + +경로 파라미터 (Path Parameters): +include::{snippets}/cart-delete/path-parameters.adoc[] + +응답: +include::{snippets}/cart-delete/http-response.adoc[] diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index c84c9618..97a15bfa 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -16,3 +16,5 @@ include::_discount.adoc[] include::_deliverypolicy.adoc[] // include::_product.adoc[] +include::_cart.adoc[] + diff --git a/src/main/java/com/irum/come2us/domain/cart/application/service/CartService.java b/src/main/java/com/irum/come2us/domain/cart/application/service/CartService.java index a006a5f8..b5b4c86b 100644 --- a/src/main/java/com/irum/come2us/domain/cart/application/service/CartService.java +++ b/src/main/java/com/irum/come2us/domain/cart/application/service/CartService.java @@ -15,86 +15,54 @@ import java.util.UUID; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @Transactional -@Slf4j public class CartService { private final CartRepository cartRepository; private final ProductOptionValueRepository productOptionValueRepository; private final MemberUtil memberUtil; - public CartResponse createCart(CartCreateRequest request) { + public void createCart(CartCreateRequest request) { Member currentMember = memberUtil.getCurrentMember(); - // 옵션값 검증 ProductOptionValue optionValue = productOptionValueRepository .findById(request.optionValueId()) .orElseThrow( () -> new CommonException(CartErrorCode.OPTION_VALUE_NOT_FOUND)); - // 기존 동일 옵션 Cart 존재 시 수량 합산 Cart existing = cartRepository.findByMemberIdAndOptionValueId( currentMember.getMemberId(), request.optionValueId()); - Cart saved; if (existing != null) { - int oldQuantity = existing.getQuantity(); - int updatedQuantity = oldQuantity + request.quantity(); + int updatedQuantity = existing.getQuantity() + request.quantity(); existing.updateQuantity(updatedQuantity); - saved = existing; - - log.info( - "장바구니 수량 합산: memberId={}, optionValueId={}, oldQuantity={}, newQuantity={}", - currentMember.getMemberId(), - request.optionValueId(), - oldQuantity, - updatedQuantity); } else { Cart newCart = Cart.createCart(currentMember, optionValue, request.quantity()); - saved = cartRepository.save(newCart); - - log.info( - "장바구니 신규 추가: memberId={}, optionValueId={}, quantity={}", - currentMember.getMemberId(), - request.optionValueId(), - request.quantity()); + cartRepository.save(newCart); } - - return CartResponse.from(saved); } - public CartResponse updateCart(UUID cartId, CartUpdateRequest request) { + public void updateCart(UUID cartId, CartUpdateRequest request) { Cart cart = cartRepository .findById(cartId) .orElseThrow(() -> new CommonException(CartErrorCode.CART_NOT_FOUND)); memberUtil.assertMemberResourceAccess(cart.getMember()); - - if (cart.getQuantity().equals(request.quantity())) { - throw new CommonException(CartErrorCode.CART_NOT_MODIFIED); - } - cart.updateQuantity(request.quantity()); - log.info("장바구니 수정 완료: cartId={}, updatedQuantity={}", cartId, request.quantity()); - return CartResponse.from(cart); } @Transactional(readOnly = true) public List getCartListByMember() { Member currentMember = memberUtil.getCurrentMember(); - List carts = cartRepository.findAllWithProductByMemberId(currentMember.getMemberId()); - log.info("장바구니 조회 완료: memberId={}, count={}", currentMember.getMemberId(), carts.size()); - return carts.stream().map(CartResponse::from).collect(Collectors.toList()); } @@ -103,10 +71,7 @@ public void deleteCart(UUID cartId) { cartRepository .findById(cartId) .orElseThrow(() -> new CommonException(CartErrorCode.CART_NOT_FOUND)); - memberUtil.assertMemberResourceAccess(cart.getMember()); cartRepository.delete(cart); - - log.info("장바구니 삭제 완료: cartId={}", cartId); } } diff --git a/src/main/java/com/irum/come2us/domain/cart/presentation/controller/CartController.java b/src/main/java/com/irum/come2us/domain/cart/presentation/controller/CartController.java index 2c88e090..5c1d9581 100644 --- a/src/main/java/com/irum/come2us/domain/cart/presentation/controller/CartController.java +++ b/src/main/java/com/irum/come2us/domain/cart/presentation/controller/CartController.java @@ -9,6 +9,8 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @@ -20,30 +22,35 @@ public class CartController { private final CartService cartService; @PostMapping - public CartResponse createCart(@Valid @RequestBody CartCreateRequest request) { + public ResponseEntity createCart(@Valid @RequestBody CartCreateRequest request) { log.info( "장바구니 추가 요청: optionValueId={}, quantity={}", request.optionValueId(), request.quantity()); - return cartService.createCart(request); + cartService.createCart(request); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @PatchMapping("/{cartId}") - public CartResponse updateCart( + public ResponseEntity updateCart( @PathVariable UUID cartId, @Valid @RequestBody CartUpdateRequest request) { log.info("장바구니 수정 요청: cartId={}, quantity={}", cartId, request.quantity()); - return cartService.updateCart(cartId, request); + cartService.updateCart(cartId, request); + return ResponseEntity.noContent().build(); } @GetMapping - public List getMyCartList() { - log.info("본인 장바구니 조회 요청"); - return cartService.getCartListByMember(); + public ResponseEntity> getMyCartList() { + log.info("장바구니 목록 조회 요청"); + List responses = cartService.getCartListByMember(); + log.info("장바구니 조회 결과: {}개 항목", responses.size()); + return ResponseEntity.ok(responses); } @DeleteMapping("/{cartId}") - public void deleteCart(@PathVariable UUID cartId) { + public ResponseEntity deleteCart(@PathVariable UUID cartId) { log.info("장바구니 삭제 요청: cartId={}", cartId); cartService.deleteCart(cartId); + return ResponseEntity.noContent().build(); } } diff --git a/src/test/java/com/irum/come2us/domain/cart/CartControllerTest.java b/src/test/java/com/irum/come2us/domain/cart/CartControllerTest.java new file mode 100644 index 00000000..5083f97f --- /dev/null +++ b/src/test/java/com/irum/come2us/domain/cart/CartControllerTest.java @@ -0,0 +1,153 @@ +package com.irum.come2us.domain.cart; + +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.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.irum.come2us.domain.cart.application.service.CartService; +import com.irum.come2us.domain.cart.presentation.controller.CartController; +import com.irum.come2us.domain.cart.presentation.dto.request.CartCreateRequest; +import com.irum.come2us.domain.cart.presentation.dto.request.CartUpdateRequest; +import com.irum.come2us.domain.cart.presentation.dto.response.CartResponse; +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.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(CartController.class) +@AutoConfigureRestDocs +@Import({SecurityTestConfig.class, TestConfig.class}) +class CartControllerTest { + + @Autowired private MockMvc mockMvc; + @Autowired private CartService cartService; + @Autowired private ObjectMapper objectMapper; + + private final UUID mockCartId = UUID.randomUUID(); + private final UUID mockOptionValueId = UUID.randomUUID(); + + @Test + @DisplayName("장바구니 추가 API (CUSTOMER)") + void createCartApiTest() throws Exception { + CartCreateRequest request = new CartCreateRequest(1L, mockOptionValueId, 2); + String requestJson = objectMapper.writeValueAsString(request); + + doNothing().when(cartService).createCart(any(CartCreateRequest.class)); + + mockMvc.perform( + RestDocumentationRequestBuilders.post("/carts") + .with(csrf()) + .with(user("1").roles("CUSTOMER")) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isCreated()) + .andDo( + document( + "cart-create", + requestFields( + fieldWithPath("memberId").description("회원 ID"), + fieldWithPath("optionValueId").description("옵션 값 ID"), + fieldWithPath("quantity").description("상품 수량")))); + } + + @Test + @DisplayName("장바구니 목록 조회 API (CUSTOMER)") + void getCartListApiTest() throws Exception { + CartResponse response = + new CartResponse( + mockCartId, + mockOptionValueId, + "테스트 상품", + "옵션A", + "https://example.com/image.jpg", + 2, + 10000, + 500, + 10500, + 21000); + + when(cartService.getCartListByMember()).thenReturn(List.of(response)); + + mockMvc.perform( + RestDocumentationRequestBuilders.get("/carts") + .with(csrf().asHeader()) + .with(user("1").roles("CUSTOMER"))) + .andExpect(status().isOk()) + .andDo( + document( + "cart-get-list", + responseFields( + fieldWithPath("success").description("true"), + fieldWithPath("status").description("200"), + fieldWithPath("timestamp").description("응답 시간"), + fieldWithPath("data[].cartId").description("장바구니 ID"), + fieldWithPath("data[].optionValueId") + .description("옵션 값 ID"), + fieldWithPath("data[].productName").description("상품 이름"), + fieldWithPath("data[].optionValueName") + .description("옵션 이름"), + fieldWithPath("data[].imageUrl").description("대표 이미지 URL"), + fieldWithPath("data[].quantity").description("수량"), + fieldWithPath("data[].basePrice").description("상품 기본가"), + fieldWithPath("data[].extraPrice").description("옵션 추가금"), + fieldWithPath("data[].unitPrice").description("단가"), + fieldWithPath("data[].lineTotal").description("총 금액")))); + } + + @Test + @DisplayName("장바구니 수정 API (CUSTOMER)") + void updateCartApiTest() throws Exception { + CartUpdateRequest request = new CartUpdateRequest(5); + String requestJson = objectMapper.writeValueAsString(request); + + doNothing().when(cartService).updateCart(eq(mockCartId), any(CartUpdateRequest.class)); + + mockMvc.perform( + RestDocumentationRequestBuilders.patch("/carts/{cartId}", mockCartId) + .with(csrf()) + .with(user("1").roles("CUSTOMER")) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isNoContent()) + .andDo( + document( + "cart-update", + pathParameters( + parameterWithName("cartId").description("수정할 장바구니 ID")), + requestFields(fieldWithPath("quantity").description("변경할 수량")))); + } + + @Test + @DisplayName("장바구니 삭제 API (CUSTOMER)") + void deleteCartApiTest() throws Exception { + doNothing().when(cartService).deleteCart(eq(mockCartId)); + + mockMvc.perform( + RestDocumentationRequestBuilders.delete("/carts/{cartId}", mockCartId) + .with(csrf()) + .with(user("1").roles("CUSTOMER"))) + .andExpect(status().isNoContent()) + .andDo( + document( + "cart-delete", + pathParameters( + parameterWithName("cartId").description("삭제할 장바구니 ID")))); + } +} 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..ad01318c 100644 --- a/src/test/java/com/irum/come2us/global/config/TestConfig.java +++ b/src/test/java/com/irum/come2us/global/config/TestConfig.java @@ -2,6 +2,7 @@ import com.irum.come2us.domain.auth.application.service.AuthService; import com.irum.come2us.domain.auth.application.service.JwtTokenService; +import com.irum.come2us.domain.cart.application.service.CartService; import com.irum.come2us.domain.deliveryaddress.application.service.DeliveryAddressService; import com.irum.come2us.domain.discount.application.service.DiscountService; import com.irum.come2us.domain.member.application.service.ManagerService; @@ -53,4 +54,9 @@ public RefundService refundService() { public DiscountService discountService() { return Mockito.mock(DiscountService.class); } + + @Bean + public CartService cartService() { + return Mockito.mock(CartService.class); + } } From d15557019b4e7e4f539d1124417726f695093433 Mon Sep 17 00:00:00 2001 From: psy Date: Wed, 29 Oct 2025 11:23:15 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor=20#200:=20Cart=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=9A=94=EC=B2=AD=EC=97=90=EC=84=9C=20memberId=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=9D=91=EB=8B=B5=EC=97=90=20?= =?UTF-8?q?cartId=20=ED=8F=AC=ED=95=A8=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CartCreateRequest에서 memberId 필드 제거 (토큰 기반 회원 식별로 변경) - CartResponse에 cartId 포함하여 생성 후 식별 가능하도록 수정 - CartController 및 CartControllerTest 로직 수정 - REST Docs 스니펫 응답 필드(cartId) 반영 --- .../cart/application/service/CartService.java | 11 ++++-- .../controller/CartController.java | 6 ++-- .../dto/request/CartCreateRequest.java | 1 - .../domain/cart/CartControllerTest.java | 34 ++++++++++++++++--- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/irum/come2us/domain/cart/application/service/CartService.java b/src/main/java/com/irum/come2us/domain/cart/application/service/CartService.java index b5b4c86b..c1866923 100644 --- a/src/main/java/com/irum/come2us/domain/cart/application/service/CartService.java +++ b/src/main/java/com/irum/come2us/domain/cart/application/service/CartService.java @@ -27,7 +27,8 @@ public class CartService { private final ProductOptionValueRepository productOptionValueRepository; private final MemberUtil memberUtil; - public void createCart(CartCreateRequest request) { + @Transactional + public CartResponse createCart(CartCreateRequest request) { Member currentMember = memberUtil.getCurrentMember(); ProductOptionValue optionValue = @@ -40,13 +41,17 @@ public void createCart(CartCreateRequest request) { cartRepository.findByMemberIdAndOptionValueId( currentMember.getMemberId(), request.optionValueId()); + Cart target; if (existing != null) { int updatedQuantity = existing.getQuantity() + request.quantity(); existing.updateQuantity(updatedQuantity); + target = existing; } else { - Cart newCart = Cart.createCart(currentMember, optionValue, request.quantity()); - cartRepository.save(newCart); + target = Cart.createCart(currentMember, optionValue, request.quantity()); + cartRepository.save(target); } + + return CartResponse.from(target); } public void updateCart(UUID cartId, CartUpdateRequest request) { diff --git a/src/main/java/com/irum/come2us/domain/cart/presentation/controller/CartController.java b/src/main/java/com/irum/come2us/domain/cart/presentation/controller/CartController.java index 5c1d9581..6e87342d 100644 --- a/src/main/java/com/irum/come2us/domain/cart/presentation/controller/CartController.java +++ b/src/main/java/com/irum/come2us/domain/cart/presentation/controller/CartController.java @@ -22,13 +22,13 @@ public class CartController { private final CartService cartService; @PostMapping - public ResponseEntity createCart(@Valid @RequestBody CartCreateRequest request) { + public ResponseEntity createCart(@Valid @RequestBody CartCreateRequest request) { log.info( "장바구니 추가 요청: optionValueId={}, quantity={}", request.optionValueId(), request.quantity()); - cartService.createCart(request); - return ResponseEntity.status(HttpStatus.CREATED).build(); + CartResponse response = cartService.createCart(request); + return ResponseEntity.status(HttpStatus.CREATED).body(response); } @PatchMapping("/{cartId}") diff --git a/src/main/java/com/irum/come2us/domain/cart/presentation/dto/request/CartCreateRequest.java b/src/main/java/com/irum/come2us/domain/cart/presentation/dto/request/CartCreateRequest.java index 77fb200e..3ebfc505 100644 --- a/src/main/java/com/irum/come2us/domain/cart/presentation/dto/request/CartCreateRequest.java +++ b/src/main/java/com/irum/come2us/domain/cart/presentation/dto/request/CartCreateRequest.java @@ -5,6 +5,5 @@ import java.util.UUID; public record CartCreateRequest( - @NotNull(message = "회원 ID는 필수 입력값입니다.") Long memberId, @NotNull(message = "옵션 값 ID는 필수 입력값입니다.") UUID optionValueId, @Min(value = 1, message = "수량은 1개 이상이어야 합니다.") int quantity) {} diff --git a/src/test/java/com/irum/come2us/domain/cart/CartControllerTest.java b/src/test/java/com/irum/come2us/domain/cart/CartControllerTest.java index 5083f97f..471a7939 100644 --- a/src/test/java/com/irum/come2us/domain/cart/CartControllerTest.java +++ b/src/test/java/com/irum/come2us/domain/cart/CartControllerTest.java @@ -46,10 +46,23 @@ class CartControllerTest { @Test @DisplayName("장바구니 추가 API (CUSTOMER)") void createCartApiTest() throws Exception { - CartCreateRequest request = new CartCreateRequest(1L, mockOptionValueId, 2); + CartCreateRequest request = new CartCreateRequest(mockOptionValueId, 2); String requestJson = objectMapper.writeValueAsString(request); - doNothing().when(cartService).createCart(any(CartCreateRequest.class)); + CartResponse response = + new CartResponse( + mockCartId, + mockOptionValueId, + "테스트 상품", + "옵션A", + "https://example.com/image.jpg", + 2, + 10000, + 500, + 10500, + 21000); + + when(cartService.createCart(any(CartCreateRequest.class))).thenReturn(response); mockMvc.perform( RestDocumentationRequestBuilders.post("/carts") @@ -62,9 +75,22 @@ void createCartApiTest() throws Exception { document( "cart-create", requestFields( - fieldWithPath("memberId").description("회원 ID"), fieldWithPath("optionValueId").description("옵션 값 ID"), - fieldWithPath("quantity").description("상품 수량")))); + fieldWithPath("quantity").description("상품 수량")), + responseFields( + fieldWithPath("success").description("true"), + fieldWithPath("status").description("201"), + fieldWithPath("timestamp").description("응답 시간"), + fieldWithPath("data.cartId").description("장바구니 ID"), + fieldWithPath("data.optionValueId").description("옵션 값 ID"), + fieldWithPath("data.productName").description("상품 이름"), + fieldWithPath("data.optionValueName").description("옵션 이름"), + fieldWithPath("data.imageUrl").description("대표 이미지 URL"), + fieldWithPath("data.quantity").description("수량"), + fieldWithPath("data.basePrice").description("상품 기본가"), + fieldWithPath("data.extraPrice").description("옵션 추가금"), + fieldWithPath("data.unitPrice").description("단가"), + fieldWithPath("data.lineTotal").description("총 금액")))); } @Test From c5cfb3316f01bb5c880b2f26086fae14221fc58e Mon Sep 17 00:00:00 2001 From: isak-kang Date: Wed, 29 Oct 2025 17:41:59 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Testconfig=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/com/irum/come2us/global/config/TestConfig.java | 3 --- 1 file changed, 3 deletions(-) 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 c8e6abd9..ad01318c 100644 --- a/src/test/java/com/irum/come2us/global/config/TestConfig.java +++ b/src/test/java/com/irum/come2us/global/config/TestConfig.java @@ -7,7 +7,6 @@ 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; @@ -59,7 +58,5 @@ public DiscountService discountService() { @Bean public CartService cartService() { return Mockito.mock(CartService.class); - public ProductImageService productImageService() { - return Mockito.mock(ProductImageService.class); } }