From d40b614eecae8d14d3148eb5c4c987deb9f02b5f Mon Sep 17 00:00:00 2001 From: HyuckJuneHong Date: Thu, 9 Nov 2023 03:26:58 +0900 Subject: [PATCH] =?UTF-8?q?test:=20=EC=BF=A0=ED=8F=B0=20=EB=B0=9C=ED=96=89?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/coupon.adoc | 35 ++ .../moabam/api/dto/CreateCouponRequest.java | 2 + .../api/presentation/CouponController.java | 3 + src/main/resources/static/docs/coupon.html | 540 ++++++++++++++++++ .../api/application/CouponServiceTest.java | 89 +++ .../moabam/api/domain/entity/CouponTest.java | 4 +- .../domain/entity/enums/CouponTypeTest.java | 4 +- .../api/dto/CreateCouponRequestTest.java | 31 + .../presentation/CouponControllerTest.java | 80 +++ .../moabam/support/fixture/CouponFixture.java | 13 + .../support/fixture/CouponSnippetFixture.java | 19 + 11 files changed, 816 insertions(+), 4 deletions(-) create mode 100644 src/docs/asciidoc/coupon.adoc create mode 100644 src/main/resources/static/docs/coupon.html create mode 100644 src/test/java/com/moabam/api/application/CouponServiceTest.java create mode 100644 src/test/java/com/moabam/api/dto/CreateCouponRequestTest.java create mode 100644 src/test/java/com/moabam/api/presentation/CouponControllerTest.java create mode 100644 src/test/java/com/moabam/support/fixture/CouponSnippetFixture.java diff --git a/src/docs/asciidoc/coupon.adoc b/src/docs/asciidoc/coupon.adoc new file mode 100644 index 00000000..f06a91ef --- /dev/null +++ b/src/docs/asciidoc/coupon.adoc @@ -0,0 +1,35 @@ +== 쿠폰(Coupon) + + 쿠폰에 대해 생성/삭제/조회/발급/사용 기능을 제공합니다. + +=== 쿠폰 생성 + + 관리자가 쿠폰을 생성합니다. + +[discrete] +==== 요청 + +include::{snippets}/coupons/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/coupons/http-response.adoc[] + +=== 쿠폰 삭제 (진행 중) + + 관리자가 쿠폰을 삭제합니다. + +=== 쿠폰 조회 (진행 중) + + 관리자 혹은 사용자가 쿠폰들을 조회합니다. + + 사용자가 자신의 보관함에 있는 쿠폰들을 조회합니다. + +=== 쿠폰 발급 (진행 중) + + 사용자가 발급 가능한 쿠폰을 선착순으로 발급 받습니다. + +=== 쿠폰 사용 (진행 중) + + 사용자가 자신의 보관함에 있는 쿠폰들을 사용합니다. diff --git a/src/main/java/com/moabam/api/dto/CreateCouponRequest.java b/src/main/java/com/moabam/api/dto/CreateCouponRequest.java index 289ebcf1..710fffc3 100644 --- a/src/main/java/com/moabam/api/dto/CreateCouponRequest.java +++ b/src/main/java/com/moabam/api/dto/CreateCouponRequest.java @@ -9,7 +9,9 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import lombok.Builder; +@Builder public record CreateCouponRequest( @NotBlank(message = "쿠폰명이 입력되지 않았거나 20자를 넘었습니다.") @Length(max = 20) String name, @Length(max = 50, message = "쿠폰 간단 소개는 최대 50자까지 가능합니다.") String description, diff --git a/src/main/java/com/moabam/api/presentation/CouponController.java b/src/main/java/com/moabam/api/presentation/CouponController.java index e984cc29..acac1ac8 100644 --- a/src/main/java/com/moabam/api/presentation/CouponController.java +++ b/src/main/java/com/moabam/api/presentation/CouponController.java @@ -1,8 +1,10 @@ package com.moabam.api.presentation; +import org.springframework.http.HttpStatus; 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.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.moabam.api.application.CouponService; @@ -18,6 +20,7 @@ public class CouponController { private final CouponService couponService; @PostMapping + @ResponseStatus(HttpStatus.OK) public void createCoupon(@RequestBody CreateCouponRequest request) { couponService.createCoupon(1L, request); } diff --git a/src/main/resources/static/docs/coupon.html b/src/main/resources/static/docs/coupon.html new file mode 100644 index 00000000..c6daacf7 --- /dev/null +++ b/src/main/resources/static/docs/coupon.html @@ -0,0 +1,540 @@ + + + + + + + +쿠폰(Coupon) + + + + + +
+
+

쿠폰(Coupon)

+
+
+
+
쿠폰에 대해 생성/삭제/조회/발급/사용 기능을 제공합니다.
+
+
+
+

쿠폰 생성

+
+
+
관리자가 쿠폰을 생성합니다.
+
+
+

요청

+
+
+
POST /admins/coupons HTTP/1.1
+Content-Type: application/json;charset=UTF-8
+Content-Length: 186
+Host: localhost:8080
+
+{
+  "name" : "couponName",
+  "description" : "coupon description",
+  "type" : "황금",
+  "point" : 10,
+  "stock" : 10,
+  "startAt" : "2000-01-22T10:30",
+  "endAt" : "2000-02-22T11:00"
+}
+
+
+

응답

+
+
+
HTTP/1.1 409 Conflict
+Vary: Origin
+Vary: Access-Control-Request-Method
+Vary: Access-Control-Request-Headers
+Content-Type: application/json
+Content-Length: 62
+
+{
+  "message" : "쿠폰의 이름이 중복되었습니다."
+}
+
+
+
+
+

쿠폰 삭제 (진행 중)

+
+
+
관리자가 쿠폰을 삭제합니다.
+
+
+
+
+

쿠폰 조회 (진행 중)

+
+
+
관리자 혹은 사용자가 쿠폰들을 조회합니다.
+
+
+
+
+
사용자가 자신의 보관함에 있는 쿠폰들을 조회합니다.
+
+
+
+
+

쿠폰 발급 (진행 중)

+
+
+
사용자가 발급 가능한 쿠폰을 선착순으로 발급 받습니다.
+
+
+
+
+

쿠폰 사용 (진행 중)

+
+
+
사용자가 자신의 보관함에 있는 쿠폰들을 사용합니다.
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/test/java/com/moabam/api/application/CouponServiceTest.java b/src/test/java/com/moabam/api/application/CouponServiceTest.java new file mode 100644 index 00000000..2759bec5 --- /dev/null +++ b/src/test/java/com/moabam/api/application/CouponServiceTest.java @@ -0,0 +1,89 @@ +package com.moabam.api.application; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.DisplayName; +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.moabam.api.domain.entity.Coupon; +import com.moabam.api.domain.entity.enums.CouponType; +import com.moabam.api.domain.repository.CouponRepository; +import com.moabam.api.dto.CreateCouponRequest; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.fixture.CouponFixture; + +@ExtendWith(MockitoExtension.class) +class CouponServiceTest { + + @InjectMocks + private CouponService couponService; + + @Mock + private CouponRepository couponRepository; + + @DisplayName("쿠폰을 성공적으로 발행한다. - Void") + @Test + void couponService_createCoupon() { + // Given + String couponType = CouponType.GOLDEN_COUPON.getTypeName(); + CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 2); + + given(couponRepository.existsByName(any(String.class))).willReturn(false); + + // When + couponService.createCoupon(1L, request); + + // Then + verify(couponRepository).save(any(Coupon.class)); + } + + @DisplayName("중복된 쿠폰명을 발행한다. - ConflictException") + @Test + void couponService_createCoupon_ConflictException() { + // Given + String couponType = CouponType.GOLDEN_COUPON.getTypeName(); + CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 2); + + given(couponRepository.existsByName(any(String.class))).willReturn(true); + + // When & Then + assertThatThrownBy(() -> couponService.createCoupon(1L, request)) + .isInstanceOf(ConflictException.class) + .hasMessage(ErrorMessage.CONFLICT_COUPON_NAME.getMessage()); + } + + @DisplayName("존재하지 않는 쿠폰 종류를 발행한다. - NotFoundException") + @Test + void couponService_createCoupon_NotFoundException() { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest("UNKNOWN", 1, 2); + given(couponRepository.existsByName(any(String.class))).willReturn(false); + + // When & Then + assertThatThrownBy(() -> couponService.createCoupon(1L, request)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.NOT_FOUND_COUPON_TYPE.getMessage()); + } + + @DisplayName("쿠폰 발급 종료 기간이 시작 기간보다 더 이전인 쿠폰을 발행한다. - BadRequestException") + @Test + void couponService_createCoupon_BadRequestException() { + // Given + String couponType = CouponType.GOLDEN_COUPON.getTypeName(); + CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 2, 1); + given(couponRepository.existsByName(any(String.class))).willReturn(false); + + // When & Then + assertThatThrownBy(() -> couponService.createCoupon(1L, request)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_COUPON_PERIOD.getMessage()); + } +} diff --git a/src/test/java/com/moabam/api/domain/entity/CouponTest.java b/src/test/java/com/moabam/api/domain/entity/CouponTest.java index e19bfa23..4d5faadd 100644 --- a/src/test/java/com/moabam/api/domain/entity/CouponTest.java +++ b/src/test/java/com/moabam/api/domain/entity/CouponTest.java @@ -11,7 +11,7 @@ class CouponTest { - @DisplayName("쿠폰 보너스 포인트가 0보다 작을 때, - BadRequestException") + @DisplayName("쿠폰 보너스 포인트가 1보다 작다. - BadRequestException") @Test void coupon_validatePoint_Point_BadRequestException() { // When& Then @@ -20,7 +20,7 @@ void coupon_validatePoint_Point_BadRequestException() { .hasMessage(ErrorMessage.INVALID_COUPON_POINT.getMessage()); } - @DisplayName("쿠폰 재고가 0보다 작을 때, - BadRequestException") + @DisplayName("쿠폰 재고가 1보다 작다. - BadRequestException") @Test void coupon_validatePoint_Stock_BadRequestException() { // When& Then diff --git a/src/test/java/com/moabam/api/domain/entity/enums/CouponTypeTest.java b/src/test/java/com/moabam/api/domain/entity/enums/CouponTypeTest.java index 79978835..4ceed56f 100644 --- a/src/test/java/com/moabam/api/domain/entity/enums/CouponTypeTest.java +++ b/src/test/java/com/moabam/api/domain/entity/enums/CouponTypeTest.java @@ -10,7 +10,7 @@ class CouponTypeTest { - @DisplayName("존재하는 쿠폰을 가져오려고 할 때, - CouponType") + @DisplayName("존재하는 쿠폰을 가져온다. - CouponType") @Test void couponType_from() { // When @@ -20,7 +20,7 @@ void couponType_from() { assertThat(actual).isEqualTo(CouponType.GOLDEN_COUPON); } - @DisplayName("존재하지 않는 쿠폰을 가져오려고 할 때, - NotFoundException") + @DisplayName("존재하지 않는 쿠폰을 가져온다. - NotFoundException") @Test void couponType_from_NotFoundException() { // When & Then diff --git a/src/test/java/com/moabam/api/dto/CreateCouponRequestTest.java b/src/test/java/com/moabam/api/dto/CreateCouponRequestTest.java new file mode 100644 index 00000000..18d2c481 --- /dev/null +++ b/src/test/java/com/moabam/api/dto/CreateCouponRequestTest.java @@ -0,0 +1,31 @@ +package com.moabam.api.dto; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +class CreateCouponRequestTest { + + @DisplayName("쿠폰 발급 가능 시작 날짜가 올바른 형식으로 입력된다. - yyyy-MM-dd'T'HH:mm") + @Test + void createCouponRequest_StartAt() throws JsonProcessingException { + // Given + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + + String json = "{\"startAt\":\"2023-11-09T10:10\"}"; + + // When + CreateCouponRequest actual = objectMapper.readValue(json, CreateCouponRequest.class); + + // Then + assertThat(actual.startAt()).isEqualTo(LocalDateTime.of(2023, 11, 9, 10, 10)); + } +} diff --git a/src/test/java/com/moabam/api/presentation/CouponControllerTest.java b/src/test/java/com/moabam/api/presentation/CouponControllerTest.java new file mode 100644 index 00000000..215e0b85 --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/CouponControllerTest.java @@ -0,0 +1,80 @@ +package com.moabam.api.presentation; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.domain.entity.enums.CouponType; +import com.moabam.api.domain.repository.CouponRepository; +import com.moabam.api.dto.CouponMapper; +import com.moabam.api.dto.CreateCouponRequest; +import com.moabam.support.fixture.CouponFixture; +import com.moabam.support.fixture.CouponSnippetFixture; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureRestDocs +class CouponControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private CouponRepository couponRepository; + + @DisplayName("쿠폰을 성공적으로 발행한다. - Void") + @Test + void couponController_createCoupon() throws Exception { + // Given + String couponType = CouponType.GOLDEN_COUPON.getTypeName(); + CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 2); + + // When & Then + mockMvc.perform(post("/admins/coupons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippetFixture.CREATE_COUPON_REQUEST)) + .andExpect(status().isOk()); + } + + @DisplayName("쿠폰명이 중복된 쿠폰을 발행한다. - ConflictException") + @Test + void couponController_createCoupon_ConflictException() throws Exception { + // Given + String couponType = CouponType.GOLDEN_COUPON.getTypeName(); + CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 2); + couponRepository.save(CouponMapper.toEntity(1L, request)); + + // When & Then + mockMvc.perform(post("/admins/coupons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippetFixture.CREATE_COUPON_REQUEST)) + .andExpect(status().isConflict()); + } +} diff --git a/src/test/java/com/moabam/support/fixture/CouponFixture.java b/src/test/java/com/moabam/support/fixture/CouponFixture.java index 22e58b8e..5b4e8e9b 100644 --- a/src/test/java/com/moabam/support/fixture/CouponFixture.java +++ b/src/test/java/com/moabam/support/fixture/CouponFixture.java @@ -4,6 +4,7 @@ import com.moabam.api.domain.entity.Coupon; import com.moabam.api.domain.entity.enums.CouponType; +import com.moabam.api.dto.CreateCouponRequest; public final class CouponFixture { @@ -17,4 +18,16 @@ public static Coupon coupon(int point, int stock) { .endAt(LocalDateTime.now()) .build(); } + + public static CreateCouponRequest createCouponRequest(String couponType, int startMonth, int endMonth) { + return CreateCouponRequest.builder() + .name("couponName") + .description("coupon description") + .point(10) + .type(couponType) + .stock(10) + .startAt(LocalDateTime.of(2000, startMonth, 22, 10, 30, 0)) + .endAt(LocalDateTime.of(2000, endMonth, 22, 11, 0, 0)) + .build(); + } } diff --git a/src/test/java/com/moabam/support/fixture/CouponSnippetFixture.java b/src/test/java/com/moabam/support/fixture/CouponSnippetFixture.java new file mode 100644 index 00000000..92345c0e --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/CouponSnippetFixture.java @@ -0,0 +1,19 @@ +package com.moabam.support.fixture; + +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; + +import org.springframework.restdocs.payload.RequestFieldsSnippet; + +public final class CouponSnippetFixture { + + public static final RequestFieldsSnippet CREATE_COUPON_REQUEST = requestFields( + fieldWithPath("name").type(STRING).description("쿠폰명"), + fieldWithPath("description").type(STRING).description("쿠폰 간단 소개 (NULL 가능)"), + fieldWithPath("type").type(STRING).description("쿠폰 종류 (아침, 저녁, 황금, 할인)"), + fieldWithPath("point").type(NUMBER).description("쿠폰 사용 시, 제공하는 포인트량"), + fieldWithPath("stock").type(NUMBER).description("쿠폰을 발급 받을 수 있는 수"), + fieldWithPath("startAt").type(STRING).description("쿠폰 발급 시작 날짜 (Ex: yyyy-MM-dd'T'HH:mm)"), + fieldWithPath("endAt").type(STRING).description("쿠폰 발급 종료 날짜 (Ex: yyyy-MM-dd'T'HH:mm)") + ); +}