diff --git a/README.md b/README.md index 677ded833..5c53181b2 100644 --- a/README.md +++ b/README.md @@ -67,4 +67,174 @@ - [x] : `Answer` 에 대한 삭제 테스트 필요하자않은가? 이유작성 - 이유 : 필요하다 Answer 을 수동적으로 바라보았기에 비즈니스 로직에 대한 테스트 코드가 생략되었다. - [ ] : `Answers` 에서 한땀한땀 삭제하는건 `Answer` 에 대한 수동적으로 바라보는 느낌아닌가? 이유 작성 - - 이유 : 맞다 또한 검증에 대해서도 모두 수행하는것은 바람직하지 않음, 검증은 `Answer` 내부에서 해야함 \ No newline at end of file + - 이유 : 맞다 또한 검증에 대해서도 모두 수행하는것은 바람직하지 않음, 검증은 `Answer` 내부에서 해야함 + +## 3차 피드백 +- [x] : `Question` 의 `title`, `contents` 필드를 별도로 `QuestionBody` 로 분리하는 것이 의미 있을까? + - 생각컨데 분리는 의마가 없다고 본다. + - 그 이유는 : `title`, `contents` 로 분리를 하면은 값을 보관해주는 역할에 불과한 점 + - 분리한 객체만의 특별한 도메인 규칙이나 행동이 없는 점 + - 때문이다. + +# 2단계 : 수강신청(도메인 모델) + +- DB 테이블 설계 없이 도메인 모델부터 구현하기 + - 도메인 모델은 TDD로 구현 (Service 클래스는 단위 테스트 없이) + +## 기능목록 + +1. 과정(Course)과 강의(Session) + - 과정(Course)은 '기수' 단위 운영 + - 과정에 여러개의 강의(Session) 를 가질 수있음 +2. 강의 기간 + - 시작일과 종료일 가짐 +3. 강의에 커버이미지 가짐 + - 이미지 크기는 1MB 이하 + - 이미지 타입은 gif, jpg(jpeg), png, svg + - 이미지 크기 : width - 300px / height - 200px 이상 + - width / height 비율 3:2 +4. 강의 타입 : '무료 강의' '유료 강의'로 나누어진다 + - 무료 강의 : 최대 수강신청 인원 제한이 없다 + - 유료 강의 : 강의 최대 수강 인원 초과할 수 없음 + - 수강생이 결제한 금액과 수강료가 일치 할 시 수강 신청 가능 +5. 강의 상태 : '준비중', '모집중', '종료' + - 모집 중 일때 강의 수강신청 가능 +6. 결제 + - 유료 강의 결제는 이미 완료된 것으로 가정 하고 이후 과정 구현함 + - 결제 완료 한 결제정보는 payments 모듈로 관리, 결제정보는 Payment 객체에 담겨 반환 + +## 객체 설계 + +### 객체 역할 + +- Course : 기수별 session 들의 관리관련 정보 전문가 및 행위 책임자 +- Session : 강의 자체의 전반적인 정보 전문가 및 행위 책임자 +- Duration : 강의의 시작, 종료 관련 정보 전문가 및 행위 책임자 +- CoverImage : 강의의 커버 이미지 관련 정보 전문가 및 행위 책임자 + - CoverImageType +- ProvideType : 강의의 무료, 유료 강의 관련 정보 전문가 및 행위 책임자 +- SessionStatus : 강의의 준비중, 모집 중 같은 정보 전문가 및 행위 책임자 + - SessionStatusType + +### 구현 순서 (작은 도메인 부터) + +Duration → CoverImage → SessionStatus → ProvideType → Session → Course + +### 중요 : 만들어진 강의에 수강신청 하는 것임 + +### 객체 별 기능 사항 + +#### Duration + +- [x] : 종료일이 시작일 보다 뒤에 있을 수없다 +- [x] : 시작일과 종료일이 오늘보다 뒤에 있다 +- [ ] : startDate, endDate 원시 값 포장 + +#### CoverImage + +- [x] : 커버 이미지의 크기는 1MB 이하이다 +- [x] : 이미지 타입은 gif, jpg(jpeg), png, svg 이다 (CoverImageType) +- [x] : 이미지 크기는 width - 300px / height - 200px 이상 이다 +- [x] : width / height 비율 3:2 이다 + +#### SessionStatus + +- [x] : 모집 중 일때 만 강의 수강신청 가능 + +#### Provide + +- [x] : 강의 타입은 무료 강의, 유료 강의 2가지 이다. + - [x] : 수강신청 제한인원은 타입에 존재한다 (규칙) +- [x] : 강의를 수강신청 하면 수강생 수가 올라간다 + - [x] : 클라이언트 코드에서 수강신청 제한인원 받아서 그 수가 넘는지 확인해준다 +- [x] : 지불금액을 보고 금액이 같은지 검증한다 + - [x] : 무료인데 지불하면 안된다. + +##### 객체 분리 필요성 고민 + +1. Provide 를 Free, Paid 로 객체를 나눌 것인가? + - 기대효과 : 각각 다른 규칙에 대해서 정리 가능 + - 예) 무료 : 인원수 제한 X/ 금액 무료 + - 한계점 : 비슷한 형태의 객체가 2개인데, 어떤 강의를 신청하느냐에 따라 요청이 오거나, 불러오는 객체가 달라짐 + +2. enrolledCount, maxEnrollment / tuitionFee, pay 각각 묶을 것인가? + - 기대효과 : 인원수 체크가 손쉽게 됨, 금액을 내야하는지 체크가 가능 / 서로 비슷한 규칙끼리 묶여서 검증 쉬움 + - 한계점 : 최초 고민인 무료인 경우 인원수 제한, 수강료에 대한 통제는 여전히 어려움 + - 규칙 : 그러나 만들어진 각각 객체안에서 무료인 경우 제한 검증 X, 수강료 검증 X 되지 않을까? + - 생성 : 무료인경우 생성자 인자를 생략하기? -> but 결국에 인원수를 0으로 세팅해야함 + +3. enrolledCount, tuitionFee / pay, maxEnrollment 각각 묶을 것인가? + - 기대 효과 : 무료, 유료인 경우에 대한 변칙 적용이 수월함 + - 한계점 : 묶이는 필드끼리 생명주기가 같을지 의문 + - 차라리 현상이 아닌 정해진 생명주기끼리 묶는게 낫지않을까? + - 예) tuitionFee, maxEnrollment (세션자체의 규칙) / enrolledCount, pay (요청 때 마다 변칙) + - 전자는 Session 에서, 후자는 Provide 에서 + +4. tuitionFee, maxEnrollment / pay, enrolledCount 각각 묶을 것인가? + - 기대 효과 : tuitionFee, maxEnrollment 는 상위 클래스 Session 에서 통제함 + - 제약과 현상을 분리함 + - 무료인 경우 0 fee 와 무제한 수강인원에 대한 검증 가능 + - 그리고 억지로 0으로 만들필요 없음 + - 한계점 : 하지만 전자를 결국에 객체로 만들면 0으로 세팅이 필요함 + - 결국에 상단 2번처럼 문제가 + +- 고민 + - 정원을 숫자가 아닌 정책으로 보아야함 + - 무제한 정책은 숫자가 필요없음 + - 제한 정책은 숫자가 필요 + - 정원이 없는것은 null 로 지정 가능 + - DDD 관점에서 '개념 부재' 표현임 (null 보다 의미없는 값(0) 으로 도메인 개념 왜곡이 더큰 문제) + - null 은 인간언어로 표현 가능함 + - 두갈래 고민 + - 2번 : 수강생 수 / 비용 으로 분리 + - 생성 방식에 대한 문제 해결함 -> 고려 가능 + - 도달 여부 쉽게 판단 + - 4번 : 기준 / 준수여부 로 분리 + - 생명주기 비슷 +- 최종 결정 + - 4번: ProvidePolicy / ProvideStatus + - 걱정 : null 로 넣는게 인위적인데 전략패턴을 이용해야할까 싶으면서 오버엔지이너링일까 우려됨 + +#### Session + +- [x] : `Provide` 에게 해당 세션의 제한인원 수 전달해서 남은 자리 조율 +- [x] : 무료, 유료에 따라서 수강신청 메서드 분리 + +#### Course + +- [x] : 신청하는 강의의 id 와 가격을 넣어서 수강신청한다 +- [x] : 수강신청 하려는 session의 id가 없으면 예외전파 + +#### CourseService + +- [x] : 해당 코스의 특정 강의를 조회한다 +- [x] : 조회한 강의를 수강신청한다 + - 무료강의 이면 바로 신청 + - 유료 강의 이면 결제 모듈 조회 후 신청 + - 당연 성공이므로 곧바로 amount 넣기 +- [x] : 조회 성공 후 결과를 저장하기 + +#### 놓치거나 마무리 부분 + +- [x] : 수강신청한 수강생의 정보 넣기 +- [x] : 결제 완료를 기준으로 던지기 +- [x] : 컬렉션을 일급 컬렉션으로 바꾸기 (EnrolledUsers, Sessions) + +## 1차 피드백 + +- [x] : 무료 유료인 상태의 객체 구분을 null로 구현이 아닌, null을 추상화한 Null Object 로 추가 + - 인터페이스로 null Object 로 구분하기 / 그에 따라서 구현하는 방향 바꾸기 + - 무료는 싱글턴 null 객체로, 유료는 생성자로 초기화 + - DI 방식으로 하니 무분별하게 분기 칠 필요가 없어짐 +- [x] : 무료 유료 구분은 생성자 유무가 아닌 ProvideType 값에 따라 결정 + - apply() 분기도 마찬가지 +- [x] : Base 추상클래스로 구현하여 직접 생상안되게 하기 +- [x] : CoverImage 필드 수 줄이기 +- [x] : boolean 타입이 아닌데 is 로 시작하는 메서드명 validate 시작으로 변경 +- [x] : `ProvidePolicy` 에 수강신청 판단 하는 모든 값 가져도 될지 고민하기 + - SessionStatus, 현재 수강신청한 수강생 목록도 신청여부 판단에 필요한 값 + - -> 이름을 Enrollment의 EnrollmentPolicy로 몀명하니 필요성을 가지게 됨 +- [x] : 복잡한 객체의 테스트 데이터 생성 시 중복코드 많고, 생성자 인자가 변경되는 경우 변경할 부분이 많음 + - 테스트 데이터 생성 시 방법 탐색해서 적용 후 앞으로 활용하기 + - 테스트 픽스쳐를 '오브젝트 마더 패턴' + 빌더패턴'(기본 값 숨기기, 빌더 덜쓰기, 팩토리 메서드를 이용해 도메인 강조, 비슷한 객체를 이용 시 코드 줄이기위한 but 사용) + - 이용해서 축소 \ No newline at end of file diff --git a/src/main/java/nextstep/courses/CanNotCreateException.java b/src/main/java/nextstep/courses/CanNotCreateException.java new file mode 100644 index 000000000..c5f261c3f --- /dev/null +++ b/src/main/java/nextstep/courses/CanNotCreateException.java @@ -0,0 +1,8 @@ +package nextstep.courses; + +public class CanNotCreateException extends Exception { + + public CanNotCreateException(String message) { + super(message); + } +} diff --git a/src/main/java/nextstep/courses/CanNotJoinException.java b/src/main/java/nextstep/courses/CanNotJoinException.java new file mode 100644 index 000000000..b3782ebbc --- /dev/null +++ b/src/main/java/nextstep/courses/CanNotJoinException.java @@ -0,0 +1,8 @@ +package nextstep.courses; + +public class CanNotJoinException extends Exception { + + public CanNotJoinException(String message) { + super(message); + } +} diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/Course.java deleted file mode 100644 index 0f6971604..000000000 --- a/src/main/java/nextstep/courses/domain/Course.java +++ /dev/null @@ -1,53 +0,0 @@ -package nextstep.courses.domain; - -import java.time.LocalDateTime; - -public class Course { - private Long id; - - 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); - } - - public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.title = title; - this.creatorId = creatorId; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - public String getTitle() { - return title; - } - - public Long getCreatorId() { - return creatorId; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - @Override - public String toString() { - return "Course{" + - "id=" + id + - ", title='" + title + '\'' + - ", creatorId=" + creatorId + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - '}'; - } -} diff --git a/src/main/java/nextstep/courses/domain/builder/EnrollmentBuilder.java b/src/main/java/nextstep/courses/domain/builder/EnrollmentBuilder.java new file mode 100644 index 000000000..afeb64ac7 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/builder/EnrollmentBuilder.java @@ -0,0 +1,52 @@ +package nextstep.courses.domain.builder; + +import static nextstep.courses.domain.builder.EnrollmentPolicyBuilder.aFreeEnrollmentPolicyBuilder; +import static nextstep.courses.domain.builder.EnrollmentPolicyBuilder.aPaidEnrollmentPolicyBuilder; + +import nextstep.courses.CanNotCreateException; +import nextstep.courses.domain.enrollment.Enrollment; +import nextstep.courses.domain.enrollment.EnrollmentPolicy; +import nextstep.courses.domain.enumerate.EnrollmentType; + +public class EnrollmentBuilder { + + private EnrollmentType enrollmentType; + private EnrollmentPolicy enrollmentPolicy; + + public static EnrollmentBuilder aFreeEnrollmentBuilder() { + return new EnrollmentBuilder() + .withEnrollmentType(EnrollmentType.FREE) + .withEnrollmentPolicy(aFreeEnrollmentPolicyBuilder().build()); + } + + public static EnrollmentBuilder aPaidEnrollmentBuilder() { + return new EnrollmentBuilder() + .withEnrollmentType(EnrollmentType.PAID) + .withEnrollmentPolicy(aPaidEnrollmentPolicyBuilder().build()); + } + + private EnrollmentBuilder() {} + + private EnrollmentBuilder(EnrollmentBuilder copy) { + this.enrollmentType = copy.enrollmentType; + this.enrollmentPolicy = copy.enrollmentPolicy; + } + + public EnrollmentBuilder withEnrollmentType(EnrollmentType enrollmentType) { + this.enrollmentType = enrollmentType; + return this; + } + + public EnrollmentBuilder withEnrollmentPolicy(EnrollmentPolicy enrollmentPolicy) { + this.enrollmentPolicy = enrollmentPolicy; + return this; + } + + public Enrollment build() throws CanNotCreateException { + return new Enrollment(enrollmentType, enrollmentPolicy); + } + + public EnrollmentBuilder but() { + return new EnrollmentBuilder(this); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/builder/EnrollmentPolicyBuilder.java b/src/main/java/nextstep/courses/domain/builder/EnrollmentPolicyBuilder.java new file mode 100644 index 000000000..816525bfb --- /dev/null +++ b/src/main/java/nextstep/courses/domain/builder/EnrollmentPolicyBuilder.java @@ -0,0 +1,63 @@ +package nextstep.courses.domain.builder; + +import java.util.List; +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.courses.domain.enrollment.EnrollmentPolicy; +import nextstep.courses.domain.enrollment.SessionStatus; +import nextstep.courses.domain.enrollment.enrollmentcondition.EnrollmentCondition; +import nextstep.courses.domain.enrollment.enrollmentcondition.FreeEnrollmentCondition; +import nextstep.courses.domain.enrollment.enrollmentcondition.PaidEnrollmentCondition; +import nextstep.courses.domain.enumerate.SessionStatusType; + +public class EnrollmentPolicyBuilder { + + private EnrollmentCondition enrollmentCondition; + private EnrolledUsers enrolledUsers = new EnrolledUsers(List.of(1L, 2L, 3L, 4L, 5L)); + private SessionStatus status = new SessionStatus(SessionStatusType.RECRUITING); + + public static EnrollmentPolicyBuilder aFreeEnrollmentPolicyBuilder() { + return new EnrollmentPolicyBuilder() + .withEnrollmentCondition(FreeEnrollmentCondition.INSTANCE); + } + + public static EnrollmentPolicyBuilder aPaidEnrollmentPolicyBuilder() { + return new EnrollmentPolicyBuilder() + .withEnrollmentCondition(new PaidEnrollmentCondition(10L, 10)); + } + + private EnrollmentPolicyBuilder() {} + + private EnrollmentPolicyBuilder(EnrollmentCondition enrollmentCondition) { + this.enrollmentCondition = enrollmentCondition; + } + + private EnrollmentPolicyBuilder(EnrollmentPolicyBuilder copy) { + this.enrollmentCondition = copy.enrollmentCondition; + this.enrolledUsers = copy.enrolledUsers; + this.status = copy.status; + } + + public EnrollmentPolicyBuilder withEnrollmentCondition(EnrollmentCondition enrollmentCondition) { + this.enrollmentCondition = enrollmentCondition; + return this; + } + + public EnrollmentPolicyBuilder withEnrolledUsers(EnrolledUsers enrolledUsers) { + this.enrolledUsers = enrolledUsers; + return this; + } + + public EnrollmentPolicyBuilder withSessionStatus(SessionStatus sessionStatus) { + this.status = sessionStatus; + return this; + } + + public EnrollmentPolicy build() { + return new EnrollmentPolicy(enrollmentCondition, enrolledUsers, status); + } + + public EnrollmentPolicyBuilder but() { + return new EnrollmentPolicyBuilder(this); + } + +} diff --git a/src/main/java/nextstep/courses/domain/builder/SessionBuilder.java b/src/main/java/nextstep/courses/domain/builder/SessionBuilder.java new file mode 100644 index 000000000..a1f261a52 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/builder/SessionBuilder.java @@ -0,0 +1,83 @@ +package nextstep.courses.domain.builder; + +import static nextstep.courses.domain.builder.EnrollmentBuilder.aFreeEnrollmentBuilder; +import static nextstep.courses.domain.builder.EnrollmentBuilder.aPaidEnrollmentBuilder; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import nextstep.courses.CanNotCreateException; +import nextstep.courses.domain.enrollment.Enrollment; +import nextstep.courses.domain.enumerate.CoverImageType; +import nextstep.courses.domain.session.CoverImage; +import nextstep.courses.domain.session.Duration; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionBody; + +public class SessionBuilder { + + private Long id = 1L; + private String creatorId = "1"; + private SessionBody body = new SessionBody("title", "content"); + private Duration duration = new Duration(LocalDate.now().plusDays(1), LocalDate.now().plusDays(3)); + private CoverImage coverImage = new CoverImage(1_500_000, CoverImageType.JPEG, 300, 200); + private Enrollment enrollment; + + public static SessionBuilder aPaidSessionBuilder() throws CanNotCreateException { + return new SessionBuilder() + .withEnrollment(aPaidEnrollmentBuilder().build()); + } + + public static SessionBuilder aFreeSessionBuilder() throws CanNotCreateException { + return new SessionBuilder() + .withEnrollment(aFreeEnrollmentBuilder().build()); + } + + public SessionBuilder(SessionBuilder copy) throws CanNotCreateException { + this.id = copy.id; + this.creatorId = copy.creatorId; + this.body = copy.body; + this.duration = copy.duration; + this.coverImage = copy.coverImage; + this.enrollment = copy.enrollment; + } + + private SessionBuilder() throws CanNotCreateException {} + + public SessionBuilder withId(Long id) { + this.id = id; + return this; + } + + public SessionBuilder withCreatorId(String creatorId) { + this.creatorId = creatorId; + return this; + } + + public SessionBuilder withBody(SessionBody body) { + this.body = body; + return this; + } + + public SessionBuilder withDuration(Duration duration) { + this.duration = duration; + return this; + } + + public SessionBuilder withCoverImage(CoverImage coverImage) { + this.coverImage = coverImage; + return this; + } + + public SessionBuilder withEnrollment(Enrollment enrollment) { + this.enrollment = enrollment; + return this; + } + + public Session build() { + return new Session(id, creatorId, body, duration, coverImage, enrollment, LocalDateTime.now(), null); + } + + public SessionBuilder but() throws CanNotCreateException { + return new SessionBuilder(this); + } +} diff --git a/src/main/java/nextstep/courses/domain/common/Base.java b/src/main/java/nextstep/courses/domain/common/Base.java new file mode 100644 index 000000000..1dc12de80 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/common/Base.java @@ -0,0 +1,26 @@ +package nextstep.courses.domain.common; + +import java.time.LocalDateTime; + +public abstract class Base { + + private final LocalDateTime createdDate; + private final LocalDateTime updatedDate; + + public Base() { + this(LocalDateTime.now(), null); + } + + public Base(LocalDateTime createdDate, LocalDateTime updatedDate) { + this.createdDate = createdDate; + this.updatedDate = updatedDate; + } + + protected LocalDateTime getCreatedDate() { + return createdDate; + } + + protected LocalDateTime getUpdatedDate() { + return updatedDate; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java new file mode 100644 index 000000000..8d7b94d15 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -0,0 +1,73 @@ +package nextstep.courses.domain.course; + +import java.time.LocalDateTime; +import java.util.List; +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.common.Base; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.Sessions; +import nextstep.payments.domain.Payment; + +public class Course extends Base { + + private final Long id; + private final String title; + private final Long creatorId; + private final Sessions sessions; + + public Course(String title, Long creatorId) { + this(0L, title, creatorId, new Sessions(), LocalDateTime.now(), null); + } + + public Course(String title, Long creatorId, List sessions) { + this(0L, title, creatorId, new Sessions(sessions), LocalDateTime.now(), null); + } + + public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { + this(0L, title, creatorId, new Sessions(), LocalDateTime.now(), null); + } + + public Course(Long id, String title, Long creatorId, Sessions sessions, LocalDateTime createdAt, LocalDateTime updatedAt) { + super(createdAt, updatedAt); + this.id = id; + this.title = title; + this.creatorId = creatorId; + this.sessions = sessions; + } + + public String getTitle() { + return title; + } + + public Long getCreatorId() { + return creatorId; + } + + public LocalDateTime getCreatedAt() { + return super.getCreatedDate(); + } + + public Sessions getSessions() { + return sessions; + } + + @Override + public String toString() { + return "Course{" + + "id=" + id + + ", title='" + title + '\'' + + ", creatorId=" + creatorId + + ", sessions=" + sessions + + '}'; + } + + public void enrollCourse(long userId, long sessionId, Payment payment) throws CanNotJoinException { + this.enrollCourse(new SessionApply(userId, payment), sessionId); + } + + public void enrollCourse(SessionApply request, long sessionId) throws CanNotJoinException { + Session session = sessions.findEnrollSession(sessionId); + session.enrollSession(request); + } + +} 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/course/SessionApply.java b/src/main/java/nextstep/courses/domain/course/SessionApply.java new file mode 100644 index 000000000..117431891 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/SessionApply.java @@ -0,0 +1,22 @@ +package nextstep.courses.domain.course; + +import nextstep.payments.domain.Payment; + +public class SessionApply { + + private final long userId; + private final Payment payment; + + public SessionApply(long userId, Payment payment) { + this.userId = userId; + this.payment = payment; + } + + public long getUserId() { + return userId; + } + + public Payment getPayment() { + return payment; + } +} diff --git a/src/main/java/nextstep/courses/domain/enrollment/EnrolledUsers.java b/src/main/java/nextstep/courses/domain/enrollment/EnrolledUsers.java new file mode 100644 index 000000000..cffe57b70 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enrollment/EnrolledUsers.java @@ -0,0 +1,43 @@ +package nextstep.courses.domain.enrollment; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import nextstep.courses.CanNotJoinException; + +public class EnrolledUsers { + + private final List enrolledUserList; + + public EnrolledUsers() { + this(List.of()); + } + + public EnrolledUsers(int size) { + this(new ArrayList<>(Collections.nCopies(size, null))); + } + + public EnrolledUsers(Long... enrolledUserList) { + this(List.of(enrolledUserList)); + } + + public EnrolledUsers(List enrolledUserList) { + this.enrolledUserList = new ArrayList<>(enrolledUserList); + } + + public void registerUserId(Long userId) throws CanNotJoinException { + this.alreadyRegisterUser(userId); + this.enrolledUserList.add(userId); + } + + private void alreadyRegisterUser(Long userId) throws CanNotJoinException { + if(enrolledUserList.contains(userId)) { + throw new CanNotJoinException("이미 수강신청이 완료된 유저입니다"); + } + } + + public boolean isAlreadyExceed(int maxEnrollment) { + return maxEnrollment <= this.enrolledUserList.size(); + } + +} diff --git a/src/main/java/nextstep/courses/domain/enrollment/Enrollment.java b/src/main/java/nextstep/courses/domain/enrollment/Enrollment.java new file mode 100644 index 000000000..fb00a9299 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enrollment/Enrollment.java @@ -0,0 +1,50 @@ +package nextstep.courses.domain.enrollment; + +import nextstep.courses.CanNotCreateException; +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.course.SessionApply; +import nextstep.courses.domain.enrollment.enrollmentcondition.FreeEnrollmentCondition; +import nextstep.courses.domain.enumerate.EnrollmentType; +import nextstep.payments.domain.Payment; + +public class Enrollment { + + private final EnrollmentType type; + private final EnrollmentPolicy policy; + + public Enrollment(EnrollmentType type) throws CanNotCreateException { + this(type, new EnrollmentPolicy(FreeEnrollmentCondition.INSTANCE)); + } + + public Enrollment(EnrollmentType type, EnrollmentPolicy policy) throws CanNotCreateException { + validate(type, policy); + this.type = type; + this.policy = policy; + } + + private void validate(EnrollmentType type, EnrollmentPolicy policy) throws CanNotCreateException { + if(policy.isNotCorrectBetween(type)) { + throw new CanNotCreateException("강의타입과 정책이 일치하지 않습니다"); + } + } + + public void enroll(SessionApply sessionApply) throws CanNotJoinException { + policy.enroll(sessionApply); + } + + public void enroll(Long userid) throws CanNotJoinException { + enroll(new SessionApply(userid, null)); + } + + public void enroll(Long userid, Payment payment) throws CanNotJoinException { + enroll(new SessionApply(userid, payment)); + } + + public boolean isPaid() { + return this.type.isPaid(); + } + + public boolean isFree() { + return this.type.isFree(); + } +} diff --git a/src/main/java/nextstep/courses/domain/enrollment/EnrollmentPolicy.java b/src/main/java/nextstep/courses/domain/enrollment/EnrollmentPolicy.java new file mode 100644 index 000000000..26839fb59 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enrollment/EnrollmentPolicy.java @@ -0,0 +1,48 @@ +package nextstep.courses.domain.enrollment; + +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.course.SessionApply; +import nextstep.courses.domain.enrollment.enrollmentcondition.EnrollmentCondition; +import nextstep.courses.domain.enumerate.EnrollmentType; + +public class EnrollmentPolicy { + + private final EnrollmentCondition enrollmentCondition; + private final EnrolledUsers enrolledUsers; + private final SessionStatus status; + + public EnrollmentPolicy(EnrollmentCondition enrollmentCondition) { + this(enrollmentCondition, new EnrolledUsers(), new SessionStatus()); + } + + public EnrollmentPolicy(EnrollmentCondition enrollmentCondition, EnrolledUsers enrolledUsers, SessionStatus status) { + this.enrollmentCondition = enrollmentCondition; + this.enrolledUsers = enrolledUsers; + this.status = status; + } + + public void enroll(SessionApply sessionApply) throws CanNotJoinException { + validateEnrollStatus(); + enrollmentCondition.isFull(enrolledUsers); + enrollmentCondition.isPaid(sessionApply.getPayment()); + registerUser(sessionApply.getUserId()); + } + + private void validateEnrollStatus() throws CanNotJoinException { + status.isApplyStatus(); + } + + private void registerUser(Long userId) throws CanNotJoinException { + enrolledUsers.registerUserId(userId); + } + + public boolean isNotCorrectBetween(EnrollmentType type) { + if(type.isFree()) { + return !(this.enrollmentCondition.maxEnrollment().isEmpty() && this.enrollmentCondition.tuitionFee().isEmpty()); + } + if(type.isPaid()) { + return !(this.enrollmentCondition.maxEnrollment().isPresent() && this.enrollmentCondition.tuitionFee().isPresent()); + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/enrollment/SessionStatus.java b/src/main/java/nextstep/courses/domain/enrollment/SessionStatus.java new file mode 100644 index 000000000..5ed67d625 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enrollment/SessionStatus.java @@ -0,0 +1,24 @@ +package nextstep.courses.domain.enrollment; + +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.enumerate.SessionStatusType; + +public class SessionStatus { + + private final SessionStatusType sessionStatusType; + + public SessionStatus() { + this(SessionStatusType.PREPARATION); + } + + public SessionStatus(SessionStatusType sessionStatusType) { + this.sessionStatusType = sessionStatusType; + } + + public void isApplyStatus() throws CanNotJoinException { + if(this.sessionStatusType != SessionStatusType.RECRUITING) { + throw new CanNotJoinException("모집 중 일때만 신청 가능합니다"); + } + } + +} diff --git a/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/EnrollmentCondition.java b/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/EnrollmentCondition.java new file mode 100644 index 000000000..84a0f6142 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/EnrollmentCondition.java @@ -0,0 +1,18 @@ +package nextstep.courses.domain.enrollment.enrollmentcondition; + +import java.util.Optional; +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.payments.domain.Payment; + +public interface EnrollmentCondition { + + void isPaid(Payment payment) throws CanNotJoinException; + + void isFull(EnrolledUsers enrolledUsers) throws CanNotJoinException; + + Optional tuitionFee(); + + Optional maxEnrollment(); + +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/FreeEnrollmentCondition.java b/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/FreeEnrollmentCondition.java new file mode 100644 index 000000000..4c1141dcb --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/FreeEnrollmentCondition.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.enrollment.enrollmentcondition; + +import java.util.Optional; +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.payments.domain.Payment; + +public class FreeEnrollmentCondition implements EnrollmentCondition { + + public static final FreeEnrollmentCondition INSTANCE = new FreeEnrollmentCondition(); + + private FreeEnrollmentCondition() {} + + @Override + public void isPaid(Payment payment) { + return; + } + + @Override + public void isFull(EnrolledUsers enrolledUsers) { + return; + } + + @Override + public Optional tuitionFee() { + return Optional.empty(); + } + + @Override + public Optional maxEnrollment() { + return Optional.empty(); + } +} diff --git a/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/PaidEnrollmentCondition.java b/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/PaidEnrollmentCondition.java new file mode 100644 index 000000000..52503538f --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enrollment/enrollmentcondition/PaidEnrollmentCondition.java @@ -0,0 +1,51 @@ +package nextstep.courses.domain.enrollment.enrollmentcondition; + +import java.util.Optional; +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.payments.domain.Payment; + +public class PaidEnrollmentCondition implements EnrollmentCondition { + + private final long tuitionFee; + private final int maxEnrollment; + + public PaidEnrollmentCondition(long tuitionFee, int maxEnrollment) { + validate(tuitionFee, maxEnrollment); + this.tuitionFee = tuitionFee; + this.maxEnrollment = maxEnrollment; + } + + private void validate(long tuitionFee, int maxEnrollment) { + if(tuitionFee <= 0) { + throw new IllegalArgumentException("수강료는 양수이어야 한다"); + } + if(maxEnrollment <= 0) { + throw new IllegalArgumentException("정원은 양수이어야 한다"); + } + } + + @Override + public void isPaid(Payment payment) throws CanNotJoinException { + if(payment.isNotSameAmount(this.tuitionFee)) { + throw new CanNotJoinException("지불한 금액과 수강료 금액이 다르다"); + } + } + + @Override + public void isFull(EnrolledUsers enrolledUsers) throws CanNotJoinException { + if(enrolledUsers.isAlreadyExceed(this.maxEnrollment)) { + throw new CanNotJoinException("이미 정원을 초과했다"); + } + } + + @Override + public Optional tuitionFee() { + return Optional.of(tuitionFee); + } + + @Override + public Optional maxEnrollment() { + return Optional.of(maxEnrollment); + } +} diff --git a/src/main/java/nextstep/courses/domain/enumerate/CoverImageType.java b/src/main/java/nextstep/courses/domain/enumerate/CoverImageType.java new file mode 100644 index 000000000..30aa7c242 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enumerate/CoverImageType.java @@ -0,0 +1,28 @@ +package nextstep.courses.domain.enumerate; + +import nextstep.courses.CanNotCreateException; + +public enum CoverImageType { + GIF("gif"), + JPG("jpg"), + JPEG("jpeg"), + PNG("png"), + SVG("svg"); + + CoverImageType(String type) {} + + public static CoverImageType valueOfIgnoreCase(String type) throws CanNotCreateException { + validate(type); + return Enum.valueOf(CoverImageType.class, type); + } + + private static void validate(String type) throws CanNotCreateException { + if(isNotCoverImageType(type)) { + throw new CanNotCreateException("커버 이미지의 타입은 gif, jpg(jpeg), png, svg 만 가능하다"); + } + } + + private static boolean isNotCoverImageType(String type) { + return !(type.equals("gif") || type.equals("jpg") || type.equals("jpeg") || type.equals("png") || type.equals("svg")); + } +} diff --git a/src/main/java/nextstep/courses/domain/enumerate/EnrollmentType.java b/src/main/java/nextstep/courses/domain/enumerate/EnrollmentType.java new file mode 100644 index 000000000..bb6213e0e --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enumerate/EnrollmentType.java @@ -0,0 +1,19 @@ +package nextstep.courses.domain.enumerate; + +public enum EnrollmentType { + PAID("paid", true), + FREE("free", false); + + private String provideType; + private boolean isExistTuitionFee; + + EnrollmentType(String provideType, boolean isExistTuitionFee) {} + + public boolean isFree() { + return this == EnrollmentType.FREE; + } + + public boolean isPaid() { + return this == EnrollmentType.PAID; + } +} diff --git a/src/main/java/nextstep/courses/domain/enumerate/SessionStatusType.java b/src/main/java/nextstep/courses/domain/enumerate/SessionStatusType.java new file mode 100644 index 000000000..6f6d76855 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/enumerate/SessionStatusType.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain.enumerate; + +public enum SessionStatusType { + PREPARATION, RECRUITING, END +} diff --git a/src/main/java/nextstep/courses/domain/session/CoverImage.java b/src/main/java/nextstep/courses/domain/session/CoverImage.java new file mode 100644 index 000000000..1f3e6e08d --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/CoverImage.java @@ -0,0 +1,34 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.CanNotCreateException; +import nextstep.courses.domain.enumerate.CoverImageType; + +public class CoverImage { + + public static final int ONE_MEGA_BYTE = 1_000_000; // 1mb 는 1_000_000 byte 로 가정 + private final int size; // byte + private final CoverImageType type; + private final Dimensions dimensions; + + public CoverImage(int size, CoverImageType type, double width, double height) throws CanNotCreateException { + this(size, type, new Dimensions(width, height)); + } + + public CoverImage(int size, String type, Dimensions dimensions) throws CanNotCreateException { + this(size, CoverImageType.valueOfIgnoreCase(type), dimensions); + } + + public CoverImage(int size, CoverImageType type, Dimensions dimensions) throws CanNotCreateException { + validate(size); + this.size = size; + this.type = type; + this.dimensions = dimensions; + } + + private void validate(int size) throws CanNotCreateException { + if(size < ONE_MEGA_BYTE) { + throw new CanNotCreateException("커버 이미지의 크기는 1MB 이상이어야 한다"); + } + } + +} diff --git a/src/main/java/nextstep/courses/domain/session/Dimensions.java b/src/main/java/nextstep/courses/domain/session/Dimensions.java new file mode 100644 index 000000000..47e646f9e --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Dimensions.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.CanNotCreateException; + +public class Dimensions { + + public static final double COVER_IMAGE_RATIO = 1.5; + private final double width; + private final double height; + private final double ratio; + + public Dimensions(double width, double height) throws CanNotCreateException { + validate(width, height); + this.width = width; + this.height = height; + this.ratio = width / height; + } + + private void validate(double width, double height) throws CanNotCreateException { + if(width < 300) { + throw new CanNotCreateException("이미지의 너비(width)는 300px 이상이어야 한다"); + } + + if(height < 200) { + throw new CanNotCreateException("이미지의 높이(height)는 200px 이상이어야 한다"); + } + + if(width / height != COVER_IMAGE_RATIO) { + throw new CanNotCreateException("이미지의 너비(width)와 높이(height)의 비율은 3:2 이어야 한다"); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/session/Duration.java b/src/main/java/nextstep/courses/domain/session/Duration.java new file mode 100644 index 000000000..849ee79a2 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Duration.java @@ -0,0 +1,40 @@ +package nextstep.courses.domain.session; + +import java.time.LocalDate; +import nextstep.courses.CanNotCreateException; + +public class Duration { + + private final LocalDate startDate; + private final LocalDate endDate; + + public Duration(String startDate, String endDate) { + this.startDate = LocalDate.parse(startDate); + this.endDate = LocalDate.parse(endDate); + } + + public Duration(LocalDate startDate, LocalDate endDate) throws CanNotCreateException { + validate(startDate, endDate); + this.startDate = startDate; + this.endDate = endDate; + } + + private void validate(LocalDate startDate, LocalDate endDate) throws CanNotCreateException { + if(isBeforeDateThenCurrentDate(startDate, endDate)) { + throw new CanNotCreateException("강의 날짜는 오늘에서 다음날에 존재해야 한다"); + } + if(isEndDateEarly(startDate, endDate)) { + throw new CanNotCreateException("강의 종료일이 시작일보다 앞에 있을 수 없다"); + } + + } + + private static boolean isEndDateEarly(LocalDate startDate, LocalDate endDate) { + return endDate.isBefore(startDate); + } + + private static boolean isBeforeDateThenCurrentDate(LocalDate startDate, LocalDate endDate) { + LocalDate currentDate = LocalDate.now(); + return startDate.isBefore(currentDate) || endDate.isBefore(currentDate); + } +} \ 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 new file mode 100644 index 000000000..c75e27c0e --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -0,0 +1,50 @@ +package nextstep.courses.domain.session; + +import java.time.LocalDateTime; +import java.util.Objects; +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.common.Base; +import nextstep.courses.domain.course.SessionApply; +import nextstep.courses.domain.enrollment.Enrollment; +import nextstep.payments.domain.Payment; + +public class Session extends Base { + + private final Long id; + private final String creatorId; + private final SessionBody body; + private final Duration duration; + private final CoverImage coverImage; + private final Enrollment enrollment; + + public Session(String creatorId, SessionBody body, Duration duration, CoverImage coverImage, Enrollment enrollment) { + this(0L, creatorId, body, duration, coverImage, enrollment, LocalDateTime.now(), null); + } + + public Session(Long id, String creatorId, SessionBody body, Duration duration, CoverImage coverImage, Enrollment enrollment, LocalDateTime createdDate, LocalDateTime updatedDate) { + super(createdDate, updatedDate); + this.id = id; + this.creatorId = creatorId; + this.body = body; + this.duration = duration; + this.coverImage = coverImage; + this.enrollment = enrollment; + } + + public void enrollSession(SessionApply request) throws CanNotJoinException { + enrollment.enroll(request); + } + + public void enrollSession(Long userid) throws CanNotJoinException { + enrollSession(new SessionApply(userid, null)); + } + + public void enrollSession(Long userid, Payment payment) throws CanNotJoinException { + enrollSession(new SessionApply(userid, payment)); + } + + public boolean isSameSessionId(Long id) { + return Objects.equals(this.id, id); + } + +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/SessionBody.java b/src/main/java/nextstep/courses/domain/session/SessionBody.java new file mode 100644 index 000000000..8b76c501a --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionBody.java @@ -0,0 +1,24 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.CanNotCreateException; + +public class SessionBody { + + private final String title; + private final String content; + + public SessionBody(String title, String content) throws CanNotCreateException { + validate(title, content); + this.title = title; + this.content = content; + } + + private void validate(String title, String content) throws CanNotCreateException { + if(title == null) { + throw new CanNotCreateException("제목에 내용이 없다"); + } + if(content == null) { + throw new CanNotCreateException("컨텐츠에 내용이 없다"); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/session/Sessions.java b/src/main/java/nextstep/courses/domain/session/Sessions.java new file mode 100644 index 000000000..76c52f41a --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/Sessions.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.session; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import nextstep.courses.CanNotJoinException; + +public class Sessions { + + private final List sessions; + + public Sessions() { + this.sessions = new ArrayList<>(); + } + + public Sessions(Session... sessions) { + this(Arrays.asList(sessions)); + } + + public Sessions(List sessions) { + this.sessions = sessions; + } + + public Session findEnrollSession(long sessionId) throws CanNotJoinException { + for(Session session: this.sessions) { + if(session.isSameSessionId(sessionId)) { + return session; + } + } + throw new CanNotJoinException("신청하려는 강의가 존재하지 않습니다"); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index f9122cbe3..d4225ec92 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -1,14 +1,13 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import java.sql.Timestamp; +import java.time.LocalDateTime; +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; -import java.sql.Timestamp; -import java.time.LocalDateTime; - @Repository("courseRepository") public class JdbcCourseRepository implements CourseRepository { private JdbcOperations jdbcTemplate; diff --git a/src/main/java/nextstep/courses/service/CourseService.java b/src/main/java/nextstep/courses/service/CourseService.java new file mode 100644 index 000000000..44776b431 --- /dev/null +++ b/src/main/java/nextstep/courses/service/CourseService.java @@ -0,0 +1,27 @@ +package nextstep.courses.service; + +import javax.annotation.Resource; +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.domain.course.SessionApply; +import nextstep.payments.domain.Payment; +import nextstep.payments.service.PaymentService; +import nextstep.users.domain.NsUser; +import org.springframework.transaction.annotation.Transactional; + +public class CourseService { + + @Resource(name = "courseRepository") + private CourseRepository courseRepository; + + @Transactional + public void enroll(NsUser loginUser, long courseId, long sessionId) throws CanNotJoinException { + Course course = courseRepository.findById(courseId); + + Payment payment = new PaymentService().payment("id"); + course.enrollCourse(new SessionApply(loginUser.getId(), payment), sessionId); + courseRepository.save(course); + } + +} diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 57d833f85..3aa24f044 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -18,6 +18,10 @@ public class Payment { public Payment() { } + + public Payment(Payment payment) { + this(payment.id, payment.sessionId, payment.nsUserId, payment.amount); + } public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.id = id; @@ -26,4 +30,9 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.amount = amount; this.createdAt = LocalDateTime.now(); } + + public boolean isNotSameAmount(Long tuitionFee) { + // 항상 false 리턴 (결제는 이미 완료된 것으로 가정) + return false; + } } diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index 5590ede7f..77ace4781 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -60,7 +60,7 @@ private void isHaveAuthority(NsUser loginUser) throws CannotDeleteException { } } - public boolean isNotOwner(NsUser writer) { + private boolean isNotOwner(NsUser writer) { return !this.writer.equals(writer); } diff --git a/src/main/java/nextstep/qna/domain/Answers.java b/src/main/java/nextstep/qna/domain/Answers.java index 2aded442b..34643c7ef 100644 --- a/src/main/java/nextstep/qna/domain/Answers.java +++ b/src/main/java/nextstep/qna/domain/Answers.java @@ -46,11 +46,6 @@ public List addInDeleteHistory() { .collect(Collectors.toList()); } - private boolean isNotAllMatch(NsUser loginUser) { - return this.answers.stream() - .anyMatch(answer -> !answer.isNotOwner(loginUser)); - } - @Override public boolean equals(Object o) { if(o == null || getClass() != o.getClass()) { diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index ad3759a21..8b8d14e4e 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -4,7 +4,7 @@ import nextstep.qna.CannotDeleteException; import nextstep.users.domain.NsUser; -public class Question extends Base{ +public class Question extends Base { private Long id; private String title; 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 000000000..b76c25aec --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/CourseTest.java @@ -0,0 +1,105 @@ +package nextstep.courses.domain.course; + +import static nextstep.courses.domain.builder.EnrollmentBuilder.aFreeEnrollmentBuilder; +import static nextstep.courses.domain.builder.EnrollmentBuilder.aPaidEnrollmentBuilder; +import static nextstep.courses.domain.builder.EnrollmentPolicyBuilder.aFreeEnrollmentPolicyBuilder; +import static nextstep.courses.domain.builder.EnrollmentPolicyBuilder.aPaidEnrollmentPolicyBuilder; +import static nextstep.courses.domain.builder.SessionBuilder.aFreeSessionBuilder; +import static nextstep.courses.domain.builder.SessionBuilder.aPaidSessionBuilder; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import nextstep.courses.CanNotCreateException; +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.courses.domain.session.Session; +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.Test; + +class CourseTest { + + public static final Session freeSession; + public static final Session paidSession; + + static { + try { + freeSession = aFreeSessionBuilder().build(); + paidSession = aPaidSessionBuilder().build(); + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void 수강신청하는_session이_없으면_예외전파() { + Course course = new Course("title", 1L, List.of(freeSession, paidSession)); + assertThatThrownBy(() -> { + course.enrollCourse(NsUserTest.JAVAJIGI.getId(), 3L, null); + }).isInstanceOf(CanNotJoinException.class) + .hasMessage("신청하려는 강의가 존재하지 않습니다"); + } + + @Test + void 무료강의_수강신청() throws CanNotCreateException { + Session freeSession = aFreeSessionBuilder() + .withEnrollment( + aFreeEnrollmentBuilder() + .withEnrollmentPolicy( + aFreeEnrollmentPolicyBuilder() + .withEnrolledUsers(new EnrolledUsers(List.of(10L, 11L, 12L, 13L, 14L, 15L))) + .build() + ).build() + ) + .build(); + Session paidSession = aPaidSessionBuilder() + .withId(2L) + .withEnrollment( + aPaidEnrollmentBuilder() + .withEnrollmentPolicy( + aPaidEnrollmentPolicyBuilder() + .withEnrolledUsers(new EnrolledUsers(List.of(10L, 11L, 12L, 13L, 14L, 15L))) + .build() + ).build() + ) + .build(); + + Course course = new Course("title", 1L, List.of(freeSession, paidSession)); + + assertThatNoException().isThrownBy(() -> { + course.enrollCourse(NsUserTest.JAVAJIGI.getId(), 1L, null); + }); + } + + @Test + void 유료강의_수강신청() throws CanNotCreateException { + Session freeSession = aFreeSessionBuilder() + .withEnrollment( + aFreeEnrollmentBuilder() + .withEnrollmentPolicy( + aFreeEnrollmentPolicyBuilder() + .withEnrolledUsers(new EnrolledUsers(List.of(10L, 11L, 12L, 13L, 14L, 15L))) + .build() + ).build() + ) + .build(); + Session paidSession = aPaidSessionBuilder() + .withId(2L) + .withEnrollment( + aPaidEnrollmentBuilder() + .withEnrollmentPolicy( + aPaidEnrollmentPolicyBuilder() + .withEnrolledUsers(new EnrolledUsers(List.of(10L, 11L, 12L, 13L, 14L, 15L))) + .build() + ).build() + ) + .build(); + Course course = new Course("title", 1L, List.of(freeSession, paidSession)); + + assertThatNoException().isThrownBy(() -> { + course.enrollCourse(NsUserTest.SANJIGI.getId(), 2L, new Payment()); + }); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/enrollment/EnrolledUsersTest.java b/src/test/java/nextstep/courses/domain/enrollment/EnrolledUsersTest.java new file mode 100644 index 000000000..3de39314f --- /dev/null +++ b/src/test/java/nextstep/courses/domain/enrollment/EnrolledUsersTest.java @@ -0,0 +1,28 @@ +package nextstep.courses.domain.enrollment; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import nextstep.courses.CanNotJoinException; +import org.junit.jupiter.api.Test; + +class EnrolledUsersTest { + + @Test + void 이미등록된_회원이다() { + EnrolledUsers enrolledUsers = new EnrolledUsers(List.of(1L, 2L, 3L)); + assertThatThrownBy(() -> { + enrolledUsers.registerUserId(3L); + } + ).isInstanceOf(CanNotJoinException.class) + .hasMessage("이미 수강신청이 완료된 유저입니다"); + } + + @Test + void 등록고객에_수를_추가한다() throws CanNotJoinException { + EnrolledUsers enrolledUsers = new EnrolledUsers(List.of(1L, 2L, 3L)); + assertThatNoException().isThrownBy(() -> enrolledUsers.registerUserId(4L)); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/enrollment/EnrollmentPolicyTest.java b/src/test/java/nextstep/courses/domain/enrollment/EnrollmentPolicyTest.java new file mode 100644 index 000000000..237a1efb9 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/enrollment/EnrollmentPolicyTest.java @@ -0,0 +1,37 @@ +package nextstep.courses.domain.enrollment; + +import static nextstep.courses.domain.builder.EnrollmentPolicyBuilder.aFreeEnrollmentPolicyBuilder; +import static nextstep.courses.domain.builder.EnrollmentPolicyBuilder.aPaidEnrollmentPolicyBuilder; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.course.SessionApply; +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.Test; + +class EnrollmentPolicyTest { + + @Test + void 수강신청_인원이_초과하면_에러전파() { + EnrollmentPolicy enrollmentPolicy = aPaidEnrollmentPolicyBuilder() + .withEnrolledUsers(new EnrolledUsers(10)) + .build(); + SessionApply sessionApply = new SessionApply(10L, new Payment()); + + assertThatThrownBy(() -> { + enrollmentPolicy.enroll(sessionApply); + }).isInstanceOf(CanNotJoinException.class) + .hasMessage("이미 정원을 초과했다"); + } + + @Test + void 무료강의인데_수강신청_시_정원체크를_해도_상관없다() { + SessionApply sessionApply = new SessionApply(1000L, new Payment()); + EnrollmentPolicy enrollmentPolicy = aFreeEnrollmentPolicyBuilder().build(); + + assertThatNoException().isThrownBy(() -> { + enrollmentPolicy.enroll(sessionApply); + }); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/enrollment/EnrollmentTest.java b/src/test/java/nextstep/courses/domain/enrollment/EnrollmentTest.java new file mode 100644 index 000000000..139317626 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/enrollment/EnrollmentTest.java @@ -0,0 +1,41 @@ +package nextstep.courses.domain.enrollment; + +import static nextstep.courses.domain.builder.EnrollmentBuilder.aFreeEnrollmentBuilder; +import static nextstep.courses.domain.builder.EnrollmentBuilder.aPaidEnrollmentBuilder; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.CanNotCreateException; +import nextstep.courses.domain.builder.EnrollmentPolicyBuilder; +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.Test; + +class EnrollmentTest { + + @Test + void 무료강의인데_수강료가있으면_에러전파() { + assertThatThrownBy(() -> { + aFreeEnrollmentBuilder() + .withEnrollmentPolicy( + EnrollmentPolicyBuilder.aPaidEnrollmentPolicyBuilder().build() + ) + .build(); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("강의타입과 정책이 일치하지 않습니다"); + } + + @Test + void 유료강의에_수강신청한다() throws Exception { + Enrollment enrollment = aPaidEnrollmentBuilder().build(); + assertThatNoException().isThrownBy(() -> { + enrollment.enroll(10L, new Payment()); + }); + } + + @Test + void 무료강의에_지불_없이_수강신청한다() throws Exception { + Enrollment enrollment = aFreeEnrollmentBuilder().build(); + assertThatNoException().isThrownBy(() -> enrollment.enroll(10L)); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/enrollment/SessionStatusTest.java b/src/test/java/nextstep/courses/domain/enrollment/SessionStatusTest.java new file mode 100644 index 000000000..4197560f0 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/enrollment/SessionStatusTest.java @@ -0,0 +1,19 @@ +package nextstep.courses.domain.enrollment; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.enumerate.SessionStatusType; +import org.junit.jupiter.api.Test; + +class SessionStatusTest { + + @Test + void 강의_상태가_모집중이_아닌데_신청하면_flase() { + SessionStatus sessionStatus = new SessionStatus(SessionStatusType.PREPARATION); + assertThatThrownBy(sessionStatus::isApplyStatus) + .isInstanceOf(CanNotJoinException.class) + .hasMessage("모집 중 일때만 신청 가능합니다"); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/enrollment/enrollmentcondition/FreeEnrollmentConditionTest.java b/src/test/java/nextstep/courses/domain/enrollment/enrollmentcondition/FreeEnrollmentConditionTest.java new file mode 100644 index 000000000..5044220f2 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/enrollment/enrollmentcondition/FreeEnrollmentConditionTest.java @@ -0,0 +1,17 @@ +package nextstep.courses.domain.enrollment.enrollmentcondition; + +import static org.assertj.core.api.Assertions.assertThatNoException; + +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.Test; + +class FreeEnrollmentConditionTest { + + @Test + void 무료강의에_지불_없이_수강신청한다() throws Exception { + FreeEnrollmentCondition freeEnrollmentCondition = FreeEnrollmentCondition.INSTANCE; + assertThatNoException().isThrownBy(() -> freeEnrollmentCondition.isPaid(new Payment())); + assertThatNoException().isThrownBy(() -> freeEnrollmentCondition.isFull(new EnrolledUsers(100))); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/enrollment/enrollmentcondition/PaidEnrollmentConditionTest.java b/src/test/java/nextstep/courses/domain/enrollment/enrollmentcondition/PaidEnrollmentConditionTest.java new file mode 100644 index 000000000..6c592d801 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/enrollment/enrollmentcondition/PaidEnrollmentConditionTest.java @@ -0,0 +1,49 @@ +package nextstep.courses.domain.enrollment.enrollmentcondition; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PaidEnrollmentConditionTest { + + @ParameterizedTest + @ValueSource(longs = {0, -1}) + void 수강료가_음수이면_에러전파(long tuitionFee) { + assertThatThrownBy(() -> new PaidEnrollmentCondition(tuitionFee, 10)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("수강료는 양수이어야 한다"); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1}) + void 정원이_음수이면_에러전파(int maxEnrollment) { + assertThatThrownBy(() -> new PaidEnrollmentCondition(5L, maxEnrollment)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("정원은 양수이어야 한다"); + } + + @Test + void 유료강의에_수강신청한다() { + PaidEnrollmentCondition paidEnrollmentCondition = new PaidEnrollmentCondition(5L, 10); + assertThatNoException().isThrownBy(() -> { + paidEnrollmentCondition.isPaid(new Payment()); + assertThatNoException().isThrownBy(() -> paidEnrollmentCondition.isFull(new EnrolledUsers(8))); + }); + } + + @Test + void 유료강의에_수강신청에_정원이_초과하면_에러전파() { + PaidEnrollmentCondition paidEnrollmentCondition + = new PaidEnrollmentCondition(5L, 10); + + assertThatThrownBy(() -> paidEnrollmentCondition.isFull(new EnrolledUsers(10))) + .isInstanceOf(CanNotJoinException.class) + .hasMessage("이미 정원을 초과했다"); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/enumerate/CoverImageTypeTest.java b/src/test/java/nextstep/courses/domain/enumerate/CoverImageTypeTest.java new file mode 100644 index 000000000..2f8aa51bb --- /dev/null +++ b/src/test/java/nextstep/courses/domain/enumerate/CoverImageTypeTest.java @@ -0,0 +1,17 @@ +package nextstep.courses.domain.enumerate; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.CanNotCreateException; +import org.junit.jupiter.api.Test; + +class CoverImageTypeTest { + + @Test + void 커버이미지의_정해진타입이_아니면_에러전파() { + assertThatThrownBy(() -> { + CoverImageType.valueOfIgnoreCase("BMP"); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("커버 이미지의 타입은 gif, jpg(jpeg), png, svg 만 가능하다"); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/CoverImageTest.java b/src/test/java/nextstep/courses/domain/session/CoverImageTest.java new file mode 100644 index 000000000..0845b5814 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/CoverImageTest.java @@ -0,0 +1,18 @@ +package nextstep.courses.domain.session; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.CanNotCreateException; +import nextstep.courses.domain.enumerate.CoverImageType; +import org.junit.jupiter.api.Test; + +class CoverImageTest { + + @Test + void 커버이미지_크기는_1mb이상이면_에러전파() { + assertThatThrownBy(() -> { + new CoverImage(900_000, CoverImageType.JPEG, 300, 200); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("커버 이미지의 크기는 1MB 이상이어야 한다"); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/DimensionsTest.java b/src/test/java/nextstep/courses/domain/session/DimensionsTest.java new file mode 100644 index 000000000..737307f0c --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/DimensionsTest.java @@ -0,0 +1,34 @@ +package nextstep.courses.domain.session; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.CanNotCreateException; +import org.junit.jupiter.api.Test; + +class DimensionsTest { + + @Test + void 커버이미지의_정해진_크기_이하이면_에러전파() { + assertThatThrownBy(() -> { + new Dimensions(299, 200); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("이미지의 너비(width)는 300px 이상이어야 한다"); + } + + @Test + void 커버이미지의_정해진_비율이_아니면_에러전파() { + assertThatThrownBy(() -> { + new Dimensions(300, 199); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("이미지의 높이(height)는 200px 이상이어야 한다"); + } + + @Test + void 커버이미지의_너비와_높이_비율은_3대2가_아니면_에러전파() { + assertThatThrownBy(() -> { + new Dimensions(300, 300); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("이미지의 너비(width)와 높이(height)의 비율은 3:2 이어야 한다"); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/DurationTest.java b/src/test/java/nextstep/courses/domain/session/DurationTest.java new file mode 100644 index 000000000..0cc241776 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/DurationTest.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.session; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.LocalDate; +import nextstep.courses.CanNotCreateException; +import org.junit.jupiter.api.Test; + +class DurationTest { + + @Test + void 강의의_시작과_종료일은_오늘보다_앞에있으면_에러전파() { + LocalDate startDate = LocalDate.now().minusDays(2); + LocalDate endDate = LocalDate.now().minusDays(1); + + assertThatThrownBy(() -> { + new Duration(startDate, endDate); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("강의 날짜는 오늘에서 다음날에 존재해야 한다"); + } + + @Test + void 강의_종료일이_시작일보다_앞에있으면_에러전파() { + LocalDate startDate = LocalDate.now().plusDays(2); + LocalDate endDate = LocalDate.now().plusDays(1); + + assertThatThrownBy(() -> { + new Duration(startDate, endDate); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("강의 종료일이 시작일보다 앞에 있을 수 없다"); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionBodyTest.java b/src/test/java/nextstep/courses/domain/session/SessionBodyTest.java new file mode 100644 index 000000000..6e1db0447 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionBodyTest.java @@ -0,0 +1,23 @@ +package nextstep.courses.domain.session; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.CanNotCreateException; +import org.junit.jupiter.api.Test; + +class SessionBodyTest { + + @Test + void session_body의_제목과_본문은_null이면_에러전파() { + assertThatThrownBy(() -> { + new SessionBody(null, "content"); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("제목에 내용이 없다"); + + assertThatThrownBy(() -> { + new SessionBody("title", null); + }).isInstanceOf(CanNotCreateException.class) + .hasMessage("컨텐츠에 내용이 없다"); + } + +} \ 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..18d3bf3e9 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -0,0 +1,80 @@ +package nextstep.courses.domain.session; + +import static nextstep.courses.domain.builder.EnrollmentBuilder.aFreeEnrollmentBuilder; +import static nextstep.courses.domain.builder.EnrollmentBuilder.aPaidEnrollmentBuilder; +import static nextstep.courses.domain.builder.EnrollmentPolicyBuilder.aFreeEnrollmentPolicyBuilder; +import static nextstep.courses.domain.builder.EnrollmentPolicyBuilder.aPaidEnrollmentPolicyBuilder; +import static nextstep.courses.domain.builder.SessionBuilder.aFreeSessionBuilder; +import static nextstep.courses.domain.builder.SessionBuilder.aPaidSessionBuilder; +import static org.assertj.core.api.Assertions.assertThatNoException; + +import java.util.List; +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.Test; + +class SessionTest { + + public static final Session freeSession; + public static final Session paidSession; + + static { + try { + freeSession = aFreeSessionBuilder().build(); + paidSession = aPaidSessionBuilder().build(); + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void 무료_session을_생성한다() { + assertThatNoException().isThrownBy(() -> { + aFreeSessionBuilder().build(); + }); + } + + + @Test + void 유료_session을_생성한다() throws Exception { + assertThatNoException().isThrownBy(() -> { + aPaidSessionBuilder().build(); + }); + } + + @Test + void 무료_session을_수강신청한다() { + assertThatNoException().isThrownBy(() -> { + Session freeSession = aFreeSessionBuilder() + .withEnrollment( + aFreeEnrollmentBuilder() + .withEnrollmentPolicy( + aFreeEnrollmentPolicyBuilder() + .withEnrolledUsers(new EnrolledUsers(List.of(10L, 11L, 12L, 13L, 14L, 15L))) + .build() + ).build() + ) + .build(); + freeSession.enrollSession(NsUserTest.JAVAJIGI.getId()); + }); + } + + @Test + void 유료_session을_수강신청한다() { + assertThatNoException().isThrownBy(() -> { + Session paidSession = aPaidSessionBuilder() + .withEnrollment( + aPaidEnrollmentBuilder() + .withEnrollmentPolicy( + aPaidEnrollmentPolicyBuilder() + .withEnrolledUsers(new EnrolledUsers(List.of(10L, 11L, 12L, 13L, 14L, 15L))) + .build() + ).build() + ) + .build(); + paidSession.enrollSession(NsUserTest.JAVAJIGI.getId(), new Payment()); + }); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/session/SessionsTest.java new file mode 100644 index 000000000..b0770bb12 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/SessionsTest.java @@ -0,0 +1,71 @@ +package nextstep.courses.domain.session; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import nextstep.courses.CanNotJoinException; +import nextstep.courses.domain.enrollment.EnrolledUsers; +import nextstep.courses.domain.enrollment.Enrollment; +import nextstep.courses.domain.enrollment.EnrollmentPolicy; +import nextstep.courses.domain.enrollment.SessionStatus; +import nextstep.courses.domain.enrollment.enrollmentcondition.FreeEnrollmentCondition; +import nextstep.courses.domain.enrollment.enrollmentcondition.PaidEnrollmentCondition; +import nextstep.courses.domain.enumerate.CoverImageType; +import nextstep.courses.domain.enumerate.EnrollmentType; +import nextstep.courses.domain.enumerate.SessionStatusType; +import org.junit.jupiter.api.Test; + +class SessionsTest { + + public static final Session freeSession; + public static final Session paidSession; + + static { + try { + freeSession = new Session( + 1L, + "1", + new SessionBody("title", "content"), + new Duration(LocalDate.now().plusDays(1), LocalDate.now().plusDays(3)), + new CoverImage(1_500_000, CoverImageType.JPEG, 300, 200), + new Enrollment( + EnrollmentType.FREE, + new EnrollmentPolicy( + FreeEnrollmentCondition.INSTANCE, + new EnrolledUsers(List.of(1L, 2L, 3L, 4L, 5L)), + new SessionStatus(SessionStatusType.RECRUITING))), + LocalDateTime.now(), + null + ); + paidSession = new Session( + 2L, + "1", + new SessionBody("title", "content"), + new Duration(LocalDate.now().plusDays(1), LocalDate.now().plusDays(3)), + new CoverImage(1_500_000, CoverImageType.JPEG, 300, 200), + new Enrollment( + EnrollmentType.PAID, + new EnrollmentPolicy( + new PaidEnrollmentCondition(10L, 10), + new EnrolledUsers(List.of(1L, 2L, 3L, 4L, 5L)), + new SessionStatus(SessionStatusType.RECRUITING))), + LocalDateTime.now(), + null + ); + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void 찾고자하는_세션이_없으면_에러전파() { + assertThatThrownBy(() -> { + new Sessions(freeSession, paidSession) + .findEnrollSession(3L); + }) + .isInstanceOf(CanNotJoinException.class) + .hasMessage("신청하려는 강의가 존재하지 않습니다"); + } +} \ 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..a1279a37f 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -1,7 +1,9 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import static org.assertj.core.api.Assertions.assertThat; + +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; @@ -10,8 +12,6 @@ import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.jdbc.core.JdbcTemplate; -import static org.assertj.core.api.Assertions.assertThat; - @JdbcTest public class CourseRepositoryTest { private static final Logger LOGGER = LoggerFactory.getLogger(CourseRepositoryTest.class);