From 1a6abb603c233242e7274235256de3e3e9dd8ec2 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Tue, 2 Dec 2025 23:33:36 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=9A=80=202=EB=8B=A8=EA=B3=84=20-=20?= =?UTF-8?q?=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD(=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=8D=B8)=20:=20TDD=201=EC=B0=A8=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 --- Step.md => docs/Step01.md | 0 docs/Step02.md | 38 ++++++++ .../java/nextstep/courses/domain/Course.java | 15 +++ .../java/nextstep/courses/domain/Courses.java | 5 + .../courses/domain/RecruitmentState.java | 23 +++++ .../java/nextstep/courses/domain/Session.java | 91 ++++++++++++++++++ .../courses/domain/SessionCoverImage.java | 17 ++++ .../courses/domain/SessionImageCapacity.java | 29 ++++++ .../courses/domain/SessionImageDimension.java | 36 +++++++ .../courses/domain/SessionImageExtension.java | 28 ++++++ .../courses/domain/SessionPeriod.java | 24 +++++ .../nextstep/courses/domain/CourseTest.java | 9 ++ .../courses/domain/SessionCoverImageTest.java | 28 ++++++ .../domain/SessionImageCapacityTest.java | 48 ++++++++++ .../domain/SessionImageDimensionTest.java | 38 ++++++++ .../courses/domain/SessionPeriodTest.java | 38 ++++++++ .../nextstep/courses/domain/SessionTest.java | 94 +++++++++++++++++++ 17 files changed, 561 insertions(+) rename Step.md => docs/Step01.md (100%) create mode 100644 docs/Step02.md create mode 100644 src/main/java/nextstep/courses/domain/Courses.java create mode 100644 src/main/java/nextstep/courses/domain/RecruitmentState.java create mode 100644 src/main/java/nextstep/courses/domain/Session.java create mode 100644 src/main/java/nextstep/courses/domain/SessionCoverImage.java create mode 100644 src/main/java/nextstep/courses/domain/SessionImageCapacity.java create mode 100644 src/main/java/nextstep/courses/domain/SessionImageDimension.java create mode 100644 src/main/java/nextstep/courses/domain/SessionImageExtension.java create mode 100644 src/main/java/nextstep/courses/domain/SessionPeriod.java create mode 100644 src/test/java/nextstep/courses/domain/CourseTest.java create mode 100644 src/test/java/nextstep/courses/domain/SessionCoverImageTest.java create mode 100644 src/test/java/nextstep/courses/domain/SessionImageCapacityTest.java create mode 100644 src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java create mode 100644 src/test/java/nextstep/courses/domain/SessionPeriodTest.java create mode 100644 src/test/java/nextstep/courses/domain/SessionTest.java diff --git a/Step.md b/docs/Step01.md similarity index 100% rename from Step.md rename to docs/Step01.md diff --git a/docs/Step02.md b/docs/Step02.md new file mode 100644 index 0000000000..f00b4718a8 --- /dev/null +++ b/docs/Step02.md @@ -0,0 +1,38 @@ +## 수강 신청 기능 요구사항 +- 과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다. +- 강의는 시작일과 종료일을 가진다. +- 강의는 강의 커버 이미지 정보를 가진다. + - 이미지 크기는 1MB 이하여야 한다. + - 이미지 타입은 gif, jpg(jpeg 포함),, png, svg만 허용한다. + - 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. +- 강의는 무료 강의와 유료 강의로 나뉜다. + - 무료 강의는 최대 수강 인원 제한이 없다. + - 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. + - 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. +- 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. +- 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. +- 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다. + - 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다. + + +## 클래스 +- `과정(Course)` : 1 + - 기수 + +- `강의(Session)` : N + - 금액 : 유료 / 무료 + - 유료 강의 + - 강의 최대 `수강 인원` 제한 + - `결재 금액` = 수강료] + - 무료 강의 + - 최대 수강인원 제한 없음 + - 상태 : 준비중, 모집중, 종료 + - 시작일, 종료일 + - 강의 커버 이미지 + +- `강의 커버 이미지 (SessionCoverImage)` + - 크기 : 1MB + - 타입 : gif, jpg(jpeg), png, svg + - dimension + - width >= 300, height >= 200 + - width : height = 3 : 2 diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/Course.java index 0f69716043..55febf9f3e 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/Course.java @@ -1,6 +1,7 @@ package nextstep.courses.domain; import java.time.LocalDateTime; +import java.util.Objects; public class Course { private Long id; @@ -50,4 +51,18 @@ public String toString() { ", updatedAt=" + updatedAt + '}'; } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Course course = (Course) o; + return Objects.equals(id, course.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } } diff --git a/src/main/java/nextstep/courses/domain/Courses.java b/src/main/java/nextstep/courses/domain/Courses.java new file mode 100644 index 0000000000..20ddf04166 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Courses.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain; + +public class Courses { + +} diff --git a/src/main/java/nextstep/courses/domain/RecruitmentState.java b/src/main/java/nextstep/courses/domain/RecruitmentState.java new file mode 100644 index 0000000000..9611fd9f20 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/RecruitmentState.java @@ -0,0 +1,23 @@ +package nextstep.courses.domain; + +public enum RecruitmentState { + PREPARING, + RECRUITING, + CLOSED; + + public RecruitmentState next() { + switch (this) { + case PREPARING : + return RECRUITING; + case RECRUITING : + return CLOSED; + case CLOSED : + throw new IllegalStateException("종료된 강의는 상태를 변경할 수 없습니다."); + } + throw new IllegalStateException("잘못된 강의 상태 입니다."); + } + + public boolean canEnroll() { + return this == RECRUITING; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java new file mode 100644 index 0000000000..f7c97f74ef --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -0,0 +1,91 @@ +package nextstep.courses.domain; + +public class Session { + private final Course course; + private final SessionCoverImage coverImage; + private final SessionPeriod period; + private final RecruitmentState state; + + private final int maxCapacity; + private final int tuitionFee; + private final int studentCount; + + public Session(Course course, SessionCoverImage coverImage, String startDay, String endDay) { + this(course, coverImage, startDay, endDay, Integer.MAX_VALUE, 0); + } + + public Session(Course course, SessionCoverImage coverImage, String startDay, String endDay, int maxCapacity, int tuitionFee) { + this(course, coverImage, new SessionPeriod(startDay, endDay), maxCapacity, tuitionFee, 0); + } + + public Session(Course course, SessionCoverImage coverImage, String startDay, String endDay, int maxCapacity, int tuitionFee, int studentCount) { + this(course, coverImage, new SessionPeriod(startDay, endDay), maxCapacity, tuitionFee, studentCount); + } + + public Session(Course course, SessionCoverImage coverImage, SessionPeriod period, int maxCapacity, int tuitionFee, int studentCount) { + this(course, coverImage, period, maxCapacity, tuitionFee, studentCount, RecruitmentState.PREPARING); + } + + public Session(Course course, SessionCoverImage coverImage, SessionPeriod period, int maxCapacity, int tuitionFee, int studentCount, RecruitmentState state) { + validateCapacity(maxCapacity, studentCount); + this.course = course; + this.coverImage = coverImage; + this.period = period; + this.maxCapacity = maxCapacity; + this.tuitionFee = tuitionFee; + this.studentCount = studentCount; + this.state = state; + } + + public Session enroll(){ + return this.enroll(0); + } + + public Session enroll(int payAmount) { + validateState(); + validateCapacity(maxCapacity, studentCount + 1); + validateTuitionFee(payAmount); + return increaseStudent(); + } + + public Session openEnrollment() { + if (state != RecruitmentState.PREPARING) { + throw new IllegalStateException("준비중인 강의만 모집을 시작할 수 있습니다."); + } + return new Session(this.course, this.coverImage, this.period, this.maxCapacity, this.tuitionFee, this.studentCount, RecruitmentState.RECRUITING); + } + + public Session closeEnrollment() { + if (state != RecruitmentState.RECRUITING) { + throw new IllegalStateException("모집중인 강의만 종료할 수 있습니다."); + } + return new Session(this.course, this.coverImage, this.period, this.maxCapacity, this.tuitionFee, this.studentCount, RecruitmentState.CLOSED); + } + + public RecruitmentState getState() { + return state; + } + + private Session increaseStudent() { + return new Session(this.course, this.coverImage, this.period, this.maxCapacity, this.tuitionFee, this.studentCount + 1, this.state); + } + + private void validateState() { + if (!state.canEnroll()) { + throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); + } + } + + private void validateTuitionFee(int payAmount){ + if(payAmount != tuitionFee){ + throw new IllegalArgumentException("수강료와 지불한 금액이 정확히 일치해야 합니다."); + } + } + + private void validateCapacity(int maxCapacity, int studentCount){ + if(maxCapacity < studentCount){ + throw new IllegalArgumentException("최대 수강 인원을 초과할 수 없습니다."); + } + } + +} diff --git a/src/main/java/nextstep/courses/domain/SessionCoverImage.java b/src/main/java/nextstep/courses/domain/SessionCoverImage.java new file mode 100644 index 0000000000..dcda9177ed --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionCoverImage.java @@ -0,0 +1,17 @@ +package nextstep.courses.domain; + +public class SessionCoverImage { + private final SessionImageDimension dimension; + private final SessionImageExtension extension; + private final SessionImageCapacity capacity; + + public SessionCoverImage(int width, int height, String extension, long bytes) { + this(new SessionImageDimension(width, height), SessionImageExtension.from(extension), new SessionImageCapacity(bytes)); + } + + public SessionCoverImage(SessionImageDimension dimension, SessionImageExtension extension, SessionImageCapacity capacity) { + this.dimension = dimension; + this.extension = extension; + this.capacity = capacity; + } +} diff --git a/src/main/java/nextstep/courses/domain/SessionImageCapacity.java b/src/main/java/nextstep/courses/domain/SessionImageCapacity.java new file mode 100644 index 0000000000..7f86d6e9c0 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionImageCapacity.java @@ -0,0 +1,29 @@ +package nextstep.courses.domain; + +public class SessionImageCapacity { + private static final long MAX_CAPACITY_BYTES = 1024 * 1024; // 1MB + private final long bytes; + + public SessionImageCapacity(long bytes) { + validate(bytes); + this.bytes = bytes; + } + + public static SessionImageCapacity ofKB(long kb) { + return new SessionImageCapacity(kb * 1024); + } + + public static SessionImageCapacity ofMB(long mb) { + return new SessionImageCapacity(mb * 1024 * 1024); + } + + private void validate(long bytes) { + if (bytes <= 0) { + throw new IllegalArgumentException("이미지 크기는 0보다 커야 합니다."); + } + if (bytes > MAX_CAPACITY_BYTES) { + throw new IllegalArgumentException("이미지 크기는 1MB 이하여야 합니다."); + } + } + +} diff --git a/src/main/java/nextstep/courses/domain/SessionImageDimension.java b/src/main/java/nextstep/courses/domain/SessionImageDimension.java new file mode 100644 index 0000000000..31b5326038 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionImageDimension.java @@ -0,0 +1,36 @@ +package nextstep.courses.domain; + +public class SessionImageDimension { + private int width; + private int height; + private static final int MIN_WIDTH = 300; + private static final int MIN_HEIGHT = 200; + private static final int RATIO_W = 3; + private static final int RATIO_H = 2; + public SessionImageDimension(int width, int height) { + validateMinLength(width, height); + validateRatio(width, height); + this.width = width; + this.height = height; + } + private void validateMinLength(int width, int height){ + if(!(width >= MIN_WIDTH && height >= MIN_HEIGHT)) { + throw new IllegalArgumentException("이미지는 가로 300이상, 세로 200 이상이어야 합니다."); + } + } + + private void validateRatio(int width, int height){ + int gcd = gcd(width, height); + int ratioW = width / gcd; + int ratioH = height / gcd; + if(!(ratioW == RATIO_W && ratioH == RATIO_H)){ + throw new IllegalArgumentException("이미지는 가로 x 세로 3대 2이어야 합니다."); + } + } + + private int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a % b); + } + + +} diff --git a/src/main/java/nextstep/courses/domain/SessionImageExtension.java b/src/main/java/nextstep/courses/domain/SessionImageExtension.java new file mode 100644 index 0000000000..7eb89aec8f --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionImageExtension.java @@ -0,0 +1,28 @@ +package nextstep.courses.domain; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public enum SessionImageExtension { + GIF("gif"), + JPG("jpg", "jpeg"), + PNG("png"), + SVG("svg"); + + private List names; + + SessionImageExtension(String... names){ + this.names = new ArrayList<>(List.of(names)); + } + + public static SessionImageExtension from(String extension) { + String lowerExt = extension.toLowerCase(); + for (SessionImageExtension type : values()) { + if (type.names.contains(lowerExt)) { + return type; + } + } + throw new IllegalArgumentException("지원하지 않는 이미지 확장자입니다: " + extension); + } +} diff --git a/src/main/java/nextstep/courses/domain/SessionPeriod.java b/src/main/java/nextstep/courses/domain/SessionPeriod.java new file mode 100644 index 0000000000..7300090d79 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionPeriod.java @@ -0,0 +1,24 @@ +package nextstep.courses.domain; + +import java.time.LocalDate; + +public class SessionPeriod { + private final LocalDate startDay; + private final LocalDate endDay; + + public SessionPeriod(String startDay, String endDay) { + this(LocalDate.parse(startDay), LocalDate.parse(endDay)); + } + + public SessionPeriod(LocalDate startDay, LocalDate endDay) { + validate(startDay, endDay); + this.startDay = startDay; + this.endDay = endDay; + } + + private void validate(LocalDate startDay, LocalDate endDay) { + if (startDay.isAfter(endDay)) { + throw new IllegalArgumentException("시작일은 종료일보다 이전이어야 합니다."); + } + } +} diff --git a/src/test/java/nextstep/courses/domain/CourseTest.java b/src/test/java/nextstep/courses/domain/CourseTest.java new file mode 100644 index 0000000000..65a3fa9fc9 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/CourseTest.java @@ -0,0 +1,9 @@ +package nextstep.courses.domain; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class CourseTest { + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionCoverImageTest.java b/src/test/java/nextstep/courses/domain/SessionCoverImageTest.java new file mode 100644 index 0000000000..7d77ae7957 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionCoverImageTest.java @@ -0,0 +1,28 @@ +package nextstep.courses.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class SessionCoverImageTest { + private static final int WIDTH = 300, HEIGHT = 200; + private static final long VALID_BYTES = 1024 * 500; // 500KB + + @ParameterizedTest + @CsvSource({"gif","jpg","jpeg", "png", "svg"}) + void 허용된_확장자(String extension){ + assertDoesNotThrow(() -> new SessionCoverImage(WIDTH, HEIGHT, extension, VALID_BYTES)); + } + + @ParameterizedTest + @CsvSource({"webp"}) + void 허용하지않은_확장자는_예외(String extension){ + assertThatThrownBy(() -> new SessionCoverImage(WIDTH, HEIGHT, extension, VALID_BYTES)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("지원하지 않는 이미지 확장자입니다: " + extension); + } + + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionImageCapacityTest.java b/src/test/java/nextstep/courses/domain/SessionImageCapacityTest.java new file mode 100644 index 0000000000..2479355a7d --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionImageCapacityTest.java @@ -0,0 +1,48 @@ +package nextstep.courses.domain; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class SessionImageCapacityTest { + + @Test + void 크기_1MB이하_정상생성() { + assertThatCode(() -> new SessionImageCapacity(1024 * 1024)) + .doesNotThrowAnyException(); + } + + @Test + void 크기_1MB초과_예외() { + assertThatThrownBy(() -> new SessionImageCapacity(1024 * 1024 + 1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이미지 크기는 1MB 이하여야 합니다."); + } + + @Test + void 크기_0이하_예외() { + assertThatThrownBy(() -> new SessionImageCapacity(0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이미지 크기는 0보다 커야 합니다."); + } + + @Test + void KB단위_생성() { + assertThatCode(() -> SessionImageCapacity.ofKB(500)) + .doesNotThrowAnyException(); + } + + @Test + void MB단위_1MB_정상생성() { + assertThatCode(() -> SessionImageCapacity.ofMB(1)) + .doesNotThrowAnyException(); + } + + @Test + void MB단위_2MB_예외() { + assertThatThrownBy(() -> SessionImageCapacity.ofMB(2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이미지 크기는 1MB 이하여야 합니다."); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java b/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java new file mode 100644 index 0000000000..df6cf1dba7 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java @@ -0,0 +1,38 @@ +package nextstep.courses.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class SessionImageDimensionTest { + + @ParameterizedTest + @CsvSource({"300,200"}) + void 너비는_300이상_높이는_200이상(int width, int height){ + assertDoesNotThrow(() -> new SessionImageDimension(width, height)); + } + + @ParameterizedTest + @CsvSource({"299,299", "299,300", "300,299"}) + void 그_외_너비_예외(int width, int height){ + assertThatThrownBy(() -> new SessionImageDimension(width, height)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이미지는 가로 x 세로 3대 1이어야 합니다."); + } + + @ParameterizedTest + @CsvSource({"300,200","1200,800"}) + void 너비와_높이_비율은_3_대_2(int width, int height){ + assertDoesNotThrow(() -> new SessionImageDimension(width, height)); + + } + + @ParameterizedTest + @CsvSource({"200,300", "300,1", "300,199"}) + void 그_외_비율_예외(int width, int height){ + + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionPeriodTest.java b/src/test/java/nextstep/courses/domain/SessionPeriodTest.java new file mode 100644 index 0000000000..3dd2b6f8ab --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionPeriodTest.java @@ -0,0 +1,38 @@ +package nextstep.courses.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.time.LocalDate; +import org.junit.jupiter.api.Test; + +class SessionPeriodTest { + + @Test + void 시작일이_종료일보다_이전이면_정상_생성() { + assertThatCode(() -> new SessionPeriod("2024-01-01", "2024-01-31")) + .doesNotThrowAnyException(); + } + + @Test + void 시작일과_종료일이_같으면_정상_생성() { + assertThatCode(() -> new SessionPeriod("2024-01-01", "2024-01-01")) + .doesNotThrowAnyException(); + } + + @Test + void 시작일이_종료일보다_이후면_예외() { + assertThatThrownBy(() -> new SessionPeriod("2024-01-31", "2024-01-01")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("시작일은 종료일보다 이전이어야 합니다."); + } + + @Test + void LocalDate로_생성() { + LocalDate start = LocalDate.of(2024, 1, 1); + LocalDate end = LocalDate.of(2024, 1, 31); + + assertThatCode(() -> new SessionPeriod(start, end)) + .doesNotThrowAnyException(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java new file mode 100644 index 0000000000..22482d7262 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -0,0 +1,94 @@ +package nextstep.courses.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SessionTest { + private Session costSession; + private Session freeSession; + + private final int MAX_CAPACITY = 300; + private final int TUITION_FEE = 1000; + private final int STUDENT_COUNT = 299; + + private static final SessionCoverImage COVER_IMAGE = new SessionCoverImage(300, 200, "png", 1024 * 500); + + @BeforeEach + void init(){ + costSession = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31", MAX_CAPACITY, TUITION_FEE, STUDENT_COUNT); + costSession = costSession.openEnrollment(); + freeSession = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31"); + freeSession = freeSession.openEnrollment(); + } + + // 금액 + @Test + void 유료강의_최대수강인원_초과하면_예외(){ + costSession = costSession.enroll(TUITION_FEE); + + assertThatThrownBy(() -> costSession.enroll(TUITION_FEE)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("최대 수강 인원을 초과할 수 없습니다."); + } + + @Test + void 유료강의_결재금액과_수강료가_동일하지_않으면_예외(){ + assertThatThrownBy(() -> costSession.enroll(TUITION_FEE - 1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); + } + + @Test + void 유료강의_최대수강인원이하_결재금액과수강료동일(){ + assertDoesNotThrow(() -> costSession.enroll(TUITION_FEE)); + } + + @Test + void 무료강의_최대수강인원제한_없음(){ + Session free = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31"); + free = free.openEnrollment(); + + for (int i = 0; i < 1000; i++) { + free = free.enroll(); + } + } + + @Test + void 무료강의_수강료_0원(){ + assertDoesNotThrow(() -> freeSession.enroll(0)); + } + + + //상태 + @Test + void 강의상태_준비중_모집중_종료순으로_변화(){ + Session session = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31"); + + assertThat(session.getState()).isEqualTo(RecruitmentState.PREPARING); + + session = session.openEnrollment(); + assertThat(session.getState()).isEqualTo(RecruitmentState.RECRUITING); + + session = session.closeEnrollment(); + assertThat(session.getState()).isEqualTo(RecruitmentState.CLOSED); + } + + @Test + void 강의상태_모집중에만_수강신청가능(){ + assertDoesNotThrow(() -> costSession.enroll(TUITION_FEE)); + } + + @Test + void 강의상태_그외_수강신청하면_예외(){ + Session preparing = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31"); + + assertThatThrownBy(() -> preparing.enroll(0)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("모집중인 강의만 수강신청이 가능합니다."); + } + +} \ No newline at end of file From 4e163dbc0694ce35178c318b5da3424c44859ab9 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Wed, 3 Dec 2025 00:44:18 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=9A=80=202=EB=8B=A8=EA=B3=84=20-=20?= =?UTF-8?q?=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD(=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=8D=B8)=20:=20id,=20enroll=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/courses/domain/BaseEntity.java | 36 ++++++++ .../java/nextstep/courses/domain/Course.java | 52 +++++------ .../java/nextstep/courses/domain/Session.java | 66 +++++--------- .../courses/domain/SessionEnrollment.java | 44 +++++++++ .../courses/domain/RecruitmentStateTest.java | 33 +++++++ .../courses/domain/SessionEnrollmentTest.java | 52 +++++++++++ .../domain/SessionImageDimensionTest.java | 10 ++- .../nextstep/courses/domain/SessionTest.java | 89 +++++-------------- 8 files changed, 236 insertions(+), 146 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/BaseEntity.java create mode 100644 src/main/java/nextstep/courses/domain/SessionEnrollment.java create mode 100644 src/test/java/nextstep/courses/domain/RecruitmentStateTest.java create mode 100644 src/test/java/nextstep/courses/domain/SessionEnrollmentTest.java diff --git a/src/main/java/nextstep/courses/domain/BaseEntity.java b/src/main/java/nextstep/courses/domain/BaseEntity.java new file mode 100644 index 0000000000..2f8bbf5b31 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/BaseEntity.java @@ -0,0 +1,36 @@ +package nextstep.courses.domain; + +import java.time.LocalDateTime; + +public abstract class BaseEntity { + private Long id; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + protected BaseEntity() { + this.createdAt = LocalDateTime.now(); + } + + protected BaseEntity(Long id) { + this.id = id; + this.createdAt = LocalDateTime.now(); + } + + protected BaseEntity(Long id, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/Course.java index 55febf9f3e..17eb2ba275 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/Course.java @@ -3,30 +3,27 @@ import java.time.LocalDateTime; import java.util.Objects; -public class Course { - private Long id; - +public class Course extends BaseEntity { private String title; - private Long creatorId; - private LocalDateTime createdAt; - - private LocalDateTime updatedAt; - public Course() { } public Course(String title, Long creatorId) { - this(0L, title, creatorId, LocalDateTime.now(), null); + this(null, title, creatorId); + } + + public Course(Long id, String title, Long creatorId) { + super(id); + this.title = title; + this.creatorId = creatorId; } public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + super(id, createdAt, updatedAt); this.title = title; this.creatorId = creatorId; - this.createdAt = createdAt; - this.updatedAt = updatedAt; } public String getTitle() { @@ -37,32 +34,27 @@ public Long getCreatorId() { return creatorId; } - public LocalDateTime getCreatedAt() { - return createdAt; - } - @Override public String toString() { return "Course{" + - "id=" + id + + "id=" + getId() + ", title='" + title + '\'' + ", creatorId=" + creatorId + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + + ", createdAt=" + getCreatedAt() + '}'; } - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Course course = (Course) o; + return Objects.equals(getId(), course.getId()); } - Course course = (Course) o; - return Objects.equals(id, course.id); - } - @Override - public int hashCode() { - return Objects.hashCode(id); - } + @Override + public int hashCode() { + return Objects.hashCode(getId()); + } } diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index f7c97f74ef..a8781bdf6c 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -1,91 +1,69 @@ package nextstep.courses.domain; -public class Session { +public class Session extends BaseEntity { private final Course course; - private final SessionCoverImage coverImage; + private final int term; + private final SessionCoverImage cover; private final SessionPeriod period; private final RecruitmentState state; + private final SessionEnrollment enrollment; - private final int maxCapacity; - private final int tuitionFee; - private final int studentCount; - - public Session(Course course, SessionCoverImage coverImage, String startDay, String endDay) { - this(course, coverImage, startDay, endDay, Integer.MAX_VALUE, 0); + public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay) { + this(null, course, term, cover, new SessionPeriod(startDay, endDay), SessionEnrollment.free()); } - public Session(Course course, SessionCoverImage coverImage, String startDay, String endDay, int maxCapacity, int tuitionFee) { - this(course, coverImage, new SessionPeriod(startDay, endDay), maxCapacity, tuitionFee, 0); + public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay, int maxCapacity, int tuitionFee) { + this(null, course, term, cover, new SessionPeriod(startDay, endDay), SessionEnrollment.paid(maxCapacity, tuitionFee)); } - public Session(Course course, SessionCoverImage coverImage, String startDay, String endDay, int maxCapacity, int tuitionFee, int studentCount) { - this(course, coverImage, new SessionPeriod(startDay, endDay), maxCapacity, tuitionFee, studentCount); + public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay, int maxCapacity, int tuitionFee, int studentCount) { + this(null, course, term, cover, new SessionPeriod(startDay, endDay), new SessionEnrollment(maxCapacity, tuitionFee, studentCount)); } - public Session(Course course, SessionCoverImage coverImage, SessionPeriod period, int maxCapacity, int tuitionFee, int studentCount) { - this(course, coverImage, period, maxCapacity, tuitionFee, studentCount, RecruitmentState.PREPARING); + public Session(Long id, Course course, int term, SessionCoverImage cover, SessionPeriod period, SessionEnrollment enrollment) { + this(id, course, term, cover, period, enrollment, RecruitmentState.PREPARING); } - public Session(Course course, SessionCoverImage coverImage, SessionPeriod period, int maxCapacity, int tuitionFee, int studentCount, RecruitmentState state) { - validateCapacity(maxCapacity, studentCount); + public Session(Long id, Course course, int term, SessionCoverImage cover, SessionPeriod period, SessionEnrollment enrollment, RecruitmentState state) { + super(id); this.course = course; - this.coverImage = coverImage; + this.term = term; + this.cover = cover; this.period = period; - this.maxCapacity = maxCapacity; - this.tuitionFee = tuitionFee; - this.studentCount = studentCount; + this.enrollment = enrollment; this.state = state; } - public Session enroll(){ - return this.enroll(0); + public Session enroll() { + return enroll(0); } public Session enroll(int payAmount) { validateState(); - validateCapacity(maxCapacity, studentCount + 1); - validateTuitionFee(payAmount); - return increaseStudent(); + return new Session(getId(), course, term, cover, period, enrollment.enroll(payAmount), state); } public Session openEnrollment() { if (state != RecruitmentState.PREPARING) { throw new IllegalStateException("준비중인 강의만 모집을 시작할 수 있습니다."); } - return new Session(this.course, this.coverImage, this.period, this.maxCapacity, this.tuitionFee, this.studentCount, RecruitmentState.RECRUITING); + return new Session(getId(), course, term, cover, period, enrollment, RecruitmentState.RECRUITING); } public Session closeEnrollment() { if (state != RecruitmentState.RECRUITING) { throw new IllegalStateException("모집중인 강의만 종료할 수 있습니다."); } - return new Session(this.course, this.coverImage, this.period, this.maxCapacity, this.tuitionFee, this.studentCount, RecruitmentState.CLOSED); + return new Session(getId(), course, term, cover, period, enrollment, RecruitmentState.CLOSED); } public RecruitmentState getState() { return state; } - private Session increaseStudent() { - return new Session(this.course, this.coverImage, this.period, this.maxCapacity, this.tuitionFee, this.studentCount + 1, this.state); - } - private void validateState() { if (!state.canEnroll()) { throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); } } - - private void validateTuitionFee(int payAmount){ - if(payAmount != tuitionFee){ - throw new IllegalArgumentException("수강료와 지불한 금액이 정확히 일치해야 합니다."); - } - } - - private void validateCapacity(int maxCapacity, int studentCount){ - if(maxCapacity < studentCount){ - throw new IllegalArgumentException("최대 수강 인원을 초과할 수 없습니다."); - } - } - } diff --git a/src/main/java/nextstep/courses/domain/SessionEnrollment.java b/src/main/java/nextstep/courses/domain/SessionEnrollment.java new file mode 100644 index 0000000000..523ca4edff --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionEnrollment.java @@ -0,0 +1,44 @@ +package nextstep.courses.domain; + +public class SessionEnrollment { + private final int maxCapacity; + private final int tuitionFee; + private final int studentCount; + + public static SessionEnrollment free() { + return new SessionEnrollment(Integer.MAX_VALUE, 0, 0); + } + + public static SessionEnrollment paid(int maxCapacity, int tuitionFee) { + return new SessionEnrollment(maxCapacity, tuitionFee, 0); + } + + public SessionEnrollment(int maxCapacity, int tuitionFee, int studentCount) { + validateCapacity(maxCapacity, studentCount); + this.maxCapacity = maxCapacity; + this.tuitionFee = tuitionFee; + this.studentCount = studentCount; + } + + public SessionEnrollment enroll(int payAmount) { + validateCapacity(maxCapacity, studentCount + 1); + validateTuitionFee(payAmount); + return new SessionEnrollment(maxCapacity, tuitionFee, studentCount + 1); + } + + public SessionEnrollment enroll() { + return enroll(0); + } + + private void validateTuitionFee(int payAmount) { + if (payAmount != tuitionFee) { + throw new IllegalArgumentException("수강료와 지불한 금액이 정확히 일치해야 합니다."); + } + } + + private void validateCapacity(int maxCapacity, int studentCount) { + if (maxCapacity < studentCount) { + throw new IllegalArgumentException("최대 수강 인원을 초과할 수 없습니다."); + } + } +} diff --git a/src/test/java/nextstep/courses/domain/RecruitmentStateTest.java b/src/test/java/nextstep/courses/domain/RecruitmentStateTest.java new file mode 100644 index 0000000000..b9d0db5ace --- /dev/null +++ b/src/test/java/nextstep/courses/domain/RecruitmentStateTest.java @@ -0,0 +1,33 @@ +package nextstep.courses.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class RecruitmentStateTest { + + @Test + void 준비중_다음_상태는_모집중() { + assertThat(RecruitmentState.PREPARING.next()).isEqualTo(RecruitmentState.RECRUITING); + } + + @Test + void 모집중_다음_상태는_종료() { + assertThat(RecruitmentState.RECRUITING.next()).isEqualTo(RecruitmentState.CLOSED); + } + + @Test + void 종료_상태에서_다음_상태로_변경하면_예외() { + assertThatThrownBy(() -> RecruitmentState.CLOSED.next()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("종료된 강의는 상태를 변경할 수 없습니다."); + } + + @Test + void 모집중일때만_수강신청_가능() { + assertThat(RecruitmentState.PREPARING.canEnroll()).isFalse(); + assertThat(RecruitmentState.RECRUITING.canEnroll()).isTrue(); + assertThat(RecruitmentState.CLOSED.canEnroll()).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionEnrollmentTest.java b/src/test/java/nextstep/courses/domain/SessionEnrollmentTest.java new file mode 100644 index 0000000000..b36bd121d4 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionEnrollmentTest.java @@ -0,0 +1,52 @@ +package nextstep.courses.domain; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class SessionEnrollmentTest { + + @Test + void 유료강의_최대수강인원_초과하면_예외() { + SessionEnrollment enrollment = new SessionEnrollment(300, 1000, 300); + + assertThatThrownBy(() -> enrollment.enroll(1000)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("최대 수강 인원을 초과할 수 없습니다."); + } + + @Test + void 유료강의_결제금액과_수강료가_동일하지_않으면_예외() { + SessionEnrollment enrollment = SessionEnrollment.paid(300, 1000); + + assertThatThrownBy(() -> enrollment.enroll(999)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); + } + + @Test + void 유료강의_최대수강인원이하_결제금액과수강료동일() { + SessionEnrollment enrollment = SessionEnrollment.paid(300, 1000); + + assertThatCode(() -> enrollment.enroll(1000)) + .doesNotThrowAnyException(); + } + + @Test + void 무료강의_최대수강인원제한_없음() { + SessionEnrollment enrollment = SessionEnrollment.free(); + + for (int i = 0; i < 1000; i++) { + enrollment = enrollment.enroll(); + } + } + + @Test + void 무료강의_수강료_0원() { + SessionEnrollment enrollment = SessionEnrollment.free(); + + assertThatCode(() -> enrollment.enroll(0)) + .doesNotThrowAnyException(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java b/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java index df6cf1dba7..9d2b3b284c 100644 --- a/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java @@ -15,11 +15,11 @@ class SessionImageDimensionTest { } @ParameterizedTest - @CsvSource({"299,299", "299,300", "300,299"}) + @CsvSource({"299,299", "299,300", "300,199"}) void 그_외_너비_예외(int width, int height){ assertThatThrownBy(() -> new SessionImageDimension(width, height)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("이미지는 가로 x 세로 3대 1이어야 합니다."); + .hasMessage("이미지는 가로 300이상, 세로 200 이상이어야 합니다."); } @ParameterizedTest @@ -30,9 +30,11 @@ class SessionImageDimensionTest { } @ParameterizedTest - @CsvSource({"200,300", "300,1", "300,199"}) + @CsvSource({"400,200", "600,300", "900,400"}) void 그_외_비율_예외(int width, int height){ - + assertThatThrownBy(() -> new SessionImageDimension(width, height)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이미지는 가로 x 세로 3대 2이어야 합니다."); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index 22482d7262..ab45a82146 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -1,94 +1,47 @@ package nextstep.courses.domain; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class SessionTest { - private Session costSession; - private Session freeSession; - - private final int MAX_CAPACITY = 300; - private final int TUITION_FEE = 1000; - private final int STUDENT_COUNT = 299; - private static final SessionCoverImage COVER_IMAGE = new SessionCoverImage(300, 200, "png", 1024 * 500); + private static final int TERM = 19; - @BeforeEach - void init(){ - costSession = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31", MAX_CAPACITY, TUITION_FEE, STUDENT_COUNT); - costSession = costSession.openEnrollment(); - freeSession = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31"); - freeSession = freeSession.openEnrollment(); - } - - // 금액 @Test - void 유료강의_최대수강인원_초과하면_예외(){ - costSession = costSession.enroll(TUITION_FEE); + void 모집중일때_수강신청_가능() { + Session session = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); + Session recruiting = session.openEnrollment(); - assertThatThrownBy(() -> costSession.enroll(TUITION_FEE)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("최대 수강 인원을 초과할 수 없습니다."); + assertDoesNotThrow(() -> recruiting.enroll(0)); } @Test - void 유료강의_결재금액과_수강료가_동일하지_않으면_예외(){ - assertThatThrownBy(() -> costSession.enroll(TUITION_FEE - 1)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); - } + void 모집중이_아닐때_수강신청하면_예외() { + Session preparing = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); - @Test - void 유료강의_최대수강인원이하_결재금액과수강료동일(){ - assertDoesNotThrow(() -> costSession.enroll(TUITION_FEE)); - } - - @Test - void 무료강의_최대수강인원제한_없음(){ - Session free = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31"); - free = free.openEnrollment(); - - for (int i = 0; i < 1000; i++) { - free = free.enroll(); - } - } - - @Test - void 무료강의_수강료_0원(){ - assertDoesNotThrow(() -> freeSession.enroll(0)); + assertThatThrownBy(() -> preparing.enroll(0)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("모집중인 강의만 수강신청이 가능합니다."); } - - //상태 @Test - void 강의상태_준비중_모집중_종료순으로_변화(){ - Session session = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31"); + void 준비중이_아닐때_모집시작하면_예외() { + Session session = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); + Session recruiting = session.openEnrollment(); - assertThat(session.getState()).isEqualTo(RecruitmentState.PREPARING); - - session = session.openEnrollment(); - assertThat(session.getState()).isEqualTo(RecruitmentState.RECRUITING); - - session = session.closeEnrollment(); - assertThat(session.getState()).isEqualTo(RecruitmentState.CLOSED); - } - - @Test - void 강의상태_모집중에만_수강신청가능(){ - assertDoesNotThrow(() -> costSession.enroll(TUITION_FEE)); + assertThatThrownBy(() -> recruiting.openEnrollment()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("준비중인 강의만 모집을 시작할 수 있습니다."); } @Test - void 강의상태_그외_수강신청하면_예외(){ - Session preparing = new Session(new Course("TDD", 1L), COVER_IMAGE, "2025-01-01", "2025-01-31"); + void 모집중이_아닐때_종료하면_예외() { + Session preparing = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); - assertThatThrownBy(() -> preparing.enroll(0)) + assertThatThrownBy(() -> preparing.closeEnrollment()) .isInstanceOf(IllegalStateException.class) - .hasMessage("모집중인 강의만 수강신청이 가능합니다."); + .hasMessage("모집중인 강의만 종료할 수 있습니다."); } - } \ No newline at end of file From fb2067e6d3d40130c03afbd32c691ff08b3a0a65 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Wed, 3 Dec 2025 07:59:05 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC,=20SessionType=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/courses/domain/Courses.java | 5 -- .../java/nextstep/courses/domain/Session.java | 69 ------------------- .../courses/domain/SessionEnrollment.java | 44 ------------ .../courses/domain/{ => course}/Course.java | 3 +- .../domain/{ => course}/CourseRepository.java | 2 +- .../domain/{ => image}/SessionCoverImage.java | 2 +- .../{ => image}/SessionImageCapacity.java | 2 +- .../{ => image}/SessionImageDimension.java | 2 +- .../{ => image}/SessionImageExtension.java | 3 +- .../courses/domain/session/Session.java | 59 ++++++++++++++++ .../domain/{ => session}/SessionPeriod.java | 2 +- .../SessionState.java} | 6 +- .../nextstep/courses/domain/session/Term.java | 37 ++++++++++ .../courses/domain/session/type/FreeType.java | 9 +++ .../courses/domain/session/type/PaidType.java | 55 +++++++++++++++ .../domain/session/type/SessionType.java | 5 ++ .../infrastructure/JdbcCourseRepository.java | 4 +- .../nextstep/courses/domain/CourseTest.java | 9 --- .../courses/domain/SessionEnrollmentTest.java | 52 -------------- .../courses/domain/course/CourseTest.java | 5 ++ .../{ => image}/SessionCoverImageTest.java | 2 +- .../{ => image}/SessionImageCapacityTest.java | 2 +- .../SessionImageDimensionTest.java | 2 +- .../{ => session}/SessionPeriodTest.java | 2 +- .../SessionStateTest.java} | 16 ++--- .../domain/{ => session}/SessionTest.java | 4 +- .../domain/session/type/FreeTypeTest.java | 34 +++++++++ .../domain/session/type/PaidTypeTest.java | 36 ++++++++++ .../infrastructure/CourseRepositoryTest.java | 4 +- 29 files changed, 270 insertions(+), 207 deletions(-) delete mode 100644 src/main/java/nextstep/courses/domain/Courses.java delete mode 100644 src/main/java/nextstep/courses/domain/Session.java delete mode 100644 src/main/java/nextstep/courses/domain/SessionEnrollment.java rename src/main/java/nextstep/courses/domain/{ => course}/Course.java (94%) rename src/main/java/nextstep/courses/domain/{ => course}/CourseRepository.java (71%) rename src/main/java/nextstep/courses/domain/{ => image}/SessionCoverImage.java (94%) rename src/main/java/nextstep/courses/domain/{ => image}/SessionImageCapacity.java (95%) rename src/main/java/nextstep/courses/domain/{ => image}/SessionImageDimension.java (96%) rename src/main/java/nextstep/courses/domain/{ => image}/SessionImageExtension.java (91%) create mode 100644 src/main/java/nextstep/courses/domain/session/Session.java rename src/main/java/nextstep/courses/domain/{ => session}/SessionPeriod.java (93%) rename src/main/java/nextstep/courses/domain/{RecruitmentState.java => session/SessionState.java} (81%) create mode 100644 src/main/java/nextstep/courses/domain/session/Term.java create mode 100644 src/main/java/nextstep/courses/domain/session/type/FreeType.java create mode 100644 src/main/java/nextstep/courses/domain/session/type/PaidType.java create mode 100644 src/main/java/nextstep/courses/domain/session/type/SessionType.java delete mode 100644 src/test/java/nextstep/courses/domain/CourseTest.java delete mode 100644 src/test/java/nextstep/courses/domain/SessionEnrollmentTest.java create mode 100644 src/test/java/nextstep/courses/domain/course/CourseTest.java rename src/test/java/nextstep/courses/domain/{ => image}/SessionCoverImageTest.java (96%) rename src/test/java/nextstep/courses/domain/{ => image}/SessionImageCapacityTest.java (97%) rename src/test/java/nextstep/courses/domain/{ => image}/SessionImageDimensionTest.java (97%) rename src/test/java/nextstep/courses/domain/{ => session}/SessionPeriodTest.java (96%) rename src/test/java/nextstep/courses/domain/{RecruitmentStateTest.java => session/SessionStateTest.java} (53%) rename src/test/java/nextstep/courses/domain/{ => session}/SessionTest.java (92%) create mode 100644 src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java create mode 100644 src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java diff --git a/src/main/java/nextstep/courses/domain/Courses.java b/src/main/java/nextstep/courses/domain/Courses.java deleted file mode 100644 index 20ddf04166..0000000000 --- a/src/main/java/nextstep/courses/domain/Courses.java +++ /dev/null @@ -1,5 +0,0 @@ -package nextstep.courses.domain; - -public class Courses { - -} diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java deleted file mode 100644 index a8781bdf6c..0000000000 --- a/src/main/java/nextstep/courses/domain/Session.java +++ /dev/null @@ -1,69 +0,0 @@ -package nextstep.courses.domain; - -public class Session extends BaseEntity { - private final Course course; - private final int term; - private final SessionCoverImage cover; - private final SessionPeriod period; - private final RecruitmentState state; - private final SessionEnrollment enrollment; - - public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay) { - this(null, course, term, cover, new SessionPeriod(startDay, endDay), SessionEnrollment.free()); - } - - public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay, int maxCapacity, int tuitionFee) { - this(null, course, term, cover, new SessionPeriod(startDay, endDay), SessionEnrollment.paid(maxCapacity, tuitionFee)); - } - - public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay, int maxCapacity, int tuitionFee, int studentCount) { - this(null, course, term, cover, new SessionPeriod(startDay, endDay), new SessionEnrollment(maxCapacity, tuitionFee, studentCount)); - } - - public Session(Long id, Course course, int term, SessionCoverImage cover, SessionPeriod period, SessionEnrollment enrollment) { - this(id, course, term, cover, period, enrollment, RecruitmentState.PREPARING); - } - - public Session(Long id, Course course, int term, SessionCoverImage cover, SessionPeriod period, SessionEnrollment enrollment, RecruitmentState state) { - super(id); - this.course = course; - this.term = term; - this.cover = cover; - this.period = period; - this.enrollment = enrollment; - this.state = state; - } - - public Session enroll() { - return enroll(0); - } - - public Session enroll(int payAmount) { - validateState(); - return new Session(getId(), course, term, cover, period, enrollment.enroll(payAmount), state); - } - - public Session openEnrollment() { - if (state != RecruitmentState.PREPARING) { - throw new IllegalStateException("준비중인 강의만 모집을 시작할 수 있습니다."); - } - return new Session(getId(), course, term, cover, period, enrollment, RecruitmentState.RECRUITING); - } - - public Session closeEnrollment() { - if (state != RecruitmentState.RECRUITING) { - throw new IllegalStateException("모집중인 강의만 종료할 수 있습니다."); - } - return new Session(getId(), course, term, cover, period, enrollment, RecruitmentState.CLOSED); - } - - public RecruitmentState getState() { - return state; - } - - private void validateState() { - if (!state.canEnroll()) { - throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); - } - } -} diff --git a/src/main/java/nextstep/courses/domain/SessionEnrollment.java b/src/main/java/nextstep/courses/domain/SessionEnrollment.java deleted file mode 100644 index 523ca4edff..0000000000 --- a/src/main/java/nextstep/courses/domain/SessionEnrollment.java +++ /dev/null @@ -1,44 +0,0 @@ -package nextstep.courses.domain; - -public class SessionEnrollment { - private final int maxCapacity; - private final int tuitionFee; - private final int studentCount; - - public static SessionEnrollment free() { - return new SessionEnrollment(Integer.MAX_VALUE, 0, 0); - } - - public static SessionEnrollment paid(int maxCapacity, int tuitionFee) { - return new SessionEnrollment(maxCapacity, tuitionFee, 0); - } - - public SessionEnrollment(int maxCapacity, int tuitionFee, int studentCount) { - validateCapacity(maxCapacity, studentCount); - this.maxCapacity = maxCapacity; - this.tuitionFee = tuitionFee; - this.studentCount = studentCount; - } - - public SessionEnrollment enroll(int payAmount) { - validateCapacity(maxCapacity, studentCount + 1); - validateTuitionFee(payAmount); - return new SessionEnrollment(maxCapacity, tuitionFee, studentCount + 1); - } - - public SessionEnrollment enroll() { - return enroll(0); - } - - private void validateTuitionFee(int payAmount) { - if (payAmount != tuitionFee) { - throw new IllegalArgumentException("수강료와 지불한 금액이 정확히 일치해야 합니다."); - } - } - - private void validateCapacity(int maxCapacity, int studentCount) { - if (maxCapacity < studentCount) { - throw new IllegalArgumentException("최대 수강 인원을 초과할 수 없습니다."); - } - } -} diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java similarity index 94% rename from src/main/java/nextstep/courses/domain/Course.java rename to src/main/java/nextstep/courses/domain/course/Course.java index 17eb2ba275..9e390236f0 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -1,7 +1,8 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course; import java.time.LocalDateTime; import java.util.Objects; +import nextstep.courses.domain.BaseEntity; public class Course extends BaseEntity { private String title; 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/domain/SessionCoverImage.java b/src/main/java/nextstep/courses/domain/image/SessionCoverImage.java similarity index 94% rename from src/main/java/nextstep/courses/domain/SessionCoverImage.java rename to src/main/java/nextstep/courses/domain/image/SessionCoverImage.java index dcda9177ed..814e160962 100644 --- a/src/main/java/nextstep/courses/domain/SessionCoverImage.java +++ b/src/main/java/nextstep/courses/domain/image/SessionCoverImage.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.image; public class SessionCoverImage { private final SessionImageDimension dimension; diff --git a/src/main/java/nextstep/courses/domain/SessionImageCapacity.java b/src/main/java/nextstep/courses/domain/image/SessionImageCapacity.java similarity index 95% rename from src/main/java/nextstep/courses/domain/SessionImageCapacity.java rename to src/main/java/nextstep/courses/domain/image/SessionImageCapacity.java index 7f86d6e9c0..d9477c35fc 100644 --- a/src/main/java/nextstep/courses/domain/SessionImageCapacity.java +++ b/src/main/java/nextstep/courses/domain/image/SessionImageCapacity.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.image; public class SessionImageCapacity { private static final long MAX_CAPACITY_BYTES = 1024 * 1024; // 1MB diff --git a/src/main/java/nextstep/courses/domain/SessionImageDimension.java b/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java similarity index 96% rename from src/main/java/nextstep/courses/domain/SessionImageDimension.java rename to src/main/java/nextstep/courses/domain/image/SessionImageDimension.java index 31b5326038..42a4db5faf 100644 --- a/src/main/java/nextstep/courses/domain/SessionImageDimension.java +++ b/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.image; public class SessionImageDimension { private int width; diff --git a/src/main/java/nextstep/courses/domain/SessionImageExtension.java b/src/main/java/nextstep/courses/domain/image/SessionImageExtension.java similarity index 91% rename from src/main/java/nextstep/courses/domain/SessionImageExtension.java rename to src/main/java/nextstep/courses/domain/image/SessionImageExtension.java index 7eb89aec8f..d1e7515d90 100644 --- a/src/main/java/nextstep/courses/domain/SessionImageExtension.java +++ b/src/main/java/nextstep/courses/domain/image/SessionImageExtension.java @@ -1,7 +1,6 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.image; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public enum SessionImageExtension { 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..7e38a3df62 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -0,0 +1,59 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.domain.BaseEntity; +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.session.type.FreeType; +import nextstep.courses.domain.session.type.SessionType; + +public class Session extends BaseEntity { + private final Course course; + private final Term term; + private final SessionCoverImage cover; + private final SessionPeriod period; + private final SessionState state; + private final SessionType type; + + public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay) { + this(null, course, new Term(term), cover, new SessionPeriod(startDay, endDay), new FreeType()); + } + + public Session(Long id, Course course, Term term, SessionCoverImage cover, SessionPeriod period, SessionType type) { + this(id, course, term, cover, period, type, SessionState.PREPARING); + } + + public Session(Long id, Course course, Term term, SessionCoverImage cover, SessionPeriod period, SessionType type, SessionState state) { + super(id); + this.course = course; + this.term = term; + this.cover = cover; + this.period = period; + this.type = type; + this.state = state; + } + + public Session enroll(int payAmount) { + validateState(); + return new Session(getId(), course, term, cover, period, type.enroll(payAmount), state); + } + + public Session openEnrollment() { + if (state != SessionState.PREPARING) { + throw new IllegalStateException("준비중인 강의만 모집을 시작할 수 있습니다."); + } + return new Session(getId(), course, term, cover, period, type, SessionState.RECRUITING); + } + + public Session closeEnrollment() { + if (state != SessionState.RECRUITING) { + throw new IllegalStateException("모집중인 강의만 종료할 수 있습니다."); + } + return new Session(getId(), course, term, cover, period, type, SessionState.CLOSED); + } + + private void validateState() { + if (!state.canEnroll()) { + throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/SessionPeriod.java b/src/main/java/nextstep/courses/domain/session/SessionPeriod.java similarity index 93% rename from src/main/java/nextstep/courses/domain/SessionPeriod.java rename to src/main/java/nextstep/courses/domain/session/SessionPeriod.java index 7300090d79..af5089b4c3 100644 --- a/src/main/java/nextstep/courses/domain/SessionPeriod.java +++ b/src/main/java/nextstep/courses/domain/session/SessionPeriod.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.session; import java.time.LocalDate; diff --git a/src/main/java/nextstep/courses/domain/RecruitmentState.java b/src/main/java/nextstep/courses/domain/session/SessionState.java similarity index 81% rename from src/main/java/nextstep/courses/domain/RecruitmentState.java rename to src/main/java/nextstep/courses/domain/session/SessionState.java index 9611fd9f20..45693b7859 100644 --- a/src/main/java/nextstep/courses/domain/RecruitmentState.java +++ b/src/main/java/nextstep/courses/domain/session/SessionState.java @@ -1,11 +1,11 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.session; -public enum RecruitmentState { +public enum SessionState { PREPARING, RECRUITING, CLOSED; - public RecruitmentState next() { + public SessionState next() { switch (this) { case PREPARING : return RECRUITING; diff --git a/src/main/java/nextstep/courses/domain/session/Term.java b/src/main/java/nextstep/courses/domain/session/Term.java new file mode 100644 index 0000000000..66523f35d7 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Term.java @@ -0,0 +1,37 @@ +package nextstep.courses.domain.session; + +import java.util.Objects; + +public class Term { + private static final int MIN_TERM = 1; + + private final int value; + + public Term(int value) { + validate(value); + this.value = value; + } + + private void validate(int value) { + if (value < MIN_TERM) { + throw new IllegalArgumentException("기수는 " + MIN_TERM + " 이상이어야 합니다."); + } + } + + public int getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Term term = (Term) o; + return value == term.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/FreeType.java b/src/main/java/nextstep/courses/domain/session/type/FreeType.java new file mode 100644 index 0000000000..57d59fa5c0 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/type/FreeType.java @@ -0,0 +1,9 @@ +package nextstep.courses.domain.session.type; + +public class FreeType implements SessionType { + + @Override + public SessionType enroll(int payAmount) { + return this; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/PaidType.java b/src/main/java/nextstep/courses/domain/session/type/PaidType.java new file mode 100644 index 0000000000..d8bf9d15b3 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/type/PaidType.java @@ -0,0 +1,55 @@ +package nextstep.courses.domain.session.type; + +import java.util.Objects; + +public class PaidType implements SessionType { + private final int maxCapacity; + private final int tuitionFee; + private final int studentCount; + + public PaidType(int maxCapacity, int tuitionFee) { + this(maxCapacity, tuitionFee, 0); + } + + public PaidType(int maxCapacity, int tuitionFee, int studentCount) { + validateCapacity(maxCapacity, studentCount); + this.maxCapacity = maxCapacity; + this.tuitionFee = tuitionFee; + this.studentCount = studentCount; + } + + @Override + public SessionType enroll(int payAmount) { + int newCount = studentCount + 1; + validateCapacity(maxCapacity, newCount); + validateTuitionFee(payAmount); + return new PaidType(maxCapacity, tuitionFee, newCount); + } + + private void validateCapacity(int maxCapacity, int studentCount) { + if (maxCapacity < studentCount) { + throw new IllegalArgumentException("최대 수강 인원을 초과할 수 없습니다."); + } + } + + private void validateTuitionFee(int payAmount) { + if (payAmount != tuitionFee) { + throw new IllegalArgumentException("수강료와 지불한 금액이 정확히 일치해야 합니다."); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PaidType that = (PaidType) o; + return maxCapacity == that.maxCapacity + && tuitionFee == that.tuitionFee + && studentCount == that.studentCount; + } + + @Override + public int hashCode() { + return Objects.hash(maxCapacity, tuitionFee, studentCount); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/SessionType.java b/src/main/java/nextstep/courses/domain/session/type/SessionType.java new file mode 100644 index 0000000000..a742b0ab5f --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/type/SessionType.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain.session.type; + +public interface SessionType { + SessionType enroll(int payAmount); +} \ No newline at end of file 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/domain/CourseTest.java b/src/test/java/nextstep/courses/domain/CourseTest.java deleted file mode 100644 index 65a3fa9fc9..0000000000 --- a/src/test/java/nextstep/courses/domain/CourseTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package nextstep.courses.domain; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -class CourseTest { - -} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionEnrollmentTest.java b/src/test/java/nextstep/courses/domain/SessionEnrollmentTest.java deleted file mode 100644 index b36bd121d4..0000000000 --- a/src/test/java/nextstep/courses/domain/SessionEnrollmentTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package nextstep.courses.domain; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.api.Test; - -class SessionEnrollmentTest { - - @Test - void 유료강의_최대수강인원_초과하면_예외() { - SessionEnrollment enrollment = new SessionEnrollment(300, 1000, 300); - - assertThatThrownBy(() -> enrollment.enroll(1000)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("최대 수강 인원을 초과할 수 없습니다."); - } - - @Test - void 유료강의_결제금액과_수강료가_동일하지_않으면_예외() { - SessionEnrollment enrollment = SessionEnrollment.paid(300, 1000); - - assertThatThrownBy(() -> enrollment.enroll(999)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); - } - - @Test - void 유료강의_최대수강인원이하_결제금액과수강료동일() { - SessionEnrollment enrollment = SessionEnrollment.paid(300, 1000); - - assertThatCode(() -> enrollment.enroll(1000)) - .doesNotThrowAnyException(); - } - - @Test - void 무료강의_최대수강인원제한_없음() { - SessionEnrollment enrollment = SessionEnrollment.free(); - - for (int i = 0; i < 1000; i++) { - enrollment = enrollment.enroll(); - } - } - - @Test - void 무료강의_수강료_0원() { - SessionEnrollment enrollment = SessionEnrollment.free(); - - assertThatCode(() -> enrollment.enroll(0)) - .doesNotThrowAnyException(); - } -} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/course/CourseTest.java b/src/test/java/nextstep/courses/domain/course/CourseTest.java new file mode 100644 index 0000000000..19f32d5394 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/CourseTest.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain.course; + +class CourseTest { + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionCoverImageTest.java b/src/test/java/nextstep/courses/domain/image/SessionCoverImageTest.java similarity index 96% rename from src/test/java/nextstep/courses/domain/SessionCoverImageTest.java rename to src/test/java/nextstep/courses/domain/image/SessionCoverImageTest.java index 7d77ae7957..795afcfeee 100644 --- a/src/test/java/nextstep/courses/domain/SessionCoverImageTest.java +++ b/src/test/java/nextstep/courses/domain/image/SessionCoverImageTest.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.image; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; diff --git a/src/test/java/nextstep/courses/domain/SessionImageCapacityTest.java b/src/test/java/nextstep/courses/domain/image/SessionImageCapacityTest.java similarity index 97% rename from src/test/java/nextstep/courses/domain/SessionImageCapacityTest.java rename to src/test/java/nextstep/courses/domain/image/SessionImageCapacityTest.java index 2479355a7d..b5be7d09c8 100644 --- a/src/test/java/nextstep/courses/domain/SessionImageCapacityTest.java +++ b/src/test/java/nextstep/courses/domain/image/SessionImageCapacityTest.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.image; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java b/src/test/java/nextstep/courses/domain/image/SessionImageDimensionTest.java similarity index 97% rename from src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java rename to src/test/java/nextstep/courses/domain/image/SessionImageDimensionTest.java index 9d2b3b284c..82328a6598 100644 --- a/src/test/java/nextstep/courses/domain/SessionImageDimensionTest.java +++ b/src/test/java/nextstep/courses/domain/image/SessionImageDimensionTest.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.image; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.*; diff --git a/src/test/java/nextstep/courses/domain/SessionPeriodTest.java b/src/test/java/nextstep/courses/domain/session/SessionPeriodTest.java similarity index 96% rename from src/test/java/nextstep/courses/domain/SessionPeriodTest.java rename to src/test/java/nextstep/courses/domain/session/SessionPeriodTest.java index 3dd2b6f8ab..9e27524fbc 100644 --- a/src/test/java/nextstep/courses/domain/SessionPeriodTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionPeriodTest.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.session; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatCode; diff --git a/src/test/java/nextstep/courses/domain/RecruitmentStateTest.java b/src/test/java/nextstep/courses/domain/session/SessionStateTest.java similarity index 53% rename from src/test/java/nextstep/courses/domain/RecruitmentStateTest.java rename to src/test/java/nextstep/courses/domain/session/SessionStateTest.java index b9d0db5ace..72d6417183 100644 --- a/src/test/java/nextstep/courses/domain/RecruitmentStateTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionStateTest.java @@ -1,33 +1,33 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.session; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; -class RecruitmentStateTest { +class SessionStateTest { @Test void 준비중_다음_상태는_모집중() { - assertThat(RecruitmentState.PREPARING.next()).isEqualTo(RecruitmentState.RECRUITING); + assertThat(SessionState.PREPARING.next()).isEqualTo(SessionState.RECRUITING); } @Test void 모집중_다음_상태는_종료() { - assertThat(RecruitmentState.RECRUITING.next()).isEqualTo(RecruitmentState.CLOSED); + assertThat(SessionState.RECRUITING.next()).isEqualTo(SessionState.CLOSED); } @Test void 종료_상태에서_다음_상태로_변경하면_예외() { - assertThatThrownBy(() -> RecruitmentState.CLOSED.next()) + assertThatThrownBy(() -> SessionState.CLOSED.next()) .isInstanceOf(IllegalStateException.class) .hasMessage("종료된 강의는 상태를 변경할 수 없습니다."); } @Test void 모집중일때만_수강신청_가능() { - assertThat(RecruitmentState.PREPARING.canEnroll()).isFalse(); - assertThat(RecruitmentState.RECRUITING.canEnroll()).isTrue(); - assertThat(RecruitmentState.CLOSED.canEnroll()).isFalse(); + assertThat(SessionState.PREPARING.canEnroll()).isFalse(); + assertThat(SessionState.RECRUITING.canEnroll()).isTrue(); + assertThat(SessionState.CLOSED.canEnroll()).isFalse(); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java similarity index 92% rename from src/test/java/nextstep/courses/domain/SessionTest.java rename to src/test/java/nextstep/courses/domain/session/SessionTest.java index ab45a82146..82f7e5d3eb 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -1,8 +1,10 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.session; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.image.SessionCoverImage; import org.junit.jupiter.api.Test; class SessionTest { diff --git a/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java b/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java new file mode 100644 index 0000000000..bc01653de8 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java @@ -0,0 +1,34 @@ +package nextstep.courses.domain.session.type; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import nextstep.courses.domain.session.type.FreeType; +import nextstep.courses.domain.session.type.SessionType; +import org.junit.jupiter.api.Test; + +class FreeTypeTest { + + @Test + void 수강인원_제한없음() { + SessionType type = new FreeType(); + + for (int i = 0; i < 1000; i++) { + type = type.enroll(0); + } + + assertThat(type).isInstanceOf(FreeType.class); + } + + @Test + void 금액_상관없이_수강가능() { + SessionType type = new FreeType(); + + assertThatCode(() -> type.enroll(0)) + .doesNotThrowAnyException(); + assertThatCode(() -> type.enroll(100)) + .doesNotThrowAnyException(); + assertThatCode(() -> type.enroll(999999)) + .doesNotThrowAnyException(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java b/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java new file mode 100644 index 0000000000..da71011700 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java @@ -0,0 +1,36 @@ +package nextstep.courses.domain.session.type; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.domain.session.type.PaidType; +import org.junit.jupiter.api.Test; + +class PaidTypeTest { + + @Test + void 최대수강인원_초과하면_예외() { + PaidType type = new PaidType(300, 1000, 300); + + assertThatThrownBy(() -> type.enroll(1000)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("최대 수강 인원을 초과할 수 없습니다."); + } + + @Test + void 결제금액과_수강료가_동일하지_않으면_예외() { + PaidType type = new PaidType(300, 1000); + + assertThatThrownBy(() -> type.enroll(999)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); + } + + @Test + void 최대수강인원이하_결제금액과수강료동일하면_성공() { + PaidType type = new PaidType(300, 1000); + + assertThatCode(() -> type.enroll(1000)) + .doesNotThrowAnyException(); + } +} \ No newline at end of file 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 0e844d2804987676c23a606b174e2dad4d35b592 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Wed, 3 Dec 2025 08:32:26 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20Registration=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 --- .../domain/registration/Registration.java | 23 +++++++++++++ .../domain/registration/Registrations.java | 33 +++++++++++++++++++ .../courses/domain/session/Session.java | 18 ++++------ .../courses/domain/session/SessionState.java | 20 ++++++----- .../nextstep/payments/domain/Payment.java | 12 +++++++ .../domain/session/SessionStateTest.java | 29 ++++++++++++---- .../courses/domain/session/SessionTest.java | 8 ++--- 7 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/registration/Registration.java create mode 100644 src/main/java/nextstep/courses/domain/registration/Registrations.java diff --git a/src/main/java/nextstep/courses/domain/registration/Registration.java b/src/main/java/nextstep/courses/domain/registration/Registration.java new file mode 100644 index 0000000000..b9e25cda08 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/registration/Registration.java @@ -0,0 +1,23 @@ +package nextstep.courses.domain.registration; + +import java.time.LocalDateTime; + +public class Registration { + private final Long sessionId; + private final Long studentId; + private final LocalDateTime enrolledAt; + + public Registration(Long sessionId, Long studentId) { + this(sessionId, studentId, LocalDateTime.now()); + } + + public Registration(Long sessionId, Long studentId, LocalDateTime enrolledAt) { + this.sessionId = sessionId; + this.studentId = studentId; + this.enrolledAt = enrolledAt; + } + public boolean contains(Long studentId) { + return this.studentId == studentId; + } + +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/registration/Registrations.java b/src/main/java/nextstep/courses/domain/registration/Registrations.java new file mode 100644 index 0000000000..d85102f1ab --- /dev/null +++ b/src/main/java/nextstep/courses/domain/registration/Registrations.java @@ -0,0 +1,33 @@ +package nextstep.courses.domain.registration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Registrations { + private final List registrations; + + public Registrations() { + this(new ArrayList<>()); + } + + public Registrations(List registrations) { + this.registrations = registrations; + } + + public Registrations add(Registration registration) { + List newList = new ArrayList<>(registrations); + newList.add(registration); + return new Registrations(newList); + } + + public int count() { + return registrations.size(); + } + + public boolean contains(Long studentId) { + return registrations.stream() + .anyMatch(r -> r.contains(studentId)); + } + +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index 7e38a3df62..8ab5246776 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -33,25 +33,19 @@ public Session(Long id, Course course, Term term, SessionCoverImage cover, Sessi } public Session enroll(int payAmount) { - validateState(); + validateEnrollState(); return new Session(getId(), course, term, cover, period, type.enroll(payAmount), state); } - public Session openEnrollment() { - if (state != SessionState.PREPARING) { - throw new IllegalStateException("준비중인 강의만 모집을 시작할 수 있습니다."); - } - return new Session(getId(), course, term, cover, period, type, SessionState.RECRUITING); + public Session open() { + return new Session(getId(), course, term, cover, period, type, state.open()); } - public Session closeEnrollment() { - if (state != SessionState.RECRUITING) { - throw new IllegalStateException("모집중인 강의만 종료할 수 있습니다."); - } - return new Session(getId(), course, term, cover, period, type, SessionState.CLOSED); + public Session close() { + return new Session(getId(), course, term, cover, period, type, state.close()); } - private void validateState() { + private void validateEnrollState() { if (!state.canEnroll()) { throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); } diff --git a/src/main/java/nextstep/courses/domain/session/SessionState.java b/src/main/java/nextstep/courses/domain/session/SessionState.java index 45693b7859..a0cab17411 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionState.java +++ b/src/main/java/nextstep/courses/domain/session/SessionState.java @@ -5,16 +5,18 @@ public enum SessionState { RECRUITING, CLOSED; - public SessionState next() { - switch (this) { - case PREPARING : - return RECRUITING; - case RECRUITING : - return CLOSED; - case CLOSED : - throw new IllegalStateException("종료된 강의는 상태를 변경할 수 없습니다."); + public SessionState open() { + if (this != PREPARING) { + throw new IllegalStateException("준비중인 강의만 모집을 시작할 수 있습니다."); } - throw new IllegalStateException("잘못된 강의 상태 입니다."); + return RECRUITING; + } + + public SessionState close() { + if (this != RECRUITING) { + throw new IllegalStateException("모집중인 강의만 종료할 수 있습니다."); + } + return CLOSED; } public boolean canEnroll() { diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 57d833f851..8dcd2fa39a 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -26,4 +26,16 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.amount = amount; this.createdAt = LocalDateTime.now(); } + + public Long getSessionId() { + return sessionId; + } + + public Long getNsUserId() { + return nsUserId; + } + + public Long getAmount() { + return amount; + } } diff --git a/src/test/java/nextstep/courses/domain/session/SessionStateTest.java b/src/test/java/nextstep/courses/domain/session/SessionStateTest.java index 72d6417183..bbd4439a3a 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionStateTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionStateTest.java @@ -8,20 +8,35 @@ class SessionStateTest { @Test - void 준비중_다음_상태는_모집중() { - assertThat(SessionState.PREPARING.next()).isEqualTo(SessionState.RECRUITING); + void 준비중에서_모집시작_가능() { + assertThat(SessionState.PREPARING.open()).isEqualTo(SessionState.RECRUITING); } @Test - void 모집중_다음_상태는_종료() { - assertThat(SessionState.RECRUITING.next()).isEqualTo(SessionState.CLOSED); + void 모집중에서_종료_가능() { + assertThat(SessionState.RECRUITING.close()).isEqualTo(SessionState.CLOSED); } @Test - void 종료_상태에서_다음_상태로_변경하면_예외() { - assertThatThrownBy(() -> SessionState.CLOSED.next()) + void 준비중이_아닐때_모집시작하면_예외() { + assertThatThrownBy(() -> SessionState.RECRUITING.open()) .isInstanceOf(IllegalStateException.class) - .hasMessage("종료된 강의는 상태를 변경할 수 없습니다."); + .hasMessage("준비중인 강의만 모집을 시작할 수 있습니다."); + + assertThatThrownBy(() -> SessionState.CLOSED.open()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("준비중인 강의만 모집을 시작할 수 있습니다."); + } + + @Test + void 모집중이_아닐때_종료하면_예외() { + assertThatThrownBy(() -> SessionState.PREPARING.close()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("모집중인 강의만 종료할 수 있습니다."); + + assertThatThrownBy(() -> SessionState.CLOSED.close()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("모집중인 강의만 종료할 수 있습니다."); } @Test diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index 82f7e5d3eb..42182730c6 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -14,7 +14,7 @@ class SessionTest { @Test void 모집중일때_수강신청_가능() { Session session = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); - Session recruiting = session.openEnrollment(); + Session recruiting = session.open(); assertDoesNotThrow(() -> recruiting.enroll(0)); } @@ -31,9 +31,9 @@ class SessionTest { @Test void 준비중이_아닐때_모집시작하면_예외() { Session session = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); - Session recruiting = session.openEnrollment(); + Session recruiting = session.open(); - assertThatThrownBy(() -> recruiting.openEnrollment()) + assertThatThrownBy(() -> recruiting.open()) .isInstanceOf(IllegalStateException.class) .hasMessage("준비중인 강의만 모집을 시작할 수 있습니다."); } @@ -42,7 +42,7 @@ class SessionTest { void 모집중이_아닐때_종료하면_예외() { Session preparing = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); - assertThatThrownBy(() -> preparing.closeEnrollment()) + assertThatThrownBy(() -> preparing.close()) .isInstanceOf(IllegalStateException.class) .hasMessage("모집중인 강의만 종료할 수 있습니다."); } From 0786c0c35129124ffc5fa1ff5c4f7492161682f5 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Wed, 3 Dec 2025 20:29:09 +0900 Subject: [PATCH 5/6] =?UTF-8?q?Enrollment=20=EB=B6=84=EB=A6=AC,=20?= =?UTF-8?q?=EA=B0=80=EB=B3=80=20=EA=B0=9D=EC=B2=B4=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/image/SessionImageDimension.java | 6 +- .../courses/domain/session/Enrollment.java | 41 ++++++++++++ .../courses/domain/session/Session.java | 35 +++------- .../domain/session/SessionBuilder.java | 65 +++++++++++++++++++ .../courses/domain/session/SessionTest.java | 23 +++---- 5 files changed, 129 insertions(+), 41 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/Enrollment.java create mode 100644 src/test/java/nextstep/courses/domain/session/SessionBuilder.java diff --git a/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java b/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java index 42a4db5faf..f033bb6351 100644 --- a/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java +++ b/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java @@ -1,12 +1,14 @@ package nextstep.courses.domain.image; public class SessionImageDimension { - private int width; - private int height; private static final int MIN_WIDTH = 300; private static final int MIN_HEIGHT = 200; private static final int RATIO_W = 3; private static final int RATIO_H = 2; + + private int width; + private int height; + public SessionImageDimension(int width, int height) { validateMinLength(width, height); validateRatio(width, height); 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..70348fe8c2 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Enrollment.java @@ -0,0 +1,41 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.domain.session.type.FreeType; +import nextstep.courses.domain.session.type.SessionType; + +public class Enrollment { + private SessionState state; + private SessionType type; + + public Enrollment() { + this(SessionState.PREPARING, new FreeType()); + } + + public Enrollment(SessionType type) { + this(SessionState.PREPARING, type); + } + + public Enrollment(SessionState state, SessionType type) { + this.state = state; + this.type = type; + } + + public void enroll(int payAmount) { + validateState(); + this.type = type.enroll(payAmount); + } + + public void open() { + this.state = state.open(); + } + + public void close() { + this.state = state.close(); + } + + private void validateState() { + if (!state.canEnroll()) { + throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); + } + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index 8ab5246776..fbf847fc86 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -3,51 +3,36 @@ import nextstep.courses.domain.BaseEntity; import nextstep.courses.domain.course.Course; import nextstep.courses.domain.image.SessionCoverImage; -import nextstep.courses.domain.session.type.FreeType; -import nextstep.courses.domain.session.type.SessionType; public class Session extends BaseEntity { private final Course course; private final Term term; private final SessionCoverImage cover; private final SessionPeriod period; - private final SessionState state; - private final SessionType type; + private final Enrollment enrollment; public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay) { - this(null, course, new Term(term), cover, new SessionPeriod(startDay, endDay), new FreeType()); + this(null, course, new Term(term), cover, new SessionPeriod(startDay, endDay), new Enrollment()); } - public Session(Long id, Course course, Term term, SessionCoverImage cover, SessionPeriod period, SessionType type) { - this(id, course, term, cover, period, type, SessionState.PREPARING); - } - - public Session(Long id, Course course, Term term, SessionCoverImage cover, SessionPeriod period, SessionType type, SessionState state) { + public Session(Long id, Course course, Term term, SessionCoverImage cover, SessionPeriod period, Enrollment enrollment) { super(id); this.course = course; this.term = term; this.cover = cover; this.period = period; - this.type = type; - this.state = state; - } - - public Session enroll(int payAmount) { - validateEnrollState(); - return new Session(getId(), course, term, cover, period, type.enroll(payAmount), state); + this.enrollment = enrollment; } - public Session open() { - return new Session(getId(), course, term, cover, period, type, state.open()); + public void enroll(int payAmount) { + enrollment.enroll(payAmount); } - public Session close() { - return new Session(getId(), course, term, cover, period, type, state.close()); + public void open() { + enrollment.open(); } - private void validateEnrollState() { - if (!state.canEnroll()) { - throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); - } + public void close() { + enrollment.close(); } } diff --git a/src/test/java/nextstep/courses/domain/session/SessionBuilder.java b/src/test/java/nextstep/courses/domain/session/SessionBuilder.java new file mode 100644 index 0000000000..76061345e8 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionBuilder.java @@ -0,0 +1,65 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.session.type.FreeType; +import nextstep.courses.domain.session.type.SessionType; + +public class SessionBuilder { + private Long id = null; + private Course course = new Course("TDD", 1L); + private Term term = new Term(1); + private SessionCoverImage cover = new SessionCoverImage(300, 200, "png", 1024 * 500); + private SessionPeriod period = new SessionPeriod("2025-01-01", "2025-01-31"); + private SessionState state = SessionState.PREPARING; + private SessionType type = new FreeType(); + + public static SessionBuilder aSession() { + return new SessionBuilder(); + } + + public SessionBuilder withId(Long id) { + this.id = id; + return this; + } + + public SessionBuilder withCourse(Course course) { + this.course = course; + return this; + } + + public SessionBuilder withTerm(int term) { + this.term = new Term(term); + return this; + } + + public SessionBuilder withCover(SessionCoverImage cover) { + this.cover = cover; + return this; + } + + public SessionBuilder withPeriod(String startDay, String endDay) { + this.period = new SessionPeriod(startDay, endDay); + return this; + } + + public SessionBuilder withState(SessionState state) { + this.state = state; + return this; + } + + public SessionBuilder withType(SessionType type) { + this.type = type; + return this; + } + + public SessionBuilder recruiting() { + this.state = SessionState.RECRUITING; + return this; + } + + public Session build() { + Enrollment enrollment = new Enrollment(state, type); + return new Session(id, course, term, cover, period, enrollment); + } +} \ 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 42182730c6..21513ea3c1 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -1,48 +1,43 @@ package nextstep.courses.domain.session; +import static nextstep.courses.domain.session.SessionBuilder.aSession; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import nextstep.courses.domain.course.Course; -import nextstep.courses.domain.image.SessionCoverImage; import org.junit.jupiter.api.Test; class SessionTest { - private static final SessionCoverImage COVER_IMAGE = new SessionCoverImage(300, 200, "png", 1024 * 500); - private static final int TERM = 19; @Test void 모집중일때_수강신청_가능() { - Session session = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); - Session recruiting = session.open(); + Session session = aSession().recruiting().build(); - assertDoesNotThrow(() -> recruiting.enroll(0)); + assertDoesNotThrow(() -> session.enroll(0)); } @Test void 모집중이_아닐때_수강신청하면_예외() { - Session preparing = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); + Session session = aSession().build(); - assertThatThrownBy(() -> preparing.enroll(0)) + assertThatThrownBy(() -> session.enroll(0)) .isInstanceOf(IllegalStateException.class) .hasMessage("모집중인 강의만 수강신청이 가능합니다."); } @Test void 준비중이_아닐때_모집시작하면_예외() { - Session session = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); - Session recruiting = session.open(); + Session session = aSession().recruiting().build(); - assertThatThrownBy(() -> recruiting.open()) + assertThatThrownBy(() -> session.open()) .isInstanceOf(IllegalStateException.class) .hasMessage("준비중인 강의만 모집을 시작할 수 있습니다."); } @Test void 모집중이_아닐때_종료하면_예외() { - Session preparing = new Session(new Course("TDD", 1L), TERM, COVER_IMAGE, "2025-01-01", "2025-01-31"); + Session session = aSession().build(); - assertThatThrownBy(() -> preparing.close()) + assertThatThrownBy(() -> session.close()) .isInstanceOf(IllegalStateException.class) .hasMessage("모집중인 강의만 종료할 수 있습니다."); } From a360bb31d2840556ff47676a445cfca097b82f90 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Wed, 3 Dec 2025 20:35:44 +0900 Subject: [PATCH 6/6] =?UTF-8?q?payAmount=20long=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=9C=BC=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 --- .../courses/domain/session/Enrollment.java | 2 +- .../nextstep/courses/domain/session/Session.java | 2 +- .../courses/domain/session/type/FreeType.java | 2 +- .../courses/domain/session/type/PaidType.java | 10 +++++----- .../courses/domain/session/type/SessionType.java | 2 +- .../courses/service/SessionEnrollService.java | 16 ++++++++++++++++ 6 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 src/main/java/nextstep/courses/service/SessionEnrollService.java diff --git a/src/main/java/nextstep/courses/domain/session/Enrollment.java b/src/main/java/nextstep/courses/domain/session/Enrollment.java index 70348fe8c2..8349d21eea 100644 --- a/src/main/java/nextstep/courses/domain/session/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/session/Enrollment.java @@ -20,7 +20,7 @@ public Enrollment(SessionState state, SessionType type) { this.type = type; } - public void enroll(int payAmount) { + public void enroll(long payAmount) { validateState(); this.type = type.enroll(payAmount); } diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index fbf847fc86..a506fbbe29 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -24,7 +24,7 @@ public Session(Long id, Course course, Term term, SessionCoverImage cover, Sessi this.enrollment = enrollment; } - public void enroll(int payAmount) { + public void enroll(long payAmount) { enrollment.enroll(payAmount); } diff --git a/src/main/java/nextstep/courses/domain/session/type/FreeType.java b/src/main/java/nextstep/courses/domain/session/type/FreeType.java index 57d59fa5c0..b6b425d1af 100644 --- a/src/main/java/nextstep/courses/domain/session/type/FreeType.java +++ b/src/main/java/nextstep/courses/domain/session/type/FreeType.java @@ -3,7 +3,7 @@ public class FreeType implements SessionType { @Override - public SessionType enroll(int payAmount) { + public SessionType enroll(long payAmount) { return this; } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/PaidType.java b/src/main/java/nextstep/courses/domain/session/type/PaidType.java index d8bf9d15b3..dc680bca29 100644 --- a/src/main/java/nextstep/courses/domain/session/type/PaidType.java +++ b/src/main/java/nextstep/courses/domain/session/type/PaidType.java @@ -4,14 +4,14 @@ public class PaidType implements SessionType { private final int maxCapacity; - private final int tuitionFee; + private final long tuitionFee; private final int studentCount; - public PaidType(int maxCapacity, int tuitionFee) { + public PaidType(int maxCapacity, long tuitionFee) { this(maxCapacity, tuitionFee, 0); } - public PaidType(int maxCapacity, int tuitionFee, int studentCount) { + public PaidType(int maxCapacity, long tuitionFee, int studentCount) { validateCapacity(maxCapacity, studentCount); this.maxCapacity = maxCapacity; this.tuitionFee = tuitionFee; @@ -19,7 +19,7 @@ public PaidType(int maxCapacity, int tuitionFee, int studentCount) { } @Override - public SessionType enroll(int payAmount) { + public SessionType enroll(long payAmount) { int newCount = studentCount + 1; validateCapacity(maxCapacity, newCount); validateTuitionFee(payAmount); @@ -32,7 +32,7 @@ private void validateCapacity(int maxCapacity, int studentCount) { } } - private void validateTuitionFee(int payAmount) { + private void validateTuitionFee(long payAmount) { if (payAmount != tuitionFee) { throw new IllegalArgumentException("수강료와 지불한 금액이 정확히 일치해야 합니다."); } diff --git a/src/main/java/nextstep/courses/domain/session/type/SessionType.java b/src/main/java/nextstep/courses/domain/session/type/SessionType.java index a742b0ab5f..3998c88427 100644 --- a/src/main/java/nextstep/courses/domain/session/type/SessionType.java +++ b/src/main/java/nextstep/courses/domain/session/type/SessionType.java @@ -1,5 +1,5 @@ package nextstep.courses.domain.session.type; public interface SessionType { - SessionType enroll(int payAmount); + SessionType enroll(long payAmount); } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/service/SessionEnrollService.java b/src/main/java/nextstep/courses/service/SessionEnrollService.java new file mode 100644 index 0000000000..5b12a385a7 --- /dev/null +++ b/src/main/java/nextstep/courses/service/SessionEnrollService.java @@ -0,0 +1,16 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.domain.session.Session; +import nextstep.payments.domain.Payment; +import org.springframework.stereotype.Service; + +@Service +public class SessionEnrollService { + private CourseRepository courseRepository; + + public void enroll(Session session, Payment payment){ + session.enroll(payment.getAmount()); + } + +}