diff --git a/STEP4.md b/STEP4.md new file mode 100644 index 000000000..5f1cc2329 --- /dev/null +++ b/STEP4.md @@ -0,0 +1,39 @@ +## 변경된 기능 요구사항 +강의 수강신청은 강의 상태가 모집중일 때만 가능하다. + * 강의가 진행 중인 상태에서도 수강신청이 가능해야 한다. + * 강의 진행 상태(준비중, 진행중, 종료)와 모집 상태(비모집중, 모집중)로 상태 값을 분리해야 한다. +강의는 강의 커버 이미지 정보를 가진다. + * 강의는 하나 이상의 커버 이미지를 가질 수 있다. +강사가 승인하지 않아도 수강 신청하는 모든 사람이 수강 가능하다. + * 우아한테크코스(무료), 우아한테크캠프 Pro(유료)와 같이 선발된 인원만 수강 가능해야 한다. + * 강사는 수강신청한 사람 중 선발된 인원에 대해서만 수강 승인이 가능해야 한다. + * 강사는 수강신청한 사람 중 선발되지 않은 사람은 수강을 취소할 수 있어야 한다. + +## 프로그래밍 요구사항 +* 리팩터링할 때 컴파일 에러와 기존의 단위 테스트의 실패를 최소화하면서 점진적인 리팩터링이 가능하도록 한다. +* DB 테이블에 데이터가 존재한다는 가정하에 리팩터링해야 한다. + * 즉, 기존에 쌓인 데이터를 제거하지 않은 상태로 리팩터링 해야 한다. +### 핵심 학습 목표 +* DB 테이블이 변경될 때도 스트랭글러 패턴을 적용해 점진적인 리팩터링을 연습한다. + * 스트랭글러(교살자) 패턴 - 마틴 파울러 + * 스트랭글러 무화과 패턴 + +## STEP4 기능분해 +* 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. +* [X] 강의가 진행중이어도 모집중이면 수강신청이 가능하다. +* [X] 강의가 진행중에 비모집중이면 수강신청 불가능하다는 Exception이 발생한다. +* [X] 강의가 준비중에 모집중이면 수강신청 가능하다. +* [X] 강의가 준비중이어도 비모집중이면 수강신청이 불가능하다는 Exception이 발생한다. +* 강의는 강의 커버 이미지 정보를 가진다. +* [X] 강의는 하나 이상의 커버 이미지를 갖는다. +* 강사가 승인하지 않아도 수강 신청하는 모든 사람이 수강 가능하다. +* [X] 수강신청한 사람 중 선발되지 않은 수강생은 수강 취소가 된다. +* [X] 수강신청한 사람 중 선발된 사람에 대해 수강 승인이 가능하다. + +## STEP4 리팩토링 +* [X] Session 인스턴스 변수 줄이기 + +## STEP4 보완사항 +* [ ] List Collection 중 일급 콜렉션으로 구현하면 의미있는 필드가 있을까? 의미 있다 생각하는 필드를 일급 콜렉션으로 구현해 보는 것은 어떨까? +* [X] equals, hashCode, toString 사용하기 -> 사용하는 이유에 대해서 알아보기 +* [X] Session 생성자 타입을 NsUser로 변경한 뒤에 객체를 전달하기 \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/SystemTimeStamp.java b/src/main/java/nextstep/courses/common/SystemTimeStamp.java similarity index 93% rename from src/main/java/nextstep/courses/domain/SystemTimeStamp.java rename to src/main/java/nextstep/courses/common/SystemTimeStamp.java index 184ab3e0f..92e65a4b0 100644 --- a/src/main/java/nextstep/courses/domain/SystemTimeStamp.java +++ b/src/main/java/nextstep/courses/common/SystemTimeStamp.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.common; import java.time.LocalDateTime; diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/Course.java index 438fc2696..05f5b69df 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/Course.java @@ -1,5 +1,6 @@ package nextstep.courses.domain; +import nextstep.courses.common.SystemTimeStamp; import nextstep.courses.domain.session.Session; import nextstep.courses.domain.session.Sessions; diff --git a/src/main/java/nextstep/courses/domain/Student.java b/src/main/java/nextstep/courses/domain/Student.java index a0b400722..d1f5afc5f 100644 --- a/src/main/java/nextstep/courses/domain/Student.java +++ b/src/main/java/nextstep/courses/domain/Student.java @@ -1,11 +1,35 @@ package nextstep.courses.domain; -import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.RegistrationState; public class Student { - private long id; - private long nsUserId; + private long sessionId; + private RegistrationState registrationState; + + public Student(long nsUserId, long sessionId, RegistrationState registrationState) { + this.nsUserId = nsUserId; + this.sessionId = sessionId; + this.registrationState = registrationState; + } + + public void cancel() { + this.registrationState = RegistrationState.CANCELED; + } + + public void approve() { + this.registrationState = RegistrationState.APPROVED; + } + + public long getNsUserId() { + return nsUserId; + } + + public long getSessionId() { + return sessionId; + } - private Session session; + public RegistrationState getRegistrationState() { + return registrationState; + } } diff --git a/src/main/java/nextstep/courses/domain/image/SessionImage.java b/src/main/java/nextstep/courses/domain/image/SessionImage.java index eb02cbd96..1576899ba 100644 --- a/src/main/java/nextstep/courses/domain/image/SessionImage.java +++ b/src/main/java/nextstep/courses/domain/image/SessionImage.java @@ -1,7 +1,6 @@ package nextstep.courses.domain.image; -import nextstep.courses.InvalidImageFormatException; -import nextstep.courses.domain.SystemTimeStamp; +import nextstep.courses.common.SystemTimeStamp; import nextstep.courses.domain.session.Session; import java.time.LocalDateTime; @@ -19,15 +18,11 @@ public class SessionImage { public static SessionImage valueOf(long id, Session session, int size, int width, int height, String imageType) { - return new SessionImage(id, "tmp", session.getSessionId() + return new SessionImage(id, "tmp", session.getId() , new ImageFormat(size, width, height, ImageType.validateImageType(imageType)) , new SystemTimeStamp(LocalDateTime.now(), null)); } - public SessionImage(long id, String name, long sessionId, int size, int width, int height, ImageType imageType) { - this(id, name, sessionId, new ImageFormat(size, width, height, imageType), new SystemTimeStamp(LocalDateTime.now(), null)); - } - public SessionImage(long id, String name, long sessionId, ImageFormat imageFormat, SystemTimeStamp systemTimeStamp) { this.id = id; this.name = name; diff --git a/src/main/java/nextstep/courses/domain/session/EnrollmentStatus.java b/src/main/java/nextstep/courses/domain/session/EnrollmentStatus.java index 920c37da2..3b8bb9c20 100644 --- a/src/main/java/nextstep/courses/domain/session/EnrollmentStatus.java +++ b/src/main/java/nextstep/courses/domain/session/EnrollmentStatus.java @@ -5,9 +5,8 @@ import java.util.function.BiFunction; public enum EnrollmentStatus { - PREPARING("준비중"), RECRUITING("모집중"), - CLOSE("종료"); + CLOSE("비모집중"); private String status; diff --git a/src/main/java/nextstep/courses/domain/session/FreeSession.java b/src/main/java/nextstep/courses/domain/session/FreeSession.java new file mode 100644 index 000000000..217f70329 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/FreeSession.java @@ -0,0 +1,27 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.common.SystemTimeStamp; +import nextstep.courses.domain.Student; +import nextstep.users.domain.NsUser; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class FreeSession extends Session { + + public static FreeSession valueOf(long id, String title, long courseId, EnrollmentStatus enrollmentStatus + , LocalDate startDate, LocalDate endDate, LocalDateTime createdAt, LocalDateTime updatedAt) { + return new FreeSession(new SessionInfo(id, title, courseId, SessionType.FREE) + , new SessionPlan(enrollmentStatus, startDate, endDate) + , new SystemTimeStamp(createdAt, updatedAt)); + } + + public FreeSession(SessionInfo sessionInfo, SessionPlan sessionPlan, SystemTimeStamp systemTimeStamp) { + super(sessionInfo, sessionPlan, systemTimeStamp); + } + + @Override + public void signUp(NsUser student) { + super.signUp(student); + } +} diff --git a/src/main/java/nextstep/courses/domain/session/PaidSession.java b/src/main/java/nextstep/courses/domain/session/PaidSession.java index 969b65b1f..e0ba6266e 100644 --- a/src/main/java/nextstep/courses/domain/session/PaidSession.java +++ b/src/main/java/nextstep/courses/domain/session/PaidSession.java @@ -1,8 +1,7 @@ package nextstep.courses.domain.session; import nextstep.courses.CannotSignUpException; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.SystemTimeStamp; +import nextstep.courses.common.SystemTimeStamp; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; @@ -17,34 +16,43 @@ public static PaidSession feeOf(long id, String title, long courseId, EnrollmentStatus enrollmentStatus, LocalDate startDate, LocalDate endDate, LocalDateTime createdAt, LocalDateTime updatedAt, int maxStudentCount, Long sessionFee) { - return new PaidSession(id, title, courseId, + return new PaidSession(new SessionInfo(id, title, courseId, SessionType.PAID), new SessionPlan(enrollmentStatus, startDate, endDate), new SystemTimeStamp(createdAt, updatedAt), maxStudentCount, sessionFee); } - public PaidSession(Long sessionId, String title, long courseId, - SessionPlan sessionPlan, SystemTimeStamp systemTimeStamp, + public PaidSession(SessionInfo sessionInfo, SessionPlan sessionPlan, SystemTimeStamp systemTimeStamp, int maxStudentCount, Long sessionFee) { - super(sessionId, title, courseId, SessionType.PAID, sessionPlan, systemTimeStamp); + super(sessionInfo, sessionPlan, systemTimeStamp); this.maxStudentCount = maxStudentCount; this.sessionFee = sessionFee; } @Override - public void signUp(NsUser nsUser, Payment payment) throws CannotSignUpException { - validateAvailableSignUp(); - validateSessionFeeMatchingPayment(payment); - super.signUp(nsUser, payment); + public void signUp(NsUser student) { + validateAvailableStudentCount(); + validatePayInfo(student, getPayInfo(student)); + super.signUp(student); } - private void validateSessionFeeMatchingPayment(Payment payment) throws CannotSignUpException { - if (payment.isNotSamePrice(sessionFee)) { + private Payment getPayInfo(NsUser student) { + return Payment.paidOf("tmp", super.getId(), student.getId(), this.sessionFee); // 결제가 완료됐다고 가정하기 위함. + } + + private void validatePayInfo(NsUser student, Payment payment) { + if (payment.isNotSameSessionId(this.getId())) { + throw new CannotSignUpException("해당 강의 결제이력이 없습니다."); + } + if (payment.isNotSameStudentId(student.getId())) { + throw new CannotSignUpException("결제자와 신청자의 정보가 일치하지 않습니다."); + } + if (payment.isNotSameSessionFee(sessionFee)) { throw new CannotSignUpException("결제금액과 수강료가 일치하지 않습니다."); } } - private void validateAvailableSignUp() throws CannotSignUpException { + private void validateAvailableStudentCount() throws CannotSignUpException { if (maxStudentCount == super.getStudentCount()) { throw new CannotSignUpException("최대 수강 인원을 초과했습니다."); } diff --git a/src/main/java/nextstep/courses/domain/session/RegistrationState.java b/src/main/java/nextstep/courses/domain/session/RegistrationState.java new file mode 100644 index 000000000..3915f96f9 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/RegistrationState.java @@ -0,0 +1,7 @@ +package nextstep.courses.domain.session; + +public enum RegistrationState { + PENDING, // 대기중 + APPROVED, // 승인 + CANCELED // 취소 +} diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index f39a2d8a6..d9df4f092 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -1,89 +1,92 @@ package nextstep.courses.domain.session; import nextstep.courses.CannotSignUpException; -import nextstep.courses.domain.SystemTimeStamp; +import nextstep.courses.common.SystemTimeStamp; +import nextstep.courses.domain.Student; import nextstep.courses.domain.image.SessionImage; -import nextstep.payments.domain.Payment; +import nextstep.qna.NotFoundException; import nextstep.users.domain.NsUser; -import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; public class Session { - private long sessionId; - private String title; - - private long courseId; - private SessionType sessionType; - private List students; - private SessionImage sessionImage; + private SessionInfo sessionInfo; + private List sessionImage; + private List students; private SessionPlan sessionPlan; private SystemTimeStamp systemTimeStamp; - public static Session valueOf(long id, String title, long courseId, EnrollmentStatus enrollmentStatus - , LocalDate startDate, LocalDate endDate, LocalDateTime createdAt, LocalDateTime updatedAt) { - return new Session(id, title, courseId, SessionType.FREE - , new SessionPlan(enrollmentStatus, startDate, endDate) - , new SystemTimeStamp(createdAt, updatedAt)); - } - - public Session(Long sessionId, String title, long courseId, SessionType sessionType, SessionPlan sessionPlan, SystemTimeStamp systemTimeStamp) { - this.sessionId = sessionId; - this.title = title; - this.courseId = courseId; + public Session(SessionInfo sessionInfo, SessionPlan sessionPlan, SystemTimeStamp systemTimeStamp) { + this.sessionInfo = sessionInfo; this.students = new ArrayList<>(Collections.emptyList()); - this.sessionType = sessionType; this.sessionPlan = sessionPlan; - this.sessionImage = null; + this.sessionImage = new ArrayList<>(Collections.emptyList());; this.systemTimeStamp = systemTimeStamp; } - public void signUp(NsUser student, Payment payment) { - validateSessionStatus(); - validatePayInfo(student, payment); - students.add(student); + public void signUp(NsUser student) { + Student studentInfo = new Student(student.getId(), this.getId(), RegistrationState.PENDING); + validateEnrollmentStatus(); + students.add(studentInfo); } - private void validatePayInfo(NsUser student, Payment payment) { - if (payment.getSessionId() != sessionId) { - throw new CannotSignUpException("결제한 강의정보가 맞지 않습니다."); - } - if (student.getId() != payment.getNsUserId()) { - throw new CannotSignUpException("결제자와 신청자의 정보가 일치하지 않습니다."); - } - } - - private void validateSessionStatus() { + private void validateEnrollmentStatus() { if (!EnrollmentStatus.canSignUp(this.sessionPlan.getEnrollmentStatus())) { throw new CannotSignUpException("강의 모집중이 아닙니다."); } } public void saveImage(SessionImage sessionImage) { - this.sessionImage = sessionImage; + this.sessionImage.add(sessionImage); + } + + public void cancelStudent(Student student) { + Student validateStudent = validateIsAStudent(student); + validateStudent.cancel(); + } + + public void approveStudent(Student student) { + Student validatedStudent = validateIsAStudent(student); + validatedStudent.approve(); + } + + private Student validateIsAStudent(Student student) { + return this.getAllStudents().stream() + .filter(x -> x.getNsUserId() == student.getNsUserId()) + .findFirst() + .orElseThrow(NotFoundException::new); } public int getStudentCount() { return students.size(); } - public Long getSessionId() { - return sessionId; + public Long getId() { + return sessionInfo.getId(); } public long getCourseId() { - return courseId; + return sessionInfo.getCourseId(); } public String getTitle() { - return title; + return sessionInfo.getTitle(); } public SessionType getSessionType() { - return sessionType; + return sessionInfo.getSessionType(); + } + + public List getStudents() { + return students.stream() + .filter(student -> student.getRegistrationState() == RegistrationState.APPROVED) + .collect(Collectors.toList()); + } + public List getAllStudents() { + return students; } public SessionPlan getSessionPlan() { @@ -94,8 +97,14 @@ public SystemTimeStamp getSystemTimeStamp() { return systemTimeStamp; } - public boolean hasImage() { - return !(sessionImage == null); + public int getImageCount() { + return sessionImage.size(); } + public boolean isFree() { + return this.getSessionType().isFree(); + } + public boolean isPaid() { + return this.getSessionType().isPaid(); + } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionInfo.java b/src/main/java/nextstep/courses/domain/session/SessionInfo.java new file mode 100644 index 000000000..d158a2e44 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionInfo.java @@ -0,0 +1,56 @@ +package nextstep.courses.domain.session; + +import java.util.Objects; + +public class SessionInfo { + private final long id; + private final String title; + private final long courseId; + private final SessionType sessionType; + + public SessionInfo(long id, String title, long courseId, SessionType sessionType) { + this.id = id; + this.title = title; + this.courseId = courseId; + this.sessionType = sessionType; + } + + public long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public long getCourseId() { + return courseId; + } + + public SessionType getSessionType() { + return sessionType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SessionInfo that = (SessionInfo) o; + return id == that.id && courseId == that.courseId && Objects.equals(title, that.title) && sessionType == that.sessionType; + } + + @Override + public int hashCode() { + return Objects.hash(id, title, courseId, sessionType); + } + + @Override + public String toString() { + return "SessionInfo{" + + "id=" + id + + ", title='" + title + '\'' + + ", courseId=" + courseId + + ", sessionType=" + sessionType + + '}'; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/SessionPlan.java b/src/main/java/nextstep/courses/domain/session/SessionPlan.java index dea68cbe0..d969c9bec 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionPlan.java +++ b/src/main/java/nextstep/courses/domain/session/SessionPlan.java @@ -8,9 +8,9 @@ public class SessionPlan { private LocalDate startDate; private LocalDate endDate; - public SessionPlan(EnrollmentStatus sessionStatus, LocalDate startDate, LocalDate endDate) { + public SessionPlan(EnrollmentStatus enrollmentStatus, LocalDate startDate, LocalDate endDate) { validateDate(startDate, endDate); - this.enrollmentStatus = sessionStatus; + this.enrollmentStatus = enrollmentStatus; this.startDate = startDate; this.endDate = endDate; } diff --git a/src/main/java/nextstep/courses/domain/session/SessionStatus.java b/src/main/java/nextstep/courses/domain/session/SessionStatus.java index 997f3a641..2b3f66d60 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionStatus.java +++ b/src/main/java/nextstep/courses/domain/session/SessionStatus.java @@ -5,9 +5,9 @@ import java.util.function.BiFunction; public enum SessionStatus { - NOT_STARTED("강의 시작 전", (startDate, endDate) -> startDate.isAfter(LocalDate.now())), - IN_PROGRESS("강의 진행 중", (startDate, endDate) -> startDate.compareTo(LocalDate.now()) <= 0 && endDate.compareTo(LocalDate.now()) >= 0), - COMPLETED("강의 종료", (startDate, endDate) -> endDate.isBefore(LocalDate.now())); + NOT_STARTED("준비중", (startDate, endDate) -> startDate.isAfter(LocalDate.now())), + IN_PROGRESS("진행중", (startDate, endDate) -> startDate.compareTo(LocalDate.now()) <= 0 && endDate.compareTo(LocalDate.now()) >= 0), + COMPLETED("종료", (startDate, endDate) -> endDate.isBefore(LocalDate.now())); private String statusName; private BiFunction getStatus; diff --git a/src/main/java/nextstep/courses/domain/session/SessionType.java b/src/main/java/nextstep/courses/domain/session/SessionType.java index d2f81dcae..98671d2eb 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionType.java +++ b/src/main/java/nextstep/courses/domain/session/SessionType.java @@ -4,4 +4,11 @@ public enum SessionType { FREE, PAID; + public boolean isFree() { + return this == FREE; + } + + public boolean isPaid() { + return this == PAID; + } } diff --git a/src/main/java/nextstep/courses/domain/session/Students.java b/src/main/java/nextstep/courses/domain/session/Students.java deleted file mode 100644 index b9df465c5..000000000 --- a/src/main/java/nextstep/courses/domain/session/Students.java +++ /dev/null @@ -1,22 +0,0 @@ -package nextstep.courses.domain.session; - -import nextstep.users.domain.NsUser; - -import java.util.List; - -public class Students { - private final List students; - - public Students(final List students) { - this.students = students; - } - - public List getStudents() { - return students; - } - - public int size() { - return students.size(); - } - -} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index 191e1bc98..243a2a192 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -2,7 +2,7 @@ import nextstep.courses.domain.Course; import nextstep.courses.domain.CourseRepository; -import nextstep.courses.domain.SystemTimeStamp; +import nextstep.courses.common.SystemTimeStamp; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java index 698c68442..6989eff14 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java @@ -1,7 +1,7 @@ package nextstep.courses.infrastructure; import nextstep.courses.InvalidImageFormatException; -import nextstep.courses.domain.SystemTimeStamp; +import nextstep.courses.common.SystemTimeStamp; import nextstep.courses.domain.image.ImageFormat; import nextstep.courses.domain.image.SessionImageRepository; import nextstep.courses.domain.image.ImageType; @@ -31,7 +31,7 @@ public int save(SessionImage image) { , image.getImageFormat().getImageSize() , image.getImageFormat().getWidth() , image.getImageFormat().getHeight() - , image.getImageFormat().getImageType() + , image.getImageFormat().getImageType().name() , image.getCreatedAt() , image.getUpdatedAt()); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 9452a4782..a14429937 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,7 +1,7 @@ package nextstep.courses.infrastructure; import nextstep.courses.InvalidImageFormatException; -import nextstep.courses.domain.SystemTimeStamp; +import nextstep.courses.common.SystemTimeStamp; import nextstep.courses.domain.session.*; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; @@ -50,10 +50,10 @@ public Session findBySessionId(Long id) { RowMapper rowMapper = (rs, rowNum) -> { try { return new Session( - rs.getLong(1), + new SessionInfo(rs.getLong(1), rs.getString(2), rs.getLong(3), - SessionType.valueOf(rs.getString(4)), + SessionType.valueOf(rs.getString(4))), new SessionPlan(EnrollmentStatus.valueOf(rs.getString(5)), rs.getDate(6).toLocalDate(), rs.getDate(7).toLocalDate()), diff --git a/src/main/java/nextstep/courses/service/EnrollSessionService.java b/src/main/java/nextstep/courses/service/EnrollSessionService.java new file mode 100644 index 000000000..8306e6980 --- /dev/null +++ b/src/main/java/nextstep/courses/service/EnrollSessionService.java @@ -0,0 +1,15 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.Student; +import nextstep.courses.domain.session.Session; + +public class EnrollSessionService { + public void registerStudent(Session session, Student student, Boolean approved) { + if (approved) { + session.approveStudent(student); + } else { + session.cancelStudent(student); + } + + } +} diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 6b2261f7d..ccd0c1228 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -1,5 +1,7 @@ package nextstep.payments.domain; +import nextstep.courses.CannotSignUpException; + import java.time.LocalDateTime; public class Payment { @@ -34,6 +36,10 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.createdAt = LocalDateTime.now(); } + public String getId() { + return id; + } + public Long getSessionId() { return sessionId; } @@ -42,7 +48,15 @@ public Long getNsUserId() { return nsUserId; } - public boolean isNotSamePrice(Long sessionFee) { + public boolean isNotSameSessionId(long sessionId) { + return this.sessionId != sessionId; + } + + public boolean isNotSameStudentId(long studentId) { + return this.nsUserId != studentId; + } + + public boolean isNotSameSessionFee(Long sessionFee) { return !sessionFee.equals(amount); } } diff --git a/src/test/java/nextstep/courses/Service/EnrollSessionTest.java b/src/test/java/nextstep/courses/Service/EnrollSessionTest.java new file mode 100644 index 000000000..0d294acbc --- /dev/null +++ b/src/test/java/nextstep/courses/Service/EnrollSessionTest.java @@ -0,0 +1,94 @@ +package nextstep.courses.Service; + +import nextstep.courses.CannotSignUpException; +import nextstep.courses.domain.Course; +import nextstep.courses.domain.Student; +import nextstep.courses.domain.session.*; +import nextstep.courses.service.EnrollSessionService; +import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class EnrollSessionTest { + + private Course course = new Course(1L, "TDD with java 17", 1L); + private Session session1 = PaidSession.feeOf(1L, "과제4 - 레거시 리팩토링", course.getId(), EnrollmentStatus.RECRUITING + , LocalDate.of(2023, 12, 01), LocalDate.of(2023, 12, 30) + , LocalDateTime.now(), null, 10, 10_000L); + private Session session2 = FreeSession.valueOf(1L, "과제4 - 레거시 리팩토링", course.getId(), EnrollmentStatus.CLOSE + , LocalDate.of(2023, 12, 01), LocalDate.of(2023, 12, 30) + , LocalDateTime.now(), null); + + private Session session3 = FreeSession.valueOf(1L, "과제4 - 레거시 리팩토링", course.getId(), EnrollmentStatus.RECRUITING + , LocalDate.of(2024, 03, 01), LocalDate.of(2024, 04, 30) + , LocalDateTime.now(), null); + + private Session session4 = FreeSession.valueOf(1L, "과제4 - 레거시 리팩토링", course.getId(), EnrollmentStatus.CLOSE + , LocalDate.of(2024, 03, 01), LocalDate.of(2024, 04, 30) + , LocalDateTime.now(), null); + + @Test + @DisplayName("강의가 진행중에 비모집중인 경우 Exception throw") + void canNotEnrollInProgressTest() { + EnrollSessionService enrollSessionService = new EnrollSessionService(); + + assertThrows(CannotSignUpException.class, () -> enrollSessionService.enrollSession(session2, NsUserTest.SANJIGI)); + } + + @Test + @DisplayName("강의가 진행중에 모집중인 경우 수강신청 가능") + void canEnrollInProgressTest() { + EnrollSessionService enrollSessionService = new EnrollSessionService(); + + assertDoesNotThrow(() -> enrollSessionService.enrollSession(session1, NsUserTest.SANJIGI)); + } + + @Test + @DisplayName("강의가 준비중일 때 모집중인 경우 수강신청 가능") + void canNotEnrollNotStartedTest() { + EnrollSessionService enrollSessionService = new EnrollSessionService(); + + assertDoesNotThrow(() -> enrollSessionService.enrollSession(session3, NsUserTest.SANJIGI)); + } + + @Test + @DisplayName("강의가 준비중일 때 모집중인 경우 수강신청 가능") + void canEnrollNotStartedTest() { + EnrollSessionService enrollSessionService = new EnrollSessionService(); + + assertThrows(CannotSignUpException.class, () -> enrollSessionService.enrollSession(session4, NsUserTest.SANJIGI)); + } + + @Test + @DisplayName("수강신청한 사람 중 선발되지 않은 수강생은 수강 취소가 된다.") + void cancelSessionTest() { + EnrollSessionService enrollSessionService = new EnrollSessionService(); + Student student = enrollSessionService.enrollSession(session3, NsUserTest.SANJIGI); + + assertThat(student.getRegistrationState()).isEqualTo(RegistrationState.PENDING); + + enrollSessionService.registerStudent(session3, student, false); + + assertThat(student.getRegistrationState()).isEqualTo(RegistrationState.CANCELED); + } + + @Test + @DisplayName("수강신청한 사람 중 선발된 사람에 대해 수강 승인이 가능하다.") + void approveSessionTest() { + EnrollSessionService enrollSessionService = new EnrollSessionService(); + Student student = enrollSessionService.enrollSession(session3, NsUserTest.SANJIGI); + + assertThat(student.getRegistrationState()).isEqualTo(RegistrationState.PENDING); + + enrollSessionService.registerStudent(session3, student, true); + + assertThat(student.getRegistrationState()).isEqualTo(RegistrationState.APPROVED); + } +} diff --git a/src/test/java/nextstep/courses/domain/CourseTest.java b/src/test/java/nextstep/courses/domain/CourseTest.java index d49c1b7b8..a1f7b558d 100644 --- a/src/test/java/nextstep/courses/domain/CourseTest.java +++ b/src/test/java/nextstep/courses/domain/CourseTest.java @@ -1,6 +1,7 @@ package nextstep.courses.domain; import nextstep.courses.domain.session.EnrollmentStatus; +import nextstep.courses.domain.session.FreeSession; import nextstep.courses.domain.session.Session; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -24,9 +25,9 @@ void setUp() { @DisplayName("과정은 강의가 추가될 수 있다.") void course_AddSession_Test() { assertThat(course.getSessions().size()).isEqualTo(0); - Session session1 = Session.valueOf(1L, "과제3 - 사다리게임", course.getId() - , EnrollmentStatus.PREPARING, LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now()); - Session session2 = Session.valueOf(1L, "과제4 - 레거시 리팩토링", course.getId() + Session session1 = FreeSession.valueOf(1L, "과제3 - 사다리게임", course.getId() + , EnrollmentStatus.CLOSE, LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now()); + Session session2 = FreeSession.valueOf(1L, "과제4 - 레거시 리팩토링", course.getId() , EnrollmentStatus.RECRUITING, LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now()); course.addSession(session1); course.addSession(session2); diff --git a/src/test/java/nextstep/courses/domain/PaidSessionTest.java b/src/test/java/nextstep/courses/domain/PaidSessionTest.java index 2caf4c8ef..29300a7af 100644 --- a/src/test/java/nextstep/courses/domain/PaidSessionTest.java +++ b/src/test/java/nextstep/courses/domain/PaidSessionTest.java @@ -3,6 +3,7 @@ import nextstep.courses.CannotSignUpException; import nextstep.courses.domain.session.EnrollmentStatus; import nextstep.courses.domain.session.PaidSession; +import nextstep.courses.domain.session.RegistrationState; import nextstep.payments.domain.Payment; import nextstep.payments.service.PaymentService; import nextstep.users.domain.NsUser; @@ -21,33 +22,19 @@ public class PaidSessionTest { private PaidSession paidSession = PaidSession.feeOf(1L,"step4", 1L, EnrollmentStatus.RECRUITING, LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now(),1, 10_000L); - private Payment payment = Payment.paidOf("1A", paidSession.getSessionId(), NsUserTest.JAVAJIGI.getId(), 10_000L); private NsUser student = NsUserTest.JAVAJIGI; @Test @DisplayName("유료 강의는 강의 최대 수강 인원을 초과할 수 없다. ") void sessionStudentTest() throws CannotSignUpException { - - paidSession.signUp(student, payment); - assertThrows(CannotSignUpException.class, () -> paidSession.signUp(student, payment)); + paidSession.signUp(student); + assertThrows(CannotSignUpException.class, () -> paidSession.signUp(student)); } @Test @DisplayName("유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다.") void payCheckTest() { - assertDoesNotThrow(() -> paidSession.signUp(student, payment)); - } - - @Test - @DisplayName("유료 강의는 수강생이 결제한 금액과 수강료가 일치하지 않는 경우 Exception Throw") - void payCheckExceptionTest() { - PaidSession paidSession = PaidSession.feeOf(1L,"step4", 1L, EnrollmentStatus.RECRUITING, - LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now(), 2, 5_000L); - - PaymentService paymentService = new PaymentService(); - Payment payment = paymentService.paymentPaid("1"); - - assertThrows(CannotSignUpException.class, () -> paidSession.signUp(student, payment)); + assertDoesNotThrow(() -> paidSession.signUp(student)); } } diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index f5e4b9df6..5f0f7efa2 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -4,8 +4,9 @@ import nextstep.courses.InvalidImageFormatException; import nextstep.courses.domain.image.SessionImage; import nextstep.courses.domain.session.EnrollmentStatus; +import nextstep.courses.domain.session.FreeSession; +import nextstep.courses.domain.session.RegistrationState; import nextstep.courses.domain.session.Session; -import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; import nextstep.users.domain.NsUserTest; import org.junit.jupiter.api.DisplayName; @@ -28,7 +29,7 @@ public class SessionTest { void notHave_StartAndEndDate_Test() { assertThrows(IllegalArgumentException.class, - () -> Session.valueOf(1L, "과제4-리팩토링", course.getId(), EnrollmentStatus.RECRUITING + () -> FreeSession.valueOf(1L, "과제4-리팩토링", course.getId(), EnrollmentStatus.RECRUITING , null, null, LocalDateTime.now(), LocalDateTime.now())); } @@ -36,48 +37,39 @@ public class SessionTest { @DisplayName("강의는 시작일과 종료일을 가진다.") void haveDateTest() { assertDoesNotThrow(() - -> Session.valueOf(1L, "과제4-리팩토링", course.getId(), EnrollmentStatus.PREPARING + -> FreeSession.valueOf(1L, "과제4-리팩토링", course.getId(), EnrollmentStatus.CLOSE , LocalDate.of(2023, 12, 01), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now())); } @Test @DisplayName("강의는 강의 커버 이미지 정보를 가진다.") void saveSessionImageTest() throws InvalidImageFormatException { - Session session = Session.valueOf(1L, "과제4 - 레거시 리팩토링", course.getId(), EnrollmentStatus.PREPARING + Session session = FreeSession.valueOf(1L, "과제4 - 레거시 리팩토링", course.getId(), EnrollmentStatus.CLOSE , LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now()); - assertThat(session.hasImage()).isFalse(); + assertThat(session.getImageCount()).isEqualTo(0); SessionImage 강의_이미지 = SessionImage.valueOf(1L, session, 1024 * 1024, 300, 200, "jpg"); session.saveImage(강의_이미지); - assertThat(session.hasImage()).isTrue(); + assertThat(session.getImageCount()).isEqualTo(1); } @Test - @DisplayName("강의가 준비중인 경우 수강신청을 하면 Exception Throw") + @DisplayName("강의가 모집중이 아닌 경우 수강신청을 하면 Exception Throw") void cannotSignUp_ForPreparingSession_Test() { - Session session = Session.valueOf(1L, "lms", course.getId(), EnrollmentStatus.PREPARING + Session session = FreeSession.valueOf(1L, "lms", course.getId(), EnrollmentStatus.CLOSE , LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now()); - Payment payment = Payment.freeOf("1", session.getSessionId(), student.getId()); - assertThrows(CannotSignUpException.class, () -> session.signUp(student, payment)); - } - @Test - @DisplayName("강의가 종료상태인 경우 수강신청을 하면 Exception Throw") - void cannotSignUp_ForClosedSession_Test() { - Session session = Session.valueOf(1L, "LMS", course.getId(), EnrollmentStatus.CLOSE - , LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now()); - Payment payment = Payment.freeOf("1", session.getSessionId(), student.getId()); - assertThrows(CannotSignUpException.class, () -> session.signUp(student, payment)); + assertThrows(CannotSignUpException.class, () -> session.signUp(student)); } @Test - @DisplayName("강의가 준비상태인 경우 수강신청이 가능하다.") + @DisplayName("강의가 모집중인 경우 수강신청이 가능하다.") void signUp_ForRecruitingSession_Test() throws CannotSignUpException { - Session session = Session.valueOf(1L,"lms", course.getId(), EnrollmentStatus.RECRUITING, + Session session = FreeSession.valueOf(1L, "lms", course.getId(), EnrollmentStatus.RECRUITING, LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now()); - Payment payment = Payment.freeOf("1", session.getSessionId(), student.getId()); - session.signUp(student, payment); + + session.signUp(student); assertThat(session.getStudentCount()).isEqualTo(1); } } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionImageRepositoryTest.java index 607e6bcb3..719b2442b 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionImageRepositoryTest.java @@ -1,9 +1,12 @@ package nextstep.courses.infrastructure; import nextstep.courses.InvalidImageFormatException; +import nextstep.courses.domain.CourseTest; +import nextstep.courses.domain.SessionTest; import nextstep.courses.domain.image.SessionImageRepository; import nextstep.courses.domain.image.ImageType; import nextstep.courses.domain.image.SessionImage; +import nextstep.courses.domain.session.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,6 +16,8 @@ import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.jdbc.core.JdbcTemplate; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -35,7 +40,10 @@ void setUp() { @Test @DisplayName("강의는 강의커버 이미지를 갖는다.") void save_강의커버이미지() throws InvalidImageFormatException { - SessionImage sessionImage = new SessionImage(1L, "커버이미지", 1L, 1024 * 1024, 300, 200, ImageType.PNG); + PaidSession session = PaidSession.feeOf(1L,"step4", 1L, EnrollmentStatus.RECRUITING, + LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now(), 1, 10_000L); + SessionImage sessionImage = SessionImage.valueOf(1L, session + , 1024 * 1024, 300, 200, "png"); int count = sessionImageRepository.save(sessionImage); assertThat(count).isEqualTo(1); Optional savedImage = sessionImageRepository.findBySessionId(1L); diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 7c35d9b91..4af7a70e1 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -2,10 +2,8 @@ import nextstep.courses.InvalidImageFormatException; import nextstep.courses.domain.Course; -import nextstep.courses.domain.image.ImageType; -import nextstep.courses.domain.image.SessionImage; -import nextstep.courses.domain.image.SessionImageRepository; import nextstep.courses.domain.session.EnrollmentStatus; +import nextstep.courses.domain.session.FreeSession; import nextstep.courses.domain.session.Session; import nextstep.courses.domain.session.SessionRepository; import org.junit.jupiter.api.BeforeEach; @@ -19,7 +17,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -42,7 +39,7 @@ void setUp() { @DisplayName("강의 정보를 저장한다.") void save_강의_Test() throws InvalidImageFormatException { Course course = new Course(1L, "TDD with java", 1L); - Session session = Session.valueOf(1L, "LMS", course.getId(), EnrollmentStatus.CLOSE + Session session = FreeSession.valueOf(1L, "LMS", course.getId(), EnrollmentStatus.CLOSE , LocalDate.now(), LocalDate.now(), LocalDateTime.now(), LocalDateTime.now()); int count = sessionRepository.save(session);