From 67d8c98af944c3a617e053ac0873d15fcb30acc1 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Thu, 10 Apr 2025 19:40:03 +0900 Subject: [PATCH 01/15] =?UTF-8?q?[docs]=20=EA=B8=B0=EB=8A=A5=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4985061663..51b6a10b9e 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,25 @@ * 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다. ## 온라인 코드 리뷰 과정 -* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview) \ No newline at end of file +* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview) + +## 2단계 요구사항 +- 강의 +- [ ] 과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다. + - [ ] 강의는 시작일과 종료일을 가진다. + - [ ] 강의는 강의 커버 이미지 정보를 가진다. + - [ ] 강의는 무료 강의와 유료 강의로 나뉜다. + - [ ] 무료 강의는 최대 수강 인원 제한이 없다. + - [ ] 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. + - [ ] 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. + - [ ] 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. + - [ ] 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. + - [ ] 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다. + +- 이미지 +- [ ] 이미지 크기는 1MB 이하여야 한다. +- [ ] 이미지 타입은 gif, jpg(jpeg 포함),, png, svg만 허용한다. +- [ ] 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. + +- 결제 + - [ ] 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다. \ No newline at end of file From 69f7da00fbf1c3a808a41f41fd70098c127370e7 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Thu, 10 Apr 2025 19:56:44 +0900 Subject: [PATCH 02/15] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=83=80=EC=9E=85=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../domain/session/SessionImageType.java | 21 ++++++++++++++++ .../domain/session/SessionImageTypeTest.java | 24 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/main/java/nextstep/courses/domain/session/SessionImageType.java create mode 100644 src/test/java/nextstep/courses/domain/session/SessionImageTypeTest.java diff --git a/README.md b/README.md index 51b6a10b9e..159b70686d 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ - 이미지 - [ ] 이미지 크기는 1MB 이하여야 한다. -- [ ] 이미지 타입은 gif, jpg(jpeg 포함),, png, svg만 허용한다. +- [x] 이미지 타입은 gif, jpg(jpeg 포함), png, svg만 허용한다. - [ ] 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. - 결제 diff --git a/src/main/java/nextstep/courses/domain/session/SessionImageType.java b/src/main/java/nextstep/courses/domain/session/SessionImageType.java new file mode 100644 index 0000000000..c52bdef779 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionImageType.java @@ -0,0 +1,21 @@ +package nextstep.courses.domain.session; + +import java.util.Arrays; + +public enum SessionImageType { + gif("gif"), jpg("jpg"), jpeg("jpeg"), png("png"), svg("svg"); + + private final String name; + + SessionImageType(String name) { + this.name = name; + } + + public static SessionImageType of(String name) { + return Arrays.stream(values()) + .filter(type -> type.name.equals(name)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + + } +} diff --git a/src/test/java/nextstep/courses/domain/session/SessionImageTypeTest.java b/src/test/java/nextstep/courses/domain/session/SessionImageTypeTest.java new file mode 100644 index 0000000000..860d024f32 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionImageTypeTest.java @@ -0,0 +1,24 @@ +package nextstep.courses.domain.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static nextstep.courses.domain.session.SessionImageType.gif; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SessionImageTypeTest { + + @Test + @DisplayName("이름이 똑같은 확장자를 찾아 리턴한다.") + void of() { + assertEquals(SessionImageType.of("gif"), gif); + } + + @Test + @DisplayName("등록되지 않은 확장자를 넣으면 에러를 반환한다.") + void of_fail() { + assertThatThrownBy(() -> SessionImageType.of("xls")) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file From eafe229e0b91b775c967d9728dc570d06e49877a Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 14:01:17 +0900 Subject: [PATCH 03/15] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=81=B4=EB=9E=98=EC=8A=A4=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 --- README.md | 4 +- .../courses/domain/session/SessionImage.java | 57 +++++++++++++++++++ .../domain/session/SessionImageTest.java | 38 +++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/SessionImage.java create mode 100644 src/test/java/nextstep/courses/domain/session/SessionImageTest.java diff --git a/README.md b/README.md index 159b70686d..bbc594a7da 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ - [ ] 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다. - 이미지 -- [ ] 이미지 크기는 1MB 이하여야 한다. +- [x] 이미지 크기는 1MB 이하여야 한다. - [x] 이미지 타입은 gif, jpg(jpeg 포함), png, svg만 허용한다. -- [ ] 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. +- [x] 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. - 결제 - [ ] 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다. \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/SessionImage.java b/src/main/java/nextstep/courses/domain/session/SessionImage.java new file mode 100644 index 0000000000..69954155fb --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionImage.java @@ -0,0 +1,57 @@ +package nextstep.courses.domain.session; + +import java.util.Objects; + +public class SessionImage { + private static final int MAX_IMAGE_SIZE = 1000000; + private static final int MIN_WIDTH = 300; + private static final int MIN_HEIGHT = 200; + private static final double WIDTH_PER_HEIGHT = 1.5; + + private final int width; + private final int height; + private final int size; + private final SessionImageType imageType; + + public SessionImage(int width, int height, int size, String imageType) { + this(width, height, size, SessionImageType.of(imageType)); + } + + public SessionImage(int width, int height, int size, SessionImageType imageType) { + this.width = width; + this.height = height; + this.size = size; + this.imageType = imageType; + checkValidSessionImage(); + } + + private void checkValidSessionImage() { + if (this.width < MIN_WIDTH || this.height < MIN_HEIGHT) { + throw new IllegalArgumentException("이미지의 width는 300픽셀, height는 200픽셀 이상이어야 한다."); + } + if ((double) this.width / this.height != WIDTH_PER_HEIGHT) { + throw new IllegalArgumentException("이미지의 width, height의 비율은 3:2여야 한다."); + } + + if (this.size > MAX_IMAGE_SIZE) { + throw new IllegalArgumentException("이미지의 크기는 1MB 이하여야 한다."); + } + } + + @Override + public int hashCode() { + return Objects.hash(width, height, size, imageType); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + SessionImage image = (SessionImage) object; + return Objects.equals(image.imageType, imageType) && image.width == width && image.height == height && image.size == size; + } +} diff --git a/src/test/java/nextstep/courses/domain/session/SessionImageTest.java b/src/test/java/nextstep/courses/domain/session/SessionImageTest.java new file mode 100644 index 0000000000..05cfcff19c --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionImageTest.java @@ -0,0 +1,38 @@ +package nextstep.courses.domain.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SessionImageTest { + + @Test + @DisplayName("이미지의 width는 300 이상, height는 200이면, size는 1mb 보다 작으면 정상적으로 생성된다.") + public void init() { + SessionImage image = new SessionImage(300, 200, 10, "jpg"); + assertEquals(image, new SessionImage(300, 200, 10, "jpg")); + } + + @Test + @DisplayName("이미지의 최소 크기를 넘지 못하면 에러가 발생한다.") + public void init_invalid_heightwidth() { + assertThatThrownBy(() -> new SessionImage(30, 20, 10, "jgp")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이미지의 비율이 맞지 않으면 에러가 발생한다.") + public void init_invalid_ratio() { + assertThatThrownBy(() -> new SessionImage(20, 20, 10, "jpg")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이미지의 크기가 1mb보다 크면 에러가 발생한다.") + public void init_invalid_size() { + assertThatThrownBy(() -> new SessionImage(30, 20, 10000000, "jgp")) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file From b981844ce6a307a88567cc08c915183c93731689 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 15:34:21 +0900 Subject: [PATCH 04/15] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=88=98?= =?UTF-8?q?=EA=B0=95=20=EC=9D=B8=EC=9B=90=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../domain/session/SessionCapacity.java | 55 +++++++++++++++++++ .../domain/session/SessionCapacityTest.java | 33 +++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/main/java/nextstep/courses/domain/session/SessionCapacity.java create mode 100644 src/test/java/nextstep/courses/domain/session/SessionCapacityTest.java diff --git a/README.md b/README.md index bbc594a7da..85e34d4bd1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - [ ] 강의는 강의 커버 이미지 정보를 가진다. - [ ] 강의는 무료 강의와 유료 강의로 나뉜다. - [ ] 무료 강의는 최대 수강 인원 제한이 없다. - - [ ] 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. + - [x] 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. - [ ] 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. - [ ] 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. - [ ] 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. diff --git a/src/main/java/nextstep/courses/domain/session/SessionCapacity.java b/src/main/java/nextstep/courses/domain/session/SessionCapacity.java new file mode 100644 index 0000000000..f39804aba2 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionCapacity.java @@ -0,0 +1,55 @@ +package nextstep.courses.domain.session; + +import java.util.Objects; + +public class SessionCapacity { + private static final int MIN_CAPACITY = 1; + + private int capacity; + private final int maxCapacity; + + public SessionCapacity(int capacity, int maxCapacity) { + this.capacity = capacity; + this.maxCapacity = maxCapacity; + checkValidCapacity(); + } + + public SessionCapacity(int maxCapacity) { + this(0, maxCapacity); + } + + private void checkValidCapacity() { + if (maxCapacity < MIN_CAPACITY) { + throw new IllegalArgumentException(); + } + isValidCapacity(); + } + + public void increase() { + capacity++; + isValidCapacity(); + } + + @Override + public int hashCode() { + return Objects.hash(capacity, maxCapacity); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + SessionCapacity sessionCapacity = (SessionCapacity) object; + return sessionCapacity.capacity == this.capacity && sessionCapacity.maxCapacity == this.maxCapacity; + } + + private void isValidCapacity(){ + if(this.capacity > this.maxCapacity) { + throw new IllegalArgumentException("최대 수용 인원을 현재 인원이 초과할 수 없다."); + } + } +} diff --git a/src/test/java/nextstep/courses/domain/session/SessionCapacityTest.java b/src/test/java/nextstep/courses/domain/session/SessionCapacityTest.java new file mode 100644 index 0000000000..af32322567 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionCapacityTest.java @@ -0,0 +1,33 @@ +package nextstep.courses.domain.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SessionCapacityTest { + + @Test + @DisplayName("등록하면 하나가 찬다.") + void increase_success() { + SessionCapacity capacity = new SessionCapacity(10); + capacity.increase(); + assertEquals(capacity, new SessionCapacity(1, 10)); + } + + @Test + @DisplayName("maxCapacity보다 더 수강하려면 에러를 반환한다.") + void increase_fail() { + SessionCapacity capacity = new SessionCapacity(10, 10); + assertThatThrownBy(capacity::increase) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("초기화 시 수강인원이 최대 인원보다 크면 에러를 반환한다.") + void init() { + assertThatThrownBy(() -> new SessionCapacity(11, 10)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file From 101a9783f005cf93d1c6fa94d308f68e3d7b99a1 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 15:46:33 +0900 Subject: [PATCH 05/15] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +-- .../courses/domain/session/SessionState.java | 11 ++++++++ .../domain/session/SessionStateTest.java | 27 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/SessionState.java create mode 100644 src/test/java/nextstep/courses/domain/session/SessionStateTest.java diff --git a/README.md b/README.md index 85e34d4bd1..2262724312 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ - [ ] 무료 강의는 최대 수강 인원 제한이 없다. - [x] 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. - [ ] 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. - - [ ] 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. - - [ ] 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. + - [x] 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. + - [x] 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. - [ ] 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다. - 이미지 diff --git a/src/main/java/nextstep/courses/domain/session/SessionState.java b/src/main/java/nextstep/courses/domain/session/SessionState.java new file mode 100644 index 0000000000..973c431ab9 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionState.java @@ -0,0 +1,11 @@ +package nextstep.courses.domain.session; + +public enum SessionState { + PREPARING, + RECRUITING, + CLOSED; + + public boolean canRecruit(){ + return this == RECRUITING; + } +} diff --git a/src/test/java/nextstep/courses/domain/session/SessionStateTest.java b/src/test/java/nextstep/courses/domain/session/SessionStateTest.java new file mode 100644 index 0000000000..167cb448ca --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionStateTest.java @@ -0,0 +1,27 @@ +package nextstep.courses.domain.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SessionStateTest { + + @Test + @DisplayName("준비중 상태에는 등록할 수 있다.") + void canApply() { + SessionState state = SessionState.RECRUITING; + assertTrue(state.canRecruit()); + } + + @ParameterizedTest + @EnumSource(value = SessionState.class, mode = EnumSource.Mode.EXCLUDE, names = {"RECRUITING"}) + @DisplayName("준비중이 아닌 상태에서는 등록할 수 없다.") + void cannotApply(SessionState state) { + assertFalse(state.canRecruit()); + } + +} \ No newline at end of file From c26c840a86ce92841730abbc71c365ff0f1f4b15 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 15:52:17 +0900 Subject: [PATCH 06/15] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- .../courses/domain/session/SessionDate.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/SessionDate.java diff --git a/README.md b/README.md index 2262724312..3752031aab 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ ## 2단계 요구사항 - 강의 - [ ] 과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다. - - [ ] 강의는 시작일과 종료일을 가진다. - - [ ] 강의는 강의 커버 이미지 정보를 가진다. + - [x] 강의는 시작일과 종료일을 가진다. + - [x] 강의는 강의 커버 이미지 정보를 가진다. - [ ] 강의는 무료 강의와 유료 강의로 나뉜다. - [ ] 무료 강의는 최대 수강 인원 제한이 없다. - [x] 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. diff --git a/src/main/java/nextstep/courses/domain/session/SessionDate.java b/src/main/java/nextstep/courses/domain/session/SessionDate.java new file mode 100644 index 0000000000..c3ed184630 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionDate.java @@ -0,0 +1,17 @@ +package nextstep.courses.domain.session; + +import java.time.LocalDate; + +public class SessionDate { + private final LocalDate startDate; + private final LocalDate endDate; + + public SessionDate(LocalDate startDate, LocalDate endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + public boolean isBefore(LocalDate date) { + return date.isBefore(startDate); + } +} From 62b16733c6a43046f852d98fe7835480ae18b61b Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 15:54:39 +0900 Subject: [PATCH 07/15] =?UTF-8?q?[feat]=20=EC=9C=A0=EB=A3=8C=20/=20?= =?UTF-8?q?=EB=AC=B4=EB=A3=8C=20=EC=A0=84=EB=9E=B5=20=ED=8C=A8=ED=84=B4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../courses/strategy/FreePaymentStrategy.java | 10 ++++++++++ .../courses/strategy/PaidPaymentStrategy.java | 18 ++++++++++++++++++ .../courses/strategy/PaymentStrategy.java | 7 +++++++ .../java/nextstep/payments/domain/Payment.java | 4 ++++ 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/main/java/nextstep/courses/strategy/FreePaymentStrategy.java create mode 100644 src/main/java/nextstep/courses/strategy/PaidPaymentStrategy.java create mode 100644 src/main/java/nextstep/courses/strategy/PaymentStrategy.java diff --git a/README.md b/README.md index 3752031aab..0532101524 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - [ ] 강의는 무료 강의와 유료 강의로 나뉜다. - [ ] 무료 강의는 최대 수강 인원 제한이 없다. - [x] 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. - - [ ] 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. + - [x] 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. - [x] 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. - [x] 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. - [ ] 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다. diff --git a/src/main/java/nextstep/courses/strategy/FreePaymentStrategy.java b/src/main/java/nextstep/courses/strategy/FreePaymentStrategy.java new file mode 100644 index 0000000000..be3ce88741 --- /dev/null +++ b/src/main/java/nextstep/courses/strategy/FreePaymentStrategy.java @@ -0,0 +1,10 @@ +package nextstep.courses.strategy; + +import nextstep.payments.domain.Payment; + +public class FreePaymentStrategy implements PaymentStrategy { + @Override + public boolean payable(Payment payment) { + return true; + } +} diff --git a/src/main/java/nextstep/courses/strategy/PaidPaymentStrategy.java b/src/main/java/nextstep/courses/strategy/PaidPaymentStrategy.java new file mode 100644 index 0000000000..8edd701325 --- /dev/null +++ b/src/main/java/nextstep/courses/strategy/PaidPaymentStrategy.java @@ -0,0 +1,18 @@ +package nextstep.courses.strategy; + +import nextstep.payments.domain.Payment; + +public class PaidPaymentStrategy implements PaymentStrategy { + private final long price; + + public PaidPaymentStrategy(long price) { + this.price = price; + } + + @Override + public boolean payable(Payment payment) { + if (payment.isSameAmount(price)) + return false; + return true; + } +} diff --git a/src/main/java/nextstep/courses/strategy/PaymentStrategy.java b/src/main/java/nextstep/courses/strategy/PaymentStrategy.java new file mode 100644 index 0000000000..0ee22fe4df --- /dev/null +++ b/src/main/java/nextstep/courses/strategy/PaymentStrategy.java @@ -0,0 +1,7 @@ +package nextstep.courses.strategy; + +import nextstep.payments.domain.Payment; + +public interface PaymentStrategy { + boolean payable(Payment payment); +} diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 57d833f851..5733696bf9 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -26,4 +26,8 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.amount = amount; this.createdAt = LocalDateTime.now(); } + + public boolean isSameAmount(long amount){ + return amount == this.amount; + } } From eb4aecdd023f3abf0cfcfc89f6a5bbabe24c3330 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 16:01:55 +0900 Subject: [PATCH 08/15] =?UTF-8?q?[refactor]=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/courses/domain/{ => course}/Course.java | 2 +- .../courses/domain/{ => course}/CourseRepository.java | 2 +- .../nextstep/courses/infrastructure/JdbcCourseRepository.java | 4 ++-- .../nextstep/courses/infrastructure/CourseRepositoryTest.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/nextstep/courses/domain/{ => course}/Course.java (96%) rename src/main/java/nextstep/courses/domain/{ => course}/CourseRepository.java (71%) diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java similarity index 96% rename from src/main/java/nextstep/courses/domain/Course.java rename to src/main/java/nextstep/courses/domain/course/Course.java index 0f69716043..1c935f2fcc 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course; import java.time.LocalDateTime; diff --git a/src/main/java/nextstep/courses/domain/CourseRepository.java b/src/main/java/nextstep/courses/domain/course/CourseRepository.java similarity index 71% rename from src/main/java/nextstep/courses/domain/CourseRepository.java rename to src/main/java/nextstep/courses/domain/course/CourseRepository.java index 6aaeb638d1..28180d25e0 100644 --- a/src/main/java/nextstep/courses/domain/CourseRepository.java +++ b/src/main/java/nextstep/courses/domain/course/CourseRepository.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course; public interface CourseRepository { int save(Course course); diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index f9122cbe33..7489afc31d 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -1,7 +1,7 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index f087fc0ad2..530375d3e1 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -1,7 +1,7 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; From 1acc6f13c0c0d5880d14d3286f33b15ee4eab0c5 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 16:18:22 +0900 Subject: [PATCH 09/15] =?UTF-8?q?[feat]=20=EB=93=B1=EB=A1=9D=20=ED=96=89?= =?UTF-8?q?=EC=9C=84=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/domain/session/Enrollment.java | 51 +++++++++++++++++++ .../domain/session/EnrollmentTest.java | 30 +++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/main/java/nextstep/courses/domain/session/Enrollment.java create mode 100644 src/test/java/nextstep/courses/domain/session/EnrollmentTest.java diff --git a/src/main/java/nextstep/courses/domain/session/Enrollment.java b/src/main/java/nextstep/courses/domain/session/Enrollment.java new file mode 100644 index 0000000000..f57d740335 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Enrollment.java @@ -0,0 +1,51 @@ +package nextstep.courses.domain.session; + +import nextstep.users.domain.NsUser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class Enrollment { + private final List users; + private final SessionCapacity sessionCapacity; + + public Enrollment(List users, SessionCapacity sessionCapacity) { + this.users = users; + this.sessionCapacity = sessionCapacity; + } + + public Enrollment(SessionCapacity sessionCapacity) { + this(new ArrayList<>(), sessionCapacity); + } + + public Enrollment(int capacity) { + this(new ArrayList<>(), new SessionCapacity(capacity)); + } + + public Enrollment(List users) { + this(users, new SessionCapacity(0)); + } + + public void enroll(NsUser user) { + sessionCapacity.increase(); + this.users.add(user); + } + + @Override + public int hashCode() { + return Objects.hashCode(users); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + Enrollment enrollment = (Enrollment) object; + return Objects.equals(enrollment.users, this.users); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java new file mode 100644 index 0000000000..2e32695b9e --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java @@ -0,0 +1,30 @@ +package nextstep.courses.domain.session; + +import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EnrollmentTest { + + @Test + @DisplayName("등록에 성공한다.") + void enroll() { + Enrollment enrollment = new Enrollment(10); + enrollment.enroll(NsUserTest.JAVAJIGI); + assertEquals(enrollment, new Enrollment(List.of(NsUserTest.JAVAJIGI), new SessionCapacity(10))); + } + + @Test + @DisplayName("인원 초과로 인해 등록에 실패한다.") + void enroll_fail() { + Enrollment enrollment = new Enrollment(1); + enrollment.enroll(NsUserTest.JAVAJIGI); + assertThatThrownBy(() -> enrollment.enroll(NsUserTest.SANJIGI)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file From eeeae9090b571b1158e2fb1a839576b89dce8418 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 19:58:13 +0900 Subject: [PATCH 10/15] =?UTF-8?q?[feat]=20=EA=B2=B0=EC=A0=9C=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/strategy/PaidPaymentStrategy.java | 4 ++-- .../courses/strategy/PaymentStrategyTest.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 src/test/java/nextstep/courses/strategy/PaymentStrategyTest.java diff --git a/src/main/java/nextstep/courses/strategy/PaidPaymentStrategy.java b/src/main/java/nextstep/courses/strategy/PaidPaymentStrategy.java index 8edd701325..640a89fc7b 100644 --- a/src/main/java/nextstep/courses/strategy/PaidPaymentStrategy.java +++ b/src/main/java/nextstep/courses/strategy/PaidPaymentStrategy.java @@ -12,7 +12,7 @@ public PaidPaymentStrategy(long price) { @Override public boolean payable(Payment payment) { if (payment.isSameAmount(price)) - return false; - return true; + return true; + return false; } } diff --git a/src/test/java/nextstep/courses/strategy/PaymentStrategyTest.java b/src/test/java/nextstep/courses/strategy/PaymentStrategyTest.java new file mode 100644 index 0000000000..927fe2bd61 --- /dev/null +++ b/src/test/java/nextstep/courses/strategy/PaymentStrategyTest.java @@ -0,0 +1,24 @@ +package nextstep.courses.strategy; + +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PaymentStrategyTest { + + @Test + @DisplayName("무료인 경기는 금액을 내지 않아도 된다.") + public void free(){ + FreePaymentStrategy freePaymentStrategy = new FreePaymentStrategy(); + assertTrue(freePaymentStrategy.payable(new Payment())); + } + + @Test + @DisplayName("유료인 경기는 낸 가격과 동일한 가격이다.") + public void paid() { + PaidPaymentStrategy paidPaymentStrategy = new PaidPaymentStrategy(1000L); + assertTrue(paidPaymentStrategy.payable(new Payment("1", 1L, 1L, 1000L))); + } +} \ No newline at end of file From 5a0bb9f833f63ae78fee0e6f9e8017c6ecfed785 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sat, 12 Apr 2025 21:38:13 +0900 Subject: [PATCH 11/15] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/domain/session/Session.java | 42 +++++++++++ .../courses/domain/session/SessionTest.java | 71 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/main/java/nextstep/courses/domain/session/Session.java create mode 100644 src/test/java/nextstep/courses/domain/session/SessionTest.java diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java new file mode 100644 index 0000000000..8e211b9289 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -0,0 +1,42 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.strategy.PaymentStrategy; +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUser; + +import java.time.LocalDate; + +public class Session { + private final SessionImage image; + private final SessionDate date; + private final SessionState state; + private final Enrollment enrollment; + private final PaymentStrategy paymentStrategy; + + public Session(SessionImage image, SessionDate date, SessionState state, Enrollment enrollment, PaymentStrategy paymentStrategy) { + this.image = image; + this.date = date; + this.state = state; + this.enrollment = enrollment; + this.paymentStrategy = paymentStrategy; + } + + public void applySession(NsUser user, LocalDate enrollDate, Payment payment) { + if (!canApply(enrollDate, payment)) { + throw new IllegalArgumentException("등록 불가능한 상태입니다."); + } + this.enrollment.enroll(user); + } + + private boolean canApply(LocalDate enrollDate, Payment payment) { + if (!state.canRecruit()) { + return false; + } + + if (!date.isBefore(enrollDate)) { + return false; + } + + return paymentStrategy.payable(payment); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java new file mode 100644 index 0000000000..ae1af06805 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -0,0 +1,71 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.strategy.FreePaymentStrategy; +import nextstep.courses.strategy.PaidPaymentStrategy; +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SessionTest { + private SessionImage image; + private SessionDate date; + private SessionState state; + private Enrollment enrollment; + + @BeforeEach + void init() { + image = new SessionImage(300, 200, 10, "jpg"); + date = new SessionDate(LocalDate.now(), LocalDate.now().plusDays(7)); + state = SessionState.RECRUITING; + enrollment = new Enrollment(10); + } + + @Test + @DisplayName("무료 강의를 등록한다.") + void applyFreeSession() { + FreePaymentStrategy freePaymentStrategy = new FreePaymentStrategy(); + Session session = new Session(image, date, state, enrollment, freePaymentStrategy); + session.applySession(NsUserTest.JAVAJIGI, LocalDate.now().minusDays(1), new Payment()); + } + + @Test + @DisplayName("유료 강의를 등록한다.") + void applyPaidSession() { + PaidPaymentStrategy paidPaymentStrategy = new PaidPaymentStrategy(1000L); + Session session = new Session(image, date, state, enrollment, paidPaymentStrategy); + session.applySession(NsUserTest.JAVAJIGI, LocalDate.now().minusDays(1), new Payment("1L", 1L, 1L, 1000L)); + } + + @Test + @DisplayName("모집 상태가 아니면 등록할 수 없다.") + void applySessionFailByState() { + FreePaymentStrategy freePaymentStrategy = new FreePaymentStrategy(); + Session session = new Session(image, date, SessionState.PREPARING, enrollment, freePaymentStrategy); + assertThatThrownBy(() -> session.applySession(NsUserTest.JAVAJIGI, LocalDate.now(), new Payment())) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("강의 시작 중에는 등록할 수 없다.") + void applySessionFailByDate() { + FreePaymentStrategy freePaymentStrategy = new FreePaymentStrategy(); + Session session = new Session(image, date, state, enrollment, freePaymentStrategy); + assertThatThrownBy(() -> session.applySession(NsUserTest.JAVAJIGI, LocalDate.now().plusDays(10), new Payment())) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("돈이 맞지 않으면 유료 강의에 등록할 수 없다.") + void applySessionFailByPayment() { + PaidPaymentStrategy paidPaymentStrategy = new PaidPaymentStrategy(1000L); + Session session = new Session(image, date, state, enrollment, paidPaymentStrategy); + assertThatThrownBy(() -> session.applySession(NsUserTest.JAVAJIGI, LocalDate.now(), new Payment("1L", 1L, 1L, 2000L))) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file From d8f480f6b6b527c09ce1de5b2d14a19373c062a1 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sun, 13 Apr 2025 08:31:55 +0900 Subject: [PATCH 12/15] =?UTF-8?q?[refactor]=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/qna/domain/Question.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index ed79697773..256e185328 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -42,13 +42,6 @@ public Long getId() { return id; } - public String getTitle() { - return title; - } - - public String getContents() { - return contents; - } public NsUser getWriter() { return writer; @@ -90,4 +83,6 @@ private void checkValidDeleteUser(NsUser loginUser) throws CannotDeleteException throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); } } + + } From f249b5d344c9330db59e3ace41cf556612909797 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sun, 13 Apr 2025 08:39:04 +0900 Subject: [PATCH 13/15] =?UTF-8?q?[refactor]=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=86=8C=EB=93=9C=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 --- src/main/java/nextstep/qna/domain/Answer.java | 2 +- src/main/java/nextstep/qna/domain/DeleteHistory.java | 8 ++++++++ src/main/java/nextstep/qna/domain/Question.java | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index ccf00f8e3f..a9ccc9d2c1 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -70,6 +70,6 @@ public String toString() { public DeleteHistory delete() { this.deleted = true; - return new DeleteHistory(ContentType.ANSWER, id, writer, LocalDateTime.now()); + return DeleteHistory.deleteAnswer(id, writer); } } diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 43c37e5e5c..28df822498 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -47,4 +47,12 @@ public String toString() { return "DeleteHistory [id=" + id + ", contentType=" + contentType + ", contentId=" + contentId + ", deletedBy=" + deletedBy + ", createdDate=" + createdDate + "]"; } + + public static DeleteHistory deleteAnswer(Long id, NsUser writer) { + return new DeleteHistory(ContentType.ANSWER, id, writer, LocalDateTime.now()); + } + + public static DeleteHistory deleteQuestion(Long id, NsUser writer) { + return new DeleteHistory(ContentType.QUESTION, id, writer, LocalDateTime.now()); + } } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 256e185328..02cf57e004 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -69,7 +69,7 @@ public List delete(NsUser loginUser) throws CannotDeleteException checkValidDeleteUser(loginUser); this.deleted = true; List deleteHistories = new ArrayList<>(); - deleteHistories.add(new DeleteHistory(ContentType.QUESTION, id, writer, LocalDateTime.now())); + deleteHistories.add(DeleteHistory.deleteQuestion(id, writer)); deleteHistories.addAll(answers.deleteAll()); return deleteHistories; } From 1b7b8abbc05dc6d4d74bd34068018f3349b2c574 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sun, 13 Apr 2025 09:04:34 +0900 Subject: [PATCH 14/15] =?UTF-8?q?[refactor]=20=EC=A7=88=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20validation=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 --- src/main/java/nextstep/qna/domain/Answer.java | 12 ++++++++++-- src/main/java/nextstep/qna/domain/Answers.java | 17 +++++++---------- src/main/java/nextstep/qna/domain/Question.java | 8 +------- .../java/nextstep/qna/domain/AnswerTest.java | 5 +++-- .../java/nextstep/qna/domain/AnswersTest.java | 12 +++++++----- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index a9ccc9d2c1..0f8afb8bd9 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -1,5 +1,6 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.qna.NotFoundException; import nextstep.qna.UnAuthorizedException; import nextstep.users.domain.NsUser; @@ -51,7 +52,7 @@ public boolean isDeleted() { return deleted; } - public boolean isOwner(NsUser writer) { + private boolean isOwner(NsUser writer) { return this.writer.equals(writer); } @@ -68,8 +69,15 @@ public String toString() { return "Answer [id=" + getId() + ", writer=" + writer + ", contents=" + contents + "]"; } - public DeleteHistory delete() { + public DeleteHistory delete(NsUser loginUser) throws CannotDeleteException { + canDeleteBy(loginUser); this.deleted = true; return DeleteHistory.deleteAnswer(id, writer); } + + private void canDeleteBy(NsUser loginUser) throws CannotDeleteException { + if (!isOwner(loginUser)) { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } + } } diff --git a/src/main/java/nextstep/qna/domain/Answers.java b/src/main/java/nextstep/qna/domain/Answers.java index e074a2354a..a41addb555 100644 --- a/src/main/java/nextstep/qna/domain/Answers.java +++ b/src/main/java/nextstep/qna/domain/Answers.java @@ -1,10 +1,10 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.users.domain.NsUser; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; public class Answers { private List answers; @@ -21,14 +21,11 @@ public void add(Answer answer) { answers.add(answer); } - public boolean isAllAnswerOwner(NsUser loginUser) { - return answers.stream() - .allMatch(answer -> answer.isOwner(loginUser)); - } - - public List deleteAll() { - return answers.stream() - .map(Answer::delete) - .collect(Collectors.toList()); + public List deleteAll(NsUser loginUser) throws CannotDeleteException { + List deleteHistories = new ArrayList<>(); + for (Answer answer : answers) { + deleteHistories.add(answer.delete(loginUser)); + } + return deleteHistories; } } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 02cf57e004..c276d5310f 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -70,7 +70,7 @@ public List delete(NsUser loginUser) throws CannotDeleteException this.deleted = true; List deleteHistories = new ArrayList<>(); deleteHistories.add(DeleteHistory.deleteQuestion(id, writer)); - deleteHistories.addAll(answers.deleteAll()); + deleteHistories.addAll(answers.deleteAll(loginUser)); return deleteHistories; } @@ -78,11 +78,5 @@ private void checkValidDeleteUser(NsUser loginUser) throws CannotDeleteException if (!isOwner(loginUser)) { throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); } - - if (!answers.isAllAnswerOwner(loginUser)) { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - } } - - } diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index e5b5da8292..eceb9f1f95 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -1,5 +1,6 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -20,8 +21,8 @@ public void init() { @Test @DisplayName("delete를 통해 answer은 삭제 상태가 된다.") - public void delete() { - A1.delete(); + public void delete() throws CannotDeleteException { + A1.delete(NsUserTest.JAVAJIGI); assertThat(A1.isDeleted()).isTrue(); } } diff --git a/src/test/java/nextstep/qna/domain/AnswersTest.java b/src/test/java/nextstep/qna/domain/AnswersTest.java index 352ebfee4c..02f66b82ab 100644 --- a/src/test/java/nextstep/qna/domain/AnswersTest.java +++ b/src/test/java/nextstep/qna/domain/AnswersTest.java @@ -1,5 +1,6 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -7,7 +8,7 @@ import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class AnswersTest { Answer A1; @@ -24,16 +25,17 @@ public void init() { } @Test - @DisplayName("답변의 작성자가 전부 같지 않으면 false을 반환한다.") + @DisplayName("답변의 작성자가 전부 같지 않으면 에러를 반환한다.") void isAllAnswerOwner_false() { Answers answers = new Answers(List.of(A1, A2, A3)); - assertThat(answers.isAllAnswerOwner(NsUserTest.JAVAJIGI)).isFalse(); + assertThatThrownBy(() -> answers.deleteAll(NsUserTest.JAVAJIGI)) + .isInstanceOf(CannotDeleteException.class); } @Test @DisplayName("답변의 작성자가 전부 같으면 true 반환한다.") - void isAllAnswerOwner_true() { + void isAllAnswerOwner_true() throws CannotDeleteException { Answers answers = new Answers(List.of(A1, A3)); - assertThat(answers.isAllAnswerOwner(NsUserTest.JAVAJIGI)).isTrue(); + answers.deleteAll(NsUserTest.JAVAJIGI); } } \ No newline at end of file From 460de6b39b7b15d5e97ebf8b0e6877ea3af63547 Mon Sep 17 00:00:00 2001 From: yang Jiyoung <0112jyoung@naver.com> Date: Sun, 13 Apr 2025 09:16:17 +0900 Subject: [PATCH 15/15] =?UTF-8?q?[refactor]=20=EC=9D=B4=EB=AF=B8=EC=A7=80,?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=EC=8B=9C=20=EC=98=88=EC=99=B8=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 --- .../java/nextstep/courses/CannotEnrollException.java | 7 +++++++ .../java/nextstep/courses/InvalidImageException.java | 11 +++++++++++ .../java/nextstep/courses/domain/session/Session.java | 3 ++- .../courses/domain/session/SessionCapacity.java | 4 +++- .../nextstep/courses/domain/session/SessionImage.java | 8 +++++--- .../courses/domain/session/SessionImageType.java | 4 +++- .../courses/domain/session/EnrollmentTest.java | 3 ++- .../courses/domain/session/SessionCapacityTest.java | 5 +++-- .../courses/domain/session/SessionImageTest.java | 7 ++++--- .../courses/domain/session/SessionImageTypeTest.java | 3 ++- .../nextstep/courses/domain/session/SessionTest.java | 7 ++++--- 11 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 src/main/java/nextstep/courses/CannotEnrollException.java create mode 100644 src/main/java/nextstep/courses/InvalidImageException.java diff --git a/src/main/java/nextstep/courses/CannotEnrollException.java b/src/main/java/nextstep/courses/CannotEnrollException.java new file mode 100644 index 0000000000..4bea8e801c --- /dev/null +++ b/src/main/java/nextstep/courses/CannotEnrollException.java @@ -0,0 +1,7 @@ +package nextstep.courses; + +public class CannotEnrollException extends RuntimeException { + public CannotEnrollException(String message) { + super(message); + } +} diff --git a/src/main/java/nextstep/courses/InvalidImageException.java b/src/main/java/nextstep/courses/InvalidImageException.java new file mode 100644 index 0000000000..417c1d74f2 --- /dev/null +++ b/src/main/java/nextstep/courses/InvalidImageException.java @@ -0,0 +1,11 @@ +package nextstep.courses; + +public class InvalidImageException extends RuntimeException { + public InvalidImageException(String message) { + super(message); + } + + public InvalidImageException(){ + + } +} diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index 8e211b9289..5704d53917 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.session; +import nextstep.courses.CannotEnrollException; import nextstep.courses.strategy.PaymentStrategy; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; @@ -23,7 +24,7 @@ public Session(SessionImage image, SessionDate date, SessionState state, Enrollm public void applySession(NsUser user, LocalDate enrollDate, Payment payment) { if (!canApply(enrollDate, payment)) { - throw new IllegalArgumentException("등록 불가능한 상태입니다."); + throw new CannotEnrollException("등록 불가능한 상태입니다."); } this.enrollment.enroll(user); } diff --git a/src/main/java/nextstep/courses/domain/session/SessionCapacity.java b/src/main/java/nextstep/courses/domain/session/SessionCapacity.java index f39804aba2..55203bc996 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionCapacity.java +++ b/src/main/java/nextstep/courses/domain/session/SessionCapacity.java @@ -1,5 +1,7 @@ package nextstep.courses.domain.session; +import nextstep.courses.CannotEnrollException; + import java.util.Objects; public class SessionCapacity { @@ -49,7 +51,7 @@ public boolean equals(Object object) { private void isValidCapacity(){ if(this.capacity > this.maxCapacity) { - throw new IllegalArgumentException("최대 수용 인원을 현재 인원이 초과할 수 없다."); + throw new CannotEnrollException("최대 수용 인원을 현재 인원이 초과할 수 없다."); } } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionImage.java b/src/main/java/nextstep/courses/domain/session/SessionImage.java index 69954155fb..6ea79ab1ae 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionImage.java +++ b/src/main/java/nextstep/courses/domain/session/SessionImage.java @@ -1,5 +1,7 @@ package nextstep.courses.domain.session; +import nextstep.courses.InvalidImageException; + import java.util.Objects; public class SessionImage { @@ -27,14 +29,14 @@ public SessionImage(int width, int height, int size, SessionImageType imageType) private void checkValidSessionImage() { if (this.width < MIN_WIDTH || this.height < MIN_HEIGHT) { - throw new IllegalArgumentException("이미지의 width는 300픽셀, height는 200픽셀 이상이어야 한다."); + throw new InvalidImageException("이미지의 width는 300픽셀, height는 200픽셀 이상이어야 한다."); } if ((double) this.width / this.height != WIDTH_PER_HEIGHT) { - throw new IllegalArgumentException("이미지의 width, height의 비율은 3:2여야 한다."); + throw new InvalidImageException("이미지의 width, height의 비율은 3:2여야 한다."); } if (this.size > MAX_IMAGE_SIZE) { - throw new IllegalArgumentException("이미지의 크기는 1MB 이하여야 한다."); + throw new InvalidImageException("이미지의 크기는 1MB 이하여야 한다."); } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionImageType.java b/src/main/java/nextstep/courses/domain/session/SessionImageType.java index c52bdef779..28fbe90d32 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionImageType.java +++ b/src/main/java/nextstep/courses/domain/session/SessionImageType.java @@ -1,5 +1,7 @@ package nextstep.courses.domain.session; +import nextstep.courses.InvalidImageException; + import java.util.Arrays; public enum SessionImageType { @@ -15,7 +17,7 @@ public static SessionImageType of(String name) { return Arrays.stream(values()) .filter(type -> type.name.equals(name)) .findFirst() - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(InvalidImageException::new); } } diff --git a/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java index 2e32695b9e..f28f1f645e 100644 --- a/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java +++ b/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.session; +import nextstep.courses.CannotEnrollException; import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -25,6 +26,6 @@ void enroll_fail() { Enrollment enrollment = new Enrollment(1); enrollment.enroll(NsUserTest.JAVAJIGI); assertThatThrownBy(() -> enrollment.enroll(NsUserTest.SANJIGI)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(CannotEnrollException.class); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionCapacityTest.java b/src/test/java/nextstep/courses/domain/session/SessionCapacityTest.java index af32322567..b8c956f610 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionCapacityTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionCapacityTest.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.session; +import nextstep.courses.CannotEnrollException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -21,13 +22,13 @@ void increase_success() { void increase_fail() { SessionCapacity capacity = new SessionCapacity(10, 10); assertThatThrownBy(capacity::increase) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(CannotEnrollException.class); } @Test @DisplayName("초기화 시 수강인원이 최대 인원보다 크면 에러를 반환한다.") void init() { assertThatThrownBy(() -> new SessionCapacity(11, 10)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(CannotEnrollException.class); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionImageTest.java b/src/test/java/nextstep/courses/domain/session/SessionImageTest.java index 05cfcff19c..8bc07d7e15 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionImageTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionImageTest.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.session; +import nextstep.courses.InvalidImageException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,20 +20,20 @@ public void init() { @DisplayName("이미지의 최소 크기를 넘지 못하면 에러가 발생한다.") public void init_invalid_heightwidth() { assertThatThrownBy(() -> new SessionImage(30, 20, 10, "jgp")) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(InvalidImageException.class); } @Test @DisplayName("이미지의 비율이 맞지 않으면 에러가 발생한다.") public void init_invalid_ratio() { assertThatThrownBy(() -> new SessionImage(20, 20, 10, "jpg")) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(InvalidImageException.class); } @Test @DisplayName("이미지의 크기가 1mb보다 크면 에러가 발생한다.") public void init_invalid_size() { assertThatThrownBy(() -> new SessionImage(30, 20, 10000000, "jgp")) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(InvalidImageException.class); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionImageTypeTest.java b/src/test/java/nextstep/courses/domain/session/SessionImageTypeTest.java index 860d024f32..2668aa1d1b 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionImageTypeTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionImageTypeTest.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.session; +import nextstep.courses.InvalidImageException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,6 +20,6 @@ void of() { @DisplayName("등록되지 않은 확장자를 넣으면 에러를 반환한다.") void of_fail() { assertThatThrownBy(() -> SessionImageType.of("xls")) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(InvalidImageException.class); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index ae1af06805..2d05d568aa 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.session; +import nextstep.courses.CannotEnrollException; import nextstep.courses.strategy.FreePaymentStrategy; import nextstep.courses.strategy.PaidPaymentStrategy; import nextstep.payments.domain.Payment; @@ -48,7 +49,7 @@ void applySessionFailByState() { FreePaymentStrategy freePaymentStrategy = new FreePaymentStrategy(); Session session = new Session(image, date, SessionState.PREPARING, enrollment, freePaymentStrategy); assertThatThrownBy(() -> session.applySession(NsUserTest.JAVAJIGI, LocalDate.now(), new Payment())) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(CannotEnrollException.class); } @Test @@ -57,7 +58,7 @@ void applySessionFailByDate() { FreePaymentStrategy freePaymentStrategy = new FreePaymentStrategy(); Session session = new Session(image, date, state, enrollment, freePaymentStrategy); assertThatThrownBy(() -> session.applySession(NsUserTest.JAVAJIGI, LocalDate.now().plusDays(10), new Payment())) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(CannotEnrollException.class); } @Test @@ -66,6 +67,6 @@ void applySessionFailByPayment() { PaidPaymentStrategy paidPaymentStrategy = new PaidPaymentStrategy(1000L); Session session = new Session(image, date, state, enrollment, paidPaymentStrategy); assertThatThrownBy(() -> session.applySession(NsUserTest.JAVAJIGI, LocalDate.now(), new Payment("1L", 1L, 1L, 2000L))) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(CannotEnrollException.class); } } \ No newline at end of file