diff --git a/src/main/java/nextstep/courses/domain/Capacity.java b/src/main/java/nextstep/courses/domain/Capacity.java new file mode 100644 index 0000000000..8153d51d63 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Capacity.java @@ -0,0 +1,18 @@ +package nextstep.courses.domain; + +public class Capacity { + + private int max; + private int current; + + public Capacity(int max, int current) { + this.max = max; + this.current = current; + } + + public void validateAvailable() { + if (current > max) { + throw new IllegalStateException(); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/CoverImage.java b/src/main/java/nextstep/courses/domain/CoverImage.java new file mode 100644 index 0000000000..6c677c07e7 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/CoverImage.java @@ -0,0 +1,17 @@ +package nextstep.courses.domain; + +public class CoverImage { + private ImageSize imageSize; + private ImageType imageType; + private ImageDimension imageDimension; + + public CoverImage(int size, ImageType imageType, int width, int height) { + this(new ImageSize(size), imageType, new ImageDimension(width, height)); + } + + public CoverImage(ImageSize imageSize, ImageType imageType, ImageDimension imageDimension) { + this.imageSize = imageSize; + this.imageType = imageType; + this.imageDimension = imageDimension; + } +} diff --git a/src/main/java/nextstep/courses/domain/EnrollmentPolicy.java b/src/main/java/nextstep/courses/domain/EnrollmentPolicy.java new file mode 100644 index 0000000000..ee8b0773e7 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/EnrollmentPolicy.java @@ -0,0 +1,7 @@ +package nextstep.courses.domain; + +import nextstep.payments.domain.Payment; + +public interface EnrollmentPolicy { + void validateEnrollment(Payment payment); +} diff --git a/src/main/java/nextstep/courses/domain/FreeEnrollmentPolicy.java b/src/main/java/nextstep/courses/domain/FreeEnrollmentPolicy.java new file mode 100644 index 0000000000..21720b9ec7 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/FreeEnrollmentPolicy.java @@ -0,0 +1,10 @@ +package nextstep.courses.domain; + +import nextstep.payments.domain.Payment; + +public class FreeEnrollmentPolicy implements EnrollmentPolicy{ + @Override + public void validateEnrollment(Payment payment) { + // 무료강의는 검증하지 않는다 + } +} diff --git a/src/main/java/nextstep/courses/domain/ImageDimension.java b/src/main/java/nextstep/courses/domain/ImageDimension.java new file mode 100644 index 0000000000..806de0874b --- /dev/null +++ b/src/main/java/nextstep/courses/domain/ImageDimension.java @@ -0,0 +1,30 @@ +package nextstep.courses.domain; + +public class ImageDimension { + + public static final int HEIGHT_RATIO = 2; + public static final int WIDTH_RATIO = 3; + public static final int MIN_WIDTH = 300; + public static final int MIN_HEIGHT = 200; + private int width; + private int height; + + public ImageDimension(int width, int height) { + validateMinimumSize(width, height); + validateRatio(width, height); + this.width = width; + this.height = height; + } + + private static void validateMinimumSize(int width, int height) { + if (width < MIN_WIDTH || height < MIN_HEIGHT) { + throw new IllegalArgumentException(); + } + } + + private static void validateRatio(int width, int height) { + if (!(HEIGHT_RATIO * width == WIDTH_RATIO * height)) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/ImageSize.java b/src/main/java/nextstep/courses/domain/ImageSize.java new file mode 100644 index 0000000000..b03838043b --- /dev/null +++ b/src/main/java/nextstep/courses/domain/ImageSize.java @@ -0,0 +1,29 @@ +package nextstep.courses.domain; + +import java.util.Objects; + +public class ImageSize { + + public static final int MAX_SIZE = 1_048_576; + private int imageSize; + + public ImageSize(int imageSize) { + if (imageSize > MAX_SIZE) { + throw new IllegalArgumentException(); + } + this.imageSize = imageSize; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + ImageSize imageSize1 = (ImageSize) object; + return imageSize == imageSize1.imageSize; + } + + @Override + public int hashCode() { + return Objects.hashCode(imageSize); + } +} diff --git a/src/main/java/nextstep/courses/domain/ImageType.java b/src/main/java/nextstep/courses/domain/ImageType.java new file mode 100644 index 0000000000..a8420c326e --- /dev/null +++ b/src/main/java/nextstep/courses/domain/ImageType.java @@ -0,0 +1,30 @@ +package nextstep.courses.domain; + +import java.util.Arrays; + +public enum ImageType { + + GIF("gif"), + JPG("jpg", "jpeg"), + PNG("png"), + SVG("svg"); + + private final String[] extensions; + + ImageType(String... extensions) { + this.extensions = extensions; + } + + private boolean matches(String extension) { + return Arrays.stream(extensions) + .anyMatch(ext -> ext.equalsIgnoreCase(extension)); + } + + public static ImageType from(String extension) { + return Arrays.stream(values()) + .filter(type -> type.matches(extension)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException()); + } + +} diff --git a/src/main/java/nextstep/courses/domain/Money.java b/src/main/java/nextstep/courses/domain/Money.java new file mode 100644 index 0000000000..bd571b378f --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Money.java @@ -0,0 +1,28 @@ +package nextstep.courses.domain; + +import java.util.Objects; + +public class Money { + private final long amount; + + public Money(long amount) { + this.amount = amount; + } + + public boolean isEqualTo(Money other) { + return this.equals(other); + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + Money money = (Money) object; + return amount == money.amount; + } + + @Override + public int hashCode() { + return Objects.hashCode(amount); + } +} diff --git a/src/main/java/nextstep/courses/domain/PaidEnrollmentPolicy.java b/src/main/java/nextstep/courses/domain/PaidEnrollmentPolicy.java new file mode 100644 index 0000000000..f471b5a3fd --- /dev/null +++ b/src/main/java/nextstep/courses/domain/PaidEnrollmentPolicy.java @@ -0,0 +1,26 @@ +package nextstep.courses.domain; + +import nextstep.payments.domain.Payment; + +public class PaidEnrollmentPolicy implements EnrollmentPolicy { + + private final Money money; + private final Capacity capacity; + + public PaidEnrollmentPolicy(Money money, Capacity capacity) { + this.money = money; + this.capacity = capacity; + } + + @Override + public void validateEnrollment(Payment payment) { + capacity.validateAvailable(); + validatePayment(payment); + } + + private void validatePayment(Payment payment) { + if (!payment.isSameAmount(money)) { + throw new IllegalArgumentException(); + } + } +} 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..584e59ec69 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -0,0 +1,38 @@ +package nextstep.courses.domain; + +import nextstep.payments.domain.Payment; + +import java.time.LocalDateTime; + +public class Session { + private final long id; + private final SessionDuration sessionDuration; + private final CoverImage coverImage; + private final EnrollmentPolicy enrollmentPolicy; + private final SessionState sessionState; + + public Session(long id + , LocalDateTime startDate + , LocalDateTime endDate + , int size + , ImageType imageType + , int width + , int height + , EnrollmentPolicy enrollmentPolicy + , SessionState sessionState) { + this(id, new SessionDuration(startDate, endDate), new CoverImage(size, imageType, width, height) + , enrollmentPolicy, sessionState); + } + + public Session(long id, SessionDuration sessionDuration, CoverImage coverImage + , EnrollmentPolicy enrollmentPolicy, SessionState sessionState) { + this.id = id; + this.sessionDuration = sessionDuration; + this.coverImage = coverImage; + this.enrollmentPolicy = enrollmentPolicy; + this.sessionState = sessionState; + } + + public void enroll() { + } +} diff --git a/src/main/java/nextstep/courses/domain/SessionDuration.java b/src/main/java/nextstep/courses/domain/SessionDuration.java new file mode 100644 index 0000000000..54d7714a2c --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionDuration.java @@ -0,0 +1,20 @@ +package nextstep.courses.domain; + +import java.time.LocalDateTime; + +public class SessionDuration { + private LocalDateTime startDate; + private LocalDateTime endDate; + + public SessionDuration(LocalDateTime startDate, LocalDateTime endDate) { + validateDate(startDate, endDate); + this.startDate = startDate; + this.endDate = endDate; + } + + private void validateDate(LocalDateTime startDate, LocalDateTime endDate) { + if (!startDate.isBefore(endDate)) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/SessionState.java b/src/main/java/nextstep/courses/domain/SessionState.java new file mode 100644 index 0000000000..e087309d58 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionState.java @@ -0,0 +1,13 @@ +package nextstep.courses.domain; + +public enum SessionState { + READY, + OPEN, + CLOSED; + + public void validateEnroll() { + if (this != OPEN) { + throw new IllegalStateException(); + } + } +} diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 57d833f851..6d4c2b720c 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -1,5 +1,7 @@ package nextstep.payments.domain; +import nextstep.courses.domain.Money; + import java.time.LocalDateTime; public class Payment { @@ -12,7 +14,7 @@ public class Payment { private Long nsUserId; // 결제 금액 - private Long amount; + private Money amount; private LocalDateTime createdAt; @@ -23,7 +25,11 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.id = id; this.sessionId = sessionId; this.nsUserId = nsUserId; - this.amount = amount; + this.amount = new Money(amount); this.createdAt = LocalDateTime.now(); } + + public boolean isSameAmount(Money money) { + return amount.isEqualTo(money); + } } diff --git a/src/test/java/nextstep/courses/domain/CapacityTest.java b/src/test/java/nextstep/courses/domain/CapacityTest.java new file mode 100644 index 0000000000..38d3806c08 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/CapacityTest.java @@ -0,0 +1,17 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CapacityTest { + + @Test + @DisplayName("최대 인원을 넘어서 수강신청이 들어오면 Exceptionn") + void max() { + Capacity capacity = new Capacity(300, 301); + assertThatThrownBy(() -> capacity.validateAvailable()) + .isInstanceOf(IllegalStateException.class); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/ImageDimensionTest.java b/src/test/java/nextstep/courses/domain/ImageDimensionTest.java new file mode 100644 index 0000000000..1f3805d39a --- /dev/null +++ b/src/test/java/nextstep/courses/domain/ImageDimensionTest.java @@ -0,0 +1,41 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ImageDimensionTest { + + @Test + @DisplayName("width가 300px 미만이면 예외가 발생한다") + void create_width_exception() { + assertThatThrownBy(() -> new ImageDimension(299, 200)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("height가 200px 미만이면 예외가 발생한다") + void create_height_exception() { + assertThatThrownBy(() -> new ImageDimension(300, 199)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("width와 height는 3:2 비율이 아니면 예외가 발생한다") + void create_width_height_exception() { + assertThatThrownBy(() -> new ImageDimension(300, 300)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("width_height_정상생성") + void create_success() { + assertThatNoException() + .isThrownBy(() -> new ImageDimension(300, 200)); + + assertThatNoException() + .isThrownBy(() -> new ImageDimension(600, 400)); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/ImageSizeTest.java b/src/test/java/nextstep/courses/domain/ImageSizeTest.java new file mode 100644 index 0000000000..5685a14cdc --- /dev/null +++ b/src/test/java/nextstep/courses/domain/ImageSizeTest.java @@ -0,0 +1,30 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static nextstep.courses.domain.ImageSize.MAX_SIZE; +import static org.assertj.core.api.Assertions.*; + +class ImageSizeTest { + + @Test + @DisplayName("1MB 이하의 이미지 크기는 생성된다") + void create_image_size() { + // 방법1 + ImageSize imageSize = new ImageSize(MAX_SIZE); + + assertThat(imageSize).isEqualTo(new ImageSize(MAX_SIZE)); + + // 방법2 + assertThatNoException() + .isThrownBy(() -> new ImageSize(MAX_SIZE)); + } + + @Test + @DisplayName("1MB보다 큰 이미지 크기는 예외가 발생한다") + void create_image_size_max_exception() { + assertThatThrownBy(() -> new ImageSize(MAX_SIZE + 1)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/ImageTypeTest.java b/src/test/java/nextstep/courses/domain/ImageTypeTest.java new file mode 100644 index 0000000000..080d0384e7 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/ImageTypeTest.java @@ -0,0 +1,28 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ImageTypeTest { + + @Test + @DisplayName("유효한 확장자") + void from_valid_extension() { + assertThat(ImageType.from("gif")).isEqualTo(ImageType.GIF); + assertThat(ImageType.from("GIF")).isEqualTo(ImageType.GIF); + assertThat(ImageType.from("jpg")).isEqualTo(ImageType.JPG); + assertThat(ImageType.from("jpeg")).isEqualTo(ImageType.JPG); + } + + @Test + @DisplayName("유효하지 않은 확장자") + void from_invalid_extension() { + assertThatThrownBy(() -> ImageType.from("txt")) + .isInstanceOf(IllegalArgumentException.class); + } + + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/MoneyTest.java b/src/test/java/nextstep/courses/domain/MoneyTest.java new file mode 100644 index 0000000000..361ef9858a --- /dev/null +++ b/src/test/java/nextstep/courses/domain/MoneyTest.java @@ -0,0 +1,17 @@ +package nextstep.courses.domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class MoneyTest { + + @Test + @DisplayName("금액이 같으면, true") + void isEqualTo() { + Money money = new Money(5000); + assertThat(money.isEqualTo(new Money(5000))).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionDurationTest.java b/src/test/java/nextstep/courses/domain/SessionDurationTest.java new file mode 100644 index 0000000000..fbc26915fe --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionDurationTest.java @@ -0,0 +1,31 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SessionDurationTest { + + @Test + @DisplayName("시작일과 종료일이 같으면 exception") + void start_equals_end_exception() { + LocalDateTime start = LocalDateTime.of(2025, 12, 15, 00, 00); + LocalDateTime end = LocalDateTime.of(2025, 12, 15, 00, 00); + + assertThatThrownBy(() -> new SessionDuration(start, end)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("시작일이 종료일보다 크면 exception") + void start_after_end_exception() { + LocalDateTime start = LocalDateTime.of(2025, 12, 15, 14, 00); + LocalDateTime end = LocalDateTime.of(2025, 12, 15, 13, 00); + + assertThatThrownBy(() -> new SessionDuration(start, end)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionStateTest.java b/src/test/java/nextstep/courses/domain/SessionStateTest.java new file mode 100644 index 0000000000..85ac02fc3b --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionStateTest.java @@ -0,0 +1,26 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SessionStateTest { + @Test + @DisplayName("OPEN은 등록이 가능하다") + void enroll_success() { + assertThatCode(() -> SessionState.OPEN.validateEnroll()) + .doesNotThrowAnyException(); + } + + @Test + @DisplayName("READY나 CLOSED는 등록할 수 없다") + void enroll_fail() { + assertThatThrownBy(() -> SessionState.READY.validateEnroll()) + .isInstanceOf(IllegalStateException.class); + + assertThatThrownBy(() -> SessionState.CLOSED.validateEnroll()) + .isInstanceOf(IllegalStateException.class); + } +} \ 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..a14c62e3d9 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -0,0 +1,13 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SessionTest { + + @Test + void enroll() { + + } +} \ No newline at end of file