From fa84c0618355f229f6acaa3458b0b660388fc0db Mon Sep 17 00:00:00 2001 From: Hwa Young Yang <110398814+sanchaehwa@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:11:11 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=83=81=EC=A0=90=20/=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=ED=85=9C=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20SQL=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V14__add_category_to_point_tables.sql | 26 ++++++++++++++++ .../pointshop/item/controller/DummyData.java | 30 ------------------ .../product/controller/DtoGenerator.java | 31 ------------------- 3 files changed, 26 insertions(+), 61 deletions(-) create mode 100644 src/main/resources/db/migration/V14__add_category_to_point_tables.sql delete mode 100644 src/test/java/com/example/green/domain/pointshop/item/controller/DummyData.java delete mode 100644 src/test/java/com/example/green/domain/pointshop/product/controller/DtoGenerator.java diff --git a/src/main/resources/db/migration/V14__add_category_to_point_tables.sql b/src/main/resources/db/migration/V14__add_category_to_point_tables.sql new file mode 100644 index 00000000..c1907b1d --- /dev/null +++ b/src/main/resources/db/migration/V14__add_category_to_point_tables.sql @@ -0,0 +1,26 @@ +-- point_item 테이블에 category 컬럼 추가 +ALTER TABLE point_item +ADD COLUMN category VARCHAR(20); + +-- 기존 point_item 데이터 → ITEM +UPDATE point_item +SET category = 'ITEM' +WHERE category IS NULL; + +-- NOT NULL 제약 추가 +ALTER TABLE point_item +ALTER COLUMN category SET NOT NULL; + + +-- point_products 테이블에 category 컬럼 추가 +ALTER TABLE point_products +ADD COLUMN category VARCHAR(20); + +-- 기존 point_products 데이터 → PRODUCT +UPDATE point_products +SET category = 'PRODUCT' +WHERE category IS NULL; + +-- NOT NULL 제약 추가 +ALTER TABLE point_products +ALTER COLUMN category SET NOT NULL; diff --git a/src/test/java/com/example/green/domain/pointshop/item/controller/DummyData.java b/src/test/java/com/example/green/domain/pointshop/item/controller/DummyData.java deleted file mode 100644 index ede28468..00000000 --- a/src/test/java/com/example/green/domain/pointshop/item/controller/DummyData.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.green.domain.pointshop.item.controller; - -import java.math.BigDecimal; - -import com.example.green.domain.pointshop.item.dto.request.CreatePointItemRequest; -import com.example.green.domain.pointshop.item.dto.request.UpdatePointItemRequest; - -public class DummyData { - - public static CreatePointItemRequest createPointItemRequest() { - return new CreatePointItemRequest( - "ITM-AA-001", - "맑은 뭉게 구름", - "하늘에서 포근한 구름이 내려와 식물을 감싸요. 몽글몽글 기분 좋은 하루!", - "https://thumbnail.url/rainbow-pot.jpg", - BigDecimal.valueOf(450) - ); - } - - public static UpdatePointItemRequest updatePointItemRequest() { - return new UpdatePointItemRequest( - "ITM-AA-002", - "행운의 네잎클로버", - "행운의 네잎클로버가 싱그럽게 피어나요. 오늘 하루도 행운 가득!", - "https://thumbnail.url/clover.jpg", - BigDecimal.valueOf(600) - ); - } - -} diff --git a/src/test/java/com/example/green/domain/pointshop/product/controller/DtoGenerator.java b/src/test/java/com/example/green/domain/pointshop/product/controller/DtoGenerator.java deleted file mode 100644 index e3a3bf8f..00000000 --- a/src/test/java/com/example/green/domain/pointshop/product/controller/DtoGenerator.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.green.domain.pointshop.product.controller; - -import java.math.BigDecimal; - -import com.example.green.domain.pointshop.product.controller.dto.PointProductCreateDto; -import com.example.green.domain.pointshop.product.controller.dto.PointProductUpdateDto; - -public class DtoGenerator { - - public static PointProductCreateDto getCreateDto() { - return new PointProductCreateDto( - "PRD-AA-001", - "상품1", - "내용", - "https://thumbnail.url/image.jpg", - BigDecimal.valueOf(1000), - 100 - ); - } - - public static PointProductUpdateDto getUpdateDto() { - return new PointProductUpdateDto( - "PRD-AA-001", - "상품1", - "내용", - "https://thumbnail.url/image.jpg", - BigDecimal.valueOf(1000), - 100 - ); - } -} From a3575bae04792612be6f47d6c5625b39f48efadb Mon Sep 17 00:00:00 2001 From: Hwa Young Yang <110398814+sanchaehwa@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:11:53 +0900 Subject: [PATCH 2/5] =?UTF-8?q?test:=20=EC=95=84=EC=9D=B4=ED=85=9C=20/=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=ED=83=AD=20=EB=B6=84=EB=A6=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PointShopAdminControllerTest.java | 124 +++++++++++ .../service/PointShopAdminServiceTest.java | 198 ++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 src/test/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminControllerTest.java create mode 100644 src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java diff --git a/src/test/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminControllerTest.java b/src/test/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminControllerTest.java new file mode 100644 index 00000000..85595701 --- /dev/null +++ b/src/test/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminControllerTest.java @@ -0,0 +1,124 @@ +package com.example.green.domain.pointshop.admin.controller; + +import static com.example.green.domain.pointshop.admin.controller.message.PointShopAdminResponseMessage.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.math.BigDecimal; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import com.example.green.domain.pointshop.admin.dto.request.AdminCreatePointShopRequest; +import com.example.green.domain.pointshop.admin.dto.request.AdminUpdatePointShopRequest; +import com.example.green.domain.pointshop.admin.service.PointShopAdminService; +import com.example.green.global.api.ApiTemplate; +import com.example.green.global.api.NoContent; +import com.example.green.template.base.BaseControllerUnitTest; + +import io.restassured.common.mapper.TypeRef; +import io.restassured.module.mockmvc.RestAssuredMockMvc; + +@WebMvcTest(PointShopAdminController.class) +class PointShopAdminControllerTest extends BaseControllerUnitTest { + + @MockitoBean + private PointShopAdminService pointShopAdminService; + + @Test + void 포인트샵_생성_요청에_성공한다() { + // given + AdminCreatePointShopRequest request = AdminCreatePointShopRequest.builder() + .code("ITEM001") + .name("테스트 상품") + .description("테스트 설명") + .thumbnailUrl("https://test.com/image.png") + .price(BigDecimal.valueOf(1000)) + .build(); + + when(pointShopAdminService.create(any(AdminCreatePointShopRequest.class))) + .thenReturn(1L); + + // when + ApiTemplate response = create(request); + + // then + assertThat(response.result()).isEqualTo(1L); + assertThat(response.message()) + .isEqualTo(POINT_ITEM_CREATION_SUCCESS.getMessage()); + } + + public static ApiTemplate create(AdminCreatePointShopRequest request) { + return RestAssuredMockMvc + .given().log().all() + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .when().post("/api/admin/point-shop/create") + .then().log().all() + .status(HttpStatus.OK) + .extract().as(new TypeRef<>() { + }); + } + + @Test + void 포인트샵_수정_요청에_성공한다() { + // given + AdminUpdatePointShopRequest request = AdminUpdatePointShopRequest.builder() + .code("ITEM001") + .name("테스트 상품") + .description("테스트 설명") + .thumbnailUrl("https://test.com/image.png") + .price(BigDecimal.valueOf(1000)) + .build(); + + doNothing().when(pointShopAdminService) + .update(any(AdminUpdatePointShopRequest.class), anyLong()); + + // when + NoContent response = update(1L, request); + + // then + assertThat(response.message()) + .isEqualTo(POINT_ITEM_UPDATE_SUCCESS.getMessage()); + } + + public static NoContent update(Long id, AdminUpdatePointShopRequest request) { + return RestAssuredMockMvc + .given().log().all() + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .when().put("/api/admin/point-shop/update/" + id) + .then().log().all() + .status(HttpStatus.OK) + .extract().as(new TypeRef<>() { + }); + } + + @Test + void 포인트샵_삭제_요청에_성공한다() { + // given + doNothing().when(pointShopAdminService).delete(anyLong()); + + // when + NoContent response = delete(1L); + + // then + assertThat(response.message()) + .isEqualTo(POINT_ITEM_DELETE_SUCCESS.getMessage()); + } + + public static NoContent delete(Long id) { + return RestAssuredMockMvc + .given().log().all() + .contentType(MediaType.APPLICATION_JSON) + .when().delete("/api/admin/point-shop/delete/" + id) + .then().log().all() + .status(HttpStatus.OK) + .extract().as(new TypeRef<>() { + }); + } +} diff --git a/src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java b/src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java new file mode 100644 index 00000000..d6a0c0d5 --- /dev/null +++ b/src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java @@ -0,0 +1,198 @@ +package com.example.green.domain.pointshop.admin.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.math.BigDecimal; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.example.green.domain.pointshop.admin.dto.request.AdminCreatePointShopRequest; +import com.example.green.domain.pointshop.admin.dto.request.AdminUpdatePointShopRequest; +import com.example.green.domain.pointshop.admin.exception.PointShopAdminException; +import com.example.green.domain.pointshop.item.entity.PointItem; +import com.example.green.domain.pointshop.item.repository.PointItemRepository; +import com.example.green.domain.pointshop.item.service.PointItemService; +import com.example.green.domain.pointshop.item.service.command.PointItemCreateCommand; +import com.example.green.domain.pointshop.item.service.command.PointItemUpdateCommand; +import com.example.green.domain.pointshop.product.entity.PointProduct; +import com.example.green.domain.pointshop.product.repository.PointProductRepository; +import com.example.green.domain.pointshop.product.service.PointProductService; +import com.example.green.domain.pointshop.product.service.command.PointProductCreateCommand; +import com.example.green.domain.pointshop.product.service.command.PointProductUpdateCommand; + +@ExtendWith(MockitoExtension.class) +class PointShopAdminServiceTest { + + @InjectMocks + private PointShopAdminService pointShopAdminService; + + @Mock + private PointItemService pointItemService; + + @Mock + private PointProductService pointProductService; + + @Mock + private PointItemRepository pointItemRepository; + + @Mock + private PointProductRepository pointProductRepository; + + @Test + void ITEM_타입_포인트샵_생성에_성공한다() { + // given + AdminCreatePointShopRequest request = AdminCreatePointShopRequest.builder() + .type(PointShopType.ITEM) + .code("ITM-AA-000") + .name("아이템") + .description("아이템 설명") + .thumbnailUrl("https://test.com/image.png") + .price(BigDecimal.valueOf(1000)) + .build(); + + when(pointItemService.create(any(PointItemCreateCommand.class), any())) + .thenReturn(1L); + + // when + Long result = pointShopAdminService.create(request); + + // then + assertThat(result).isEqualTo(1L); + verify(pointItemService).create(any(PointItemCreateCommand.class), any()); + verify(pointProductService, never()).create(any(), any()); + } + + @Test + void PRODUCT_타입_포인트샵_생성에_성공한다() { + // given + AdminCreatePointShopRequest request = AdminCreatePointShopRequest.builder() + .type(PointShopType.PRODUCT) + .code("PRD-AA-000") + .name("상품") + .description("상품 설명") + .thumbnailUrl("https://test.com/image.png") + .price(BigDecimal.valueOf(2000)) + .stock(10) + .build(); + + when(pointProductService.create(any(PointProductCreateCommand.class), any())) + .thenReturn(2L); + + // when + Long result = pointShopAdminService.create(request); + + // then + assertThat(result).isEqualTo(2L); + verify(pointProductService).create(any(PointProductCreateCommand.class), any()); + verify(pointItemService, never()).create(any(), any()); + } + + @Test + void 수정시_ITEM이_존재하면_ITEM_수정이_호출된다() { + // given + AdminUpdatePointShopRequest request = AdminUpdatePointShopRequest.builder() + .code("ITM-AA-000") + .name("아이템 수정") + .description("설명") + .thumbnailUrl("https://test.com/image.png") + .price(BigDecimal.valueOf(1500)) + .build(); + + when(pointItemRepository.findById(1L)) + .thenReturn(Optional.of(mock(PointItem.class))); + + // when + pointShopAdminService.update(request, 1L); + + // then + verify(pointItemService) + .updatePointItem(any(PointItemUpdateCommand.class), eq(1L)); + verify(pointProductService, never()) + .update(any(), anyLong()); + } + + @Test + void 수정시_ITEM이_없고_PRODUCT가_존재하면_PRODUCT_수정이_호출된다() { + // given + AdminUpdatePointShopRequest request = AdminUpdatePointShopRequest.builder() + .code("PRD-AA-000") + .name("상품 수정") + .description("설명") + .thumbnailUrl("https://test.com/image.png") + .price(BigDecimal.valueOf(3000)) + .stock(5) + .build(); + + when(pointItemRepository.findById(1L)) + .thenReturn(Optional.empty()); + when(pointProductRepository.findById(1L)) + .thenReturn(Optional.of(mock(PointProduct.class))); + + // when + pointShopAdminService.update(request, 1L); + + // then + verify(pointProductService) + .update(any(PointProductUpdateCommand.class), eq(1L)); + } + + @Test + void 수정시_ITEM과_PRODUCT가_모두_없으면_예외가_발생한다() { + // given + AdminUpdatePointShopRequest request = mock(AdminUpdatePointShopRequest.class); + + when(pointItemRepository.findById(1L)).thenReturn(Optional.empty()); + when(pointProductRepository.findById(1L)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> pointShopAdminService.update(request, 1L)) + .isInstanceOf(PointShopAdminException.class); + } + + @Test + void 삭제시_ITEM이_존재하면_ITEM_삭제가_호출된다() { + // given + when(pointItemRepository.findById(1L)) + .thenReturn(Optional.of(mock(PointItem.class))); + + // when + pointShopAdminService.delete(1L); + + // then + verify(pointItemService).delete(1L); + verify(pointProductService, never()).delete(anyLong()); + } + + @Test + void 삭제시_PRODUCT가_존재하면_PRODUCT_삭제가_호출된다() { + // given + when(pointItemRepository.findById(1L)) + .thenReturn(Optional.empty()); + when(pointProductRepository.findById(1L)) + .thenReturn(Optional.of(mock(PointProduct.class))); + + // when + pointShopAdminService.delete(1L); + + // then + verify(pointProductService).delete(1L); + } + + @Test + void 삭제시_ITEM과_PRODUCT가_모두_없으면_예외가_발생한다() { + // given + when(pointItemRepository.findById(1L)).thenReturn(Optional.empty()); + when(pointProductRepository.findById(1L)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> pointShopAdminService.delete(1L)) + .isInstanceOf(PointShopAdminException.class); + } +} From 275f228499fd650c0387f6aedbb4860488385bca Mon Sep 17 00:00:00 2001 From: Hwa Young Yang <110398814+sanchaehwa@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:12:30 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EC=95=84=EC=9D=B4=ED=85=9C=20/=20?= =?UTF-8?q?=EC=83=81=EC=A0=90=20=ED=83=AD=20=EB=B6=84=EB=A6=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PointShopAdminController.java | 52 ++++++++ .../docs/PointShopAdminControllerDocs.java | 45 +++++++ .../PointShopAdminResponseMessage.java | 17 +++ .../request/AdminCreatePointShopRequest.java | 44 +++++++ .../request/AdminUpdatePointShopRequest.java | 43 +++++++ .../exception/PointShopAdminException.java | 11 ++ .../PointShopAdminExceptionMessage.java | 22 ++++ .../admin/service/PointShopAdminService.java | 117 ++++++++++++++++++ .../admin/service/PointShopType.java | 6 + .../controller/PointItemAdminController.java | 43 ------- .../docs/PointItemAdminControllerDocs.java | 30 ----- .../dto/request/CreatePointItemRequest.java | 10 +- .../item/dto/response/PointItemResponse.java | 5 +- .../pointshop/item/entity/PointItem.java | 14 ++- .../pointshop/item/entity/vo/Category.java | 16 +++ .../item/service/PointItemService.java | 6 +- .../command/PointItemCreateCommand.java | 4 +- .../command/PointItemUpdateCommand.java | 4 +- .../PointProductAdminController.java | 46 +------ .../docs/PointProductAdminControllerDocs.java | 30 ----- .../product/entity/PointProduct.java | 13 +- .../product/service/PointProductService.java | 6 +- .../command/PointProductUpdateCommand.java | 4 +- .../PointItemAdminControllerTest.java | 75 ----------- .../PointItemOrderServiceControllerTest.java | 3 +- .../service/PointItemQueryServiceTest.java | 5 +- .../item/service/PointItemServiceTest.java | 115 ----------------- .../PointProductAdminControllerTest.java | 42 +------ .../PointProductControllerTest.java | 6 +- .../product/entity/PointProductTest.java | 23 ++-- .../service/PointProductServiceTest.java | 107 +--------------- .../PointProductConcurrencyTest.java | 4 +- 32 files changed, 448 insertions(+), 520 deletions(-) create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/controller/docs/PointShopAdminControllerDocs.java create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/controller/message/PointShopAdminResponseMessage.java create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminCreatePointShopRequest.java create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminUpdatePointShopRequest.java create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminExceptionMessage.java create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java create mode 100644 src/main/java/com/example/green/domain/pointshop/admin/service/PointShopType.java create mode 100644 src/main/java/com/example/green/domain/pointshop/item/entity/vo/Category.java diff --git a/src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java b/src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java new file mode 100644 index 00000000..d8e7bd78 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/controller/PointShopAdminController.java @@ -0,0 +1,52 @@ +package com.example.green.domain.pointshop.admin.controller; + +import static com.example.green.domain.pointshop.admin.controller.message.PointShopAdminResponseMessage.*; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.green.domain.pointshop.admin.controller.docs.PointShopAdminControllerDocs; +import com.example.green.domain.pointshop.admin.dto.request.AdminCreatePointShopRequest; +import com.example.green.domain.pointshop.admin.dto.request.AdminUpdatePointShopRequest; +import com.example.green.domain.pointshop.admin.service.PointShopAdminService; +import com.example.green.global.api.ApiTemplate; +import com.example.green.global.api.NoContent; +import com.example.green.global.security.annotation.AdminApi; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin/point-shop") +@AdminApi +public class PointShopAdminController implements PointShopAdminControllerDocs { + + private final PointShopAdminService pointShopAdminService; + + @PostMapping("/create") + public ApiTemplate create(@RequestBody @Valid AdminCreatePointShopRequest createPointShopRequest) { + Long result = pointShopAdminService.create(createPointShopRequest); + return ApiTemplate.ok(POINT_ITEM_CREATION_SUCCESS, result); + } + + @PutMapping("/update/{id}") + public NoContent update( + @PathVariable Long id, + @RequestBody @Valid AdminUpdatePointShopRequest updatePointShopRequest + ) { + pointShopAdminService.update(updatePointShopRequest, id); + return NoContent.ok(POINT_ITEM_UPDATE_SUCCESS); + } + + @DeleteMapping("/delete/{id}") + public NoContent delete(@PathVariable Long id) { + pointShopAdminService.delete(id); + return NoContent.ok(POINT_ITEM_DELETE_SUCCESS); + } +} diff --git a/src/main/java/com/example/green/domain/pointshop/admin/controller/docs/PointShopAdminControllerDocs.java b/src/main/java/com/example/green/domain/pointshop/admin/controller/docs/PointShopAdminControllerDocs.java new file mode 100644 index 00000000..3a5abb70 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/controller/docs/PointShopAdminControllerDocs.java @@ -0,0 +1,45 @@ +package com.example.green.domain.pointshop.admin.controller.docs; + +import com.example.green.domain.pointshop.admin.dto.request.AdminCreatePointShopRequest; +import com.example.green.domain.pointshop.admin.dto.request.AdminUpdatePointShopRequest; +import com.example.green.global.api.ApiTemplate; +import com.example.green.global.api.NoContent; +import com.example.green.global.error.dto.ExceptionResponse; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "포인트 상점 상품 / 아이템 어드민 API", description = "포인트 상점 상품 / 아이템 어드민 API 모음 입니다.") +public interface PointShopAdminControllerDocs { + + @Operation(summary = "포인트 아이템 / 상품 생성 (관리자)", description = "포인트 아이템 / 상품 을 생성합니다.") + @ApiResponse(responseCode = "200", description = "포인트 아이템 / 상품 생성에 성공했습니다.") + @ApiResponse( + responseCode = "400", description = "포인트 아이템 / 상품 생성 시 잘못된 정보 기입하면 발생", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + ApiTemplate create(AdminCreatePointShopRequest createPointShopRequest); + + @Operation(summary = "포인트 상점 아이템 / 상품 수정(관리자)", description = "포인트 상점 아이템 / 상품 정보를 수정합니다.") + @ApiResponse(responseCode = "200", description = "포인트 상점 아이템 / 상품 수정에 성공했습니다") + @ApiResponse( + responseCode = "400", description = "중복된 코드 또는 잘못된 데이터", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + @ApiResponse( + responseCode = "404", description = "아이템 / 상품 을 찾을 수 없습니다.", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + NoContent update(Long id, AdminUpdatePointShopRequest updatePointShopRequest); + + @Operation(summary = "포인트 상점 아이템 / 상품 삭제(관리자)", description = "포인트 상점 아이템 / 상품 을 삭제합니다.") + @ApiResponse(responseCode = "200", description = "아이템 / 상품 삭제에 성공했습니다") + @ApiResponse( + responseCode = "404", description = "아이템 / 상품 을 찾을 수 없습니다.", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + NoContent delete(Long id); +} diff --git a/src/main/java/com/example/green/domain/pointshop/admin/controller/message/PointShopAdminResponseMessage.java b/src/main/java/com/example/green/domain/pointshop/admin/controller/message/PointShopAdminResponseMessage.java new file mode 100644 index 00000000..33fb1625 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/controller/message/PointShopAdminResponseMessage.java @@ -0,0 +1,17 @@ +package com.example.green.domain.pointshop.admin.controller.message; + +import com.example.green.global.api.ResponseMessage; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum PointShopAdminResponseMessage implements ResponseMessage { + + POINT_ITEM_CREATION_SUCCESS("포인트 상점 아이템 / 상품 생성에 성공했습니다."), + POINT_ITEM_UPDATE_SUCCESS("포인트 상점 아이템 / 상품 수정에 성공했습니다."), + POINT_ITEM_DELETE_SUCCESS("아이템 / 상품수정 삭제에 성공했습니다."); + + private final String message; +} diff --git a/src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminCreatePointShopRequest.java b/src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminCreatePointShopRequest.java new file mode 100644 index 00000000..1f37ffa3 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminCreatePointShopRequest.java @@ -0,0 +1,44 @@ +package com.example.green.domain.pointshop.admin.dto.request; + +import java.math.BigDecimal; + +import com.example.green.domain.pointshop.admin.service.PointShopType; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; + +@Schema(description = "상품 생성 요청") +@Builder +public record AdminCreatePointShopRequest( + PointShopType type, + + @NotBlank(message = "상품 코드는 비어 있을 수 없습니다.") + @Schema(description = "상품 코드", example = "ITM-AA-001") + String code, + + @NotBlank(message = "상품명은 비어있을 수 없습니다.") + @Size(min = 2, max = 15, message = "상품명은 2글자 ~ 15글자 사이입니다.") + @Schema(description = "상품명", example = "맑은 뭉게 구름") + String name, + + @NotNull(message = "상품 설명은 필수입니다.") + @Size(max = 100, message = "상품 설명은 최대 100글자입니다.") + @Schema(description = "상품 설명", example = "하늘에서 포근한 구름이 내려와 식물을 감싸요. 몽글몽글 기분 좋은 하루!") + String description, + + @NotBlank(message = "상품 썸네일 이미지는 비어있을 수 없습니다.") + @Schema(description = "상품 썸네일", example = "https://example.com/image.png") + String thumbnailUrl, + + @NotNull(message = "상품 가격은 필수입니다.") + @Schema(description = "상품 가격", example = "10000") + BigDecimal price, + + @Schema(description = "상품 재고", example = "10") + Integer stock +) { + +} diff --git a/src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminUpdatePointShopRequest.java b/src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminUpdatePointShopRequest.java new file mode 100644 index 00000000..84924319 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/dto/request/AdminUpdatePointShopRequest.java @@ -0,0 +1,43 @@ +package com.example.green.domain.pointshop.admin.dto.request; + +import java.math.BigDecimal; + +import com.example.green.domain.pointshop.admin.service.PointShopType; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; + +@Schema(description = "상품 수정 요청") +@Builder +public record AdminUpdatePointShopRequest( + PointShopType type, + + @NotBlank(message = "상품 코드는 비어 있을 수 없습니다.") + @Schema(description = "상품 코드", example = "ITM-AA-001") + String code, + + @NotBlank(message = "상품명은 비어있을 수 없습니다.") + @Size(min = 2, max = 15, message = "상품명은 2글자 ~ 15글자 사이입니다.") + @Schema(description = "상품명", example = "맑은 뭉게 구름") + String name, + + @NotNull(message = "상품 설명은 필수입니다.") + @Size(max = 100, message = "상품 설명은 최대 100글자입니다.") + @Schema(description = "상품 설명", example = "하늘에서 포근한 구름이 내려와 식물을 감싸요. 몽글몽글 기분 좋은 하루!") + String description, + + @NotBlank(message = "상품 썸네일 이미지는 비어있을 수 없습니다.") + @Schema(description = "상품 썸네일", example = "https://example.com/image.png") + String thumbnailUrl, + + @NotNull(message = "상품 가격은 필수입니다.") + @Schema(description = "상품 가격", example = "10000") + BigDecimal price, + + @Schema(description = "상품 재고", example = "10") + Integer stock +) { +} diff --git a/src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java b/src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java new file mode 100644 index 00000000..5f5f94f3 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminException.java @@ -0,0 +1,11 @@ +package com.example.green.domain.pointshop.admin.exception; + +import com.example.green.global.error.exception.BusinessException; +import com.example.green.global.error.exception.ExceptionMessage; + +public class PointShopAdminException extends BusinessException { + + public PointShopAdminException(ExceptionMessage message) { + super(message); + } +} diff --git a/src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminExceptionMessage.java b/src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminExceptionMessage.java new file mode 100644 index 00000000..3394eb34 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/exception/PointShopAdminExceptionMessage.java @@ -0,0 +1,22 @@ +package com.example.green.domain.pointshop.admin.exception; + +import static org.springframework.http.HttpStatus.*; + +import org.springframework.http.HttpStatus; + +import com.example.green.global.error.exception.ExceptionMessage; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum PointShopAdminExceptionMessage implements ExceptionMessage { + + NOT_FOUND_CATEGORY(NOT_FOUND, "카테고리를 찾을 수가 없습니다"), + NOT_FOUND_PRODUCT(NOT_FOUND, "등록된 상품 / 아이템을 찾을 수 없습니다"); + + private final HttpStatus httpStatus; + private final String message; + +} diff --git a/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java b/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java new file mode 100644 index 00000000..37f4bcdf --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java @@ -0,0 +1,117 @@ +package com.example.green.domain.pointshop.admin.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.example.green.domain.pointshop.admin.dto.request.AdminCreatePointShopRequest; +import com.example.green.domain.pointshop.admin.dto.request.AdminUpdatePointShopRequest; +import com.example.green.domain.pointshop.admin.exception.PointShopAdminException; +import com.example.green.domain.pointshop.admin.exception.PointShopAdminExceptionMessage; +import com.example.green.domain.pointshop.item.entity.PointItem; +import com.example.green.domain.pointshop.item.entity.vo.Category; +import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; +import com.example.green.domain.pointshop.item.entity.vo.ItemCode; +import com.example.green.domain.pointshop.item.entity.vo.ItemMedia; +import com.example.green.domain.pointshop.item.entity.vo.ItemPrice; +import com.example.green.domain.pointshop.item.repository.PointItemRepository; +import com.example.green.domain.pointshop.item.service.PointItemService; +import com.example.green.domain.pointshop.item.service.command.PointItemCreateCommand; +import com.example.green.domain.pointshop.item.service.command.PointItemUpdateCommand; +import com.example.green.domain.pointshop.product.entity.PointProduct; +import com.example.green.domain.pointshop.product.entity.vo.BasicInfo; +import com.example.green.domain.pointshop.product.entity.vo.Code; +import com.example.green.domain.pointshop.product.entity.vo.Media; +import com.example.green.domain.pointshop.product.entity.vo.Price; +import com.example.green.domain.pointshop.product.entity.vo.Stock; +import com.example.green.domain.pointshop.product.repository.PointProductRepository; +import com.example.green.domain.pointshop.product.service.PointProductService; +import com.example.green.domain.pointshop.product.service.command.PointProductCreateCommand; +import com.example.green.domain.pointshop.product.service.command.PointProductUpdateCommand; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PointShopAdminService { + + private final PointItemService pointItemService; + private final PointProductService pointProductService; + private final PointItemRepository pointItemRepository; + private final PointProductRepository pointProductRepository; + + //생성 + public Long create(AdminCreatePointShopRequest request) { + + if (request.type() == PointShopType.ITEM) { + PointItemCreateCommand command = new PointItemCreateCommand( + new ItemCode(request.code()), + new ItemBasicInfo(request.name(), request.description()), + new ItemMedia(request.thumbnailUrl()), + new ItemPrice(request.price()), + Category.ITEM + ); + return pointItemService.create(command, Category.ITEM); + } + + if (request.type() == PointShopType.PRODUCT) { + PointProductCreateCommand command = new PointProductCreateCommand( + new Code(request.code()), + new BasicInfo(request.name(), request.description()), + new Media(request.thumbnailUrl()), + new Price(request.price()), + new Stock(request.stock()) + ); + return pointProductService.create(command, Category.PRODUCT); + } + + throw new PointShopAdminException(PointShopAdminExceptionMessage.NOT_FOUND_CATEGORY); + } + + //수정 + public void update(AdminUpdatePointShopRequest request, Long productId) { + //item 먼저 찾기 + Optional pointItem = pointItemRepository.findById(productId); + if (pointItem.isPresent()) { + PointItemUpdateCommand command = new PointItemUpdateCommand( + new ItemCode(request.code()), + new ItemBasicInfo(request.name(), request.description()), + new ItemMedia(request.thumbnailUrl()), + new ItemPrice(request.price()), + Category.ITEM + ); + pointItemService.updatePointItem(command, productId); + return; + } + //product 찾기 + Optional pointProduct = pointProductRepository.findById(productId); + if (pointProduct.isPresent()) { + PointProductUpdateCommand command = new PointProductUpdateCommand( + new Code(request.code()), + new BasicInfo(request.name(), request.description()), + new Media(request.thumbnailUrl()), + new Price(request.price()), + new Stock(request.stock()), + Category.PRODUCT + ); + pointProductService.update(command, productId); + return; + } + throw new PointShopAdminException(PointShopAdminExceptionMessage.NOT_FOUND_PRODUCT); + } + + //삭제 + public void delete(Long productId) { + Optional pointItem = pointItemRepository.findById(productId); + if (pointItem.isPresent()) { + pointItemService.delete(productId); + return; + } + Optional pointProduct = pointProductRepository.findById(productId); + if (pointProduct.isPresent()) { + pointProductService.delete(productId); + return; + } + throw new PointShopAdminException(PointShopAdminExceptionMessage.NOT_FOUND_PRODUCT); + } +} diff --git a/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopType.java b/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopType.java new file mode 100644 index 00000000..b9f5f531 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopType.java @@ -0,0 +1,6 @@ +package com.example.green.domain.pointshop.admin.service; + +public enum PointShopType { + ITEM, + PRODUCT +} diff --git a/src/main/java/com/example/green/domain/pointshop/item/controller/PointItemAdminController.java b/src/main/java/com/example/green/domain/pointshop/item/controller/PointItemAdminController.java index 5be7c778..2bcd062a 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/controller/PointItemAdminController.java +++ b/src/main/java/com/example/green/domain/pointshop/item/controller/PointItemAdminController.java @@ -5,37 +5,25 @@ import java.util.List; import org.springdoc.core.annotations.ParameterObject; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.example.green.domain.pointshop.item.controller.docs.PointItemAdminControllerDocs; import com.example.green.domain.pointshop.item.controller.message.PointItemResponseMessage; -import com.example.green.domain.pointshop.item.dto.request.CreatePointItemRequest; import com.example.green.domain.pointshop.item.dto.request.PointItemExcelDownloadRequest; import com.example.green.domain.pointshop.item.dto.request.PointItemSearchRequest; -import com.example.green.domain.pointshop.item.dto.request.UpdatePointItemRequest; import com.example.green.domain.pointshop.item.dto.response.ItemWithApplicantsDTO; import com.example.green.domain.pointshop.item.dto.response.PointItemAdminResponse; import com.example.green.domain.pointshop.item.dto.response.PointItemSearchResponse; -import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; -import com.example.green.domain.pointshop.item.entity.vo.ItemCode; -import com.example.green.domain.pointshop.item.entity.vo.ItemMedia; -import com.example.green.domain.pointshop.item.entity.vo.ItemPrice; import com.example.green.domain.pointshop.item.repository.PointItemOrderRepository; import com.example.green.domain.pointshop.item.repository.PointItemQueryRepository; import com.example.green.domain.pointshop.item.service.PointItemQueryService; import com.example.green.domain.pointshop.item.service.PointItemService; -import com.example.green.domain.pointshop.item.service.command.PointItemCreateCommand; -import com.example.green.domain.pointshop.item.service.command.PointItemUpdateCommand; import com.example.green.global.api.ApiTemplate; import com.example.green.global.api.NoContent; import com.example.green.global.api.page.PageTemplate; @@ -43,7 +31,6 @@ import com.example.green.infra.excel.core.ExcelDownloader; import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController @@ -58,13 +45,6 @@ public class PointItemAdminController implements PointItemAdminControllerDocs { private final PointItemOrderRepository pointItemOrderRepository; private final ExcelDownloader excelDownloader; - @PostMapping - public ApiTemplate createPointItem(@RequestBody @Valid CreatePointItemRequest createPointItemRequest) { - PointItemCreateCommand command = createPointItemRequest.toCommand(); - Long result = pointItemService.create(command); - return ApiTemplate.ok(PointItemResponseMessage.POINT_ITEM_CREATION_SUCCESS, result); - } - @GetMapping public ApiTemplate> findPointItems( @ParameterObject @ModelAttribute PointItemSearchRequest request @@ -73,35 +53,12 @@ public ApiTemplate> findPointItems( return ApiTemplate.ok(POINT_ITEMS_INQUIRY_SUCCESS, result); } - @PutMapping("/{pointItemId}") - public NoContent updatePointItem( - @RequestBody @Valid UpdatePointItemRequest updatePointItemRequest, - @PathVariable Long pointItemId - ) { - PointItemUpdateCommand command = new PointItemUpdateCommand( - new ItemCode(updatePointItemRequest.code()), - new ItemBasicInfo(updatePointItemRequest.name(), updatePointItemRequest.description()), - new ItemMedia(updatePointItemRequest.thumbnailUrl()), - new ItemPrice(updatePointItemRequest.price()) - ); - - pointItemService.updatePointItem(command, pointItemId); - - return NoContent.ok(POINT_ITEM_UPDATE_SUCCESS); - } - @GetMapping("/{pointItemId}") public ApiTemplate showPointItem(@PathVariable Long pointItemId) { PointItemAdminResponse response = pointItemQueryService.getPointItemAdminResponse(pointItemId); return ApiTemplate.ok(PointItemResponseMessage.POINT_ITEM_LOAD_SUCCESS, response); } - @DeleteMapping("/{pointItemId}") - public NoContent deletePointItem(@PathVariable Long pointItemId) { - pointItemService.delete(pointItemId); - return NoContent.ok(POINT_ITEM_DELETE_SUCCESS); - } - @PatchMapping("/{pointItemId}/show") public NoContent showPointItemDisplay(@PathVariable Long pointItemId) { pointItemService.showItemDisplay(pointItemId); diff --git a/src/main/java/com/example/green/domain/pointshop/item/controller/docs/PointItemAdminControllerDocs.java b/src/main/java/com/example/green/domain/pointshop/item/controller/docs/PointItemAdminControllerDocs.java index 91b9e8b8..1d6f6d9f 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/controller/docs/PointItemAdminControllerDocs.java +++ b/src/main/java/com/example/green/domain/pointshop/item/controller/docs/PointItemAdminControllerDocs.java @@ -1,9 +1,7 @@ package com.example.green.domain.pointshop.item.controller.docs; -import com.example.green.domain.pointshop.item.dto.request.CreatePointItemRequest; import com.example.green.domain.pointshop.item.dto.request.PointItemExcelDownloadRequest; import com.example.green.domain.pointshop.item.dto.request.PointItemSearchRequest; -import com.example.green.domain.pointshop.item.dto.request.UpdatePointItemRequest; import com.example.green.domain.pointshop.item.dto.response.ItemWithApplicantsDTO; import com.example.green.domain.pointshop.item.dto.response.PointItemAdminResponse; import com.example.green.domain.pointshop.item.dto.response.PointItemSearchResponse; @@ -23,14 +21,6 @@ @Tag(name = "포인트 상점 아이템 API(관리자)", description = "포인트 상점 아이템 관련 관리자용 API 문서입니다.") public interface PointItemAdminControllerDocs { - @Operation(summary = "포인트 상점 아이템 생성(관리자)", description = "포인트 상점 아이템을 생성합니다.") - @ApiResponse(responseCode = "200", description = "포인트 상점 아이템 생성에 성공했습니다") - @ApiResponse( - responseCode = "400", description = "잘못된 요청입니다.", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - ApiTemplate createPointItem(CreatePointItemRequest request); - @Operation(summary = "포인트 상점 아이템 목록 조회(관리자)", description = "조건에 맞는 포인트 아이템 목록을 조회합니다.") @ApiResponse(responseCode = "200", description = "아이템 목록 조회에 성공했습니다.") ApiTemplate> findPointItems(PointItemSearchRequest request); @@ -43,26 +33,6 @@ public interface PointItemAdminControllerDocs { ) ApiTemplate showPointItem(Long pointItemId); - @Operation(summary = "포인트 상점 아이템 수정(관리자)", description = "포인트 상점 아이템 정보를 수정합니다.") - @ApiResponse(responseCode = "200", description = "포인트 상점 아이템 수정에 성공했습니다") - @ApiResponse( - responseCode = "400", description = "중복된 코드 또는 잘못된 데이터", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - @ApiResponse( - responseCode = "404", description = "아이템을 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - NoContent updatePointItem(UpdatePointItemRequest request, Long pointItemId); - - @Operation(summary = "포인트 상점 아이템 삭제(관리자)", description = "포인트 상점 아이템을 삭제합니다.") - @ApiResponse(responseCode = "200", description = "아이템 삭제에 성공했습니다") - @ApiResponse( - responseCode = "404", description = "아이템을 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - NoContent deletePointItem(Long pointItemId); - @Operation(summary = "포인트 상점 아이템 전시(관리자)", description = "아이템을 전시 상태로 변경합니다.") @ApiResponse(responseCode = "200", description = "아이템이 전시 상태가 됐습니다") @ApiResponse( diff --git a/src/main/java/com/example/green/domain/pointshop/item/dto/request/CreatePointItemRequest.java b/src/main/java/com/example/green/domain/pointshop/item/dto/request/CreatePointItemRequest.java index 925d2d4c..7edd89ef 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/dto/request/CreatePointItemRequest.java +++ b/src/main/java/com/example/green/domain/pointshop/item/dto/request/CreatePointItemRequest.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.entity.vo.ItemMedia; @@ -34,14 +35,19 @@ public record CreatePointItemRequest( String thumbnailUrl, @NotNull(message = "아이템 상품 가격은 필수입니다.") @Schema(description = "아이템 상품 가격", example = "10000") - BigDecimal price + BigDecimal price, + + @Schema(description = "분류 카테고리", example = "ITEM") + Category category + ) { public PointItemCreateCommand toCommand() { return new PointItemCreateCommand( new ItemCode(code), new ItemBasicInfo(name, description), new ItemMedia(thumbnailUrl), - new ItemPrice(price) + new ItemPrice(price), + category ); } } diff --git a/src/main/java/com/example/green/domain/pointshop/item/dto/response/PointItemResponse.java b/src/main/java/com/example/green/domain/pointshop/item/dto/response/PointItemResponse.java index 017b1b1d..301ff96d 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/dto/response/PointItemResponse.java +++ b/src/main/java/com/example/green/domain/pointshop/item/dto/response/PointItemResponse.java @@ -2,13 +2,10 @@ import java.math.BigDecimal; -import com.example.green.domain.pointshop.product.entity.vo.SellingStatus; - public record PointItemResponse( long pointItemId, String pointItemName, String thumbnailUrl, - BigDecimal pointPrice, - SellingStatus sellingStatus + BigDecimal pointPrice ) { } diff --git a/src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java b/src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java index 2a441e51..a3ee3eb4 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java +++ b/src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java @@ -4,6 +4,7 @@ import static com.example.green.global.utils.EntityValidator.*; import com.example.green.domain.common.BaseEntity; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.entity.vo.ItemDisplayStatus; @@ -61,20 +62,25 @@ public class PointItem extends BaseEntity { @Enumerated(EnumType.STRING) private ItemDisplayStatus displayStatus; + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private Category category; + @Builder - public PointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, ItemPrice itemPrice - ) { + public PointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, ItemPrice itemPrice, + Category category) { validatePointItem(itemCode, itemBasicInfo, itemMedia, itemPrice); this.itemCode = itemCode; this.itemBasicInfo = itemBasicInfo; this.itemMedia = itemMedia; this.itemPrice = itemPrice; this.displayStatus = ItemDisplayStatus.DISPLAY; + this.category = category; } public static PointItem create(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, - ItemPrice itemPrice) { - return new PointItem(itemCode, itemBasicInfo, itemMedia, itemPrice); + ItemPrice itemPrice, Category category) { + return new PointItem(itemCode, itemBasicInfo, itemMedia, itemPrice, category); } private static void validatePointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, diff --git a/src/main/java/com/example/green/domain/pointshop/item/entity/vo/Category.java b/src/main/java/com/example/green/domain/pointshop/item/entity/vo/Category.java new file mode 100644 index 00000000..4e082072 --- /dev/null +++ b/src/main/java/com/example/green/domain/pointshop/item/entity/vo/Category.java @@ -0,0 +1,16 @@ +package com.example.green.domain.pointshop.item.entity.vo; + +import com.fasterxml.jackson.annotation.JsonValue; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Category { + PRODUCT("상품"), + ITEM("아이템"); + + @JsonValue + private final String value; +} diff --git a/src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java b/src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java index ec8b90fa..f89c4dac 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java +++ b/src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java @@ -8,6 +8,7 @@ import com.example.green.domain.pointshop.item.dto.response.PointItemClientResponse; import com.example.green.domain.pointshop.item.dto.response.UserPointCalculation; import com.example.green.domain.pointshop.item.entity.PointItem; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.exception.PointItemException; import com.example.green.domain.pointshop.item.exception.PointItemExceptionMessage; @@ -27,13 +28,14 @@ public class PointItemService { private final PointItemQueryService pointItemQueryService; private final FileClient fileClient; - public Long create(PointItemCreateCommand createCommand) { + public Long create(PointItemCreateCommand createCommand, Category category) { validatePointItemCode(createCommand.itemCode()); PointItem pointItem = PointItem.create( createCommand.itemCode(), createCommand.info(), createCommand.media(), - createCommand.price() + createCommand.price(), + category ); PointItem savedPointItem = pointItemRepository.save(pointItem); diff --git a/src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemCreateCommand.java b/src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemCreateCommand.java index 8868b358..25b1247f 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemCreateCommand.java +++ b/src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemCreateCommand.java @@ -1,5 +1,6 @@ package com.example.green.domain.pointshop.item.service.command; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.entity.vo.ItemMedia; @@ -9,7 +10,8 @@ public record PointItemCreateCommand( ItemCode itemCode, ItemBasicInfo info, ItemMedia media, - ItemPrice price + ItemPrice price, + Category category ) { } diff --git a/src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemUpdateCommand.java b/src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemUpdateCommand.java index b6214cbf..f0f489d6 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemUpdateCommand.java +++ b/src/main/java/com/example/green/domain/pointshop/item/service/command/PointItemUpdateCommand.java @@ -1,5 +1,6 @@ package com.example.green.domain.pointshop.item.service.command; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.entity.vo.ItemMedia; @@ -9,6 +10,7 @@ public record PointItemUpdateCommand( ItemCode itemCode, ItemBasicInfo info, ItemMedia media, - ItemPrice price + ItemPrice price, + Category category ) { } diff --git a/src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java b/src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java index 53a2773a..50508808 100644 --- a/src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java +++ b/src/main/java/com/example/green/domain/pointshop/product/controller/PointProductAdminController.java @@ -5,43 +5,29 @@ import java.util.List; import org.springdoc.core.annotations.ParameterObject; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.example.green.domain.pointshop.product.controller.docs.PointProductAdminControllerDocs; -import com.example.green.domain.pointshop.product.controller.dto.PointProductCreateDto; import com.example.green.domain.pointshop.product.controller.dto.PointProductDetailForAdmin; import com.example.green.domain.pointshop.product.controller.dto.PointProductExcelCondition; import com.example.green.domain.pointshop.product.controller.dto.PointProductSearchCondition; import com.example.green.domain.pointshop.product.controller.dto.PointProductSearchResult; -import com.example.green.domain.pointshop.product.controller.dto.PointProductUpdateDto; import com.example.green.domain.pointshop.product.entity.PointProduct; -import com.example.green.domain.pointshop.product.entity.vo.BasicInfo; -import com.example.green.domain.pointshop.product.entity.vo.Code; -import com.example.green.domain.pointshop.product.entity.vo.Media; -import com.example.green.domain.pointshop.product.entity.vo.Price; -import com.example.green.domain.pointshop.product.entity.vo.Stock; import com.example.green.domain.pointshop.product.repository.PointProductQueryRepository; import com.example.green.domain.pointshop.product.service.PointProductQueryService; import com.example.green.domain.pointshop.product.service.PointProductService; -import com.example.green.domain.pointshop.product.service.command.PointProductCreateCommand; -import com.example.green.domain.pointshop.product.service.command.PointProductUpdateCommand; import com.example.green.global.api.ApiTemplate; import com.example.green.global.api.NoContent; import com.example.green.global.api.page.PageTemplate; -import com.example.green.infra.excel.core.ExcelDownloader; import com.example.green.global.security.annotation.AdminApi; +import com.example.green.infra.excel.core.ExcelDownloader; import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController @@ -55,13 +41,6 @@ public class PointProductAdminController implements PointProductAdminControllerD private final PointProductQueryRepository pointProductQueryRepository; private final ExcelDownloader excelDownloader; - @PostMapping - public ApiTemplate createPointProduct(@RequestBody @Valid PointProductCreateDto dto) { - PointProductCreateCommand command = dto.toCommand(); - Long result = pointProductService.create(command); - return ApiTemplate.ok(POINT_PRODUCT_CREATION_SUCCESS, result); - } - @GetMapping public ApiTemplate> findPointProducts( @ParameterObject @ModelAttribute PointProductSearchCondition condition @@ -87,29 +66,6 @@ public void findPointProducts( excelDownloader.downloadAsStream(result, response); } - @PutMapping("/{pointProductId}") - public NoContent updatePointProduct( - @RequestBody @Valid PointProductUpdateDto dto, - @PathVariable Long pointProductId - ) { - PointProductUpdateCommand command = new PointProductUpdateCommand( - new Code(dto.code()), - new BasicInfo(dto.name(), dto.description()), - new Media(dto.thumbnailUrl()), - new Price(dto.price()), - new Stock(dto.stock()) - ); - - pointProductService.update(command, pointProductId); - return NoContent.ok(POINT_PRODUCT_UPDATE_SUCCESS); - } - - @DeleteMapping("/{pointProductId}") - public NoContent deletePointProduct(@PathVariable Long pointProductId) { - pointProductService.delete(pointProductId); - return NoContent.ok(POINT_PRODUCT_DELETE_SUCCESS); - } - @PatchMapping("/{pointProductId}/show") public NoContent showDisplay(@PathVariable Long pointProductId) { pointProductService.showDisplay(pointProductId); diff --git a/src/main/java/com/example/green/domain/pointshop/product/controller/docs/PointProductAdminControllerDocs.java b/src/main/java/com/example/green/domain/pointshop/product/controller/docs/PointProductAdminControllerDocs.java index 8befada6..9e3019df 100644 --- a/src/main/java/com/example/green/domain/pointshop/product/controller/docs/PointProductAdminControllerDocs.java +++ b/src/main/java/com/example/green/domain/pointshop/product/controller/docs/PointProductAdminControllerDocs.java @@ -1,11 +1,9 @@ package com.example.green.domain.pointshop.product.controller.docs; -import com.example.green.domain.pointshop.product.controller.dto.PointProductCreateDto; import com.example.green.domain.pointshop.product.controller.dto.PointProductDetailForAdmin; import com.example.green.domain.pointshop.product.controller.dto.PointProductExcelCondition; import com.example.green.domain.pointshop.product.controller.dto.PointProductSearchCondition; import com.example.green.domain.pointshop.product.controller.dto.PointProductSearchResult; -import com.example.green.domain.pointshop.product.controller.dto.PointProductUpdateDto; import com.example.green.global.api.ApiTemplate; import com.example.green.global.api.NoContent; import com.example.green.global.api.page.PageTemplate; @@ -21,14 +19,6 @@ @Tag(name = "포인트 상품 API", description = "포인트 상품 관련 API 모음 입니다.") public interface PointProductAdminControllerDocs { - @Operation(summary = "포인트 상품 생성 (관리자)", description = "포인트 상품을 생성합니다.") - @ApiResponse(responseCode = "200", description = "포인트 상품 생성에 성공했습니다.") - @ApiResponse( - responseCode = "400", description = "포인트 상품 생성 시 잘못된 정보 기입하면 발생", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - ApiTemplate createPointProduct(PointProductCreateDto dto); - @Operation(summary = "포인트 상품 목록 조회(관리자)", description = "포인트 상품 목록을 조회합니다.") @ApiResponse(responseCode = "200", description = "포인트 상품 목록 조회에 성공했습니다.") ApiTemplate> findPointProducts(PointProductSearchCondition condition); @@ -48,26 +38,6 @@ public interface PointProductAdminControllerDocs { ) void findPointProducts(PointProductExcelCondition condition, HttpServletResponse response); - @Operation(summary = "포인트 상품 수정(관리자)", description = "포인트 상품을 수정합니다.") - @ApiResponse(responseCode = "200", description = "포인트 상품 수정에 성공했습니다.") - @ApiResponse( - responseCode = "400", description = "중복된 상품 코드가 존재합니다.", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - @ApiResponse( - responseCode = "404", description = "포인트 상품을 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - NoContent updatePointProduct(PointProductUpdateDto dto, Long pointProductId); - - @Operation(summary = "포인트 상품 삭제(관리자)", description = "포인트 상품을 삭제합니다.") - @ApiResponse(responseCode = "200", description = "포인트 상품 삭제에 성공했습니다.") - @ApiResponse( - responseCode = "404", description = "포인트 상품을 찾을 수 없습니다.", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) - ) - NoContent deletePointProduct(Long pointProductId); - @Operation(summary = "포인트 상품 전시(관리자)", description = "포인트 상품을 전시합니다.") @ApiResponse(responseCode = "200", description = "포인트 상품이 전시 상태가 됐습니다.") @ApiResponse( diff --git a/src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java b/src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java index 739166c8..d55d1976 100644 --- a/src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java +++ b/src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java @@ -4,6 +4,7 @@ import static com.example.green.global.utils.EntityValidator.*; import com.example.green.domain.common.BaseEntity; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.product.entity.vo.BasicInfo; import com.example.green.domain.pointshop.product.entity.vo.Code; import com.example.green.domain.pointshop.product.entity.vo.DisplayStatus; @@ -65,8 +66,12 @@ public class PointProduct extends BaseEntity { @Enumerated(EnumType.STRING) private DisplayStatus displayStatus; + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private Category category; + @Builder - public PointProduct(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock) { + public PointProduct(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock, Category category) { validatePointProduct(code, basicInfo, media, price, stock); this.code = code; this.basicInfo = basicInfo; @@ -75,10 +80,12 @@ public PointProduct(Code code, BasicInfo basicInfo, Media media, Price price, St this.stock = stock; this.sellingStatus = SellingStatus.EXCHANGEABLE; this.displayStatus = DisplayStatus.DISPLAY; + this.category = category; } - public static PointProduct create(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock) { - return new PointProduct(code, basicInfo, media, price, stock); + public static PointProduct create(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock, + Category category) { + return new PointProduct(code, basicInfo, media, price, stock, category); } private static void validatePointProduct(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock) { diff --git a/src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java b/src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java index 46784d8b..29a3441f 100644 --- a/src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java +++ b/src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.order.entity.vo.ItemSnapshot; import com.example.green.domain.pointshop.product.entity.PointProduct; import com.example.green.domain.pointshop.product.entity.vo.Code; @@ -24,14 +25,15 @@ public class PointProductService { private final PointProductRepository pointProductRepository; private final FileClient fileClient; - public Long create(PointProductCreateCommand command) { + public Long create(PointProductCreateCommand command, Category category) { validateProductCode(command.code()); PointProduct pointProduct = PointProduct.create( command.code(), command.basicInfo(), command.media(), command.price(), - command.stock() + command.stock(), + category ); PointProduct saved = pointProductRepository.save(pointProduct); diff --git a/src/main/java/com/example/green/domain/pointshop/product/service/command/PointProductUpdateCommand.java b/src/main/java/com/example/green/domain/pointshop/product/service/command/PointProductUpdateCommand.java index 7b4bb25e..a6040fdf 100644 --- a/src/main/java/com/example/green/domain/pointshop/product/service/command/PointProductUpdateCommand.java +++ b/src/main/java/com/example/green/domain/pointshop/product/service/command/PointProductUpdateCommand.java @@ -1,5 +1,6 @@ package com.example.green.domain.pointshop.product.service.command; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.product.entity.vo.BasicInfo; import com.example.green.domain.pointshop.product.entity.vo.Code; import com.example.green.domain.pointshop.product.entity.vo.Media; @@ -11,6 +12,7 @@ public record PointProductUpdateCommand( BasicInfo basicInfo, Media media, Price price, - Stock stock + Stock stock, + Category category ) { } diff --git a/src/test/java/com/example/green/domain/pointshop/item/controller/PointItemAdminControllerTest.java b/src/test/java/com/example/green/domain/pointshop/item/controller/PointItemAdminControllerTest.java index 2e64a986..c9a478cb 100644 --- a/src/test/java/com/example/green/domain/pointshop/item/controller/PointItemAdminControllerTest.java +++ b/src/test/java/com/example/green/domain/pointshop/item/controller/PointItemAdminControllerTest.java @@ -13,17 +13,13 @@ import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; -import com.example.green.domain.pointshop.item.dto.request.CreatePointItemRequest; import com.example.green.domain.pointshop.item.dto.request.PointItemExcelDownloadRequest; import com.example.green.domain.pointshop.item.dto.request.PointItemSearchRequest; -import com.example.green.domain.pointshop.item.dto.request.UpdatePointItemRequest; import com.example.green.domain.pointshop.item.dto.response.PointItemSearchResponse; import com.example.green.domain.pointshop.item.repository.PointItemOrderRepository; import com.example.green.domain.pointshop.item.repository.PointItemQueryRepository; import com.example.green.domain.pointshop.item.service.PointItemQueryService; import com.example.green.domain.pointshop.item.service.PointItemService; -import com.example.green.domain.pointshop.item.service.command.PointItemCreateCommand; -import com.example.green.domain.pointshop.item.service.command.PointItemUpdateCommand; import com.example.green.global.api.ApiTemplate; import com.example.green.global.api.NoContent; import com.example.green.global.api.page.PageTemplate; @@ -52,32 +48,6 @@ class PointItemAdminControllerTest extends BaseControllerUnitTest { @MockitoBean private ExcelDownloader excelDownloader; - @Test - void 포인트_아이템_생성_요청에_성공한다() { - //given - CreatePointItemRequest createPointItemRequest = DummyData.createPointItemRequest(); - when(pointItemService.create(any(PointItemCreateCommand.class))).thenReturn(1L); - - //when - ApiTemplate response = create(createPointItemRequest); - - //then - assertThat(response.result()).isEqualTo(1L); - assertThat(response.message()).isEqualTo(POINT_ITEM_CREATION_SUCCESS.getMessage()); - } - - public static ApiTemplate create(CreatePointItemRequest createPointItemRequest) { - return RestAssuredMockMvc - .given().log().all() - .contentType(MediaType.APPLICATION_JSON) - .body(createPointItemRequest) - .when().post("/api/admin/point-items") - .then().log().all() - .status(HttpStatus.OK) - .extract().as(new TypeRef<>() { - }); - } - @Test void 포인트_아이템_목록_조회_요청에_성공한다() { //given @@ -106,51 +76,6 @@ public static ApiTemplate> searchItems() { }); } - @Test - void 포인트_아이템_수정_요청에_성공한다() { - //given - UpdatePointItemRequest updatePointItemRequest = DummyData.updatePointItemRequest(); - - // when - NoContent response = update(updatePointItemRequest); - - // then - assertThat(response.message()).isEqualTo(POINT_ITEM_UPDATE_SUCCESS.getMessage()); - verify(pointItemService).updatePointItem(any(PointItemUpdateCommand.class), anyLong()); - - } - - public static NoContent update(UpdatePointItemRequest updatePointItemRequest) { - return RestAssuredMockMvc - .given().log().all() - .contentType(MediaType.APPLICATION_JSON) - .body(updatePointItemRequest) - .when().put("/api/admin/point-items/1") - .then().log().all() - .status(HttpStatus.OK) - .extract().as(new TypeRef<>() { - }); - } - - @Test - void 포인트_아이템_삭제_요청_성공한다() { - //when - NoContent deleteResponse = delete(1L); - assertThat(deleteResponse.message()).isEqualTo(POINT_ITEM_DELETE_SUCCESS.getMessage()); - - } - - public static NoContent delete(long id) { - return RestAssuredMockMvc - .given().log().all() - .contentType(MediaType.APPLICATION_JSON) - .when().delete("/api/admin/point-items/" + id) - .then().log().all() - .status(HttpStatus.OK) - .extract().as(new TypeRef<>() { - }); - } - @Test void 포인트_아이템_전시_요청_성공한다() { //when diff --git a/src/test/java/com/example/green/domain/pointshop/item/controller/PointItemOrderServiceControllerTest.java b/src/test/java/com/example/green/domain/pointshop/item/controller/PointItemOrderServiceControllerTest.java index 6a3612e8..016ef79d 100644 --- a/src/test/java/com/example/green/domain/pointshop/item/controller/PointItemOrderServiceControllerTest.java +++ b/src/test/java/com/example/green/domain/pointshop/item/controller/PointItemOrderServiceControllerTest.java @@ -19,6 +19,7 @@ import com.example.green.domain.pointshop.item.dto.request.OrderPointItemRequest; import com.example.green.domain.pointshop.item.dto.response.OrderPointItemResponse; import com.example.green.domain.pointshop.item.entity.PointItem; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.entity.vo.ItemMedia; @@ -51,7 +52,7 @@ class PointItemOrderServiceControllerTest extends BaseControllerUnitTest { ItemMedia itemMedia = new ItemMedia("https://thumbnail.url/rainbow-pot.jpg"); ItemPrice itemPrice = new ItemPrice(BigDecimal.valueOf(400)); - PointItem pointItem = new PointItem(itemCode, itemBasicInfo, itemMedia, itemPrice); + PointItem pointItem = new PointItem(itemCode, itemBasicInfo, itemMedia, itemPrice, Category.ITEM); when(pointItemRepository.findById(itemId)).thenReturn(Optional.of(pointItem)); diff --git a/src/test/java/com/example/green/domain/pointshop/item/service/PointItemQueryServiceTest.java b/src/test/java/com/example/green/domain/pointshop/item/service/PointItemQueryServiceTest.java index 0ad1e630..5c1b0459 100644 --- a/src/test/java/com/example/green/domain/pointshop/item/service/PointItemQueryServiceTest.java +++ b/src/test/java/com/example/green/domain/pointshop/item/service/PointItemQueryServiceTest.java @@ -13,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.example.green.domain.pointshop.item.entity.PointItem; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.entity.vo.ItemMedia; @@ -74,7 +75,9 @@ class PointItemQueryServiceTest { new ItemCode("ITM-AA-002"), new ItemBasicInfo("무지개 ", "무지개가 식물을 감싸요. 날씨가 좋을 것 같은 하루!"), new ItemMedia("https://thumbnail.url/image.jpg"), - new ItemPrice(BigDecimal.valueOf(450)))); + new ItemPrice(BigDecimal.valueOf(450)), + Category.ITEM + )); //when when(repository.findById(anyLong())).thenReturn(Optional.of(dummyEntity)); PointItem pointItem = service.getPointItem(1L); diff --git a/src/test/java/com/example/green/domain/pointshop/item/service/PointItemServiceTest.java b/src/test/java/com/example/green/domain/pointshop/item/service/PointItemServiceTest.java index 87537685..08540ac4 100644 --- a/src/test/java/com/example/green/domain/pointshop/item/service/PointItemServiceTest.java +++ b/src/test/java/com/example/green/domain/pointshop/item/service/PointItemServiceTest.java @@ -1,8 +1,6 @@ package com.example.green.domain.pointshop.item.service; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.*; -import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.math.BigDecimal; @@ -16,14 +14,9 @@ import com.example.green.domain.pointshop.item.dto.response.UserPointCalculation; import com.example.green.domain.pointshop.item.entity.PointItem; import com.example.green.domain.pointshop.item.entity.vo.ItemBasicInfo; -import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.entity.vo.ItemMedia; import com.example.green.domain.pointshop.item.entity.vo.ItemPrice; -import com.example.green.domain.pointshop.item.exception.PointItemException; -import com.example.green.domain.pointshop.item.exception.PointItemExceptionMessage; import com.example.green.domain.pointshop.item.repository.PointItemRepository; -import com.example.green.domain.pointshop.item.service.command.PointItemCreateCommand; -import com.example.green.domain.pointshop.item.service.command.PointItemUpdateCommand; import com.example.green.infra.client.FileClient; @ExtendWith(MockitoExtension.class) @@ -41,97 +34,6 @@ class PointItemServiceTest { @InjectMocks private PointItemService pointItemService; - @Test - void 포인트_아이템_상품을_생성하고_등록한다() { - //given - PointItemCreateCommand command = getCreateItemCommand(); - PointItem dummyEntity = mock(PointItem.class); - when(pointItemRepository.existsByItemCode(any(ItemCode.class))).thenReturn(false); - when(dummyEntity.getId()).thenReturn(1L); - when(pointItemRepository.save(any(PointItem.class))).thenReturn(dummyEntity); - - //when - Long result = pointItemService.create(command); - - //then - assertThat(result).isEqualTo(1L); - assertThat(command.info().getItemName()).isEqualTo("맑은 뭉게 구름"); - assertEquals(new BigDecimal("1200.00"), command.price().getItemPrice()); - } - - @Test - void 포인트_아이템_상품_생성시_중복된_코드가_존재하면_예외발생() { - //given - PointItemCreateCommand command = getCreateItemCommand(); - when(pointItemRepository.existsByItemCode(any(ItemCode.class))).thenReturn(true); - - assertThatThrownBy(() -> pointItemService.create(command)) - .isInstanceOf(PointItemException.class) - .hasMessage(PointItemExceptionMessage.EXISTS_ITEM_CODE.getMessage()); - } - - @Test - void 포인트_아이템_상품_수정시_새로운_이미지가_아닌경우_기본정보만변경() { - - //given - PointItemUpdateCommand command = getUpdateItemCommand(); - PointItem dummyEntity = spy( - new PointItem( - new ItemCode("ITM-AA-002"), - new ItemBasicInfo("무지개 ", "무지개가 식물을 감싸요. 날씨가 좋을 것 같은 하루!"), - new ItemMedia("https://thumbnail.url/image.jpg"), - new ItemPrice(BigDecimal.valueOf(450)) - )); - when(pointItemQueryService.getPointItem(anyLong())).thenReturn(dummyEntity); - when(dummyEntity.isNewImage(command.media())).thenReturn(false); - - //when - pointItemService.updatePointItem(command, 1L); - - //then - verify(pointItemQueryService).validateUniqueCodeForUpdate(command.itemCode(), 1L); - assertThat(dummyEntity.getItemPrice()).isEqualTo(new ItemPrice(BigDecimal.valueOf(600))); - assertThat(dummyEntity.getItemBasicInfo().getItemName()).isEqualTo("행운의 네잎클로버"); - } - - @Test - void 포인트_아이템_상품_수정하는경우_새로운_이미지라면_이미지_정보가_수정되고_사용_및_미사용_처리한다() { - PointItemUpdateCommand command = getUpdateItemCommand(); - PointItem dummyEntity = spy( - new PointItem( - new ItemCode("ITM-AA-002"), - new ItemBasicInfo("무지개 ", "무지개가 식물을 감싸요. 날씨가 좋을 것 같은 하루!"), - new ItemMedia("https://thumbnail.url/image.jpg"), - new ItemPrice(BigDecimal.valueOf(450)) - )); - String oldImageUrl = "https://thumbnail.url/image.jpg"; - String newImageUrl = command.media().getItemThumbNailUrl(); - - when(pointItemQueryService.getPointItem(anyLong())).thenReturn(dummyEntity); - doReturn(true).when(dummyEntity).isNewImage(command.media()); - doReturn(oldImageUrl).when(dummyEntity).getThumbnailUrl(); - - pointItemService.updatePointItem(command, 1L); - - verify(pointItemQueryService).validateUniqueCodeForUpdate(command.itemCode(), 1L); - verify(fileClient).unUseImage(oldImageUrl); // 기존 이미지 비활성화 - verify(fileClient).confirmUsingImage(newImageUrl); // 새 이미지 활성화 - verify(dummyEntity).updateItemMedia(command.media()); // 도메인 객체에 반영됨 - } - - @Test - void 아이템_삭제한다() { - //given - PointItem dummyEntity = mock(PointItem.class); - when(pointItemQueryService.getPointItem(anyLong())).thenReturn(dummyEntity); - - //when - pointItemService.delete(1L); - - //then - verify(dummyEntity).markDeleted(); - } - @Test void 로그인_안한_사용자_아이템_조회() { // given @@ -196,21 +98,4 @@ class PointItemServiceTest { verify(pointItemQueryService).userPointsCalculate(memberId, itemId); } - - private PointItemCreateCommand getCreateItemCommand() { - return new PointItemCreateCommand( - new ItemCode("ITM-AA-001"), - new ItemBasicInfo("맑은 뭉게 구름 ", "하늘에서 포근한 구름이 내려와 식물을 감싸요. 몽글몽글 기분 좋은 하루!"), - new ItemMedia("https://thumbnail.url/image.jpg"), - new ItemPrice(BigDecimal.valueOf(1200)) - ); - } - - private PointItemUpdateCommand getUpdateItemCommand() { - return new PointItemUpdateCommand( - new ItemCode("ITM-AA-001"), - new ItemBasicInfo("행운의 네잎클로버 ", "행운의 네잎클로버가 식물을 감싸요. 뭔가 운이 좋을 것 같은 하루!"), - new ItemMedia("https://thumbnail.url/image.jpg"), - new ItemPrice(BigDecimal.valueOf(600))); - } } diff --git a/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductAdminControllerTest.java b/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductAdminControllerTest.java index e3f275ca..4ef8b614 100644 --- a/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductAdminControllerTest.java +++ b/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductAdminControllerTest.java @@ -11,19 +11,15 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; -import com.example.green.domain.pointshop.product.controller.dto.PointProductCreateDto; import com.example.green.domain.pointshop.product.controller.dto.PointProductDetailForAdmin; import com.example.green.domain.pointshop.product.controller.dto.PointProductExcelCondition; import com.example.green.domain.pointshop.product.controller.dto.PointProductSearchCondition; import com.example.green.domain.pointshop.product.controller.dto.PointProductSearchResult; -import com.example.green.domain.pointshop.product.controller.dto.PointProductUpdateDto; import com.example.green.domain.pointshop.product.entity.PointProduct; import com.example.green.domain.pointshop.product.entity.vo.SellingStatus; import com.example.green.domain.pointshop.product.repository.PointProductQueryRepository; import com.example.green.domain.pointshop.product.service.PointProductQueryService; import com.example.green.domain.pointshop.product.service.PointProductService; -import com.example.green.domain.pointshop.product.service.command.PointProductCreateCommand; -import com.example.green.domain.pointshop.product.service.command.PointProductUpdateCommand; import com.example.green.global.api.ApiTemplate; import com.example.green.global.api.NoContent; import com.example.green.global.api.page.PageTemplate; @@ -44,20 +40,6 @@ class PointProductAdminControllerTest extends BaseControllerUnitTest { @MockitoBean private ExcelDownloader excelDownloader; - @Test - void 포인트_상품_생성_요청에_성공한다() { - // given - PointProductCreateDto dto = DtoGenerator.getCreateDto(); - when(pointProductService.create(any(PointProductCreateCommand.class))).thenReturn(1L); - - // when - ApiTemplate response = PointProductRequest.create(dto); - - // then - assertThat(response.result()).isEqualTo(1L); - assertThat(response.message()).isEqualTo(POINT_PRODUCT_CREATION_SUCCESS.getMessage()); - } - @Test void 포인트_상품_목록_조회에_성공한다() { // given @@ -89,19 +71,6 @@ private static PointProductSearchCondition getSearchCondition() { return new PointProductSearchCondition(1, 1, "keyword", SellingStatus.SOLD_OUT); } - @Test - void 포인트_상품_수정_요청에_성공한다() { - // given - PointProductUpdateDto dto = DtoGenerator.getUpdateDto(); - - // when - NoContent response = PointProductRequest.update(dto); - - // then - assertThat(response.message()).isEqualTo(POINT_PRODUCT_UPDATE_SUCCESS.getMessage()); - verify(pointProductService).update(any(PointProductUpdateCommand.class), anyLong()); - } - @Test void 포인트_아이디가_주어지면_포인트_상품_상세_조회에_성공한다() { // given @@ -117,15 +86,6 @@ private static PointProductSearchCondition getSearchCondition() { assertThat(response.message()).isEqualTo(POINT_PRODUCT_DETAIL_INQUIRY_SUCCESS.getMessage()); } - @Test - void 포인트_상품_삭제_요청에_성공한다() { - // when - NoContent response = PointProductRequest.delete(1L); - - // then - assertThat(response.message()).isEqualTo(POINT_PRODUCT_DELETE_SUCCESS.getMessage()); - } - @Test void 포인트_상품_전시_요청에_성공한다() { // when @@ -141,4 +101,4 @@ private static PointProductSearchCondition getSearchCondition() { // then assertThat(response.message()).isEqualTo(DISPLAY_HIDE_SUCCESS.getMessage()); } -} \ No newline at end of file +} diff --git a/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java b/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java index d5342752..605a3899 100644 --- a/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java +++ b/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java @@ -12,6 +12,7 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.util.ReflectionTestUtils; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.product.controller.dto.PointProductDetail; import com.example.green.domain.pointshop.product.controller.dto.PointProductView; import com.example.green.domain.pointshop.product.entity.PointProduct; @@ -70,8 +71,9 @@ public static PointProduct getMockPointProductWithStub() { Media media = new Media("https://image.png"); Stock mockStock = new Stock(10); Price mockPrice = new Price(BigDecimal.ONE); - PointProduct pointProduct = PointProduct.create(code, mockBasicInfo, media, mockPrice, mockStock); + Category category = Category.PRODUCT; + PointProduct pointProduct = PointProduct.create(code, mockBasicInfo, media, mockPrice, mockStock, category); ReflectionTestUtils.setField(pointProduct, "id", 1L); return pointProduct; } -} \ No newline at end of file +} diff --git a/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java b/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java index cb469a25..50acaac6 100644 --- a/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java +++ b/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.product.entity.vo.BasicInfo; import com.example.green.domain.pointshop.product.entity.vo.Code; import com.example.green.domain.pointshop.product.entity.vo.DisplayStatus; @@ -28,6 +29,7 @@ class PointProductTest { static Media media; static Price price; static Stock stock; + static Category category; PointProduct pointProduct; @BeforeEach @@ -37,7 +39,8 @@ void setUp() { media = mock(Media.class); price = mock(Price.class); stock = mock(Stock.class); - pointProduct = PointProduct.create(code, basicInfo, media, price, stock); + category = mock(Category.class); + pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); } @Test @@ -51,9 +54,9 @@ void setUp() { @ParameterizedTest @MethodSource("invalidPointProductTestCases") void 상품_기본_정보는_필수_값이고_재고는_1개_이상이어야한다(Code code, BasicInfo basicInfo, Media media, - Price price, Stock stock) { + Price price, Stock stock, Category category) { // given & when & then - assertThatThrownBy(() -> PointProduct.create(code, basicInfo, media, price, stock)) + assertThatThrownBy(() -> PointProduct.create(code, basicInfo, media, price, stock, category)) .isInstanceOf(BusinessException.class); } @@ -112,7 +115,7 @@ void setUp() { @Test void 상품_재고_정보가_수정된다() { // given - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); Stock newStock = new Stock(100); // when @@ -125,7 +128,7 @@ void setUp() { @Test void 상품_재고가_0개로_수정되면_매진_상태이다() { // given - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); Stock newStock = new Stock(0); // when @@ -139,7 +142,7 @@ void setUp() { @Test void 상품이_매진_상태일_때_1개_이상으로_수정하면_교환_가능상태이다() { // given - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); pointProduct.updateStock(new Stock(0)); Stock newStock = new Stock(1); @@ -155,7 +158,7 @@ void setUp() { void 상품_재고가_감소한다() { // given Stock stock = new Stock(100); - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); int amount = 50; // when @@ -169,7 +172,7 @@ void setUp() { void 상품_재고가_감소시_재고가_남아있지_않다면_매진_상태가_된다() { // given Stock stock = new Stock(100); - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); int amount = 100; // when @@ -206,7 +209,7 @@ void setUp() { void 새로운_이미지_정보인지_알_수_있다(Media newMeida, boolean expected) { // given media = new Media("https://example1.com/image.jpg"); - pointProduct = PointProduct.create(code, basicInfo, media, price, stock); + pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); // when boolean result = pointProduct.isNewImage(newMeida); @@ -232,4 +235,4 @@ static Stream invalidPointProductTestCases() { Arguments.of(code, basicInfo, media, price, new Stock(0)) ); } -} \ No newline at end of file +} diff --git a/src/test/java/com/example/green/domain/pointshop/product/service/PointProductServiceTest.java b/src/test/java/com/example/green/domain/pointshop/product/service/PointProductServiceTest.java index d4e367b9..ea14c1a6 100644 --- a/src/test/java/com/example/green/domain/pointshop/product/service/PointProductServiceTest.java +++ b/src/test/java/com/example/green/domain/pointshop/product/service/PointProductServiceTest.java @@ -1,6 +1,5 @@ package com.example.green.domain.pointshop.product.service; -import static com.example.green.domain.pointshop.product.exception.PointProductExceptionMessage.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; @@ -16,13 +15,8 @@ import com.example.green.domain.pointshop.product.entity.PointProduct; import com.example.green.domain.pointshop.product.entity.vo.BasicInfo; import com.example.green.domain.pointshop.product.entity.vo.Code; -import com.example.green.domain.pointshop.product.entity.vo.Media; import com.example.green.domain.pointshop.product.entity.vo.Price; -import com.example.green.domain.pointshop.product.entity.vo.Stock; -import com.example.green.domain.pointshop.product.exception.PointProductException; import com.example.green.domain.pointshop.product.repository.PointProductRepository; -import com.example.green.domain.pointshop.product.service.command.PointProductCreateCommand; -import com.example.green.domain.pointshop.product.service.command.PointProductUpdateCommand; import com.example.green.infra.client.FileClient; @ExtendWith(MockitoExtension.class) @@ -38,85 +32,6 @@ class PointProductServiceTest { @InjectMocks private PointProductService pointProductService; - @Test - void 포인트_상품을_생성하고_등록한다() { - // given - PointProductCreateCommand command = getCreateCommand(); - PointProduct mockEntity = mock(PointProduct.class); - when(pointProductRepository.existsByCode(any(Code.class))).thenReturn(false); - when(mockEntity.getId()).thenReturn(1L); - when(pointProductRepository.save(any(PointProduct.class))).thenReturn(mockEntity); - - // when - Long result = pointProductService.create(command); - - // then - assertThat(result).isEqualTo(1L); - } - - @Test - void 포인트_상품_생성시_중복된_코드가_존재하면_예외가_발생한다() { - // given - PointProductCreateCommand command = getCreateCommand(); - when(pointProductRepository.existsByCode(any(Code.class))).thenReturn(true); - - // when & then - assertThatThrownBy(() -> pointProductService.create(command)) - .isInstanceOf(PointProductException.class) - .hasFieldOrPropertyWithValue("exceptionMessage", EXISTS_PRODUCT_CODE); - } - - @Test - void 포인트_상품_수정시_새로운_이미지가_아니면_기본_정보만_변경된다() { - // given - PointProductUpdateCommand command = getUpdateCommand(); - PointProduct mockPointProduct = mock(PointProduct.class); - when(pointProductQueryService.getPointProduct(anyLong())).thenReturn(mockPointProduct); - when(mockPointProduct.isNewImage(command.media())).thenReturn(false); - - // when - pointProductService.update(command, 1L); - - // then - verify(pointProductQueryService).validateUniqueCodeForUpdate(command.code(), 1L); - verify(mockPointProduct).updateBasicInfo(command.basicInfo()); - verify(mockPointProduct).updatePrice(command.price()); - verify(mockPointProduct).updateStock(command.stock()); - } - - @Test - void 포인트_상품_수정시_새로운_이미지라면_이미지_정보가_수정되고_사용_및_미사용_처리한다() { - // given - PointProductUpdateCommand command = getUpdateCommand(); - PointProduct mockPointProduct = mock(PointProduct.class); - String oldImageUrl = "oldImageUrl"; - when(pointProductQueryService.getPointProduct(anyLong())).thenReturn(mockPointProduct); - when(mockPointProduct.isNewImage(command.media())).thenReturn(true); - when(mockPointProduct.getThumbnailUrl()).thenReturn(oldImageUrl) - .thenReturn(command.media().getThumbnailUrl()); - - // when - pointProductService.update(command, 1L); - - // then - verify(fileClient).unUseImage(oldImageUrl); - verify(mockPointProduct).updateMedia(command.media()); - verify(fileClient).confirmUsingImage(command.media().getThumbnailUrl()); - } - - @Test - void 포인트_상품을_삭제한다() { - // given - PointProduct mockPointProduct = mock(PointProduct.class); - when(pointProductQueryService.getPointProduct(anyLong())).thenReturn(mockPointProduct); - - // when - pointProductService.delete(1L); - - // then - verify(mockPointProduct).markDeleted(); - } - @Test void 포인트_상품을_전시한다() { // given @@ -181,24 +96,4 @@ class PointProductServiceTest { // then verify(mockPointProduct).decreaseStock(10); } - - private PointProductUpdateCommand getUpdateCommand() { - return new PointProductUpdateCommand( - new Code("PRD-AA-001"), - new BasicInfo("상품명", "상품 소개"), - new Media("https://thumbnail.url/image.jpg"), - new Price(BigDecimal.valueOf(1000)), - new Stock(50) - ); - } - - private PointProductCreateCommand getCreateCommand() { - return new PointProductCreateCommand( - new Code("PRD-AA-001"), - new BasicInfo("상품명", "상품 소개"), - new Media("https://thumbnail.url/image.jpg"), - new Price(BigDecimal.valueOf(1000)), - new Stock(50) - ); - } -} \ No newline at end of file +} diff --git a/src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java b/src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java index bb65ad34..45978860 100644 --- a/src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java +++ b/src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.product.entity.PointProduct; import com.example.green.domain.pointshop.product.entity.vo.BasicInfo; import com.example.green.domain.pointshop.product.entity.vo.Code; @@ -40,7 +41,8 @@ void setUp() { new BasicInfo("name", "desc"), new Media("http://thumbnail.com"), new Price(BigDecimal.valueOf(1000)), - stock + stock, + Category.PRODUCT )); } From b3c1227f76050fb329d8369d3ec1126b68ef9bf3 Mon Sep 17 00:00:00 2001 From: Hwa Young Yang <110398814+sanchaehwa@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:27:56 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20PointProduct=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../product/entity/PointProductTest.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java b/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java index 50acaac6..20cc4739 100644 --- a/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java +++ b/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java @@ -20,7 +20,6 @@ import com.example.green.domain.pointshop.product.entity.vo.Price; import com.example.green.domain.pointshop.product.entity.vo.SellingStatus; import com.example.green.domain.pointshop.product.entity.vo.Stock; -import com.example.green.global.error.exception.BusinessException; class PointProductTest { @@ -51,15 +50,6 @@ void setUp() { assertThat(pointProduct.getDisplayStatus()).isEqualTo(DisplayStatus.DISPLAY); } - @ParameterizedTest - @MethodSource("invalidPointProductTestCases") - void 상품_기본_정보는_필수_값이고_재고는_1개_이상이어야한다(Code code, BasicInfo basicInfo, Media media, - Price price, Stock stock, Category category) { - // given & when & then - assertThatThrownBy(() -> PointProduct.create(code, basicInfo, media, price, stock, category)) - .isInstanceOf(BusinessException.class); - } - @Test void 상품_코드가_수정된다() { // given @@ -227,12 +217,13 @@ public static Stream newImageTestCases() { static Stream invalidPointProductTestCases() { return Stream.of( - Arguments.of(null, basicInfo, media, price, stock), - Arguments.of(code, null, media, price, stock), - Arguments.of(code, basicInfo, null, price, stock), - Arguments.of(code, basicInfo, media, null, stock), - Arguments.of(code, basicInfo, media, price, null), - Arguments.of(code, basicInfo, media, price, new Stock(0)) + Arguments.of(null, basicInfo, media, price, stock, category), + Arguments.of(code, null, media, price, stock, category), + Arguments.of(code, basicInfo, null, price, stock, category), + Arguments.of(code, basicInfo, media, null, stock, category), + Arguments.of(code, basicInfo, media, price, null, category), + Arguments.of(code, basicInfo, media, price, new Stock(0), category), + Arguments.of(code, basicInfo, media, price, stock, Category.PRODUCT) ); } } From 7ee2567ff2b2935bcf27d21c00a32bd4d902ccbc Mon Sep 17 00:00:00 2001 From: Hwa Young Yang <110398814+sanchaehwa@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:44:02 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=83=81=EC=A0=90=20=EC=95=84=EC=9D=B4=ED=85=9C=20/=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EC=97=94=ED=8B=B0=ED=8B=B0=20Category=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/PointShopAdminService.java | 4 ++-- .../domain/pointshop/item/entity/PointItem.java | 4 ++-- .../pointshop/item/service/PointItemService.java | 6 ++---- .../pointshop/product/entity/PointProduct.java | 5 ++--- .../product/service/PointProductService.java | 6 ++---- .../admin/service/PointShopAdminServiceTest.java | 12 ++++++------ .../controller/PointProductControllerTest.java | 4 +--- .../product/entity/PointProductTest.java | 15 +++++++-------- .../pointproduct/PointProductConcurrencyTest.java | 4 +--- 9 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java b/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java index 37f4bcdf..b78bc8e9 100644 --- a/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java +++ b/src/main/java/com/example/green/domain/pointshop/admin/service/PointShopAdminService.java @@ -51,7 +51,7 @@ public Long create(AdminCreatePointShopRequest request) { new ItemPrice(request.price()), Category.ITEM ); - return pointItemService.create(command, Category.ITEM); + return pointItemService.create(command); } if (request.type() == PointShopType.PRODUCT) { @@ -62,7 +62,7 @@ public Long create(AdminCreatePointShopRequest request) { new Price(request.price()), new Stock(request.stock()) ); - return pointProductService.create(command, Category.PRODUCT); + return pointProductService.create(command); } throw new PointShopAdminException(PointShopAdminExceptionMessage.NOT_FOUND_CATEGORY); diff --git a/src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java b/src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java index a3ee3eb4..53d64c85 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java +++ b/src/main/java/com/example/green/domain/pointshop/item/entity/PointItem.java @@ -79,8 +79,8 @@ public PointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemM } public static PointItem create(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, - ItemPrice itemPrice, Category category) { - return new PointItem(itemCode, itemBasicInfo, itemMedia, itemPrice, category); + ItemPrice itemPrice) { + return new PointItem(itemCode, itemBasicInfo, itemMedia, itemPrice, Category.ITEM); } private static void validatePointItem(ItemCode itemCode, ItemBasicInfo itemBasicInfo, ItemMedia itemMedia, diff --git a/src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java b/src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java index f89c4dac..ec8b90fa 100644 --- a/src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java +++ b/src/main/java/com/example/green/domain/pointshop/item/service/PointItemService.java @@ -8,7 +8,6 @@ import com.example.green.domain.pointshop.item.dto.response.PointItemClientResponse; import com.example.green.domain.pointshop.item.dto.response.UserPointCalculation; import com.example.green.domain.pointshop.item.entity.PointItem; -import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.item.entity.vo.ItemCode; import com.example.green.domain.pointshop.item.exception.PointItemException; import com.example.green.domain.pointshop.item.exception.PointItemExceptionMessage; @@ -28,14 +27,13 @@ public class PointItemService { private final PointItemQueryService pointItemQueryService; private final FileClient fileClient; - public Long create(PointItemCreateCommand createCommand, Category category) { + public Long create(PointItemCreateCommand createCommand) { validatePointItemCode(createCommand.itemCode()); PointItem pointItem = PointItem.create( createCommand.itemCode(), createCommand.info(), createCommand.media(), - createCommand.price(), - category + createCommand.price() ); PointItem savedPointItem = pointItemRepository.save(pointItem); diff --git a/src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java b/src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java index d55d1976..2511b294 100644 --- a/src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java +++ b/src/main/java/com/example/green/domain/pointshop/product/entity/PointProduct.java @@ -83,9 +83,8 @@ public PointProduct(Code code, BasicInfo basicInfo, Media media, Price price, St this.category = category; } - public static PointProduct create(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock, - Category category) { - return new PointProduct(code, basicInfo, media, price, stock, category); + public static PointProduct create(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock) { + return new PointProduct(code, basicInfo, media, price, stock, Category.PRODUCT); } private static void validatePointProduct(Code code, BasicInfo basicInfo, Media media, Price price, Stock stock) { diff --git a/src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java b/src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java index 29a3441f..46784d8b 100644 --- a/src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java +++ b/src/main/java/com/example/green/domain/pointshop/product/service/PointProductService.java @@ -3,7 +3,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.order.entity.vo.ItemSnapshot; import com.example.green.domain.pointshop.product.entity.PointProduct; import com.example.green.domain.pointshop.product.entity.vo.Code; @@ -25,15 +24,14 @@ public class PointProductService { private final PointProductRepository pointProductRepository; private final FileClient fileClient; - public Long create(PointProductCreateCommand command, Category category) { + public Long create(PointProductCreateCommand command) { validateProductCode(command.code()); PointProduct pointProduct = PointProduct.create( command.code(), command.basicInfo(), command.media(), command.price(), - command.stock(), - category + command.stock() ); PointProduct saved = pointProductRepository.save(pointProduct); diff --git a/src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java b/src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java index d6a0c0d5..67cee6f0 100644 --- a/src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java +++ b/src/test/java/com/example/green/domain/pointshop/admin/service/PointShopAdminServiceTest.java @@ -57,7 +57,7 @@ class PointShopAdminServiceTest { .price(BigDecimal.valueOf(1000)) .build(); - when(pointItemService.create(any(PointItemCreateCommand.class), any())) + when(pointItemService.create(any(PointItemCreateCommand.class))) .thenReturn(1L); // when @@ -65,8 +65,8 @@ class PointShopAdminServiceTest { // then assertThat(result).isEqualTo(1L); - verify(pointItemService).create(any(PointItemCreateCommand.class), any()); - verify(pointProductService, never()).create(any(), any()); + verify(pointItemService).create(any(PointItemCreateCommand.class)); + verify(pointProductService, never()).create(any()); } @Test @@ -82,7 +82,7 @@ class PointShopAdminServiceTest { .stock(10) .build(); - when(pointProductService.create(any(PointProductCreateCommand.class), any())) + when(pointProductService.create(any(PointProductCreateCommand.class))) .thenReturn(2L); // when @@ -90,8 +90,8 @@ class PointShopAdminServiceTest { // then assertThat(result).isEqualTo(2L); - verify(pointProductService).create(any(PointProductCreateCommand.class), any()); - verify(pointItemService, never()).create(any(), any()); + verify(pointProductService).create(any(PointProductCreateCommand.class)); + verify(pointItemService, never()).create(any()); } @Test diff --git a/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java b/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java index 605a3899..1b4e0291 100644 --- a/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java +++ b/src/test/java/com/example/green/domain/pointshop/product/controller/PointProductControllerTest.java @@ -12,7 +12,6 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.util.ReflectionTestUtils; -import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.product.controller.dto.PointProductDetail; import com.example.green.domain.pointshop.product.controller.dto.PointProductView; import com.example.green.domain.pointshop.product.entity.PointProduct; @@ -71,8 +70,7 @@ public static PointProduct getMockPointProductWithStub() { Media media = new Media("https://image.png"); Stock mockStock = new Stock(10); Price mockPrice = new Price(BigDecimal.ONE); - Category category = Category.PRODUCT; - PointProduct pointProduct = PointProduct.create(code, mockBasicInfo, media, mockPrice, mockStock, category); + PointProduct pointProduct = PointProduct.create(code, mockBasicInfo, media, mockPrice, mockStock); ReflectionTestUtils.setField(pointProduct, "id", 1L); return pointProduct; } diff --git a/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java b/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java index 20cc4739..058482dd 100644 --- a/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java +++ b/src/test/java/com/example/green/domain/pointshop/product/entity/PointProductTest.java @@ -38,8 +38,7 @@ void setUp() { media = mock(Media.class); price = mock(Price.class); stock = mock(Stock.class); - category = mock(Category.class); - pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); + pointProduct = PointProduct.create(code, basicInfo, media, price, stock); } @Test @@ -105,7 +104,7 @@ void setUp() { @Test void 상품_재고_정보가_수정된다() { // given - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); Stock newStock = new Stock(100); // when @@ -118,7 +117,7 @@ void setUp() { @Test void 상품_재고가_0개로_수정되면_매진_상태이다() { // given - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); Stock newStock = new Stock(0); // when @@ -132,7 +131,7 @@ void setUp() { @Test void 상품이_매진_상태일_때_1개_이상으로_수정하면_교환_가능상태이다() { // given - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); pointProduct.updateStock(new Stock(0)); Stock newStock = new Stock(1); @@ -148,7 +147,7 @@ void setUp() { void 상품_재고가_감소한다() { // given Stock stock = new Stock(100); - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); int amount = 50; // when @@ -162,7 +161,7 @@ void setUp() { void 상품_재고가_감소시_재고가_남아있지_않다면_매진_상태가_된다() { // given Stock stock = new Stock(100); - PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); + PointProduct pointProduct = PointProduct.create(code, basicInfo, media, price, stock); int amount = 100; // when @@ -199,7 +198,7 @@ void setUp() { void 새로운_이미지_정보인지_알_수_있다(Media newMeida, boolean expected) { // given media = new Media("https://example1.com/image.jpg"); - pointProduct = PointProduct.create(code, basicInfo, media, price, stock, category); + pointProduct = PointProduct.create(code, basicInfo, media, price, stock); // when boolean result = pointProduct.isNewImage(newMeida); diff --git a/src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java b/src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java index 45978860..bb65ad34 100644 --- a/src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java +++ b/src/test/java/com/example/integration/pointproduct/PointProductConcurrencyTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import com.example.green.domain.pointshop.item.entity.vo.Category; import com.example.green.domain.pointshop.product.entity.PointProduct; import com.example.green.domain.pointshop.product.entity.vo.BasicInfo; import com.example.green.domain.pointshop.product.entity.vo.Code; @@ -41,8 +40,7 @@ void setUp() { new BasicInfo("name", "desc"), new Media("http://thumbnail.com"), new Price(BigDecimal.valueOf(1000)), - stock, - Category.PRODUCT + stock )); }