From 58b40e95903495841c4072c9a5cd7261b0774544 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:04:58 +0900 Subject: [PATCH 01/52] =?UTF-8?q?[test]=20CouponIssuanceTest=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20CouponIssuanceTest.testConstruct:=20CouponIssua?= =?UTF-8?q?nce=20=EC=83=9D=EC=84=B1=EC=9E=90=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 --- .../lab/coupon/domain/CouponIssuanceTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java diff --git a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java new file mode 100644 index 00000000..0b704c88 --- /dev/null +++ b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java @@ -0,0 +1,48 @@ +package camp.woowak.lab.coupon.domain; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import camp.woowak.lab.customer.domain.Customer; +import camp.woowak.lab.fixture.CouponFixture; +import camp.woowak.lab.fixture.CustomerFixture; +import camp.woowak.lab.payaccount.domain.PayAccount; +import camp.woowak.lab.web.authentication.NoOpPasswordEncoder; +import camp.woowak.lab.web.authentication.PasswordEncoder; + +class CouponIssuanceTest implements CouponFixture, CustomerFixture { + private static PasswordEncoder passwordEncoder; + + @BeforeAll + static void setUpAll() { + passwordEncoder = new NoOpPasswordEncoder(); + } + + @Test + @DisplayName("CouponIssuance 생성 테스트") + void testConstruct() { + // given + Long fakeCouponId = 1L; + String title = "할인 쿠폰"; + int discountAmount = 1000; + int quantity = 100; + LocalDateTime expiredAt = LocalDateTime.now().plusDays(7); + PayAccount payAccount = createPayAccount(); + Coupon coupon = createCoupon(fakeCouponId, title, discountAmount, quantity, expiredAt); + Customer customer = createCustomer(payAccount, passwordEncoder); + + // when + CouponIssuance couponIssuance = new CouponIssuance(coupon, customer); + + // then + assertEquals(coupon, couponIssuance.getCoupon()); + assertEquals(customer, couponIssuance.getCustomer()); + assertNotNull(couponIssuance.getIssuedAt()); + assertNull(couponIssuance.getUsedAt()); + } +} From 2a9ea1d4012a2412bb1870f95a56d1277834023b Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:08:43 +0900 Subject: [PATCH 02/52] =?UTF-8?q?[feat]=20CouponIssuance=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20CouponIssuance:=20Customer=EC=99=80=20Coupon=20?= =?UTF-8?q?=EC=9D=84=20=EA=B0=96=EB=8A=94=EB=8B=A4.=20=EB=98=90=ED=95=9C,?= =?UTF-8?q?=20=EB=B0=9C=EA=B8=89=EC=9D=BC,=20=EC=82=AC=EC=9A=A9=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=EA=B8=B0=EB=A1=9D=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/coupon/domain/CouponIssuance.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java diff --git a/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java new file mode 100644 index 00000000..217ad5e1 --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java @@ -0,0 +1,49 @@ +package camp.woowak.lab.coupon.domain; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; + +import camp.woowak.lab.customer.domain.Customer; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.Getter; + +@Entity +@Getter +public class CouponIssuance { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @JoinColumn(name = "coupon_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private Coupon coupon; + + @JoinColumn(name = "customer_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private Customer customer; + + @Column(nullable = false) + @CreatedDate + private LocalDateTime issuedAt; + + @Column + private LocalDateTime usedAt; + + protected CouponIssuance() { + } + + public CouponIssuance(Coupon coupon, Customer customer) { + + this.coupon = coupon; + this.customer = customer; + this.issuedAt = LocalDateTime.now(); + } +} From b18a1c4d0bfd9921553ad44f09dee9f0810af1a7 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:13:52 +0900 Subject: [PATCH 03/52] =?UTF-8?q?[fix]=20DuplicateCouponTitleException=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=EC=9E=98=EB=AA=BB=EB=90=9C=20Exceptio?= =?UTF-8?q?n=20=EC=83=81=EC=86=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/coupon/exception/DuplicateCouponTitleException.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/camp/woowak/lab/coupon/exception/DuplicateCouponTitleException.java b/src/main/java/camp/woowak/lab/coupon/exception/DuplicateCouponTitleException.java index 646bbcd8..510ddccf 100644 --- a/src/main/java/camp/woowak/lab/coupon/exception/DuplicateCouponTitleException.java +++ b/src/main/java/camp/woowak/lab/coupon/exception/DuplicateCouponTitleException.java @@ -1,8 +1,8 @@ package camp.woowak.lab.coupon.exception; -import camp.woowak.lab.common.exception.BadRequestException; +import camp.woowak.lab.common.exception.ConflictException; -public class DuplicateCouponTitleException extends BadRequestException { +public class DuplicateCouponTitleException extends ConflictException { public DuplicateCouponTitleException(String message) { super(CouponErrorCode.DUPLICATE_COUPON_TITLE, message); } From 3006357a173aa215a190894674e8887cbe5017aa Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:15:28 +0900 Subject: [PATCH 04/52] =?UTF-8?q?[test]=20=20CouponIssuanceTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20CouponIssuanceTest.testConstructWithNu?= =?UTF-8?q?llCoupon:=20Coupon=20=EC=9D=B4=20Null=20=EC=9D=B8=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20-=20Coupo?= =?UTF-8?q?nIssuanceTest.testConstructWithNullCustomer:=20Customer=20?= =?UTF-8?q?=EA=B0=80=20Null=20=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20-=20CouponIssuanceTest.testCon?= =?UTF-8?q?structWithExpiredCoupon:=20=EB=A7=8C=EB=A3=8C=EB=90=9C=20?= =?UTF-8?q?=EC=BF=A0=ED=8F=B0=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/coupon/domain/CouponIssuanceTest.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java index 0b704c88..73d27e7a 100644 --- a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java +++ b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import camp.woowak.lab.coupon.exception.CouponExpiredException; import camp.woowak.lab.customer.domain.Customer; import camp.woowak.lab.fixture.CouponFixture; import camp.woowak.lab.fixture.CustomerFixture; @@ -45,4 +46,43 @@ void testConstruct() { assertNotNull(couponIssuance.getIssuedAt()); assertNull(couponIssuance.getUsedAt()); } + + @Test + @DisplayName("CouponIssuance 생성 테스트 - 쿠폰이 null 인 경우") + void testConstructWithNullCoupon() { + // given + Coupon coupon = null; + Customer customer = createCustomer(createPayAccount(), passwordEncoder); + + // when & then + assertThrows(NullPointerException.class, () -> new CouponIssuance(coupon, customer)); + } + + @Test + @DisplayName("CouponIssuance 생성 테스트 - 고객이 null 인 경우") + void testConstructWithNullCustomer() { + // given + Coupon coupon = createCoupon(1L, "할인 쿠폰", 1000, 100, LocalDateTime.now().plusDays(7)); + Customer customer = null; + + // when & then + assertThrows(NullPointerException.class, () -> new CouponIssuance(coupon, customer)); + } + + @Test + @DisplayName("CouponIssuance 생성 테스트 - 쿠폰이 만료되었을 때") + void testConstructWithExpiredCoupon() { + // given + Long fakeCouponId = 1L; + String title = "할인 쿠폰"; + int discountAmount = 1000; + int quantity = 100; + LocalDateTime expiredAt = LocalDateTime.now().minusDays(1); + PayAccount payAccount = createPayAccount(); + Coupon coupon = createCoupon(fakeCouponId, title, discountAmount, quantity, expiredAt); + Customer customer = createCustomer(payAccount, passwordEncoder); + + // when & then + assertThrows(CouponExpiredException.class, () -> new CouponIssuance(coupon, customer)); + } } From d2f5b69286ac781acb2e26b21f106d42ca5e5c12 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:16:23 +0900 Subject: [PATCH 05/52] =?UTF-8?q?[feat]=20CouponErrorCode=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20CouponErrorCode.EXPIRED=5FCOUPON:=20=EB=A7=8C?= =?UTF-8?q?=EB=A3=8C=EB=90=9C=20=EC=BF=A0=ED=8F=B0=20=EC=97=90=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/camp/woowak/lab/coupon/exception/CouponErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java b/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java index 100b6c0b..33d6fda9 100644 --- a/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java +++ b/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java @@ -7,6 +7,7 @@ public enum CouponErrorCode implements ErrorCode { INVALID_CREATION(HttpStatus.BAD_REQUEST, "cp_1_1", "잘못된 요청입니다."), DUPLICATE_COUPON_TITLE(HttpStatus.CONFLICT, "cp_1_2", "중복된 쿠폰 제목입니다."), + EXPIRED_COUPON(HttpStatus.CONFLICT, "cp_1_3", "만료된 쿠폰입니다."), ; int status; From 7b968c5226d212e47140a4b10408ee25a34b93d4 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:17:03 +0900 Subject: [PATCH 06/52] =?UTF-8?q?[feat]=20ExpiredCouponException=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/coupon/exception/ExpiredCouponException.java | 9 +++++++++ .../woowak/lab/coupon/domain/CouponIssuanceTest.java | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 src/main/java/camp/woowak/lab/coupon/exception/ExpiredCouponException.java diff --git a/src/main/java/camp/woowak/lab/coupon/exception/ExpiredCouponException.java b/src/main/java/camp/woowak/lab/coupon/exception/ExpiredCouponException.java new file mode 100644 index 00000000..c27aa7e6 --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/exception/ExpiredCouponException.java @@ -0,0 +1,9 @@ +package camp.woowak.lab.coupon.exception; + +import camp.woowak.lab.common.exception.ConflictException; + +public class ExpiredCouponException extends ConflictException { + public ExpiredCouponException(String message) { + super(CouponErrorCode.EXPIRED_COUPON, message); + } +} diff --git a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java index 73d27e7a..bfcf7932 100644 --- a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java +++ b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import camp.woowak.lab.coupon.exception.CouponExpiredException; +import camp.woowak.lab.coupon.exception.ExpiredCouponException; import camp.woowak.lab.customer.domain.Customer; import camp.woowak.lab.fixture.CouponFixture; import camp.woowak.lab.fixture.CustomerFixture; @@ -83,6 +83,6 @@ void testConstructWithExpiredCoupon() { Customer customer = createCustomer(payAccount, passwordEncoder); // when & then - assertThrows(CouponExpiredException.class, () -> new CouponIssuance(coupon, customer)); + assertThrows(ExpiredCouponException.class, () -> new CouponIssuance(coupon, customer)); } } From 3d94a012a3b3d5f7d9f5a200f14bff03110a5198 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:25:17 +0900 Subject: [PATCH 07/52] =?UTF-8?q?[fix]=20CouponIssuanceTest=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=EA=B1=B4=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?-=20NullPointException=20=EC=9D=B4=20=EC=95=84=EB=8B=8C=20Inval?= =?UTF-8?q?idICreationIssuanceException=20=EC=9D=84=20=EB=8D=98=EC=A7=80?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camp/woowak/lab/coupon/domain/CouponIssuanceTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java index bfcf7932..eedd655d 100644 --- a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java +++ b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import camp.woowak.lab.coupon.exception.ExpiredCouponException; +import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; import camp.woowak.lab.customer.domain.Customer; import camp.woowak.lab.fixture.CouponFixture; import camp.woowak.lab.fixture.CustomerFixture; @@ -55,7 +56,7 @@ void testConstructWithNullCoupon() { Customer customer = createCustomer(createPayAccount(), passwordEncoder); // when & then - assertThrows(NullPointerException.class, () -> new CouponIssuance(coupon, customer)); + assertThrows(InvalidICreationIssuanceException.class, () -> new CouponIssuance(coupon, customer)); } @Test @@ -66,7 +67,7 @@ void testConstructWithNullCustomer() { Customer customer = null; // when & then - assertThrows(NullPointerException.class, () -> new CouponIssuance(coupon, customer)); + assertThrows(InvalidICreationIssuanceException.class, () -> new CouponIssuance(coupon, customer)); } @Test From a78ffb6c0e4730c77f7042b7d703e886860ffd99 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:26:24 +0900 Subject: [PATCH 08/52] =?UTF-8?q?[feat]=20CouponIssuanceErrorCode=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/CouponIssuanceErrorCode.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/coupon/exception/CouponIssuanceErrorCode.java diff --git a/src/main/java/camp/woowak/lab/coupon/exception/CouponIssuanceErrorCode.java b/src/main/java/camp/woowak/lab/coupon/exception/CouponIssuanceErrorCode.java new file mode 100644 index 00000000..390afb39 --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/exception/CouponIssuanceErrorCode.java @@ -0,0 +1,38 @@ +package camp.woowak.lab.coupon.exception; + +import org.springframework.http.HttpStatus; + +import camp.woowak.lab.common.exception.ErrorCode; + +public enum CouponIssuanceErrorCode implements ErrorCode { + INVALID_ISSUANCE(HttpStatus.BAD_REQUEST, "cp_2_1", "잘못된 발급 요청입니다."), + NOT_FOUND_COUPON(HttpStatus.NOT_FOUND, "cp_2_2", "존재하지 않는 쿠폰입니다."), + EXPIRED_COUPON(HttpStatus.CONFLICT, "cp_2_3", "만료된 쿠폰입니다."), + NOT_ENOUGH_COUPON(HttpStatus.CONFLICT, "cp_2_4", "쿠폰이 부족합니다."), + ; + + int status; + String errorCode; + String message; + + CouponIssuanceErrorCode(HttpStatus status, String errorCode, String message) { + this.status = status.value(); + this.errorCode = errorCode; + this.message = message; + } + + @Override + public int getStatus() { + return status; + } + + @Override + public String getErrorCode() { + return errorCode; + } + + @Override + public String getMessage() { + return message; + } +} From 0edbd0529db00ca637af5155fbd0761f00928de5 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:26:59 +0900 Subject: [PATCH 09/52] =?UTF-8?q?[fix]=20ExpiredCouponException=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20CouponIssuanceErrorCode=20=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woowak/lab/coupon/exception/ExpiredCouponException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/coupon/exception/ExpiredCouponException.java b/src/main/java/camp/woowak/lab/coupon/exception/ExpiredCouponException.java index c27aa7e6..0a03f74a 100644 --- a/src/main/java/camp/woowak/lab/coupon/exception/ExpiredCouponException.java +++ b/src/main/java/camp/woowak/lab/coupon/exception/ExpiredCouponException.java @@ -4,6 +4,6 @@ public class ExpiredCouponException extends ConflictException { public ExpiredCouponException(String message) { - super(CouponErrorCode.EXPIRED_COUPON, message); + super(CouponIssuanceErrorCode.EXPIRED_COUPON, message); } } From dcc1a5b1e40a2edc345da5a3213adb0d73c30dd8 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:27:16 +0900 Subject: [PATCH 10/52] =?UTF-8?q?[feat]=20InvalidICreationIssuanceExceptio?= =?UTF-8?q?n=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/InvalidICreationIssuanceException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/coupon/exception/InvalidICreationIssuanceException.java diff --git a/src/main/java/camp/woowak/lab/coupon/exception/InvalidICreationIssuanceException.java b/src/main/java/camp/woowak/lab/coupon/exception/InvalidICreationIssuanceException.java new file mode 100644 index 00000000..83924bf4 --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/exception/InvalidICreationIssuanceException.java @@ -0,0 +1,9 @@ +package camp.woowak.lab.coupon.exception; + +import camp.woowak.lab.common.exception.BadRequestException; + +public class InvalidICreationIssuanceException extends BadRequestException { + public InvalidICreationIssuanceException(String message) { + super(CouponIssuanceErrorCode.INVALID_ISSUANCE, message); + } +} From ab07e6253891e54e0747b1f0595a4533af4cd22b Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:28:31 +0900 Subject: [PATCH 11/52] =?UTF-8?q?[feat]=20CouponIssuanceValidator=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20-=20CouponIssuance=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=20Customer=20=EC=99=80=20Coupon=20=EC=9D=98=20NUll=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20=EB=B0=8F=20Coupon=20?= =?UTF-8?q?=EB=A7=8C=EB=A3=8C=EC=9D=BC=EC=9D=B4=20=EB=B0=9C=EA=B8=89?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=84=EC=9D=B8=EC=A7=80=20=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/CouponIssuanceValidator.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/coupon/domain/CouponIssuanceValidator.java diff --git a/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuanceValidator.java b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuanceValidator.java new file mode 100644 index 00000000..6dd566fa --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuanceValidator.java @@ -0,0 +1,30 @@ +package camp.woowak.lab.coupon.domain; + +import camp.woowak.lab.coupon.exception.ExpiredCouponException; +import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; +import camp.woowak.lab.customer.domain.Customer; + +public class CouponIssuanceValidator { + private CouponIssuanceValidator() { + } + + public static void validate(Customer customer, Coupon coupon) { + validateNotNull(customer, coupon); + validateNotExpired(coupon); + + } + + private static void validateNotExpired(Coupon coupon) { + if (coupon.isExpired()) { + throw new ExpiredCouponException("만료된 쿠폰입니다."); + } + } + + private static void validateNotNull(Object... objects) { + for (var object : objects) { + if (object == null) { + throw new InvalidICreationIssuanceException("쿠폰 발급에 필요한 정보가 없습니다."); + } + } + } +} From c7300a5d30400282267c7ec7c8dec8665ab86d00 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:30:16 +0900 Subject: [PATCH 12/52] =?UTF-8?q?[fix]=20CouponIssuanceErrorCode=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20errorCode=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/coupon/exception/CouponIssuanceErrorCode.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/camp/woowak/lab/coupon/exception/CouponIssuanceErrorCode.java b/src/main/java/camp/woowak/lab/coupon/exception/CouponIssuanceErrorCode.java index 390afb39..64a752f8 100644 --- a/src/main/java/camp/woowak/lab/coupon/exception/CouponIssuanceErrorCode.java +++ b/src/main/java/camp/woowak/lab/coupon/exception/CouponIssuanceErrorCode.java @@ -5,10 +5,10 @@ import camp.woowak.lab.common.exception.ErrorCode; public enum CouponIssuanceErrorCode implements ErrorCode { - INVALID_ISSUANCE(HttpStatus.BAD_REQUEST, "cp_2_1", "잘못된 발급 요청입니다."), - NOT_FOUND_COUPON(HttpStatus.NOT_FOUND, "cp_2_2", "존재하지 않는 쿠폰입니다."), - EXPIRED_COUPON(HttpStatus.CONFLICT, "cp_2_3", "만료된 쿠폰입니다."), - NOT_ENOUGH_COUPON(HttpStatus.CONFLICT, "cp_2_4", "쿠폰이 부족합니다."), + INVALID_ISSUANCE(HttpStatus.BAD_REQUEST, "ci_1_1", "잘못된 발급 요청입니다."), + NOT_FOUND_COUPON(HttpStatus.NOT_FOUND, "ci_1_2", "존재하지 않는 쿠폰입니다."), + EXPIRED_COUPON(HttpStatus.CONFLICT, "ci_1_3", "만료된 쿠폰입니다."), + NOT_ENOUGH_COUPON(HttpStatus.CONFLICT, "ci_1_4", "쿠폰이 부족합니다."), ; int status; From f912959290b869472a235113ca05d9e60f791993 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:30:57 +0900 Subject: [PATCH 13/52] =?UTF-8?q?[feat]=20Coupon=20=EC=88=98=EC=A0=95=20-?= =?UTF-8?q?=20Coupon.isExpired:=20=EC=BF=A0=ED=8F=B0=20=EB=A7=8C=EB=A3=8C?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20=ED=99=95=EC=9D=B8=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/camp/woowak/lab/coupon/domain/Coupon.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/camp/woowak/lab/coupon/domain/Coupon.java b/src/main/java/camp/woowak/lab/coupon/domain/Coupon.java index e4ce24a5..919de149 100644 --- a/src/main/java/camp/woowak/lab/coupon/domain/Coupon.java +++ b/src/main/java/camp/woowak/lab/coupon/domain/Coupon.java @@ -47,4 +47,8 @@ public Coupon(String title, int discountAmount, int quantity, LocalDateTime expi this.quantity = quantity; this.expiredAt = expiredAt; } + + public boolean isExpired() { + return expiredAt.isBefore(LocalDateTime.now()); + } } From 4f5ed5bfdbff0a4cf3c0b7b8ca385531d0ae8109 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:31:25 +0900 Subject: [PATCH 14/52] =?UTF-8?q?[feat]=20CouponIssuance=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20CouponIssuance=20=EC=83=9D=EC=84=B1=20=EC=8B=9C?= =?UTF-8?q?=20Null=20=EA=B0=92=20=EA=B3=BC=20=EC=BF=A0=ED=8F=B0=20?= =?UTF-8?q?=EB=A7=8C=EB=A3=8C=20=EC=83=81=ED=83=9C=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java index 217ad5e1..4c41d5dd 100644 --- a/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java +++ b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java @@ -41,7 +41,7 @@ protected CouponIssuance() { } public CouponIssuance(Coupon coupon, Customer customer) { - + CouponIssuanceValidator.validate(customer, coupon); this.coupon = coupon; this.customer = customer; this.issuedAt = LocalDateTime.now(); From eeaff6ce907844ba85681d86d4802328792e7691 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:32:39 +0900 Subject: [PATCH 15/52] =?UTF-8?q?[fix]=20CouponErrorCode=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20Coupon=20=EC=97=90=EC=84=9C=EB=8A=94=20?= =?UTF-8?q?=EC=BF=A0=ED=8F=B0=EC=9D=B4=20=EB=A7=8C=EB=A3=8C=EB=90=98?= =?UTF-8?q?=EC=97=88=EB=8D=94=EB=9D=BC=EB=8F=84=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=9C=EC=83=9D=ED=95=A0=20=EC=9D=B4=EC=9C=A0?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EC=96=B4=20=EC=97=90=EB=9F=AC=20=EC=86=8C?= =?UTF-8?q?=EC=9C=A0=EB=A5=BC=20CouponIssuance=20=EB=A1=9C=20=EB=84=98?= =?UTF-8?q?=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/camp/woowak/lab/coupon/exception/CouponErrorCode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java b/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java index 33d6fda9..100b6c0b 100644 --- a/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java +++ b/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java @@ -7,7 +7,6 @@ public enum CouponErrorCode implements ErrorCode { INVALID_CREATION(HttpStatus.BAD_REQUEST, "cp_1_1", "잘못된 요청입니다."), DUPLICATE_COUPON_TITLE(HttpStatus.CONFLICT, "cp_1_2", "중복된 쿠폰 제목입니다."), - EXPIRED_COUPON(HttpStatus.CONFLICT, "cp_1_3", "만료된 쿠폰입니다."), ; int status; From 2d0c0172716e807b9c9e6795dbeb9b671e4d12f9 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:45:37 +0900 Subject: [PATCH 16/52] =?UTF-8?q?[fix]=20TestCoupon=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20-=20=EB=A7=8C=EB=A3=8C=EC=9D=BC=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=B4=20=EC=88=98=EB=8F=99?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B0=94=EA=BF=80=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camp/woowak/lab/coupon/domain/TestCoupon.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/camp/woowak/lab/coupon/domain/TestCoupon.java b/src/test/java/camp/woowak/lab/coupon/domain/TestCoupon.java index e6577002..c3cca9d9 100644 --- a/src/test/java/camp/woowak/lab/coupon/domain/TestCoupon.java +++ b/src/test/java/camp/woowak/lab/coupon/domain/TestCoupon.java @@ -4,14 +4,25 @@ public class TestCoupon extends Coupon { private final Long id; + private LocalDateTime expiredAt; public TestCoupon(Long id, String title, int discountAmount, int amount, LocalDateTime expiredAt) { super(title, discountAmount, amount, expiredAt); this.id = id; + this.expiredAt = expiredAt; } @Override public Long getId() { return id; } + + public void setExpiredAt(LocalDateTime expiredAt) { + this.expiredAt = expiredAt; + } + + @Override + public boolean isExpired() { + return expiredAt.isBefore(LocalDateTime.now()); + } } From fa6f35a58807d0fb331655aac67d660ba875cd9d Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:48:02 +0900 Subject: [PATCH 17/52] =?UTF-8?q?[fix]=20CouponIssuanceTest=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20CouponIssuanceTest.testConstructWithExpiredCoup?= =?UTF-8?q?on=20=EC=97=90=EC=84=9C=20Coupon=20=EC=9D=B4=20=EC=A0=95?= =?UTF-8?q?=EC=83=81=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EB=90=9C=20=ED=9B=84=20=EB=A7=8C=EB=A3=8C=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camp/woowak/lab/coupon/domain/CouponIssuanceTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java index eedd655d..c585d71b 100644 --- a/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java +++ b/src/test/java/camp/woowak/lab/coupon/domain/CouponIssuanceTest.java @@ -80,7 +80,9 @@ void testConstructWithExpiredCoupon() { int quantity = 100; LocalDateTime expiredAt = LocalDateTime.now().minusDays(1); PayAccount payAccount = createPayAccount(); - Coupon coupon = createCoupon(fakeCouponId, title, discountAmount, quantity, expiredAt); + TestCoupon coupon = (TestCoupon)createCoupon(fakeCouponId, title, discountAmount, quantity, + LocalDateTime.now().plusDays(7)); + coupon.setExpiredAt(expiredAt); Customer customer = createCustomer(payAccount, passwordEncoder); // when & then From 55999ee18c8912ab3032b36a13dcdbd431999c56 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:48:18 +0900 Subject: [PATCH 18/52] =?UTF-8?q?[feat]=20CouponIssuanceRepository=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/coupon/repository/CouponIssuanceRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/coupon/repository/CouponIssuanceRepository.java diff --git a/src/main/java/camp/woowak/lab/coupon/repository/CouponIssuanceRepository.java b/src/main/java/camp/woowak/lab/coupon/repository/CouponIssuanceRepository.java new file mode 100644 index 00000000..fb345111 --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/repository/CouponIssuanceRepository.java @@ -0,0 +1,8 @@ +package camp.woowak.lab.coupon.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import camp.woowak.lab.coupon.domain.CouponIssuance; + +public interface CouponIssuanceRepository extends JpaRepository { +} From 776eedd1ddcdf7ef1aa3e97fc69c439b47bead6a Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 21:59:50 +0900 Subject: [PATCH 19/52] =?UTF-8?q?[fix]=20CustomerRepository=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20JpaRepository=20=EA=B8=B0=EB=B3=B8=20=ED=82=A4?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woowak/lab/customer/repository/CustomerRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/customer/repository/CustomerRepository.java b/src/main/java/camp/woowak/lab/customer/repository/CustomerRepository.java index 615781b0..28be0c62 100644 --- a/src/main/java/camp/woowak/lab/customer/repository/CustomerRepository.java +++ b/src/main/java/camp/woowak/lab/customer/repository/CustomerRepository.java @@ -1,11 +1,12 @@ package camp.woowak.lab.customer.repository; import java.util.Optional; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import camp.woowak.lab.customer.domain.Customer; -public interface CustomerRepository extends JpaRepository { +public interface CustomerRepository extends JpaRepository { Optional findByEmail(String email); } From e2bcbf5e2eb360a143bf1da988389c3bace6ae33 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 22:59:12 +0900 Subject: [PATCH 20/52] =?UTF-8?q?[test]=20IssueCouponServiceTest=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20-=20IssueCouponServiceTest.testIssueCoupon?= =?UTF-8?q?:=20=EC=BF=A0=ED=8F=B0=20=EB=B0=9C=EA=B8=89=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=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 --- .../service/IssueCouponServiceTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java diff --git a/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java new file mode 100644 index 00000000..57f5cae3 --- /dev/null +++ b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java @@ -0,0 +1,67 @@ +package camp.woowak.lab.coupon.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; + +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 camp.woowak.lab.coupon.domain.Coupon; +import camp.woowak.lab.coupon.domain.CouponIssuance; +import camp.woowak.lab.coupon.repository.CouponIssuanceRepository; +import camp.woowak.lab.coupon.repository.CouponRepository; +import camp.woowak.lab.coupon.service.command.IssueCouponCommand; +import camp.woowak.lab.customer.domain.Customer; +import camp.woowak.lab.customer.repository.CustomerRepository; +import camp.woowak.lab.fixture.CouponFixture; +import camp.woowak.lab.fixture.CouponIssuanceFixture; +import camp.woowak.lab.fixture.CustomerFixture; + +@ExtendWith(MockitoExtension.class) +class IssueCouponServiceTest implements CouponFixture, CustomerFixture, CouponIssuanceFixture { + @InjectMocks + private IssueCouponService issueCouponService; + + @Mock + private CouponIssuanceRepository couponIssuanceRepository; + + @Mock + private CustomerRepository customerRepository; + + @Mock + private CouponRepository couponRepository; + + @Test + @DisplayName("Coupon 발급 테스트 - 성공") + void testIssueCoupon() { + // given + UUID fakeCustomerId = UUID.randomUUID(); + Long fakeCouponId = 1L; + Long fakeCouponIssuanceId = 1L; + Coupon fakeCoupon = createCoupon(fakeCouponId, "할인 쿠폰", 1000, 100, LocalDateTime.now().plusDays(7)); + Customer fakeCustomer = createCustomer(fakeCustomerId); + CouponIssuance fakeCouponIssuance = createCouponIssuance(fakeCouponId, fakeCoupon, fakeCustomer); + given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.of(fakeCustomer)); + given(couponRepository.findById(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); + given(couponIssuanceRepository.save(any(CouponIssuance.class))).willReturn(fakeCouponIssuance); + + IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); + + // when + Long saveId = issueCouponService.issueCoupon(cmd); + + // then + assertEquals(fakeCouponIssuanceId, saveId); + verify(customerRepository).findById(fakeCustomerId); + verify(couponRepository).findById(fakeCouponId); + verify(couponIssuanceRepository).save(any(CouponIssuance.class)); + } +} From 3c542d32666f14088f23f9a3a21fa251fc2ba91e Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:00:05 +0900 Subject: [PATCH 21/52] =?UTF-8?q?[feat]=20TestCustomer=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20test=EB=A5=BC=20=EC=9C=84=ED=95=B4=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=A0=20Customer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/customer/domain/TestCustomer.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/test/java/camp/woowak/lab/customer/domain/TestCustomer.java diff --git a/src/test/java/camp/woowak/lab/customer/domain/TestCustomer.java b/src/test/java/camp/woowak/lab/customer/domain/TestCustomer.java new file mode 100644 index 00000000..b7a7fa30 --- /dev/null +++ b/src/test/java/camp/woowak/lab/customer/domain/TestCustomer.java @@ -0,0 +1,22 @@ +package camp.woowak.lab.customer.domain; + +import java.util.UUID; + +import camp.woowak.lab.payaccount.domain.PayAccount; +import camp.woowak.lab.web.authentication.NoOpPasswordEncoder; + +public class TestCustomer extends Customer { + private final UUID id; + + public TestCustomer(UUID id, String customerName, String mail, String customerPassword, String s, + PayAccount payAccount, + NoOpPasswordEncoder noOpPasswordEncoder) { + super(customerName, mail, customerPassword, s, payAccount, noOpPasswordEncoder); + this.id = id; + } + + @Override + public UUID getId() { + return id; + } +} From 60e55cb380800ca2c30027d844786a837650dd78 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:00:26 +0900 Subject: [PATCH 22/52] =?UTF-8?q?[feat]=20TestCouponIssuance=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20test=EB=A5=BC=20=EC=9C=84=ED=95=B4=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=A0=20CouponIssuance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/coupon/domain/TestCouponIssuance.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/java/camp/woowak/lab/coupon/domain/TestCouponIssuance.java diff --git a/src/test/java/camp/woowak/lab/coupon/domain/TestCouponIssuance.java b/src/test/java/camp/woowak/lab/coupon/domain/TestCouponIssuance.java new file mode 100644 index 00000000..4e80bcf2 --- /dev/null +++ b/src/test/java/camp/woowak/lab/coupon/domain/TestCouponIssuance.java @@ -0,0 +1,17 @@ +package camp.woowak.lab.coupon.domain; + +import camp.woowak.lab.customer.domain.Customer; + +public class TestCouponIssuance extends CouponIssuance { + private final Long id; + + public TestCouponIssuance(Long id, Coupon coupon, Customer customer) { + super(coupon, customer); + this.id = id; + } + + @Override + public Long getId() { + return id; + } +} From 908f356c89cc976b61936de6fd4296423792421d Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:01:21 +0900 Subject: [PATCH 23/52] =?UTF-8?q?[feat]=20CustomerFixture=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20CustomerFixture.createCustomer(UUID):=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=84=A4=EC=A0=95=EC=97=90=20id=20?= =?UTF-8?q?=EA=B0=92=EB=A7=8C=20=EB=B0=94=EB=80=8C=EB=8A=94=20customer=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=86=8C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camp/woowak/lab/fixture/CustomerFixture.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/camp/woowak/lab/fixture/CustomerFixture.java b/src/test/java/camp/woowak/lab/fixture/CustomerFixture.java index d30b2ea2..d5d212f4 100644 --- a/src/test/java/camp/woowak/lab/fixture/CustomerFixture.java +++ b/src/test/java/camp/woowak/lab/fixture/CustomerFixture.java @@ -1,8 +1,12 @@ package camp.woowak.lab.fixture; +import java.util.UUID; + import camp.woowak.lab.customer.domain.Customer; +import camp.woowak.lab.customer.domain.TestCustomer; import camp.woowak.lab.customer.exception.InvalidCreationException; import camp.woowak.lab.payaccount.domain.PayAccount; +import camp.woowak.lab.web.authentication.NoOpPasswordEncoder; import camp.woowak.lab.web.authentication.PasswordEncoder; /** @@ -23,4 +27,13 @@ default Customer createCustomer(PayAccount payAccount, PasswordEncoder passwordE } } + default Customer createCustomer(UUID id) { + try { + return new TestCustomer(id, "customerName", "customer@email.com", "customerPassword", "010-0000-0000", + createPayAccount(), + new NoOpPasswordEncoder()); + } catch (InvalidCreationException e) { + throw new RuntimeException(e); + } + } } From 203b319b19d4ef8ffd54d6ee5f6b22659501abf6 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:02:38 +0900 Subject: [PATCH 24/52] =?UTF-8?q?[feat]=20CouponIssuanceFixture=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20-=20CouponIssuanceFixture.createCouponIssu?= =?UTF-8?q?ance:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=A0=20CouponIssuance=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woowak/lab/fixture/CouponIssuanceFixture.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/test/java/camp/woowak/lab/fixture/CouponIssuanceFixture.java diff --git a/src/test/java/camp/woowak/lab/fixture/CouponIssuanceFixture.java b/src/test/java/camp/woowak/lab/fixture/CouponIssuanceFixture.java new file mode 100644 index 00000000..53c3c94e --- /dev/null +++ b/src/test/java/camp/woowak/lab/fixture/CouponIssuanceFixture.java @@ -0,0 +1,15 @@ +package camp.woowak.lab.fixture; + +import camp.woowak.lab.coupon.domain.Coupon; +import camp.woowak.lab.coupon.domain.CouponIssuance; +import camp.woowak.lab.coupon.domain.TestCouponIssuance; +import camp.woowak.lab.customer.domain.Customer; + +/** + * CouponIssuanceFixture는 테스트에서 CouponIssuance를 생성할 때 사용하는 Fixture입니다. + */ +public interface CouponIssuanceFixture { + default CouponIssuance createCouponIssuance(Long id, Coupon coupon, Customer customer) { + return new TestCouponIssuance(id, coupon, customer); + } +} From e4ba26465cb00a9d973e8ee6175dd87bdd9cbd9b Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:02:53 +0900 Subject: [PATCH 25/52] =?UTF-8?q?[feat]=20IssueCouponCommand=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 --- .../lab/coupon/service/command/IssueCouponCommand.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/coupon/service/command/IssueCouponCommand.java diff --git a/src/main/java/camp/woowak/lab/coupon/service/command/IssueCouponCommand.java b/src/main/java/camp/woowak/lab/coupon/service/command/IssueCouponCommand.java new file mode 100644 index 00000000..a2324396 --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/service/command/IssueCouponCommand.java @@ -0,0 +1,6 @@ +package camp.woowak.lab.coupon.service.command; + +import java.util.UUID; + +public record IssueCouponCommand(UUID customerId, Long couponId) { +} From d44df702eadad9c6b1f5c35b83a088c02e5438f8 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:03:45 +0900 Subject: [PATCH 26/52] =?UTF-8?q?[feat]=20IssueCouponService=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20IssueCouponService.issueCoupon:=20=EA=B5=AC?= =?UTF-8?q?=EB=A7=A4=EC=9E=90=EC=97=90=EA=B2=8C=20=EC=BF=A0=ED=8F=B0?= =?UTF-8?q?=EC=9D=84=20=EB=B0=9C=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coupon/service/IssueCouponService.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java diff --git a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java new file mode 100644 index 00000000..3217b727 --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java @@ -0,0 +1,48 @@ +package camp.woowak.lab.coupon.service; + +import org.springframework.stereotype.Service; + +import camp.woowak.lab.coupon.domain.Coupon; +import camp.woowak.lab.coupon.domain.CouponIssuance; +import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; +import camp.woowak.lab.coupon.repository.CouponIssuanceRepository; +import camp.woowak.lab.coupon.repository.CouponRepository; +import camp.woowak.lab.coupon.service.command.IssueCouponCommand; +import camp.woowak.lab.customer.domain.Customer; +import camp.woowak.lab.customer.repository.CustomerRepository; +import jakarta.transaction.Transactional; + +@Service +public class IssueCouponService { + private final CouponIssuanceRepository couponIssuanceRepository; + private final CouponRepository couponRepository; + private final CustomerRepository customerRepository; + + public IssueCouponService(CouponIssuanceRepository couponIssuanceRepository, CouponRepository couponRepository, + CustomerRepository customerRepository) { + this.couponIssuanceRepository = couponIssuanceRepository; + this.couponRepository = couponRepository; + this.customerRepository = customerRepository; + } + + /** + * + * @throws InvalidICreationIssuanceException customer 또는 coupon이 존재하지 않을 경우 또는 coupon이 만료되었을 경우 + */ + @Transactional + public Long issueCoupon(IssueCouponCommand cmd) { + // customer 조회 + Customer targetCustomer = customerRepository.findById(cmd.customerId()) + .orElseThrow(() -> new InvalidICreationIssuanceException("customer not found")); + + // coupon 조회 + Coupon targetCoupon = couponRepository.findById(cmd.couponId()) + .orElseThrow(() -> new InvalidICreationIssuanceException("coupon not found")); + + // coupon issuance 생성 + CouponIssuance newCouponIssuance = new CouponIssuance(targetCoupon, targetCustomer); + + // coupon issuance 저장 + return couponIssuanceRepository.save(newCouponIssuance).getId(); + } +} From afafcd5e21429bc318d90484b8ffe950bae8f082 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:08:46 +0900 Subject: [PATCH 27/52] =?UTF-8?q?[test]=20IssueCouponServiceTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20IssueCouponServiceTest.testIssueCoupon?= =?UTF-8?q?FailWithNotExistCustomer:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EC=9A=94=EC=B2=AD=20=EA=B2=80=EC=A6=9D=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20-=20IssueCouponServiceTest.testIssueCoupon?= =?UTF-8?q?FailWithNotExistCoupon:=20=EC=BF=A0=ED=8F=B0=EC=9D=B4=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20-=20IssueCouponServiceTest.testIssueCouponFailWithE?= =?UTF-8?q?xpiredCoupon:=20=EB=A7=8C=EB=A3=8C=EB=90=9C=20=EC=BF=A0?= =?UTF-8?q?=ED=8F=B0=EC=9D=84=20=EB=B0=9C=ED=96=89=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/IssueCouponServiceTest.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java index 57f5cae3..cdd7bf26 100644 --- a/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java +++ b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java @@ -16,6 +16,9 @@ import camp.woowak.lab.coupon.domain.Coupon; import camp.woowak.lab.coupon.domain.CouponIssuance; +import camp.woowak.lab.coupon.domain.TestCoupon; +import camp.woowak.lab.coupon.exception.ExpiredCouponException; +import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; import camp.woowak.lab.coupon.repository.CouponIssuanceRepository; import camp.woowak.lab.coupon.repository.CouponRepository; import camp.woowak.lab.coupon.service.command.IssueCouponCommand; @@ -64,4 +67,62 @@ void testIssueCoupon() { verify(couponRepository).findById(fakeCouponId); verify(couponIssuanceRepository).save(any(CouponIssuance.class)); } + + @Test + @DisplayName("Coupon 발급 테스트 - 존재하지 않는 Customer") + void testIssueCouponFailWithNotExistCustomer() { + // given + UUID fakeCustomerId = UUID.randomUUID(); + Long fakeCouponId = 1L; + given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.empty()); + + IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); + + // when & then + assertThrows(InvalidICreationIssuanceException.class, () -> issueCouponService.issueCoupon(cmd)); + verify(customerRepository).findById(fakeCustomerId); + verify(couponRepository, never()).findById(fakeCouponId); + verify(couponIssuanceRepository, never()).save(any(CouponIssuance.class)); + } + + @Test + @DisplayName("Coupon 발급 테스트 - 존재하지 않는 Coupon") + void testIssueCouponFailWithNotExistCoupon() { + // given + UUID fakeCustomerId = UUID.randomUUID(); + Long fakeCouponId = 1L; + Customer fakeCustomer = createCustomer(fakeCustomerId); + given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.of(fakeCustomer)); + given(couponRepository.findById(fakeCouponId)).willReturn(Optional.empty()); + + IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); + + // when & then + assertThrows(InvalidICreationIssuanceException.class, () -> issueCouponService.issueCoupon(cmd)); + verify(customerRepository).findById(fakeCustomerId); + verify(couponRepository).findById(fakeCouponId); + verify(couponIssuanceRepository, never()).save(any(CouponIssuance.class)); + } + + @Test + @DisplayName("Coupon 발급 테스트 - 만료된 Coupon") + void testIssueCouponFailWithExpiredCoupon() { + // given + UUID fakeCustomerId = UUID.randomUUID(); + Long fakeCouponId = 1L; + TestCoupon fakeCoupon = (TestCoupon)createCoupon(fakeCouponId, "할인 쿠폰", 1000, 100, + LocalDateTime.now().plusDays(7)); + fakeCoupon.setExpiredAt(LocalDateTime.now().minusDays(1)); + Customer fakeCustomer = createCustomer(fakeCustomerId); + given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.of(fakeCustomer)); + given(couponRepository.findById(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); + + IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); + + // when & then + assertThrows(ExpiredCouponException.class, () -> issueCouponService.issueCoupon(cmd)); + verify(customerRepository).findById(fakeCustomerId); + verify(couponRepository).findById(fakeCouponId); + verify(couponIssuanceRepository, never()).save(any(CouponIssuance.class)); + } } From a322afbcacb1b5d9bb556b9d18187d5c76454727 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:09:35 +0900 Subject: [PATCH 28/52] =?UTF-8?q?[docs]=20IssueCouponService=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=88=98=EC=A0=95=20-=20IssueCouponService.issueCo?= =?UTF-8?q?upon=20=EC=97=90=20ExpiredCouponException=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camp/woowak/lab/coupon/service/IssueCouponService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java index 3217b727..5d5f4956 100644 --- a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java +++ b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java @@ -4,6 +4,7 @@ import camp.woowak.lab.coupon.domain.Coupon; import camp.woowak.lab.coupon.domain.CouponIssuance; +import camp.woowak.lab.coupon.exception.ExpiredCouponException; import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; import camp.woowak.lab.coupon.repository.CouponIssuanceRepository; import camp.woowak.lab.coupon.repository.CouponRepository; @@ -28,6 +29,7 @@ public IssueCouponService(CouponIssuanceRepository couponIssuanceRepository, Cou /** * * @throws InvalidICreationIssuanceException customer 또는 coupon이 존재하지 않을 경우 또는 coupon이 만료되었을 경우 + * @throws ExpiredCouponException coupon이 만료되었을 경우 */ @Transactional public Long issueCoupon(IssueCouponCommand cmd) { @@ -38,7 +40,7 @@ public Long issueCoupon(IssueCouponCommand cmd) { // coupon 조회 Coupon targetCoupon = couponRepository.findById(cmd.couponId()) .orElseThrow(() -> new InvalidICreationIssuanceException("coupon not found")); - + // coupon issuance 생성 CouponIssuance newCouponIssuance = new CouponIssuance(targetCoupon, targetCustomer); From 620330169d6215a73124bdd693c9bb9f4c7f7020 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:42:47 +0900 Subject: [PATCH 29/52] =?UTF-8?q?[test]=20IssueCouponServiceTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20IssueCouponServiceTest.testIssueCoupon?= =?UTF-8?q?FailWithInsufficientCouponQuantity:=20=EC=88=98=EB=9F=89=20?= =?UTF-8?q?=EB=B6=80=EC=A1=B1=20=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=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 --- .../service/IssueCouponServiceTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java index cdd7bf26..672adccb 100644 --- a/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java +++ b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java @@ -18,6 +18,7 @@ import camp.woowak.lab.coupon.domain.CouponIssuance; import camp.woowak.lab.coupon.domain.TestCoupon; import camp.woowak.lab.coupon.exception.ExpiredCouponException; +import camp.woowak.lab.coupon.exception.InsufficientCouponQuantityException; import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; import camp.woowak.lab.coupon.repository.CouponIssuanceRepository; import camp.woowak.lab.coupon.repository.CouponRepository; @@ -125,4 +126,27 @@ void testIssueCouponFailWithExpiredCoupon() { verify(couponRepository).findById(fakeCouponId); verify(couponIssuanceRepository, never()).save(any(CouponIssuance.class)); } + + @Test + @DisplayName("Coupon 발급 테스트 - 수량 부족") + void testIssueCouponFailWithInsufficientCouponQuantity() { + // given + UUID fakeCustomerId = UUID.randomUUID(); + Long fakeCouponId = 1L; + TestCoupon fakeCoupon = (TestCoupon)createCoupon(fakeCouponId, "할인 쿠폰", 1000, 1, + LocalDateTime.now().plusDays(7)); + Customer fakeCustomer = createCustomer(fakeCustomerId); + fakeCoupon.setQuantity(0); // 수량 부족 시나리오 적용 + given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.of(fakeCustomer)); + given(couponRepository.findById(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); + given(couponIssuanceRepository.save(any(CouponIssuance.class))).willThrow( + new InsufficientCouponQuantityException("the quantity of coupon is insufficient")); + IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); + + // when & then + assertThrows(InsufficientCouponQuantityException.class, () -> issueCouponService.issueCoupon(cmd)); + verify(customerRepository).findById(fakeCustomerId); + verify(couponRepository).findById(fakeCouponId); + verify(couponIssuanceRepository).save(any(CouponIssuance.class)); + } } From 601bd0da94a137fcfbef43a07180fc7ad8133905 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:45:40 +0900 Subject: [PATCH 30/52] =?UTF-8?q?[feat]=20TestCoupon=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20-=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=95=A0=20=EC=88=98?= =?UTF-8?q?=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woowak/lab/coupon/domain/TestCoupon.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/test/java/camp/woowak/lab/coupon/domain/TestCoupon.java b/src/test/java/camp/woowak/lab/coupon/domain/TestCoupon.java index c3cca9d9..5cf83d03 100644 --- a/src/test/java/camp/woowak/lab/coupon/domain/TestCoupon.java +++ b/src/test/java/camp/woowak/lab/coupon/domain/TestCoupon.java @@ -2,14 +2,18 @@ import java.time.LocalDateTime; +import camp.woowak.lab.coupon.exception.InsufficientCouponQuantityException; + public class TestCoupon extends Coupon { private final Long id; + private int quantity; private LocalDateTime expiredAt; - public TestCoupon(Long id, String title, int discountAmount, int amount, LocalDateTime expiredAt) { - super(title, discountAmount, amount, expiredAt); + public TestCoupon(Long id, String title, int discountAmount, int quantity, LocalDateTime expiredAt) { + super(title, discountAmount, quantity, expiredAt); this.id = id; this.expiredAt = expiredAt; + this.quantity = quantity; } @Override @@ -25,4 +29,21 @@ public void setExpiredAt(LocalDateTime expiredAt) { public boolean isExpired() { return expiredAt.isBefore(LocalDateTime.now()); } + + @Override + public void decreaseQuantity() { + if (!hasAvailableQuantity()) { + throw new InsufficientCouponQuantityException("쿠폰의 수량이 부족합니다."); + } + this.quantity--; + } + + @Override + public boolean hasAvailableQuantity() { + return this.quantity > 0; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } } From e39f161b8616e86fb9170fc74ee2a61177259c5a Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:46:34 +0900 Subject: [PATCH 31/52] =?UTF-8?q?[refactor]=20CouponFixture=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EB=AA=85=EC=B9=AD=20=ED=86=B5=EC=9D=BC=20amoun?= =?UTF-8?q?t=20->=20quantity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/camp/woowak/lab/fixture/CouponFixture.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/camp/woowak/lab/fixture/CouponFixture.java b/src/test/java/camp/woowak/lab/fixture/CouponFixture.java index 16b45882..ccfbcb39 100644 --- a/src/test/java/camp/woowak/lab/fixture/CouponFixture.java +++ b/src/test/java/camp/woowak/lab/fixture/CouponFixture.java @@ -9,7 +9,7 @@ * CouponFixture는 테스트에서 사용할 Coupon 객체를 생성하는 역할을 합니다. */ public interface CouponFixture { - default Coupon createCoupon(Long id, String title, int discountAmount, int amount, LocalDateTime expiredAt) { - return new TestCoupon(id, title, discountAmount, amount, expiredAt); + default Coupon createCoupon(Long id, String title, int discountAmount, int quantity, LocalDateTime expiredAt) { + return new TestCoupon(id, title, discountAmount, quantity, expiredAt); } } From 10c06136fa28195fd3c24268cadb2aa30ca00db4 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:47:27 +0900 Subject: [PATCH 32/52] =?UTF-8?q?[feat]=20IssueCouponService=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20IssueCouponService.issueCoupon=EC=97=90=20?= =?UTF-8?q?=EC=88=98=EB=9F=89=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camp/woowak/lab/coupon/service/IssueCouponService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java index 5d5f4956..02706d5a 100644 --- a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java +++ b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java @@ -5,6 +5,7 @@ import camp.woowak.lab.coupon.domain.Coupon; import camp.woowak.lab.coupon.domain.CouponIssuance; import camp.woowak.lab.coupon.exception.ExpiredCouponException; +import camp.woowak.lab.coupon.exception.InsufficientCouponQuantityException; import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; import camp.woowak.lab.coupon.repository.CouponIssuanceRepository; import camp.woowak.lab.coupon.repository.CouponRepository; @@ -30,6 +31,7 @@ public IssueCouponService(CouponIssuanceRepository couponIssuanceRepository, Cou * * @throws InvalidICreationIssuanceException customer 또는 coupon이 존재하지 않을 경우 또는 coupon이 만료되었을 경우 * @throws ExpiredCouponException coupon이 만료되었을 경우 + * @throws InsufficientCouponQuantityException coupon 수량이 부족할 경우 */ @Transactional public Long issueCoupon(IssueCouponCommand cmd) { @@ -41,6 +43,11 @@ public Long issueCoupon(IssueCouponCommand cmd) { Coupon targetCoupon = couponRepository.findById(cmd.couponId()) .orElseThrow(() -> new InvalidICreationIssuanceException("coupon not found")); + // coupon 수량 확인 + if (!targetCoupon.hasAvailableQuantity()) { + throw new InsufficientCouponQuantityException("coupon is expired"); + } + // coupon issuance 생성 CouponIssuance newCouponIssuance = new CouponIssuance(targetCoupon, targetCustomer); From f05341e2f5a90f191ed667ccdec74fdf8966159d Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:47:39 +0900 Subject: [PATCH 33/52] =?UTF-8?q?[feat]=20InsufficientCouponQuantityExcept?= =?UTF-8?q?ion=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/InsufficientCouponQuantityException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/coupon/exception/InsufficientCouponQuantityException.java diff --git a/src/main/java/camp/woowak/lab/coupon/exception/InsufficientCouponQuantityException.java b/src/main/java/camp/woowak/lab/coupon/exception/InsufficientCouponQuantityException.java new file mode 100644 index 00000000..5a97df10 --- /dev/null +++ b/src/main/java/camp/woowak/lab/coupon/exception/InsufficientCouponQuantityException.java @@ -0,0 +1,9 @@ +package camp.woowak.lab.coupon.exception; + +import camp.woowak.lab.common.exception.ConflictException; + +public class InsufficientCouponQuantityException extends ConflictException { + public InsufficientCouponQuantityException(String message) { + super(CouponErrorCode.INSUFFICIENT_QUANTITY, message); + } +} From 19fae512e41efbae2a839b78fa942e5968b1b8e1 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:48:24 +0900 Subject: [PATCH 34/52] =?UTF-8?q?[feat]=20CouponErrorCode=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20CouponErrorCode.INSUFFICIENT=5FQUANTITY:=20?= =?UTF-8?q?=EC=BF=A0=ED=8F=B0=20=EC=88=98=EB=9F=89=20=EB=B6=80=EC=A1=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EC=97=90=EB=9F=AC=20=EC=BD=94=EB=93=9C=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 --- .../java/camp/woowak/lab/coupon/exception/CouponErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java b/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java index 100b6c0b..8e1279d1 100644 --- a/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java +++ b/src/main/java/camp/woowak/lab/coupon/exception/CouponErrorCode.java @@ -7,6 +7,7 @@ public enum CouponErrorCode implements ErrorCode { INVALID_CREATION(HttpStatus.BAD_REQUEST, "cp_1_1", "잘못된 요청입니다."), DUPLICATE_COUPON_TITLE(HttpStatus.CONFLICT, "cp_1_2", "중복된 쿠폰 제목입니다."), + INSUFFICIENT_QUANTITY(HttpStatus.CONFLICT, "cp_1_3", "발급 가능한 쿠폰이 부족합니다."), ; int status; From fd797a8f8789545b14d504a3590708be3951d992 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:49:34 +0900 Subject: [PATCH 35/52] =?UTF-8?q?[feat]=20CouponIssuance=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=BF=A0=ED=8F=B0=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=8B=9C=20=EC=BF=A0=ED=8F=B0=20=EC=88=98=EB=9F=89=20=EA=B0=90?= =?UTF-8?q?=EC=86=8C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java index 4c41d5dd..dd676245 100644 --- a/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java +++ b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java @@ -42,6 +42,8 @@ protected CouponIssuance() { public CouponIssuance(Coupon coupon, Customer customer) { CouponIssuanceValidator.validate(customer, coupon); + // coupon 수량 감소 + coupon.decreaseQuantity(); this.coupon = coupon; this.customer = customer; this.issuedAt = LocalDateTime.now(); From f1ea78f64ed590293123c1dfbe961dc228238628 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Thu, 15 Aug 2024 23:50:42 +0900 Subject: [PATCH 36/52] =?UTF-8?q?[feat]=20Coupon=20=EC=88=98=EC=A0=95=20-?= =?UTF-8?q?=20Coupon.hasAvailableQuantity:=20=EC=BF=A0=ED=8F=B0=20?= =?UTF-8?q?=EC=88=98=EB=9F=89=EC=9D=B4=200=20=EC=B4=88=EA=B3=BC=EC=9D=B8?= =?UTF-8?q?=EC=A7=80=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20-=20Coupon.decreaseQuantity:=20=EC=BF=A0?= =?UTF-8?q?=ED=8F=B0=20=EC=88=98=EB=9F=89=EC=9D=84=20=EA=B0=90=EC=86=8C?= =?UTF-8?q?=EC=8B=9C=ED=82=A4=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/camp/woowak/lab/coupon/domain/Coupon.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/camp/woowak/lab/coupon/domain/Coupon.java b/src/main/java/camp/woowak/lab/coupon/domain/Coupon.java index 919de149..18f76cd6 100644 --- a/src/main/java/camp/woowak/lab/coupon/domain/Coupon.java +++ b/src/main/java/camp/woowak/lab/coupon/domain/Coupon.java @@ -6,6 +6,7 @@ import org.springframework.format.annotation.DateTimeFormat; +import camp.woowak.lab.coupon.exception.InsufficientCouponQuantityException; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -51,4 +52,15 @@ public Coupon(String title, int discountAmount, int quantity, LocalDateTime expi public boolean isExpired() { return expiredAt.isBefore(LocalDateTime.now()); } + + public boolean hasAvailableQuantity() { + return quantity > 0; + } + + public void decreaseQuantity() { + if (!hasAvailableQuantity()) { + throw new InsufficientCouponQuantityException("quantity is not enough"); + } + quantity--; + } } From 542480787411d4c9d3edee5c0bc0deb6c627ab74 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:36:44 +0900 Subject: [PATCH 37/52] =?UTF-8?q?[test]=20IssueCouponServiceIntegrationTes?= =?UTF-8?q?t=20=EA=B5=AC=ED=98=84=20-=20IssueCouponServiceIntegrationTest.?= =?UTF-8?q?testIssueCoupon:=20=EC=BF=A0=ED=8F=B0=20=EB=B0=9C=EA=B8=89=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20-=20IssueCouponServiceIntegrationTest.testIssueCoup?= =?UTF-8?q?onFailWithInsufficientQuantity:=20=EC=BF=A0=ED=8F=B0=20?= =?UTF-8?q?=EC=88=98=EB=9F=89=20=EB=B6=80=EC=A1=B1=20=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20-=20IssueCou?= =?UTF-8?q?ponServiceIntegrationTest.testIssueCouponWithConcurrency:=20?= =?UTF-8?q?=EB=8F=99=EC=8B=9C=EC=84=B1=20=EC=A0=9C=EC=96=B4=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=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 --- .../IssueCouponServiceIntegrationTest.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceIntegrationTest.java diff --git a/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceIntegrationTest.java b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceIntegrationTest.java new file mode 100644 index 00000000..5573e906 --- /dev/null +++ b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceIntegrationTest.java @@ -0,0 +1,157 @@ +package camp.woowak.lab.coupon.service; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import camp.woowak.lab.coupon.domain.Coupon; +import camp.woowak.lab.coupon.domain.CouponIssuance; +import camp.woowak.lab.coupon.exception.InsufficientCouponQuantityException; +import camp.woowak.lab.coupon.repository.CouponIssuanceRepository; +import camp.woowak.lab.coupon.repository.CouponRepository; +import camp.woowak.lab.coupon.service.command.IssueCouponCommand; +import camp.woowak.lab.customer.domain.Customer; +import camp.woowak.lab.customer.repository.CustomerRepository; +import camp.woowak.lab.fixture.CouponFixture; +import camp.woowak.lab.fixture.CustomerFixture; +import camp.woowak.lab.payaccount.domain.PayAccount; +import camp.woowak.lab.payaccount.repository.PayAccountRepository; +import camp.woowak.lab.web.authentication.NoOpPasswordEncoder; +import jakarta.transaction.Transactional; + +@SpringBootTest +class IssueCouponServiceIntegrationTest implements CouponFixture, CustomerFixture { + @Autowired + private IssueCouponService service; + + @Autowired + private CouponIssuanceRepository couponIssuanceRepository; + + @Autowired + private CouponRepository couponRepository; + + @Autowired + private CustomerRepository customerRepository; + + @Autowired + private PayAccountRepository payAccountRepository; + + @Test + @DisplayName("쿠폰 발급 테스트 - 성공") + @Transactional + void testIssueCoupon() { + // given + int initialQuantity = 100; + Coupon coupon = createCoupon("할인 쿠폰", 1000, initialQuantity, LocalDateTime.now().plusDays(7)); + // 쿠폰 등록 + Long couponId = couponRepository.save(coupon).getId(); + + // 고객 등록 + // 계좌 생성 + PayAccount payAccount = payAccountRepository.save(createPayAccount()); + Customer customer = createCustomer(payAccount, new NoOpPasswordEncoder()); + UUID customerId = customerRepository.saveAndFlush(customer).getId(); + IssueCouponCommand cmd = new IssueCouponCommand(customerId, couponId); + + // when + Long saveCouponIssuanceId = service.issueCoupon(cmd); + CouponIssuance couponIssuance = couponIssuanceRepository.findById(saveCouponIssuanceId).get(); + + // then + // 쿠폰 발급 확인 + assertNotNull(saveCouponIssuanceId); + assertEquals(coupon.getId(), couponIssuance.getCoupon().getId()); + assertEquals(customer.getId(), couponIssuance.getCustomer().getId()); + assertEquals(initialQuantity - 1, couponIssuance.getCoupon().getQuantity()); + } + + @Test + @DisplayName("쿠폰 발급 테스트 - 수량 부족 실패") + @Transactional + void testIssueCouponFailWithInsufficientQuantity() { + // given + Coupon coupon = createCoupon("할인 쿠폰", 1000, 1, LocalDateTime.now().plusDays(7)); + coupon.decreaseQuantity(); + // 쿠폰 등록 + Long couponId = couponRepository.save(coupon).getId(); + + // 고객 등록 + // 계좌 생성 + PayAccount payAccount = payAccountRepository.save(createPayAccount()); + Customer customer = createCustomer(payAccount, new NoOpPasswordEncoder()); + UUID customerId = customerRepository.saveAndFlush(customer).getId(); + IssueCouponCommand cmd = new IssueCouponCommand(customerId, couponId); + + // when & then + assertThrows(InsufficientCouponQuantityException.class, + () -> service.issueCoupon(cmd)); + } + + @Test + @DisplayName("쿠폰 발급 테스트 - 동시성 제어") + void testIssueCouponWithConcurrency() throws InterruptedException { + // given + int couponQuantity = 10; + int numberOfThreads = 20; + Coupon coupon = createCoupon("할인 쿠폰", 1000, couponQuantity, LocalDateTime.now().plusDays(7)); + Long couponId = couponRepository.save(coupon).getId(); + + PayAccount payAccount = payAccountRepository.save(createPayAccount()); + Customer customer = createCustomer(payAccount, new NoOpPasswordEncoder()); + UUID customerId = customerRepository.saveAndFlush(customer).getId(); + + IssueCouponCommand cmd = new IssueCouponCommand(customerId, couponId); + + ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); + CountDownLatch latch = new CountDownLatch(numberOfThreads); + AtomicInteger successCount = new AtomicInteger(0); + AtomicInteger failCount = new AtomicInteger(0); + List exceptions = new ArrayList<>(); + + // when + for (int i = 0; i < numberOfThreads; i++) { + executorService.submit(() -> { + try { + service.issueCoupon(cmd); + successCount.incrementAndGet(); + } catch (InsufficientCouponQuantityException e) { + failCount.incrementAndGet(); + } catch (Exception e) { + exceptions.add(e); + } finally { + latch.countDown(); + } + }); + } + + latch.await(); // 모든 스레드가 작업을 마칠 때까지 대기 + executorService.shutdown(); + + // then + assertEquals(couponQuantity, successCount.get(), "성공적으로 발급된 쿠폰 수가 초기 수량과 일치해야 합니다."); + assertEquals(numberOfThreads - couponQuantity, failCount.get(), "실패한 요청 수가 예상과 일치해야 합니다."); + assertTrue(exceptions.isEmpty(), "예상치 못한 예외가 발생하지 않아야 합니다."); + + Coupon updatedCoupon = couponRepository.findById(couponId).orElseThrow(); + assertEquals(0, updatedCoupon.getQuantity(), "모든 쿠폰이 소진되어야 합니다."); + + // 쓰레드 트랜잭션 전파 문제로 인해 수동 데이터 제거 + // 데이터 제거 + couponIssuanceRepository.deleteAll(); + couponRepository.delete(updatedCoupon); + customerRepository.delete(customer); + payAccountRepository.delete(payAccount); + } +} From 8e1ad327666f1906da20d45cca910c6ed19afc3b Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:37:58 +0900 Subject: [PATCH 38/52] =?UTF-8?q?[feat]=20CouponFixture=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20CouponFixture.createCoupon(String,=20int,=20int?= =?UTF-8?q?,=20LocalDateTime):=20Coupon=20Entity=20=EB=A5=BC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=ED=95=98=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/camp/woowak/lab/fixture/CouponFixture.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/camp/woowak/lab/fixture/CouponFixture.java b/src/test/java/camp/woowak/lab/fixture/CouponFixture.java index ccfbcb39..8921ebf7 100644 --- a/src/test/java/camp/woowak/lab/fixture/CouponFixture.java +++ b/src/test/java/camp/woowak/lab/fixture/CouponFixture.java @@ -9,6 +9,10 @@ * CouponFixture는 테스트에서 사용할 Coupon 객체를 생성하는 역할을 합니다. */ public interface CouponFixture { + default Coupon createCoupon(String title, int discountAmount, int quantity, LocalDateTime expiredAt) { + return new Coupon(title, discountAmount, quantity, expiredAt); + } + default Coupon createCoupon(Long id, String title, int discountAmount, int quantity, LocalDateTime expiredAt) { return new TestCoupon(id, title, discountAmount, quantity, expiredAt); } From 035af13f471d8c139b3696dee9bd6248c0e6573b Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:39:27 +0900 Subject: [PATCH 39/52] =?UTF-8?q?[feat]=20CouponRepository=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20CouponRepository.findByIdWithPessimisticLock:?= =?UTF-8?q?=20=EB=B9=84=EA=B4=80=EC=A0=81=20=EB=9D=BD=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woowak/lab/coupon/repository/CouponRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/camp/woowak/lab/coupon/repository/CouponRepository.java b/src/main/java/camp/woowak/lab/coupon/repository/CouponRepository.java index b204e72a..0ba029e1 100644 --- a/src/main/java/camp/woowak/lab/coupon/repository/CouponRepository.java +++ b/src/main/java/camp/woowak/lab/coupon/repository/CouponRepository.java @@ -1,8 +1,16 @@ package camp.woowak.lab.coupon.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; import camp.woowak.lab.coupon.domain.Coupon; +import jakarta.persistence.LockModeType; public interface CouponRepository extends JpaRepository { + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("SELECT c FROM Coupon c WHERE c.id = :id") + Optional findByIdWithPessimisticLock(Long id); } From dba84a258c2d917a29ce62dde65adcfbc4d4acd3 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:40:05 +0900 Subject: [PATCH 40/52] =?UTF-8?q?[feat]=20IssueCouponService=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=BF=A0=ED=8F=B0=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20CouponRepository.findByIdWithPessimisticLock=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/camp/woowak/lab/coupon/service/IssueCouponService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java index 02706d5a..e872d2d2 100644 --- a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java +++ b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java @@ -40,7 +40,7 @@ public Long issueCoupon(IssueCouponCommand cmd) { .orElseThrow(() -> new InvalidICreationIssuanceException("customer not found")); // coupon 조회 - Coupon targetCoupon = couponRepository.findById(cmd.couponId()) + Coupon targetCoupon = couponRepository.findByIdWithPessimisticLock(cmd.couponId()) .orElseThrow(() -> new InvalidICreationIssuanceException("coupon not found")); // coupon 수량 확인 From b42a7f2b01439c8e373a6734f601388eea48660d Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:41:25 +0900 Subject: [PATCH 41/52] =?UTF-8?q?[docs]=20CouponApiControllerTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20`@DisplayName`=20=EC=88=98=EC=A0=95:?= =?UTF-8?q?=20=EC=BF=A0=ED=8F=B0=20=EB=B0=9C=EA=B8=89=20->=20=EC=BF=A0?= =?UTF-8?q?=ED=8F=B0=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lab/web/api/coupon/CouponApiControllerTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java index 2342ee0b..3ae49350 100644 --- a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java +++ b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java @@ -36,7 +36,7 @@ class CouponApiControllerTest { private ObjectMapper objectMapper; @Test - @DisplayName("쿠폰 발급 테스트 - 성공") + @DisplayName("쿠폰 등록 테스트 - 성공") void testCreateCoupon() throws Exception { mockMvc.perform(post("/coupons") .contentType(MediaType.APPLICATION_JSON) @@ -49,7 +49,7 @@ void testCreateCoupon() throws Exception { } @Test - @DisplayName("쿠폰 발급 테스트 - 잘못된 제목 입력 시 실패") + @DisplayName("쿠폰 등록 테스트 - 잘못된 제목 입력 시 실패") void testCreateCouponFailWithInvalidTitle() throws Exception { mockMvc.perform(post("/coupons") .contentType(MediaType.APPLICATION_JSON) @@ -61,7 +61,7 @@ void testCreateCouponFailWithInvalidTitle() throws Exception { } @Test - @DisplayName("쿠폰 발급 테스트 - 잘못된 할인 금액 입력 시 실패") + @DisplayName("쿠폰 등록 테스트 - 잘못된 할인 금액 입력 시 실패") void testCreateCouponFailWithInvalidDiscountAmount() throws Exception { mockMvc.perform(post("/coupons") .contentType(MediaType.APPLICATION_JSON) @@ -73,7 +73,7 @@ void testCreateCouponFailWithInvalidDiscountAmount() throws Exception { } @Test - @DisplayName("쿠폰 발급 테스트 - 잘못된 수량 입력 시 실패") + @DisplayName("쿠폰 등록 테스트 - 잘못된 수량 입력 시 실패") void testCreateCouponFailWithInvalidQuantity() throws Exception { mockMvc.perform(post("/coupons") .contentType(MediaType.APPLICATION_JSON) @@ -85,7 +85,7 @@ void testCreateCouponFailWithInvalidQuantity() throws Exception { } @Test - @DisplayName("쿠폰 발급 테스트 - 잘못된 만료일 입력 시 실패") + @DisplayName("쿠폰 등록 테스트 - 잘못된 만료일 입력 시 실패") void testCreateCouponFailWithInvalidExpiredAt() throws Exception { mockMvc.perform(post("/coupons") .contentType(MediaType.APPLICATION_JSON) @@ -97,7 +97,7 @@ void testCreateCouponFailWithInvalidExpiredAt() throws Exception { } @Test - @DisplayName("쿠폰 발급 테스트 - 중복된 제목 입력 시 실패") + @DisplayName("쿠폰 등록 테스트 - 중복된 제목 입력 시 실패") void testCreateCouponFailWithDuplicateTitle() throws Exception { // given CreateCouponRequest request = new CreateCouponRequest("테스트 쿠폰", 1000, 100, LocalDateTime.now().plusDays(7)); @@ -113,4 +113,5 @@ void testCreateCouponFailWithDuplicateTitle() throws Exception { .andDo(print()) .andExpect(status().isConflict()); } + } From c148289885af8bc7a83f53965941ea898909007a Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:46:39 +0900 Subject: [PATCH 42/52] =?UTF-8?q?[fix]=20CouponApiControllerTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20CouponApiControllerTest.testIssueCoupo?= =?UTF-8?q?n:=20=EC=A1=B0=EA=B1=B4=20=EB=AA=85=EC=8B=9C=EC=A0=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woowak/lab/web/api/coupon/CouponApiControllerTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java index 3ae49350..3fee2cdd 100644 --- a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java +++ b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java @@ -38,6 +38,9 @@ class CouponApiControllerTest { @Test @DisplayName("쿠폰 등록 테스트 - 성공") void testCreateCoupon() throws Exception { + // given + given(createCouponService.createCoupon(any(CreateCouponCommand.class))).willReturn(1L); + mockMvc.perform(post("/coupons") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString( From 10143d9dae2a54a7346a38860790a88c05d6f97e Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:50:39 +0900 Subject: [PATCH 43/52] =?UTF-8?q?[test]=20CouponApiControllerTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20CouponApiControllerTest.testIssueCoupo?= =?UTF-8?q?n:=20=EC=BF=A0=ED=8F=B0=20=EB=B0=9C=EA=B8=89=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EA=B2=80=EC=A6=9D=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 --- .../api/coupon/CouponApiControllerTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java index 3fee2cdd..5f9d1116 100644 --- a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java +++ b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java @@ -6,6 +6,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.time.LocalDateTime; +import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -14,13 +15,17 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpSession; import org.springframework.test.web.servlet.MockMvc; import com.fasterxml.jackson.databind.ObjectMapper; import camp.woowak.lab.coupon.exception.DuplicateCouponTitleException; import camp.woowak.lab.coupon.service.CreateCouponService; +import camp.woowak.lab.coupon.service.IssueCouponService; import camp.woowak.lab.coupon.service.command.CreateCouponCommand; +import camp.woowak.lab.coupon.service.command.IssueCouponCommand; +import camp.woowak.lab.web.authentication.LoginCustomer; import camp.woowak.lab.web.dto.request.coupon.CreateCouponRequest; @WebMvcTest(CouponApiController.class) @@ -32,6 +37,9 @@ class CouponApiControllerTest { @MockBean private CreateCouponService createCouponService; + @MockBean + private IssueCouponService issueCouponService; + @Autowired private ObjectMapper objectMapper; @@ -117,4 +125,25 @@ void testCreateCouponFailWithDuplicateTitle() throws Exception { .andExpect(status().isConflict()); } + @Test + @DisplayName("쿠폰 발급 테스트 - 성공") + void testIssueCoupon() throws Exception { + // given + Long couponId = 1L; + UUID customerId = UUID.randomUUID(); + LoginCustomer loginCustomer = new LoginCustomer(customerId); + + MockHttpSession session = new MockHttpSession(); + session.setAttribute("loginCustomer", loginCustomer); + + given(issueCouponService.issueCoupon(any(IssueCouponCommand.class))).willReturn(1L); + + // when & then + mockMvc.perform(post("/coupons/" + couponId + "/issue") + .session(session) // 세션 설정 + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + } } From 435e59d3c7f4affc045d416ca4290217155aaae8 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:57:16 +0900 Subject: [PATCH 44/52] =?UTF-8?q?[fix]=20CouponApiControllerTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20session=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EC=8B=9C=20SessionConst.SESSION=5FCUSTOMER=5FKEY=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=EC=BD=94=EB=93=9C=20CREATED=20?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../woowak/lab/web/api/coupon/CouponApiControllerTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java index 5f9d1116..84be786a 100644 --- a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java +++ b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java @@ -27,6 +27,7 @@ import camp.woowak.lab.coupon.service.command.IssueCouponCommand; import camp.woowak.lab.web.authentication.LoginCustomer; import camp.woowak.lab.web.dto.request.coupon.CreateCouponRequest; +import camp.woowak.lab.web.resolver.session.SessionConst; @WebMvcTest(CouponApiController.class) @MockBean(JpaMetamodelMappingContext.class) @@ -134,7 +135,7 @@ void testIssueCoupon() throws Exception { LoginCustomer loginCustomer = new LoginCustomer(customerId); MockHttpSession session = new MockHttpSession(); - session.setAttribute("loginCustomer", loginCustomer); + session.setAttribute(SessionConst.SESSION_CUSTOMER_KEY, loginCustomer); given(issueCouponService.issueCoupon(any(IssueCouponCommand.class))).willReturn(1L); @@ -144,6 +145,6 @@ void testIssueCoupon() throws Exception { .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) - .andExpect(status().isOk()); + .andExpect(status().isCreated()); } } From b72fbc0692dfe2dca32168fbecee018a580b3a37 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:57:34 +0900 Subject: [PATCH 45/52] =?UTF-8?q?[feat]=20IssueCouponResponse=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 --- .../lab/web/dto/response/coupon/IssueCouponResponse.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/camp/woowak/lab/web/dto/response/coupon/IssueCouponResponse.java diff --git a/src/main/java/camp/woowak/lab/web/dto/response/coupon/IssueCouponResponse.java b/src/main/java/camp/woowak/lab/web/dto/response/coupon/IssueCouponResponse.java new file mode 100644 index 00000000..ca3d0654 --- /dev/null +++ b/src/main/java/camp/woowak/lab/web/dto/response/coupon/IssueCouponResponse.java @@ -0,0 +1,4 @@ +package camp.woowak.lab.web.dto.response.coupon; + +public record IssueCouponResponse() { +} From ace070298f5dab96de9c8a8510021f934dc3e0a4 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 00:58:00 +0900 Subject: [PATCH 46/52] =?UTF-8?q?[feat]=20CouponApiController=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20CouponApiController.issueCoupon:=20=EC=BF=A0?= =?UTF-8?q?=ED=8F=B0=20=EB=B0=9C=EA=B8=89=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/api/coupon/CouponApiController.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/web/api/coupon/CouponApiController.java b/src/main/java/camp/woowak/lab/web/api/coupon/CouponApiController.java index be2f35ff..0b355d40 100644 --- a/src/main/java/camp/woowak/lab/web/api/coupon/CouponApiController.java +++ b/src/main/java/camp/woowak/lab/web/api/coupon/CouponApiController.java @@ -1,23 +1,31 @@ package camp.woowak.lab.web.api.coupon; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import camp.woowak.lab.coupon.service.CreateCouponService; +import camp.woowak.lab.coupon.service.IssueCouponService; import camp.woowak.lab.coupon.service.command.CreateCouponCommand; +import camp.woowak.lab.coupon.service.command.IssueCouponCommand; +import camp.woowak.lab.web.authentication.LoginCustomer; +import camp.woowak.lab.web.authentication.annotation.AuthenticationPrincipal; import camp.woowak.lab.web.dto.request.coupon.CreateCouponRequest; import camp.woowak.lab.web.dto.response.coupon.CreateCouponResponse; +import camp.woowak.lab.web.dto.response.coupon.IssueCouponResponse; import jakarta.validation.Valid; @RestController public class CouponApiController { private final CreateCouponService createCouponService; + private final IssueCouponService issueCouponService; - public CouponApiController(CreateCouponService createCouponService) { + public CouponApiController(CreateCouponService createCouponService, IssueCouponService issueCouponService) { this.createCouponService = createCouponService; + this.issueCouponService = issueCouponService; } @PostMapping("/coupons") @@ -31,4 +39,14 @@ public CreateCouponResponse createCoupon(@Valid @RequestBody CreateCouponRequest return new CreateCouponResponse(couponId); } + @PostMapping("/coupons/{couponId}/issue") + @ResponseStatus(HttpStatus.CREATED) + public IssueCouponResponse issueCoupon(@AuthenticationPrincipal LoginCustomer loginCustomer, + @PathVariable Long couponId) { + IssueCouponCommand cmd = new IssueCouponCommand(loginCustomer.getId(), couponId); + + issueCouponService.issueCoupon(cmd); + + return new IssueCouponResponse(); + } } From 8aff49772a77384f8e7e98834452bdbb6f630305 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 01:05:27 +0900 Subject: [PATCH 47/52] =?UTF-8?q?[feat]=20IssueCouponService=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20InsufficientCouponQuantityException=20message?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/camp/woowak/lab/coupon/service/IssueCouponService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java index e872d2d2..c75b4761 100644 --- a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java +++ b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java @@ -45,7 +45,7 @@ public Long issueCoupon(IssueCouponCommand cmd) { // coupon 수량 확인 if (!targetCoupon.hasAvailableQuantity()) { - throw new InsufficientCouponQuantityException("coupon is expired"); + throw new InsufficientCouponQuantityException("quantity of coupon is insufficient"); } // coupon issuance 생성 From 588a223e3e4fbbadf9a1a539c23806c004fae07f Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 01:06:29 +0900 Subject: [PATCH 48/52] =?UTF-8?q?[docs]=20IssueCouponService=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20IssueCouponService.issueCoupon=20throws=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/camp/woowak/lab/coupon/service/IssueCouponService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java index c75b4761..d85d5b5f 100644 --- a/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java +++ b/src/main/java/camp/woowak/lab/coupon/service/IssueCouponService.java @@ -29,7 +29,7 @@ public IssueCouponService(CouponIssuanceRepository couponIssuanceRepository, Cou /** * - * @throws InvalidICreationIssuanceException customer 또는 coupon이 존재하지 않을 경우 또는 coupon이 만료되었을 경우 + * @throws InvalidICreationIssuanceException customer 또는 coupon이 존재하지 않을 경우 * @throws ExpiredCouponException coupon이 만료되었을 경우 * @throws InsufficientCouponQuantityException coupon 수량이 부족할 경우 */ From 4df13ffabc2bcc004abfc57c46137393ec14f940 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 01:11:35 +0900 Subject: [PATCH 49/52] =?UTF-8?q?[test]=20CouponApiControllerTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20CouponApiControllerTest.testIssueCoupo?= =?UTF-8?q?nFailWithoutSession:=20=EC=84=B8=EC=85=98=20=EC=97=86=EC=9D=B4?= =?UTF-8?q?=20=EC=BF=A0=ED=8F=B0=20=EB=B0=9C=EA=B8=89=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20-=20Coupo?= =?UTF-8?q?nApiControllerTest.testIssueCouponFailWithNotExistsId:=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EC=BF=A0=ED=8F=B0=20=EB=98=90=EB=8A=94=20=EA=B5=AC=EB=A7=A4?= =?UTF-8?q?=EC=9E=90=20=EC=BF=A0=ED=8F=B0=20=EB=B0=9C=EA=B8=89=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?-=20CouponApiControllerTest.testIssueCouponFailWithExpiredCoupo?= =?UTF-8?q?n:=20=EB=A7=8C=EB=A3=8C=EB=90=9C=20=EC=BF=A0=ED=8F=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EC=9A=94=EC=B2=AD=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20-=20CouponApiControllerTest.testI?= =?UTF-8?q?ssueCouponFailWithInsufficientQuantity:=20=EC=88=98=EB=9F=89?= =?UTF-8?q?=EC=9D=B4=20=EB=B6=80=EC=A1=B1=ED=95=9C=20=EC=BF=A0=ED=8F=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EC=9A=94=EC=B2=AD=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/coupon/CouponApiControllerTest.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java index 84be786a..b8ba8faf 100644 --- a/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java +++ b/src/test/java/camp/woowak/lab/web/api/coupon/CouponApiControllerTest.java @@ -21,6 +21,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import camp.woowak.lab.coupon.exception.DuplicateCouponTitleException; +import camp.woowak.lab.coupon.exception.ExpiredCouponException; +import camp.woowak.lab.coupon.exception.InsufficientCouponQuantityException; +import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; import camp.woowak.lab.coupon.service.CreateCouponService; import camp.woowak.lab.coupon.service.IssueCouponService; import camp.woowak.lab.coupon.service.command.CreateCouponCommand; @@ -147,4 +150,78 @@ void testIssueCoupon() throws Exception { .andDo(print()) .andExpect(status().isCreated()); } + + @Test + @DisplayName("쿠폰 발급 테스트 - 세션 없이 요청 시 실패") + void testIssueCouponFailWithoutSession() throws Exception { + // given + Long couponId = 1L; + + // when & then + mockMvc.perform(post("/coupons/" + couponId + "/issue") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isUnauthorized()); + } + + @Test + @DisplayName("쿠폰 발급 테스트 - 존재하지 않는 쿠폰 or 구매자 ID 입력 시 실패") + void testIssueCouponFailWithNotExistsId() throws Exception { + // given + UUID customerId = UUID.randomUUID(); + LoginCustomer loginCustomer = new LoginCustomer(customerId); + + MockHttpSession session = new MockHttpSession(); + session.setAttribute(SessionConst.SESSION_CUSTOMER_KEY, loginCustomer); + given(issueCouponService.issueCoupon(any(IssueCouponCommand.class))) + .willThrow(new InvalidICreationIssuanceException("존재하지 않는 쿠폰 or 구매자 ID 입력입니다.")); + // when & then + mockMvc.perform(post("/coupons/999/issue") + .session(session) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("쿠폰 발급 테스트 - 쿠폰 만료 실패") + void testIssueCouponFailWithExpiredCoupon() throws Exception { + // given + UUID customerId = UUID.randomUUID(); + LoginCustomer loginCustomer = new LoginCustomer(customerId); + + MockHttpSession session = new MockHttpSession(); + session.setAttribute(SessionConst.SESSION_CUSTOMER_KEY, loginCustomer); + given(issueCouponService.issueCoupon(any(IssueCouponCommand.class))) + .willThrow(new ExpiredCouponException("쿠폰이 만료되었습니다.")); + // when & then + mockMvc.perform(post("/coupons/999/issue") + .session(session) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isConflict()); + } + + @Test + @DisplayName("쿠폰 발급 테스트 - 수량 부족 실패") + void testIssueCouponFailWithInsufficientQuantity() throws Exception { + // given + UUID customerId = UUID.randomUUID(); + LoginCustomer loginCustomer = new LoginCustomer(customerId); + + MockHttpSession session = new MockHttpSession(); + session.setAttribute(SessionConst.SESSION_CUSTOMER_KEY, loginCustomer); + given(issueCouponService.issueCoupon(any(IssueCouponCommand.class))) + .willThrow(new InsufficientCouponQuantityException("수량이 부족합니다.")); + // when & then + mockMvc.perform(post("/coupons/999/issue") + .session(session) // 세션 설정 + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isConflict()); + } } From bb1650ef09457a8b88a9b246c5443dd07601cea3 Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 01:12:09 +0900 Subject: [PATCH 50/52] =?UTF-8?q?[feat]=20CouponExceptionHandler=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=20InvalidICreationIssuanceException,?= =?UTF-8?q?=20ExpiredCouponException=20=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4=EB=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/api/coupon/CouponExceptionHandler.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/camp/woowak/lab/web/api/coupon/CouponExceptionHandler.java b/src/main/java/camp/woowak/lab/web/api/coupon/CouponExceptionHandler.java index bffc3ab4..65dd9307 100644 --- a/src/main/java/camp/woowak/lab/web/api/coupon/CouponExceptionHandler.java +++ b/src/main/java/camp/woowak/lab/web/api/coupon/CouponExceptionHandler.java @@ -7,6 +7,8 @@ import camp.woowak.lab.common.advice.DomainExceptionHandler; import camp.woowak.lab.common.exception.HttpStatusException; import camp.woowak.lab.coupon.exception.DuplicateCouponTitleException; +import camp.woowak.lab.coupon.exception.ExpiredCouponException; +import camp.woowak.lab.coupon.exception.InvalidICreationIssuanceException; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -23,6 +25,22 @@ public ProblemDetail handleDuplicateCouponTitleException(DuplicateCouponTitleExc return getProblemDetail(e, HttpStatus.CONFLICT); } + /** + * + * InvalidICreationIssuanceException.class 를 처리한다. + */ + @ExceptionHandler(value = InvalidICreationIssuanceException.class) + public ProblemDetail handleInvalidICreationIssuanceException(InvalidICreationIssuanceException e) { + log.warn("Bad Request", e.getMessage()); + return getProblemDetail(e, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(value = ExpiredCouponException.class) + public ProblemDetail handleExpiredCouponException(ExpiredCouponException e) { + log.warn("Conflict", e.getMessage()); + return getProblemDetail(e, HttpStatus.CONFLICT); + } + private ProblemDetail getProblemDetail(HttpStatusException e, HttpStatus status) { ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(status, e.errorCode().getMessage()); problemDetail.setProperty("errorCode", e.errorCode().getErrorCode()); From f770f0d134c248ec750f991c236c54aeb1e01e8d Mon Sep 17 00:00:00 2001 From: kimhyun5u <22kimhyun5u@gmail.com> Date: Fri, 16 Aug 2024 01:16:46 +0900 Subject: [PATCH 51/52] =?UTF-8?q?[fix]=20IssueCouponServiceTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=20=EB=B9=84=EA=B4=80=EC=A0=81=20?= =?UTF-8?q?=EB=9D=BD=20=EC=A0=81=EC=9A=A9=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/IssueCouponServiceTest.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java index 672adccb..8e8c2644 100644 --- a/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java +++ b/src/test/java/camp/woowak/lab/coupon/service/IssueCouponServiceTest.java @@ -54,7 +54,7 @@ void testIssueCoupon() { Customer fakeCustomer = createCustomer(fakeCustomerId); CouponIssuance fakeCouponIssuance = createCouponIssuance(fakeCouponId, fakeCoupon, fakeCustomer); given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.of(fakeCustomer)); - given(couponRepository.findById(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); + given(couponRepository.findByIdWithPessimisticLock(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); given(couponIssuanceRepository.save(any(CouponIssuance.class))).willReturn(fakeCouponIssuance); IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); @@ -65,7 +65,7 @@ void testIssueCoupon() { // then assertEquals(fakeCouponIssuanceId, saveId); verify(customerRepository).findById(fakeCustomerId); - verify(couponRepository).findById(fakeCouponId); + verify(couponRepository).findByIdWithPessimisticLock(fakeCouponId); verify(couponIssuanceRepository).save(any(CouponIssuance.class)); } @@ -94,14 +94,14 @@ void testIssueCouponFailWithNotExistCoupon() { Long fakeCouponId = 1L; Customer fakeCustomer = createCustomer(fakeCustomerId); given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.of(fakeCustomer)); - given(couponRepository.findById(fakeCouponId)).willReturn(Optional.empty()); + given(couponRepository.findByIdWithPessimisticLock(fakeCouponId)).willReturn(Optional.empty()); IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); // when & then assertThrows(InvalidICreationIssuanceException.class, () -> issueCouponService.issueCoupon(cmd)); verify(customerRepository).findById(fakeCustomerId); - verify(couponRepository).findById(fakeCouponId); + verify(couponRepository).findByIdWithPessimisticLock(fakeCouponId); verify(couponIssuanceRepository, never()).save(any(CouponIssuance.class)); } @@ -116,14 +116,14 @@ void testIssueCouponFailWithExpiredCoupon() { fakeCoupon.setExpiredAt(LocalDateTime.now().minusDays(1)); Customer fakeCustomer = createCustomer(fakeCustomerId); given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.of(fakeCustomer)); - given(couponRepository.findById(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); + given(couponRepository.findByIdWithPessimisticLock(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); // when & then assertThrows(ExpiredCouponException.class, () -> issueCouponService.issueCoupon(cmd)); verify(customerRepository).findById(fakeCustomerId); - verify(couponRepository).findById(fakeCouponId); + verify(couponRepository).findByIdWithPessimisticLock(fakeCouponId); verify(couponIssuanceRepository, never()).save(any(CouponIssuance.class)); } @@ -138,15 +138,13 @@ void testIssueCouponFailWithInsufficientCouponQuantity() { Customer fakeCustomer = createCustomer(fakeCustomerId); fakeCoupon.setQuantity(0); // 수량 부족 시나리오 적용 given(customerRepository.findById(fakeCustomerId)).willReturn(Optional.of(fakeCustomer)); - given(couponRepository.findById(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); - given(couponIssuanceRepository.save(any(CouponIssuance.class))).willThrow( - new InsufficientCouponQuantityException("the quantity of coupon is insufficient")); + given(couponRepository.findByIdWithPessimisticLock(fakeCouponId)).willReturn(Optional.of(fakeCoupon)); IssueCouponCommand cmd = new IssueCouponCommand(fakeCustomerId, fakeCouponId); // when & then assertThrows(InsufficientCouponQuantityException.class, () -> issueCouponService.issueCoupon(cmd)); verify(customerRepository).findById(fakeCustomerId); - verify(couponRepository).findById(fakeCouponId); - verify(couponIssuanceRepository).save(any(CouponIssuance.class)); + verify(couponRepository).findByIdWithPessimisticLock(fakeCouponId); + verify(couponIssuanceRepository, never()).save(any(CouponIssuance.class)); } } From 94cfa48433c4f80a69ed73fb11993fe3050e1351 Mon Sep 17 00:00:00 2001 From: Kim Hyunsu <38347891+kimhyun5u@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:17:02 +0900 Subject: [PATCH 52/52] =?UTF-8?q?[feat]=20CouponIssuance=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 발급 시간 변경 제한 Co-authored-by: 김현욱 <43038815+Hyeon-Uk@users.noreply.github.com> --- src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java index dd676245..6dc7e4ef 100644 --- a/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java +++ b/src/main/java/camp/woowak/lab/coupon/domain/CouponIssuance.java @@ -30,7 +30,7 @@ public class CouponIssuance { @ManyToOne(fetch = FetchType.LAZY) private Customer customer; - @Column(nullable = false) + @Column(nullable = false, updatable = false) @CreatedDate private LocalDateTime issuedAt;