From db8f68021c731afdb2008732c1b5ec046b99aa5d Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Sun, 28 Jan 2024 06:24:04 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat=20:=20CouponStatus=20=EC=97=AD?= =?UTF-8?q?=EC=A7=81=EB=A0=AC=ED=99=94=20=ED=95=A8=EC=88=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gream/domain/coupon/entity/CouponStatus.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/bc1/gream/domain/coupon/entity/CouponStatus.java b/src/main/java/bc1/gream/domain/coupon/entity/CouponStatus.java index 173bc0fa..14d34f8b 100644 --- a/src/main/java/bc1/gream/domain/coupon/entity/CouponStatus.java +++ b/src/main/java/bc1/gream/domain/coupon/entity/CouponStatus.java @@ -1,7 +1,20 @@ package bc1.gream.domain.coupon.entity; +import bc1.gream.global.common.ResultCase; +import bc1.gream.global.exception.GlobalException; +import com.fasterxml.jackson.annotation.JsonCreator; +import java.util.stream.Stream; + public enum CouponStatus { AVAILABLE, IN_USE, - ALREADY_USED + ALREADY_USED; + + @JsonCreator + public static CouponStatus deserializeRequest(String couponStatusRequest) { + return Stream.of(CouponStatus.values()) + .filter(couponStatus -> couponStatus.toString().equals(couponStatusRequest.toUpperCase())) + .findAny() + .orElseThrow(() -> new GlobalException(ResultCase.INVALID_INPUT)); + } } From f6067994aa28b17ce33bb6d83f39a984ef4fefa1 Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Sun, 28 Jan 2024 06:24:19 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat=20:=20DiscountType=20=EC=97=AD?= =?UTF-8?q?=EC=A7=81=EB=A0=AC=ED=99=94=20=ED=95=A8=EC=88=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bc1/gream/domain/coupon/entity/DiscountType.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/bc1/gream/domain/coupon/entity/DiscountType.java b/src/main/java/bc1/gream/domain/coupon/entity/DiscountType.java index 8b751793..20ff7fa0 100644 --- a/src/main/java/bc1/gream/domain/coupon/entity/DiscountType.java +++ b/src/main/java/bc1/gream/domain/coupon/entity/DiscountType.java @@ -2,6 +2,8 @@ import bc1.gream.global.common.ResultCase; import bc1.gream.global.exception.GlobalException; +import com.fasterxml.jackson.annotation.JsonCreator; +import java.util.stream.Stream; public enum DiscountType { RATE { @@ -23,5 +25,13 @@ public Long calculateDiscount(Coupon coupon, Long price) { } }; + @JsonCreator + public static DiscountType deserializeRequest(String discountTypeRequest) { + return Stream.of(DiscountType.values()) + .filter(discountType -> discountType.toString().equals(discountTypeRequest.toUpperCase())) + .findAny() + .orElseThrow(() -> new GlobalException(ResultCase.INVALID_INPUT)); + } + public abstract Long calculateDiscount(Coupon coupon, Long price); } From 69453a5bd230b38c72429ae6f505cae139e7f0bc Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Sun, 28 Jan 2024 06:24:45 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat=20:=20GlobalExceptionHandler=20?= =?UTF-8?q?=EC=97=90=20BindException=20=ED=95=B8=EB=93=A4=EB=9F=AC=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/bc1/gream/global/exception/GlobalExceptionHandler.java b/src/main/java/bc1/gream/global/exception/GlobalExceptionHandler.java index 3a0dfa0d..61a8970e 100644 --- a/src/main/java/bc1/gream/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/bc1/gream/global/exception/GlobalExceptionHandler.java @@ -7,6 +7,7 @@ import bc1.gream.global.common.ResultCase; import java.util.List; import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -14,6 +15,12 @@ @RestControllerAdvice public class GlobalExceptionHandler { + /** + * RequestBody 파라미터 검증 오류 발생에 대한 핸들러 + * + * @param ex RequestBody 파라미터 검증오류에 따른 MethodArgumentNotValidException + * @return 잘못입력된 값 리스트 + */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity>> handlerValidationException(MethodArgumentNotValidException ex) { List invalidInputList = ex.getBindingResult() @@ -25,6 +32,29 @@ public ResponseEntity>> handlerValida return RestResponse.error(ResultCase.INVALID_INPUT, invalidInputList); } + /** + * ModelAttribute 파라미터 검증 오류 발생에 대한 핸들러 + * + * @param ex ModelAttribute 파라미터 검증오류에 따른 BindException + * @return 잘못입력된 값 리스트 + */ + @ExceptionHandler(BindException.class) + public ResponseEntity>> handlerValidationException(BindException ex) { + List invalidInputList = ex.getBindingResult() + .getFieldErrors() + .stream() + .map(InvalidInputMapper.INSTANCE::toInvalidInputResponseDto) + .toList(); + + return RestResponse.error(ResultCase.INVALID_INPUT, invalidInputList); + } + + /** + * Business 오류 발생에 대한 핸들러 + * + * @param e Business 오류에 따른 GlobalException + * @return 에러케이스와 에러리스폰스 + */ @ExceptionHandler(GlobalException.class) public ResponseEntity> handleGlobalException(GlobalException e) { return RestResponse.error(e.getResultCase(), new ErrorResponseDto()); From bdc4b4f9b7a9c8a2f815857f84410ec37aefb54e Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Sun, 28 Jan 2024 06:25:11 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat=20:=20WithMockCustomUser=20=EC=97=90?= =?UTF-8?q?=20UserRole=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bc1/gream/global/security/WithMockCustomUser.java | 3 +++ .../WithMockCustomUserSecurityContextFactory.java | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/java/bc1/gream/global/security/WithMockCustomUser.java b/src/test/java/bc1/gream/global/security/WithMockCustomUser.java index eecbd797..59338a33 100644 --- a/src/test/java/bc1/gream/global/security/WithMockCustomUser.java +++ b/src/test/java/bc1/gream/global/security/WithMockCustomUser.java @@ -1,5 +1,6 @@ package bc1.gream.global.security; +import bc1.gream.domain.user.entity.UserRole; import bc1.gream.test.UserTest; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -12,4 +13,6 @@ String loginId() default UserTest.TEST_USER_LOGIN_ID; String password() default UserTest.TEST_USER_PASSWORD; + + UserRole userRole() default UserRole.USER; } diff --git a/src/test/java/bc1/gream/global/security/WithMockCustomUserSecurityContextFactory.java b/src/test/java/bc1/gream/global/security/WithMockCustomUserSecurityContextFactory.java index 6abe8d84..96472645 100644 --- a/src/test/java/bc1/gream/global/security/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/bc1/gream/global/security/WithMockCustomUserSecurityContextFactory.java @@ -1,5 +1,6 @@ package bc1.gream.global.security; +import bc1.gream.domain.user.entity.User; import bc1.gream.test.UserTest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -13,7 +14,12 @@ public class WithMockCustomUserSecurityContextFactory implements WithSecurityCon @Override public SecurityContext createSecurityContext(WithMockCustomUser customUser) { SecurityContext context = SecurityContextHolder.createEmptyContext(); - UserDetails userDetails = new UserDetailsImpl(TEST_USER); + User mockUser = User.builder() + .loginId(customUser.loginId()) + .password(customUser.password()) + .role(customUser.userRole()) + .build(); + UserDetails userDetails = new UserDetailsImpl(mockUser); Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); context.setAuthentication(auth); return context; From 557d3175f9ad0d9aebdea4aa3810b8b2d383d3f5 Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Sun, 28 Jan 2024 06:25:47 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat=20:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=83=88=EB=A1=9C=EC=9A=B4=20=EC=BF=A0=ED=8F=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 33 ++++++ .../request/AdminCreateCouponRequestDto.java | 28 +++++ .../AdminCreateCouponResponseDto.java | 9 ++ .../admin/controller/AdminControllerTest.java | 100 ++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 src/main/java/bc1/gream/domain/admin/controller/AdminController.java create mode 100644 src/main/java/bc1/gream/domain/admin/dto/request/AdminCreateCouponRequestDto.java create mode 100644 src/main/java/bc1/gream/domain/admin/dto/response/AdminCreateCouponResponseDto.java create mode 100644 src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java diff --git a/src/main/java/bc1/gream/domain/admin/controller/AdminController.java b/src/main/java/bc1/gream/domain/admin/controller/AdminController.java new file mode 100644 index 00000000..0f989e3c --- /dev/null +++ b/src/main/java/bc1/gream/domain/admin/controller/AdminController.java @@ -0,0 +1,33 @@ +package bc1.gream.domain.admin.controller; + +import bc1.gream.domain.admin.dto.request.AdminCreateCouponRequestDto; +import bc1.gream.domain.admin.dto.response.AdminCreateCouponResponseDto; +import bc1.gream.domain.coupon.entity.Coupon; +import bc1.gream.domain.coupon.mapper.CouponMapper; +import bc1.gream.domain.coupon.service.command.CouponCommandService; +import bc1.gream.global.common.RestResponse; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin") +public class AdminController { + + private final CouponCommandService couponCommandService; + + @PostMapping("/coupon") + @Operation(summary = "쿠폰 생성요청 [어드민 ONLY]", description = "어드민 권한의 관리자의 쿠폰 생성 요청을 처리합니다.") + public RestResponse createCoupon( + @Valid @RequestBody AdminCreateCouponRequestDto adminCreateCouponRequestDto + ) { + Coupon coupon = couponCommandService.createCoupon(adminCreateCouponRequestDto); + AdminCreateCouponResponseDto responseDto = CouponMapper.INSTANCE.toAdminCreateCouponResponseDto(coupon); + return RestResponse.success(responseDto); + } +} \ No newline at end of file diff --git a/src/main/java/bc1/gream/domain/admin/dto/request/AdminCreateCouponRequestDto.java b/src/main/java/bc1/gream/domain/admin/dto/request/AdminCreateCouponRequestDto.java new file mode 100644 index 00000000..c552b445 --- /dev/null +++ b/src/main/java/bc1/gream/domain/admin/dto/request/AdminCreateCouponRequestDto.java @@ -0,0 +1,28 @@ +package bc1.gream.domain.admin.dto.request; + +import bc1.gream.domain.coupon.entity.CouponStatus; +import bc1.gream.domain.coupon.entity.DiscountType; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Positive; + +@JsonIgnoreProperties +public record AdminCreateCouponRequestDto( + @NotBlank(message = "null 혹은 공백 입력은 불가합니다.") + @Pattern( + regexp = "^[a-zA-Z0-9가-힣]{4,30}$", + message = "쿠폰이름은 영문자, 한글 및 숫자, 4이상 30이하 길이로 가능합니다." + ) + String name, + @NotNull(message = "null 입력은 불가합니다.") + DiscountType discountType, + @NotNull(message = "null 입력은 불가합니다.") + @Positive(message = "음수 혹은 0 입력은 불가합니다.") + Long discount, + @NotNull(message = "null 입력은 불가합니다.") + CouponStatus status +) { + +} diff --git a/src/main/java/bc1/gream/domain/admin/dto/response/AdminCreateCouponResponseDto.java b/src/main/java/bc1/gream/domain/admin/dto/response/AdminCreateCouponResponseDto.java new file mode 100644 index 00000000..a7f4201f --- /dev/null +++ b/src/main/java/bc1/gream/domain/admin/dto/response/AdminCreateCouponResponseDto.java @@ -0,0 +1,9 @@ +package bc1.gream.domain.admin.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties +public record AdminCreateCouponResponseDto( +) { + +} diff --git a/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java b/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java new file mode 100644 index 00000000..44e33af7 --- /dev/null +++ b/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java @@ -0,0 +1,100 @@ +package bc1.gream.domain.admin.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import bc1.gream.domain.admin.dto.request.AdminCreateCouponRequestDto; +import bc1.gream.domain.coupon.entity.CouponStatus; +import bc1.gream.domain.coupon.entity.DiscountType; +import bc1.gream.domain.coupon.service.command.CouponCommandService; +import bc1.gream.domain.user.entity.UserRole; +import bc1.gream.global.security.WithMockCustomUser; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(controllers = AdminController.class) +@WithMockCustomUser(loginId = "adminId12", password = "adminPw12!", userRole = UserRole.ADMIN) +@ActiveProfiles("test") +class AdminControllerTest { + + @Autowired + WebApplicationContext context; + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private CouponCommandService couponCommandService; + + @BeforeEach + void setUp() { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .build(); + } + + @Test + public void 새로운_쿠폰_추가_정상요청() throws Exception { + // GIVEN + String url = "/api/admin/coupon"; + AdminCreateCouponRequestDto requestDto = new AdminCreateCouponRequestDto("쿠폰이름", DiscountType.FIX, 1000L, CouponStatus.AVAILABLE); + + // WHEN + // THEN + mockMvc.perform( + post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDto)) + ).andExpect(status().isOk()); + } + + @Test + public void 새로운_쿠폰_추가_유효하지_않은_요청() throws Exception { + // GIVEN + String url = "/api/admin/coupon"; + AdminCreateCouponRequestDto requestDtoOfNull = new AdminCreateCouponRequestDto(null, null, null, null); + AdminCreateCouponRequestDto requestDtoOfNameOutOfSize = new AdminCreateCouponRequestDto("01234567890123456789012345678901234567891", + DiscountType.FIX, 1000L, CouponStatus.AVAILABLE); + AdminCreateCouponRequestDto requestDtoOfDiscountZero = new AdminCreateCouponRequestDto("쿠폰이름", DiscountType.FIX, 0L, + CouponStatus.AVAILABLE); + AdminCreateCouponRequestDto requestDtoOfDiscountNegative = new AdminCreateCouponRequestDto("쿠폰이름", DiscountType.FIX, -1000L, + CouponStatus.AVAILABLE); + + // WHEN + // THEN + mockMvc.perform( + post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDtoOfNull)) + ).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").exists()); + mockMvc.perform( + post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDtoOfNameOutOfSize)) + ).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").exists()); + mockMvc.perform( + post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDtoOfDiscountZero)) + ).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").exists()); + mockMvc.perform( + post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDtoOfDiscountNegative)) + ).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").exists()); + } +} \ No newline at end of file From 15da5c16571da761e1e053d8304995328722dd8f Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Sun, 28 Jan 2024 06:25:59 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat=20:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=83=88=EB=A1=9C=EC=9A=B4=20=EC=BF=A0=ED=8F=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/command/CouponCommandService.java | 14 +++++ .../command/CouponCommandServiceTest.java | 63 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 src/test/java/bc1/gream/domain/coupon/service/command/CouponCommandServiceTest.java diff --git a/src/main/java/bc1/gream/domain/coupon/service/command/CouponCommandService.java b/src/main/java/bc1/gream/domain/coupon/service/command/CouponCommandService.java index 9f9058a6..4847e0e6 100644 --- a/src/main/java/bc1/gream/domain/coupon/service/command/CouponCommandService.java +++ b/src/main/java/bc1/gream/domain/coupon/service/command/CouponCommandService.java @@ -1,7 +1,9 @@ package bc1.gream.domain.coupon.service.command; +import bc1.gream.domain.admin.dto.request.AdminCreateCouponRequestDto; import bc1.gream.domain.coupon.entity.Coupon; import bc1.gream.domain.coupon.entity.CouponStatus; +import bc1.gream.domain.coupon.repository.CouponRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -9,7 +11,19 @@ @RequiredArgsConstructor public class CouponCommandService { + private final CouponRepository couponRepository; + public void changeCouponStatus(Coupon coupon, CouponStatus couponStatus) { coupon.changeStatus(couponStatus); } + + public Coupon createCoupon(AdminCreateCouponRequestDto adminCreateCouponRequestDto) { + Coupon coupon = Coupon.builder() + .name(adminCreateCouponRequestDto.name()) + .discountType(adminCreateCouponRequestDto.discountType()) + .discount(adminCreateCouponRequestDto.discount()) + .status(adminCreateCouponRequestDto.status()) + .build(); + return couponRepository.save(coupon); + } } diff --git a/src/test/java/bc1/gream/domain/coupon/service/command/CouponCommandServiceTest.java b/src/test/java/bc1/gream/domain/coupon/service/command/CouponCommandServiceTest.java new file mode 100644 index 00000000..147decf5 --- /dev/null +++ b/src/test/java/bc1/gream/domain/coupon/service/command/CouponCommandServiceTest.java @@ -0,0 +1,63 @@ +package bc1.gream.domain.coupon.service.command; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; + +import bc1.gream.domain.admin.dto.request.AdminCreateCouponRequestDto; +import bc1.gream.domain.coupon.entity.Coupon; +import bc1.gream.domain.coupon.entity.CouponStatus; +import bc1.gream.domain.coupon.entity.DiscountType; +import bc1.gream.domain.coupon.repository.CouponRepository; +import bc1.gream.test.CouponTest; +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; + +@ExtendWith(MockitoExtension.class) +class CouponCommandServiceTest implements CouponTest { + + @Mock + private CouponRepository couponRepository; + @InjectMocks + private CouponCommandService couponCommandService; + + @Test + void 쿠폰상태_변경_성공() { + // GIVEN + Coupon mockCoupon = mock(Coupon.class); + CouponStatus newStatus = CouponStatus.ALREADY_USED; + + // WHEN + couponCommandService.changeCouponStatus(mockCoupon, newStatus); + + // THEN + then(mockCoupon).should().changeStatus(newStatus); + } + + @Test + void 새로운_쿠폰_생성_성공() { + // GIVEN + AdminCreateCouponRequestDto adminCreateCouponRequestDto = new AdminCreateCouponRequestDto("쿠폰이름", DiscountType.FIX, 1000L, + CouponStatus.AVAILABLE); + Coupon expectedCoupon = Coupon.builder() + .name(adminCreateCouponRequestDto.name()) + .discountType(adminCreateCouponRequestDto.discountType()) + .discount(adminCreateCouponRequestDto.discount()) + .status(adminCreateCouponRequestDto.status()) + .build(); + given(couponRepository.save(any(Coupon.class))).willReturn(expectedCoupon); + + // WHEN + Coupon savedCoupon = couponCommandService.createCoupon(adminCreateCouponRequestDto); + + // THEN + then(couponRepository).should(times(1)).save(any(Coupon.class)); + assertEquals(expectedCoupon, savedCoupon); + } +} \ No newline at end of file From ac6aef9655de5fc30db6c86bafb0ee7365b0af7d Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Sun, 28 Jan 2024 06:26:15 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat=20:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=83=88=EB=A1=9C=EC=9A=B4=20=EC=BF=A0=ED=8F=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B2=B0=EA=B3=BC=20=EB=A7=A4=ED=95=91=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/coupon/mapper/CouponMapper.java | 3 +++ .../coupon/mapper/CouponMapperTest.java | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/test/java/bc1/gream/domain/coupon/mapper/CouponMapperTest.java diff --git a/src/main/java/bc1/gream/domain/coupon/mapper/CouponMapper.java b/src/main/java/bc1/gream/domain/coupon/mapper/CouponMapper.java index d2ea6983..7eba2f13 100644 --- a/src/main/java/bc1/gream/domain/coupon/mapper/CouponMapper.java +++ b/src/main/java/bc1/gream/domain/coupon/mapper/CouponMapper.java @@ -1,5 +1,6 @@ package bc1.gream.domain.coupon.mapper; +import bc1.gream.domain.admin.dto.response.AdminCreateCouponResponseDto; import bc1.gream.domain.coupon.dto.response.CouponAvailableResponseDto; import bc1.gream.domain.coupon.dto.response.CouponUnavailableResponseDto; import bc1.gream.domain.coupon.entity.Coupon; @@ -14,4 +15,6 @@ public interface CouponMapper { CouponAvailableResponseDto toCouponListResponseDto(Coupon coupon); CouponUnavailableResponseDto toCouponUsedListResponseDto(Coupon coupon); + + AdminCreateCouponResponseDto toAdminCreateCouponResponseDto(Coupon coupon); } diff --git a/src/test/java/bc1/gream/domain/coupon/mapper/CouponMapperTest.java b/src/test/java/bc1/gream/domain/coupon/mapper/CouponMapperTest.java new file mode 100644 index 00000000..2dfbf6f4 --- /dev/null +++ b/src/test/java/bc1/gream/domain/coupon/mapper/CouponMapperTest.java @@ -0,0 +1,23 @@ +package bc1.gream.domain.coupon.mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import bc1.gream.domain.admin.dto.response.AdminCreateCouponResponseDto; +import bc1.gream.domain.coupon.entity.Coupon; +import bc1.gream.test.CouponTest; +import org.junit.jupiter.api.Test; + +class CouponMapperTest implements CouponTest { + + @Test + void 어드민_쿠폰생성결과_매핑() { + // GIVEN + Coupon coupon = TEST_COUPON_FIX; + + // WHEN + AdminCreateCouponResponseDto responseDto = CouponMapper.INSTANCE.toAdminCreateCouponResponseDto(coupon); + + // THEN + assertEquals("AdminCreateCouponResponseDto[]", responseDto.toString()); + } +} \ No newline at end of file From d9d9ee2808e524990db4c93ca6dda68fea7176db Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Tue, 30 Jan 2024 12:59:33 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat=20:=20CouponProvider=20=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EC=BF=A0=ED=8F=B0=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - User 와 CouponCommandService 사용 --- .../admin/controller/AdminController.java | 6 ++--- .../coupon/provider/CouponProvider.java | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/main/java/bc1/gream/domain/coupon/provider/CouponProvider.java diff --git a/src/main/java/bc1/gream/domain/admin/controller/AdminController.java b/src/main/java/bc1/gream/domain/admin/controller/AdminController.java index 0f989e3c..2266d21d 100644 --- a/src/main/java/bc1/gream/domain/admin/controller/AdminController.java +++ b/src/main/java/bc1/gream/domain/admin/controller/AdminController.java @@ -4,7 +4,7 @@ import bc1.gream.domain.admin.dto.response.AdminCreateCouponResponseDto; import bc1.gream.domain.coupon.entity.Coupon; import bc1.gream.domain.coupon.mapper.CouponMapper; -import bc1.gream.domain.coupon.service.command.CouponCommandService; +import bc1.gream.domain.coupon.provider.CouponProvider; import bc1.gream.global.common.RestResponse; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; @@ -19,14 +19,14 @@ @RequestMapping("/api/admin") public class AdminController { - private final CouponCommandService couponCommandService; + private final CouponProvider couponProvider; @PostMapping("/coupon") @Operation(summary = "쿠폰 생성요청 [어드민 ONLY]", description = "어드민 권한의 관리자의 쿠폰 생성 요청을 처리합니다.") public RestResponse createCoupon( @Valid @RequestBody AdminCreateCouponRequestDto adminCreateCouponRequestDto ) { - Coupon coupon = couponCommandService.createCoupon(adminCreateCouponRequestDto); + Coupon coupon = couponProvider.createCoupon(adminCreateCouponRequestDto); AdminCreateCouponResponseDto responseDto = CouponMapper.INSTANCE.toAdminCreateCouponResponseDto(coupon); return RestResponse.success(responseDto); } diff --git a/src/main/java/bc1/gream/domain/coupon/provider/CouponProvider.java b/src/main/java/bc1/gream/domain/coupon/provider/CouponProvider.java new file mode 100644 index 00000000..3a0bf8da --- /dev/null +++ b/src/main/java/bc1/gream/domain/coupon/provider/CouponProvider.java @@ -0,0 +1,27 @@ +package bc1.gream.domain.coupon.provider; + +import bc1.gream.domain.admin.dto.request.AdminCreateCouponRequestDto; +import bc1.gream.domain.coupon.entity.Coupon; +import bc1.gream.domain.coupon.service.command.CouponCommandService; +import bc1.gream.domain.user.entity.User; +import bc1.gream.domain.user.repository.UserRepository; +import bc1.gream.global.common.ResultCase; +import bc1.gream.global.exception.GlobalException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CouponProvider { + + private final UserRepository userRepository; + private final CouponCommandService couponCommandService; + + @Transactional + public Coupon createCoupon(AdminCreateCouponRequestDto adminCreateCouponRequestDto) { + User user = userRepository.findByLoginId(adminCreateCouponRequestDto.userLoginId()) + .orElseThrow(() -> new GlobalException(ResultCase.USER_NOT_FOUND)); + return couponCommandService.createCoupon(user, adminCreateCouponRequestDto); + } +} From ef375df05a7b3fdc0b1fcf4960a5f1cd73681aa4 Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Tue, 30 Jan 2024 13:01:06 +0900 Subject: [PATCH 09/12] =?UTF-8?q?refactor=20:=20CouponCommandService=20?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=BF=A0=ED=8F=B0=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CouponStatus.AVAILABLE 로 고정생성하게끔 수정 - User를 입력받아 생성한 쿠폰의 주인으로 지정 --- .../coupon/service/command/CouponCommandService.java | 6 ++++-- .../service/command/CouponCommandServiceTest.java | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/bc1/gream/domain/coupon/service/command/CouponCommandService.java b/src/main/java/bc1/gream/domain/coupon/service/command/CouponCommandService.java index 4847e0e6..ab426b43 100644 --- a/src/main/java/bc1/gream/domain/coupon/service/command/CouponCommandService.java +++ b/src/main/java/bc1/gream/domain/coupon/service/command/CouponCommandService.java @@ -4,6 +4,7 @@ import bc1.gream.domain.coupon.entity.Coupon; import bc1.gream.domain.coupon.entity.CouponStatus; import bc1.gream.domain.coupon.repository.CouponRepository; +import bc1.gream.domain.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -17,12 +18,13 @@ public void changeCouponStatus(Coupon coupon, CouponStatus couponStatus) { coupon.changeStatus(couponStatus); } - public Coupon createCoupon(AdminCreateCouponRequestDto adminCreateCouponRequestDto) { + public Coupon createCoupon(User user, AdminCreateCouponRequestDto adminCreateCouponRequestDto) { Coupon coupon = Coupon.builder() .name(adminCreateCouponRequestDto.name()) .discountType(adminCreateCouponRequestDto.discountType()) .discount(adminCreateCouponRequestDto.discount()) - .status(adminCreateCouponRequestDto.status()) + .status(CouponStatus.AVAILABLE) + .user(user) .build(); return couponRepository.save(coupon); } diff --git a/src/test/java/bc1/gream/domain/coupon/service/command/CouponCommandServiceTest.java b/src/test/java/bc1/gream/domain/coupon/service/command/CouponCommandServiceTest.java index 147decf5..592b1fb0 100644 --- a/src/test/java/bc1/gream/domain/coupon/service/command/CouponCommandServiceTest.java +++ b/src/test/java/bc1/gream/domain/coupon/service/command/CouponCommandServiceTest.java @@ -43,18 +43,21 @@ class CouponCommandServiceTest implements CouponTest { @Test void 새로운_쿠폰_생성_성공() { // GIVEN - AdminCreateCouponRequestDto adminCreateCouponRequestDto = new AdminCreateCouponRequestDto("쿠폰이름", DiscountType.FIX, 1000L, - CouponStatus.AVAILABLE); + AdminCreateCouponRequestDto adminCreateCouponRequestDto = AdminCreateCouponRequestDto.builder() + .name("쿠폰이름") + .discountType(DiscountType.FIX) + .discount(1000L) + .userLoginId(TEST_USER_LOGIN_ID) + .build(); Coupon expectedCoupon = Coupon.builder() .name(adminCreateCouponRequestDto.name()) .discountType(adminCreateCouponRequestDto.discountType()) .discount(adminCreateCouponRequestDto.discount()) - .status(adminCreateCouponRequestDto.status()) .build(); given(couponRepository.save(any(Coupon.class))).willReturn(expectedCoupon); // WHEN - Coupon savedCoupon = couponCommandService.createCoupon(adminCreateCouponRequestDto); + Coupon savedCoupon = couponCommandService.createCoupon(TEST_USER, adminCreateCouponRequestDto); // THEN then(couponRepository).should(times(1)).save(any(Coupon.class)); From 3bb01f232224f3cb5eff8b6723f8b88e57342c0a Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Tue, 30 Jan 2024 13:01:54 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor=20:=20AdminCreateCouponRequestDt?= =?UTF-8?q?o=20=EC=97=90=EC=84=9C=20UserLoginId=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EB=B0=9B=EA=B2=8C=EB=81=94=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/AdminCreateCouponRequestDto.java | 7 ++-- .../admin/controller/AdminControllerTest.java | 40 ++++++++++++++----- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/main/java/bc1/gream/domain/admin/dto/request/AdminCreateCouponRequestDto.java b/src/main/java/bc1/gream/domain/admin/dto/request/AdminCreateCouponRequestDto.java index c552b445..67914ec2 100644 --- a/src/main/java/bc1/gream/domain/admin/dto/request/AdminCreateCouponRequestDto.java +++ b/src/main/java/bc1/gream/domain/admin/dto/request/AdminCreateCouponRequestDto.java @@ -1,14 +1,15 @@ package bc1.gream.domain.admin.dto.request; -import bc1.gream.domain.coupon.entity.CouponStatus; import bc1.gream.domain.coupon.entity.DiscountType; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Positive; +import lombok.Builder; @JsonIgnoreProperties +@Builder public record AdminCreateCouponRequestDto( @NotBlank(message = "null 혹은 공백 입력은 불가합니다.") @Pattern( @@ -21,8 +22,8 @@ public record AdminCreateCouponRequestDto( @NotNull(message = "null 입력은 불가합니다.") @Positive(message = "음수 혹은 0 입력은 불가합니다.") Long discount, - @NotNull(message = "null 입력은 불가합니다.") - CouponStatus status + @NotBlank(message = "null 입력은 불가합니다.") + String userLoginId ) { } diff --git a/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java b/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java index 44e33af7..470cf538 100644 --- a/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java +++ b/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java @@ -5,11 +5,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import bc1.gream.domain.admin.dto.request.AdminCreateCouponRequestDto; -import bc1.gream.domain.coupon.entity.CouponStatus; import bc1.gream.domain.coupon.entity.DiscountType; import bc1.gream.domain.coupon.service.command.CouponCommandService; import bc1.gream.domain.user.entity.UserRole; import bc1.gream.global.security.WithMockCustomUser; +import bc1.gream.test.UserTest; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,7 +47,12 @@ void setUp() { public void 새로운_쿠폰_추가_정상요청() throws Exception { // GIVEN String url = "/api/admin/coupon"; - AdminCreateCouponRequestDto requestDto = new AdminCreateCouponRequestDto("쿠폰이름", DiscountType.FIX, 1000L, CouponStatus.AVAILABLE); + AdminCreateCouponRequestDto requestDto = AdminCreateCouponRequestDto.builder() + .name("쿠폰이름") + .discountType(DiscountType.FIX) + .discount(1000L) + .userLoginId(UserTest.TEST_USER_LOGIN_ID) + .build(); // WHEN // THEN @@ -62,13 +67,30 @@ void setUp() { public void 새로운_쿠폰_추가_유효하지_않은_요청() throws Exception { // GIVEN String url = "/api/admin/coupon"; - AdminCreateCouponRequestDto requestDtoOfNull = new AdminCreateCouponRequestDto(null, null, null, null); - AdminCreateCouponRequestDto requestDtoOfNameOutOfSize = new AdminCreateCouponRequestDto("01234567890123456789012345678901234567891", - DiscountType.FIX, 1000L, CouponStatus.AVAILABLE); - AdminCreateCouponRequestDto requestDtoOfDiscountZero = new AdminCreateCouponRequestDto("쿠폰이름", DiscountType.FIX, 0L, - CouponStatus.AVAILABLE); - AdminCreateCouponRequestDto requestDtoOfDiscountNegative = new AdminCreateCouponRequestDto("쿠폰이름", DiscountType.FIX, -1000L, - CouponStatus.AVAILABLE); + AdminCreateCouponRequestDto requestDtoOfNull = AdminCreateCouponRequestDto.builder() + .name(null) + .discountType(null) + .discount(null) + .userLoginId(null) + .build(); + AdminCreateCouponRequestDto requestDtoOfNameOutOfSize = AdminCreateCouponRequestDto.builder() + .name("01234567890123456789012345678901234567891") + .discountType(DiscountType.FIX) + .discount(1000L) + .userLoginId(UserTest.TEST_USER_LOGIN_ID) + .build(); + AdminCreateCouponRequestDto requestDtoOfDiscountZero = AdminCreateCouponRequestDto.builder() + .name("01234567890123456789012345678901234567891") + .discountType(DiscountType.FIX) + .discount(0L) + .userLoginId(UserTest.TEST_USER_LOGIN_ID) + .build(); + AdminCreateCouponRequestDto requestDtoOfDiscountNegative = AdminCreateCouponRequestDto.builder() + .name("01234567890123456789012345678901234567891") + .discountType(DiscountType.FIX) + .discount(-1000L) + .userLoginId(UserTest.TEST_USER_LOGIN_ID) + .build(); // WHEN // THEN From 99746869c0f76701041b34cb1afcc0e9b9c14838 Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Tue, 30 Jan 2024 13:07:38 +0900 Subject: [PATCH 11/12] =?UTF-8?q?chore=20:=20origin/main=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=EC=9D=98=20AdminController=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/admin/controller/AdminController.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/java/bc1/gream/domain/admin/controller/AdminController.java b/src/main/java/bc1/gream/domain/admin/controller/AdminController.java index dab9a35a..d40c90c2 100644 --- a/src/main/java/bc1/gream/domain/admin/controller/AdminController.java +++ b/src/main/java/bc1/gream/domain/admin/controller/AdminController.java @@ -1,23 +1,23 @@ package bc1.gream.domain.admin.controller; import bc1.gream.domain.admin.dto.request.AdminCreateCouponRequestDto; -import bc1.gream.domain.admin.dto.response.AdminCreateCouponResponseDto; import bc1.gream.domain.admin.dto.request.AdminGetRefundRequestDto; import bc1.gream.domain.admin.dto.request.AdminProductRequestDto; import bc1.gream.domain.admin.dto.request.AdminRefundPassResponseDto; +import bc1.gream.domain.admin.dto.response.AdminCreateCouponResponseDto; import bc1.gream.domain.admin.dto.response.AdminGetRefundResponseDto; import bc1.gream.domain.admin.dto.response.AdminProductResponseDto; import bc1.gream.domain.admin.mapper.RefundMapper; -import bc1.gream.domain.product.service.query.ProductService; -import bc1.gream.domain.user.service.command.RefundCommandService; -import bc1.gream.domain.user.service.query.RefundQueryService; import bc1.gream.domain.coupon.entity.Coupon; import bc1.gream.domain.coupon.mapper.CouponMapper; import bc1.gream.domain.coupon.provider.CouponProvider; +import bc1.gream.domain.product.service.query.ProductService; +import bc1.gream.domain.user.service.command.RefundCommandService; +import bc1.gream.domain.user.service.query.RefundQueryService; import bc1.gream.global.common.RestResponse; import io.swagger.v3.oas.annotations.Operation; -import java.util.List; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -35,6 +35,7 @@ public class AdminController { private final ProductService productService; private final RefundQueryService refundQueryService; private final RefundCommandService refundCommandService; + private final CouponProvider couponProvider; @GetMapping("/refunds") @Operation(summary = "신청된 환급 리스트 조회 요청", description = "사용자가 신청한 환급 요청 리스트를 반환합니다.") @@ -57,7 +58,6 @@ public RestResponse addProducts( return RestResponse.success(new AdminProductResponseDto()); } - @DeleteMapping("/refund/{id}") @Operation(summary = "유저 환급 승인", description = "유저가 신청한 환급 요청을 승인해주는 기능입니다.") public RestResponse approveRefund( @@ -67,9 +67,6 @@ public RestResponse approveRefund( return RestResponse.success(responseDto); } -} - - private final CouponProvider couponProvider; @PostMapping("/coupon") @Operation(summary = "쿠폰 생성요청 [어드민 ONLY]", description = "어드민 권한의 관리자의 쿠폰 생성 요청을 처리합니다.") From 26ccc9feaceed2c989abf8fbc2ec53a320fea1c6 Mon Sep 17 00:00:00 2001 From: Laptop-Limjihoon Date: Tue, 30 Jan 2024 13:15:32 +0900 Subject: [PATCH 12/12] =?UTF-8?q?test=20:=20AdminControllerTest=20?= =?UTF-8?q?=EC=97=90=20MockBean=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ProductService - RefundQueryService - RefundCommandService --- .../admin/controller/AdminControllerTest.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java b/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java index 470cf538..837eec11 100644 --- a/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java +++ b/src/test/java/bc1/gream/domain/admin/controller/AdminControllerTest.java @@ -6,8 +6,11 @@ import bc1.gream.domain.admin.dto.request.AdminCreateCouponRequestDto; import bc1.gream.domain.coupon.entity.DiscountType; -import bc1.gream.domain.coupon.service.command.CouponCommandService; +import bc1.gream.domain.coupon.provider.CouponProvider; +import bc1.gream.domain.product.service.query.ProductService; import bc1.gream.domain.user.entity.UserRole; +import bc1.gream.domain.user.service.command.RefundCommandService; +import bc1.gream.domain.user.service.query.RefundQueryService; import bc1.gream.global.security.WithMockCustomUser; import bc1.gream.test.UserTest; import com.fasterxml.jackson.databind.ObjectMapper; @@ -34,7 +37,13 @@ class AdminControllerTest { @Autowired private ObjectMapper objectMapper; @MockBean - private CouponCommandService couponCommandService; + private ProductService productService; + @MockBean + private RefundQueryService refundQueryService; + @MockBean + private RefundCommandService refundCommandService; + @MockBean + private CouponProvider couponProvider; @BeforeEach void setUp() {