diff --git a/README.md b/README.md index 498506166..932e5e215 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,25 @@ * 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다. ## 온라인 코드 리뷰 과정 -* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview) \ No newline at end of file +* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview) + +## 수강 신청 기능 요구사항 +- 과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다. +- 강의는 시작일과 종료일을 가진다. +- 강의는 강의 커버 이미지 정보를 가진다. +- 이미지 크기는 1MB 이하여야 한다. +- 이미지 타입은 gif, jpg(jpeg 포함), png, svg만 허용한다. +- 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. +- 강의는 무료 강의와 유료 강의로 나뉜다. +- 무료 강의는 최대 수강 인원 제한이 없다. +- 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. +- 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. +- 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. +- 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. +- 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다. +- 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다. + +## 프로그래밍 요구사항 +- DB 테이블 설계 없이 도메인 모델부터 구현한다. +- 도메인 모델은 TDD로 구현한다. +- 단, Service 클래스는 단위 테스트가 없어도 된다. diff --git a/src/main/java/nextstep/courses/InvalidImageFileException.java b/src/main/java/nextstep/courses/InvalidImageFileException.java new file mode 100644 index 000000000..152ded6b0 --- /dev/null +++ b/src/main/java/nextstep/courses/InvalidImageFileException.java @@ -0,0 +1,7 @@ +package nextstep.courses; + +public class InvalidImageFileException extends RuntimeException { + public InvalidImageFileException(String message) { + super(message); + } +} diff --git a/src/main/java/nextstep/courses/SessionUnregistrableException.java b/src/main/java/nextstep/courses/SessionUnregistrableException.java new file mode 100644 index 000000000..a9c00059d --- /dev/null +++ b/src/main/java/nextstep/courses/SessionUnregistrableException.java @@ -0,0 +1,10 @@ +package nextstep.courses; + +public class SessionUnregistrableException extends RuntimeException{ + public SessionUnregistrableException(){ + } + + public SessionUnregistrableException(String message) { + super(message); + } +} 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 000000000..7d9172da9 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/BaseEntity.java @@ -0,0 +1,21 @@ +package nextstep.courses.domain; + +import nextstep.courses.domain.session.Period; + +import java.time.LocalDate; + +public class BaseEntity { + private final Long id; + private final String title; + private final Period period; + + public BaseEntity(Long id, String title, LocalDate startDate, LocalDate endDate) { + this(id, title, new Period(startDate, endDate)); + } + + public BaseEntity(Long id, String title, Period period) { + this.id = id; + this.title = title; + this.period = period; + } +} diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java similarity index 96% rename from src/main/java/nextstep/courses/domain/Course.java rename to src/main/java/nextstep/courses/domain/course/Course.java index 0f6971604..1c935f2fc 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course; import java.time.LocalDateTime; diff --git a/src/main/java/nextstep/courses/domain/CourseRepository.java b/src/main/java/nextstep/courses/domain/course/CourseRepository.java similarity index 71% rename from src/main/java/nextstep/courses/domain/CourseRepository.java rename to src/main/java/nextstep/courses/domain/course/CourseRepository.java index 6aaeb638d..28180d25e 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/image/Image.java b/src/main/java/nextstep/courses/domain/image/Image.java new file mode 100644 index 000000000..126591c60 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/image/Image.java @@ -0,0 +1,13 @@ +package nextstep.courses.domain.image; + +public class Image { + private final ImageSize size; + private final ImageDimension imageDimension; + private final ImageFileExtension fileExtension; + + public Image(int size, int width, int height, String fileExtension) { + this.size = new ImageSize(size); + this.imageDimension = new ImageDimension(width, height); + this.fileExtension = ImageFileExtension.from(fileExtension); + } +} diff --git a/src/main/java/nextstep/courses/domain/image/ImageDimension.java b/src/main/java/nextstep/courses/domain/image/ImageDimension.java new file mode 100644 index 000000000..64fbeaf93 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/image/ImageDimension.java @@ -0,0 +1,28 @@ +package nextstep.courses.domain.image; + +import nextstep.courses.InvalidImageFileException; + +public class ImageDimension { + private final static int MIN_WIDTH = 300; // 300 px + private final static int MIN_HEIGHT = 200; // 200 px + private final static double ASPECT_RATIO = 1.5; // width : height = 3 : 2 + + private final int width; + private final int height; + + public ImageDimension(int width, int height) { + if (!isValidWidthAndHeight(width, height)) { + throw new InvalidImageFileException(String.format("이미지는 가로 %spx 이상, 세로 %spx 이상, 가로 세로 비율이 %s:1 이어야 합니다.", MIN_WIDTH, MIN_HEIGHT, ASPECT_RATIO)); + } + this.width = width; + this.height = height; + } + + private boolean isValidWidthAndHeight(int width, int height) { + return width >= MIN_WIDTH && height >= MIN_HEIGHT && isValidRatio(width, height); + } + + private boolean isValidRatio(int width, int height) { + return (double) width / height == ASPECT_RATIO; + } +} diff --git a/src/main/java/nextstep/courses/domain/image/ImageFileExtension.java b/src/main/java/nextstep/courses/domain/image/ImageFileExtension.java new file mode 100644 index 000000000..99b1edc20 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/image/ImageFileExtension.java @@ -0,0 +1,37 @@ +package nextstep.courses.domain.image; + +import nextstep.courses.InvalidImageFileException; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public enum ImageFileExtension { + GIF("gif"), + JPG("jpg"), + JPEG("jpeg"), + PNG("png"), + SVG("svg"); + + private final String value; + + ImageFileExtension(String value) { + this.value = value; + } + + private String value() { + return value; + } + + static ImageFileExtension from(String value) { + return Arrays.stream(ImageFileExtension.values()) + .filter(fileExtension -> fileExtension.value.equals(value)) + .findFirst() + .orElseThrow(() -> new InvalidImageFileException(String.format("이미지 파일은 %s 중에 하나의 형태여야 합니다.", possibleFileExtensions()))); + } + + private static String possibleFileExtensions() { + return Arrays.stream(ImageFileExtension.values()) + .map(ImageFileExtension::value) + .collect(Collectors.joining(", ")); + } +} diff --git a/src/main/java/nextstep/courses/domain/image/ImageSize.java b/src/main/java/nextstep/courses/domain/image/ImageSize.java new file mode 100644 index 000000000..7688e5e54 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/image/ImageSize.java @@ -0,0 +1,19 @@ +package nextstep.courses.domain.image; + +import nextstep.courses.InvalidImageFileException; + +class ImageSize { + private final static int MAX_SIZE = 1_024; // 1024 Byte + private final int size; + + ImageSize(int size) { + if (!isValidSize(size)) { + throw new InvalidImageFileException(String.format("이미지 사이즈는 %sB 이하여야 합니다.", MAX_SIZE)); + } + this.size = size; + } + + private boolean isValidSize(int size) { + return size <= MAX_SIZE; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/Capacity.java b/src/main/java/nextstep/courses/domain/session/Capacity.java new file mode 100644 index 000000000..83d571125 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Capacity.java @@ -0,0 +1,22 @@ +package nextstep.courses.domain.session; + +public class Capacity { + private final static int UNLIMITED = -1; + private final int capacity; + + public Capacity() { + this(UNLIMITED); + } + + public Capacity(int capacity) { + this.capacity = capacity; + } + + public boolean isUnlimited() { + return capacity == UNLIMITED; + } + + public boolean isGreaterThan(int value) { + return capacity > value; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/EnrollmentCondition.java b/src/main/java/nextstep/courses/domain/session/EnrollmentCondition.java new file mode 100644 index 000000000..ddf60a27b --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/EnrollmentCondition.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.session; + +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUser; + +/** + * 개인별 수강 신청 가능 조건 값을 관리하는 도메인 + */ +public class EnrollmentCondition { + private final NsUser user; + private final Payment payment; + + public EnrollmentCondition(NsUser user, Payment payment) { + if (!paidBySameUser(user, payment)) { + throw new IllegalArgumentException("수강 신청자와 결제자 정보가 불일치합니다."); + } + this.user = user; + this.payment = payment; + } + + private boolean paidBySameUser(NsUser user, Payment payment) { + return payment.isPaidBy(user.getId()); + } + + public NsUser getUser() { + return user; + } + + public boolean hasPaid(Long sessionId, long price) { + return payment.isPaidFor(sessionId) && payment.isSameAmount(price); + } +} diff --git a/src/main/java/nextstep/courses/domain/session/Period.java b/src/main/java/nextstep/courses/domain/session/Period.java new file mode 100644 index 000000000..909bffe84 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Period.java @@ -0,0 +1,13 @@ +package nextstep.courses.domain.session; + +import java.time.LocalDate; + +public class Period { + private final LocalDate startDate; + private final LocalDate endDate; + + public Period(LocalDate startDate, LocalDate endDate) { + this.startDate = startDate; + this.endDate = endDate; + } +} 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 000000000..2ad502ad0 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -0,0 +1,52 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.SessionUnregistrableException; +import nextstep.courses.domain.*; +import nextstep.courses.domain.image.Image; +import nextstep.courses.domain.session.type.Free; +import nextstep.courses.domain.session.type.Paid; +import nextstep.courses.domain.session.type.SessionType; + +import java.time.LocalDate; + +public class Session { + private final BaseEntity baseEntity; + private final Image image; + private SessionStatus status; + private final SessionType type; + + public Session(Long id, String title, Image image, SessionStatus status, SessionType type, LocalDate startDate, LocalDate endDate) { + this.baseEntity = new BaseEntity(id, title, new Period(startDate, endDate)); + this.image = image; + this.status = status; + this.type = type; + } + + public void openRecruiting() { + this.status = SessionStatus.RECRUITING; + } + + public boolean isRecruiting() { + return this.status == SessionStatus.RECRUITING; + } + + public boolean canEnroll(EnrollmentCondition request) { + return this.type.canEnroll(request); + } + + public String currentStatusToHumanReadable() { + return this.status.name(); + } + + public String typeToHumanReadable() { + return this.type.toHumanReadableTypeName(); + } + + public static Session createFreeSession(String title, Image image) { + return new Session(null, title, image, SessionStatus.PREPARING, new Free(), LocalDate.now(), LocalDate.MAX); + } + + public static Session createPaidSession(long id, String title, Image image, long price) { + return new Session(null, title, image, SessionStatus.PREPARING, new Paid(id, price), LocalDate.now(), LocalDate.MAX); + } +} diff --git a/src/main/java/nextstep/courses/domain/session/SessionEnrollment.java b/src/main/java/nextstep/courses/domain/session/SessionEnrollment.java new file mode 100644 index 000000000..93bc23997 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionEnrollment.java @@ -0,0 +1,53 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.SessionUnregistrableException; +import nextstep.users.domain.NsUsers; + +/** + * 강의 정원 및 현재 등록 인원을 관리하는 도메인 + */ +public class SessionEnrollment { + private final Capacity maxCapacity; + private final NsUsers enrolledUsers; + private final Session session; + + public SessionEnrollment(Session session) { + this(new Capacity(), new NsUsers(), session); + } + + public SessionEnrollment(int maxCapacity, Session session) { + this(new Capacity(maxCapacity), new NsUsers(), session); + } + + public SessionEnrollment(Capacity maxCapacity, NsUsers enrolledUsers, Session session) { + this.maxCapacity = maxCapacity; + this.enrolledUsers = enrolledUsers; + this.session = session; + } + + public void enroll(EnrollmentCondition condition) { + checkEnrollable(condition); + this.enrolledUsers.addUser(condition.getUser()); + } + + private void checkEnrollable(EnrollmentCondition condition) { + if (!session.isRecruiting()) { + throw new SessionUnregistrableException(String.format("%s 상태인 강의는 수강신청할 수 없습니다.", session.currentStatusToHumanReadable())); + } + + if (!session.canEnroll(condition)) { + throw new SessionUnregistrableException(String.format("%s 강의 수강 신청 조건 미달로 신청할 수 없습니다.", session.typeToHumanReadable())); + } + + if (!hasCapacity()) { + throw new SessionUnregistrableException("정원 초과로 수강신청할 수 없습니다."); + } + } + + private boolean hasCapacity() { + if (maxCapacity.isUnlimited()) { + return true; + } + return enrolledUsers.isLessThan(maxCapacity); + } +} diff --git a/src/main/java/nextstep/courses/domain/session/SessionStatus.java b/src/main/java/nextstep/courses/domain/session/SessionStatus.java new file mode 100644 index 000000000..d565d88b1 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionStatus.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain.session; + +public enum SessionStatus { + PREPARING, RECRUITING, COMPLETED +} diff --git a/src/main/java/nextstep/courses/domain/session/type/Free.java b/src/main/java/nextstep/courses/domain/session/type/Free.java new file mode 100644 index 000000000..4051edc9c --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/type/Free.java @@ -0,0 +1,18 @@ +package nextstep.courses.domain.session.type; + +import nextstep.courses.domain.session.EnrollmentCondition; + +public class Free implements SessionType { + public Free() { + } + + @Override + public boolean canEnroll(EnrollmentCondition request) { + return true; + } + + @Override + public String toHumanReadableTypeName() { + return "무료"; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/type/Paid.java b/src/main/java/nextstep/courses/domain/session/type/Paid.java new file mode 100644 index 000000000..5147cbfa3 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/type/Paid.java @@ -0,0 +1,23 @@ +package nextstep.courses.domain.session.type; + +import nextstep.courses.domain.session.EnrollmentCondition; + +public class Paid implements SessionType { + private final long sessionId; + private final long price; + + public Paid(long sessionId, long price) { + this.sessionId = sessionId; + this.price = price; + } + + @Override + public boolean canEnroll(EnrollmentCondition condition) { + return condition.hasPaid(this.sessionId, price); + } + + @Override + public String toHumanReadableTypeName() { + return "유료"; + } +} 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 000000000..1dd7bd61c --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/type/SessionType.java @@ -0,0 +1,9 @@ +package nextstep.courses.domain.session.type; + +import nextstep.courses.domain.session.EnrollmentCondition; + +public interface SessionType { + boolean canEnroll(EnrollmentCondition request); + + String toHumanReadableTypeName(); +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index f9122cbe3..7489afc31 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/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 57d833f85..b7b482109 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -19,6 +19,10 @@ public class Payment { public Payment() { } + public Payment(Long sessionId, Long nsUserId, Long amount) { + this(null, sessionId, nsUserId, amount); + } + public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.id = id; this.sessionId = sessionId; @@ -26,4 +30,16 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.amount = amount; this.createdAt = LocalDateTime.now(); } + + public boolean isSameAmount(long amount) { + return this.amount == amount; + } + + public boolean isPaidFor(Long sessionId) { + return sessionId.equals(this.sessionId); + } + + public boolean isPaidBy(Long userId) { + return userId.equals(this.nsUserId); + } } diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java index 62ec5138c..13aa0591c 100755 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -1,7 +1,5 @@ package nextstep.users.domain; -import nextstep.qna.UnAuthorizedException; - import java.time.LocalDateTime; import java.util.Objects; @@ -43,78 +41,20 @@ public Long getId() { return id; } - public String getUserId() { - return userId; - } - - public NsUser setUserId(String userId) { - this.userId = userId; - return this; - } - - public String getPassword() { - return password; - } - - public NsUser setPassword(String password) { - this.password = password; - return this; - } - - public String getName() { - return name; - } - - public NsUser setName(String name) { - this.name = name; - return this; - } - - public String getEmail() { - return email; - } - - public NsUser setEmail(String email) { - this.email = email; - return this; - } - - public void update(NsUser loginUser, NsUser target) { - if (!matchUserId(loginUser.getUserId())) { - throw new UnAuthorizedException(); - } - - if (!matchPassword(target.getPassword())) { - throw new UnAuthorizedException(); - } - - this.name = target.name; - this.email = target.email; - } - - public boolean matchUser(NsUser target) { - return matchUserId(target.getUserId()); - } - - private boolean matchUserId(String userId) { - return this.userId.equals(userId); - } - - public boolean matchPassword(String targetPassword) { - return password.equals(targetPassword); + public boolean isGuestUser() { + return false; } - public boolean equalsNameAndEmail(NsUser target) { - if (Objects.isNull(target)) { - return false; - } - - return name.equals(target.name) && - email.equals(target.email); + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + NsUser nsUser = (NsUser) o; + return Objects.equals(id, nsUser.id) && Objects.equals(userId, nsUser.userId) && Objects.equals(name, nsUser.name) && Objects.equals(email, nsUser.email); } - public boolean isGuestUser() { - return false; + @Override + public int hashCode() { + return Objects.hash(id, userId, name, email); } private static class GuestNsUser extends NsUser { diff --git a/src/main/java/nextstep/users/domain/NsUsers.java b/src/main/java/nextstep/users/domain/NsUsers.java new file mode 100644 index 000000000..ae10684d8 --- /dev/null +++ b/src/main/java/nextstep/users/domain/NsUsers.java @@ -0,0 +1,26 @@ +package nextstep.users.domain; + +import nextstep.courses.domain.session.Capacity; + +import java.util.HashSet; +import java.util.Set; + +public class NsUsers { + private final Set users; + + public NsUsers() { + this(new HashSet<>()); + } + + public NsUsers(Set users) { + this.users = users; + } + + public void addUser(NsUser user) { + this.users.add(user); + } + + public boolean isLessThan(Capacity capacity) { + return capacity.isGreaterThan(this.users.size()); + } +} diff --git a/src/test/java/nextstep/courses/domain/image/ImageDimensionTest.java b/src/test/java/nextstep/courses/domain/image/ImageDimensionTest.java new file mode 100644 index 000000000..50c07ebd2 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/image/ImageDimensionTest.java @@ -0,0 +1,19 @@ +package nextstep.courses.domain.image; + +import nextstep.courses.InvalidImageFileException; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ImageDimensionTest { + @ParameterizedTest + @CsvSource(value = {"90,60", "300,300"}) + void 이미지_너비와_높이와_비율에_제한이_있다(int width, int height) { + assertThatThrownBy(() -> new ImageDimension(90, 60)) + .isInstanceOf(InvalidImageFileException.class); + + assertThatThrownBy(() -> new ImageDimension(300, 300)) + .isInstanceOf(InvalidImageFileException.class); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/image/ImageFileExtensionTest.java b/src/test/java/nextstep/courses/domain/image/ImageFileExtensionTest.java new file mode 100644 index 000000000..6390c7c8b --- /dev/null +++ b/src/test/java/nextstep/courses/domain/image/ImageFileExtensionTest.java @@ -0,0 +1,14 @@ +package nextstep.courses.domain.image; + +import nextstep.courses.InvalidImageFileException; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ImageFileExtensionTest { + @Test + void 이미지_확장자는_gif_jpg_png_svg만_가능하다() { + assertThatThrownBy(() -> ImageFileExtension.from("txt")) + .isInstanceOf(InvalidImageFileException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/image/ImageSizeTest.java b/src/test/java/nextstep/courses/domain/image/ImageSizeTest.java new file mode 100644 index 000000000..c7afd3a72 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/image/ImageSizeTest.java @@ -0,0 +1,14 @@ +package nextstep.courses.domain.image; + +import nextstep.courses.InvalidImageFileException; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ImageSizeTest { + @Test + void 이미지의_크기는_1MB_이하이다() { + assertThatThrownBy(() -> new ImageSize(1025)) + .isInstanceOf(InvalidImageFileException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/EnrollmentConditionTest.java b/src/test/java/nextstep/courses/domain/session/EnrollmentConditionTest.java new file mode 100644 index 000000000..a8715a5de --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/EnrollmentConditionTest.java @@ -0,0 +1,19 @@ +package nextstep.courses.domain.session; + +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.Test; + +import static nextstep.courses.domain.session.SessionTest.*; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class EnrollmentConditionTest { + public static final EnrollmentCondition JAVAJIGI_CONDITION = new EnrollmentCondition(NsUserTest.JAVAJIGI, new Payment(SESSION_ID, NsUserTest.JAVAJIGI.getId(), 30_000L)); + public static final EnrollmentCondition SANJIGI_CONDITION = new EnrollmentCondition(NsUserTest.SANJIGI, new Payment(SESSION_ID, NsUserTest.SANJIGI.getId(), 30_000L)); + + @Test + void 수강신청자와_결제자가_서로_다르면_수강신청_조건을_충족하지_않는다() { + assertThatThrownBy(() -> new EnrollmentCondition(NsUserTest.JAVAJIGI, new Payment(SESSION_ID, NsUserTest.SANJIGI.getId(), 30_000L))) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/SessionEnrollmentTest.java new file mode 100644 index 000000000..182666b2b --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionEnrollmentTest.java @@ -0,0 +1,50 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.SessionUnregistrableException; +import nextstep.courses.domain.image.Image; +import org.junit.jupiter.api.Test; + +import static nextstep.courses.domain.session.EnrollmentConditionTest.JAVAJIGI_CONDITION; +import static nextstep.courses.domain.session.EnrollmentConditionTest.SANJIGI_CONDITION; +import static nextstep.courses.domain.session.SessionTest.SESSION_ID; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SessionEnrollmentTest { + private static final Image IMAGE = new Image(1_024, 300, 200, "png"); + + @Test + void 강의는_모집중_상태가_아니면_신청할_수_없다() { + Session freeSession = Session.createFreeSession("free", IMAGE); + SessionEnrollment enrollment = new SessionEnrollment(freeSession); + assertThatThrownBy(() -> enrollment.enroll(JAVAJIGI_CONDITION)) + .isInstanceOf(SessionUnregistrableException.class); + } + + @Test + void 무료_강의는_인원수_제한없이_들을_수_있다() { + Session freeSession = Session.createFreeSession("free", IMAGE); + freeSession.openRecruiting(); + SessionEnrollment enrollment = new SessionEnrollment(freeSession); + enrollment.enroll(JAVAJIGI_CONDITION); + enrollment.enroll(SANJIGI_CONDITION); + } + + @Test + void 유료_강의는_인원수_제한이_있다() { + Session paidSession = Session.createPaidSession(SESSION_ID, "paid", IMAGE, 30_000L); + paidSession.openRecruiting(); + SessionEnrollment enrollment = new SessionEnrollment(1, paidSession); + enrollment.enroll(JAVAJIGI_CONDITION); + assertThatThrownBy(() -> enrollment.enroll(SANJIGI_CONDITION)) + .isInstanceOf(SessionUnregistrableException.class); + } + + @Test + void 유료_강의는_지불한_금액이_맞아야_한다() { + Session paidSession = Session.createPaidSession(SESSION_ID, "paid", IMAGE, 20_000L); + paidSession.openRecruiting(); + SessionEnrollment enrollment = new SessionEnrollment(1, paidSession); + assertThatThrownBy(() -> enrollment.enroll(JAVAJIGI_CONDITION)) + .isInstanceOf(SessionUnregistrableException.class); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java new file mode 100644 index 000000000..44d33dd88 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.domain.image.Image; +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SessionTest { + public static final Long SESSION_ID = 123L; + private static final Image IMAGE = new Image(1_024, 300, 200, "png"); + + @Test + void 모집중_상태로_변경하면_정상적으로_상태가_변경된다() { + Session freeSession = Session.createFreeSession("free", IMAGE); + freeSession.openRecruiting(); + assertThat(freeSession.isRecruiting()).isTrue(); + } + + @Test + void 강의_수강신청_가능_조건을_입력해_수강신청_가능_여부를_확인한다() { + Session paidSession = Session.createPaidSession(SESSION_ID, "paid", IMAGE, 20_000L); + paidSession.openRecruiting(); + + EnrollmentCondition trueCondition = new EnrollmentCondition(NsUserTest.JAVAJIGI, new Payment(SESSION_ID, NsUserTest.JAVAJIGI.getId(), 20_000L)); + assertThat(paidSession.canEnroll(trueCondition)).isTrue(); + + EnrollmentCondition falseCondition = new EnrollmentCondition(NsUserTest.SANJIGI, new Payment(SESSION_ID, NsUserTest.SANJIGI.getId(), 30_000L)); + assertThat(paidSession.canEnroll(falseCondition)).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/type/PaidTest.java b/src/test/java/nextstep/courses/domain/session/type/PaidTest.java new file mode 100644 index 000000000..f6bc11278 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/type/PaidTest.java @@ -0,0 +1,27 @@ +package nextstep.courses.domain.session.type; + +import org.junit.jupiter.api.Test; + +import static nextstep.courses.domain.session.SessionTest.SESSION_ID; +import static nextstep.courses.domain.session.EnrollmentConditionTest.JAVAJIGI_CONDITION; +import static org.assertj.core.api.Assertions.assertThat; + +class PaidTest { + @Test + void 유료_강의는_수강생이_결제한_금액과_수강료가_일치할_때_수강_신청이_가능하다() { + Paid paidSession = new Paid(SESSION_ID, 30_000L); + assertThat(paidSession.canEnroll(JAVAJIGI_CONDITION)).isTrue(); + } + + @Test + void 유료_강의는_수강생이_결제한_금액과_수강료가_불일치하면_수강_신청이_불가하다() { + Paid paidSession = new Paid(SESSION_ID, 20_000L); + assertThat(paidSession.canEnroll(JAVAJIGI_CONDITION)).isFalse(); + } + + @Test + void 유료_강의는_수강생이_결제한_강의와_신청한_강의가_불일치하면_수강_신청이_불가하다() { + Paid paidSession = new Paid(SESSION_ID + 1, 20_000L); + assertThat(paidSession.canEnroll(JAVAJIGI_CONDITION)).isFalse(); + } +} \ 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 f087fc0ad..530375d3e 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;