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..b78bc8e9 --- /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); + } + + 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); + } + + 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..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 @@ -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); + 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/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/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..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 @@ -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,11 @@ 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); + 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/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/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/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..67cee6f0 --- /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))) + .thenReturn(1L); + + // when + Long result = pointShopAdminService.create(request); + + // then + assertThat(result).isEqualTo(1L); + verify(pointItemService).create(any(PointItemCreateCommand.class)); + verify(pointProductService, never()).create(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))) + .thenReturn(2L); + + // when + Long result = pointShopAdminService.create(request); + + // then + assertThat(result).isEqualTo(2L); + verify(pointProductService).create(any(PointProductCreateCommand.class)); + verify(pointItemService, never()).create(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); + } +} 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/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/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 - ); - } -} 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..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 @@ -74,4 +74,4 @@ public static PointProduct getMockPointProductWithStub() { 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..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 @@ -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; @@ -19,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 { @@ -28,6 +28,7 @@ class PointProductTest { static Media media; static Price price; static Stock stock; + static Category category; PointProduct pointProduct; @BeforeEach @@ -48,15 +49,6 @@ void setUp() { assertThat(pointProduct.getDisplayStatus()).isEqualTo(DisplayStatus.DISPLAY); } - @ParameterizedTest - @MethodSource("invalidPointProductTestCases") - void 상품_기본_정보는_필수_값이고_재고는_1개_이상이어야한다(Code code, BasicInfo basicInfo, Media media, - Price price, Stock stock) { - // given & when & then - assertThatThrownBy(() -> PointProduct.create(code, basicInfo, media, price, stock)) - .isInstanceOf(BusinessException.class); - } - @Test void 상품_코드가_수정된다() { // given @@ -224,12 +216,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) ); } -} \ 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 +}