diff --git a/README.md b/README.md index 5ee28f1fb..64618d5bf 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,21 @@ - [x] Enrollment 리팩토링 - [x] 생성자에 List 추가 - [x] EnrollmentManager 추가 -- [ ] 피드백 +- [x] 피드백 - [x] SessionRepository.update 제거 - [x] Enrollment.isFull 리팩토링 - - [x] Enrollment.hasEnrolledUser 제거 \ No newline at end of file + - [x] Enrollment.hasEnrolledUser 제거 +# 4단계 +- [x] 3단계 피드백 + - [x] SessionService 리팩토링 + - [x] UserService.findByUserIds 추가 + - [x] ImageRepository 의존성 제거 + - [x] Enrollment 검증 테스트 도입 +- [x] 강의 진행 상태 / 모집 상태 분리 +- [x] 강의 커버 이미지 다수 등록 +- [x] 수강 신청 변경 + - [x] 수강 신청 상태 추가 (미승인/승인/취소) + - [x] Enrollment 추가 + - [x] Enrollments 리팩토링 + - [x] 수강 신청 승인 + - [x] 수강 신청 취소 \ No newline at end of file diff --git a/docs/issues_step4.md b/docs/issues_step4.md new file mode 100644 index 000000000..1925717fd --- /dev/null +++ b/docs/issues_step4.md @@ -0,0 +1,21 @@ +# 4단계 - 수강신청(요구사항 변경) +## 3단계 피드백 +### DTO 사용 +- DTO를 사용하지 않는다고 하더라도 도메인 내부의 값을 알기 위한 getter는 필수불가결 +- 그러나 현재 요구사항에서 Session 전체 값을 update할 필요는 없음 +- SessionDto를 Session으로 변환하는 과정에서 다른 Repository에 의존하는 것에는 문제가 있음 + - UserService에서 List 반환 + - imageRepository 의존성 제거 → imageRepository에서 SessionThumbnail 반환 + - sessionEnrollmentRepository 의존성 제거 -> 단순 CRUD 기능의 계층을 늘릴 필요는 없음 +- Service 간 의존성을 반드시 제거할 필요는 없음 (단, 순환 참조는 발생하면 안됨) +### Repository +- 도메인 객체에 ID는 필요 없는 값 +- 하지만 이것을 분리하여 얻는 장점은 크지 않음 + - 참고: [https://mincanit.tistory.com/74](https://mincanit.tistory.com/74) +## 수강 신청 변경 +### 수강 신청 상태 추가 +- 기존 EnrollmentManager에서는 List를 사용하여 수강신청을 관리 + - 수강 신청 상태 - 사용자를 묶는 객체 추가 + - EnrollmentManager는 수강 신청 상태와 관계 없이 List 도 필요 + - List 대신 Map를 갖도록 변경하자 + - 이러면 별도의 Enrollment 객체는 필요하지 않음 diff --git a/docs/todo_step4.md b/docs/todo_step4.md new file mode 100644 index 000000000..547dc0752 --- /dev/null +++ b/docs/todo_step4.md @@ -0,0 +1,19 @@ +# 4단계 - 수강신청(요구사항 변경) +## 핵심 학습 목표 +- DB 테이블이 변경될 때도 스트랭글러 패턴을 적용해 점진적인 리팩터링을 연습한다. + - [스트랭글러(교살자) 패턴 - 마틴 파울러](https://martinfowler.com/bliki/StranglerFigApplication.html) + - [스트랭글러 무화과 패턴](https://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig) +## 변경된 기능 요구사항 +### 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. +- 강의가 진행 중인 상태에서도 수강신청이 가능해야 한다. + - 강의 진행 상태(준비중, 진행중, 종료)와 모집 상태(비모집중, 모집중)로 상태 값을 분리해야 한다. +### 강의는 강의 커버 이미지 정보를 가진다. +- 강의는 하나 이상의 커버 이미지를 가질 수 있다. +### 강사가 승인하지 않아도 수강 신청하는 모든 사람이 수강 가능하다. +- 우아한테크코스(무료), 우아한테크캠프 Pro(유료)와 같이 선발된 인원만 수강 가능해야 한다. + - 강사는 수강신청한 사람 중 선발된 인원에 대해서만 수강 승인이 가능해야 한다. + - 강사는 수강신청한 사람 중 선발되지 않은 사람은 수강을 취소할 수 있어야 한다. +## 프로그래밍 요구사항 +- 리팩터링할 때 컴파일 에러와 기존의 단위 테스트의 실패를 최소화하면서 점진적인 리팩터링이 가능하도록 한다. +- DB 테이블에 데이터가 존재한다는 가정하에 리팩터링해야 한다. + - 즉, 기존에 쌓인 데이터를 제거하지 않은 상태로 리팩터링 해야 한다. \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index 59490479b..aac308562 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -1,7 +1,7 @@ package nextstep.courses.domain.session; import lombok.Getter; -import nextstep.courses.domain.session.enrollment.Enrollment; +import nextstep.courses.domain.session.enrollment.Enrollments; import nextstep.courses.domain.session.info.SessionInfo; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; @@ -10,12 +10,12 @@ public class Session { private final SessionId id; private final SessionInfo info; - private final Enrollment enrollment; + private final Enrollments enrollments; - public Session(SessionId id, SessionInfo info, Enrollment enrollment) { + public Session(SessionId id, SessionInfo info, Enrollments enrollments) { this.id = id; this.info = info; - this.enrollment = enrollment; + this.enrollments = enrollments; } public void enroll(NsUser user, Payment payment) { @@ -24,7 +24,11 @@ public void enroll(NsUser user, Payment payment) { info.validatePayment(payment); } - enrollment.enroll(user); + enrollments.enroll(user); + } + + public void approve(NsUser user) { + enrollments.approve(user); } public boolean isPaid() { diff --git a/src/main/java/nextstep/courses/domain/session/SessionProgressStatus.java b/src/main/java/nextstep/courses/domain/session/SessionProgressStatus.java new file mode 100644 index 000000000..3d8e0cf59 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionProgressStatus.java @@ -0,0 +1,20 @@ +package nextstep.courses.domain.session; + +import lombok.Getter; + +@Getter +public enum SessionProgressStatus { + PREPARING("준비중"), + IN_PROGRESS("진행중"), + CLOSED("종료"); + + private final String description; + + SessionProgressStatus(String description) { + this.description = description; + } + + public boolean isInProgress() { + return this == IN_PROGRESS; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/SessionStatus.java b/src/main/java/nextstep/courses/domain/session/SessionRecruitmentStatus.java similarity index 55% rename from src/main/java/nextstep/courses/domain/session/SessionStatus.java rename to src/main/java/nextstep/courses/domain/session/SessionRecruitmentStatus.java index 3c55ea70d..41fcd346f 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionStatus.java +++ b/src/main/java/nextstep/courses/domain/session/SessionRecruitmentStatus.java @@ -1,20 +1,18 @@ package nextstep.courses.domain.session; -public enum SessionStatus { - PREPARING("준비중"), +import lombok.Getter; + +@Getter +public enum SessionRecruitmentStatus { RECRUITING("모집중"), - CLOSED("종료"); + NOT_RECRUITING("비모집중"); private final String description; - SessionStatus(String description) { + SessionRecruitmentStatus(String description) { this.description = description; } - public String getDescription() { - return description; - } - public boolean isRecruiting() { return this == RECRUITING; } diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/Enrollment.java b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollment.java index d4d319ee7..c600a5bd6 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollment.java @@ -1,9 +1,54 @@ package nextstep.courses.domain.session.enrollment; -import nextstep.courses.domain.session.SessionStatus; import nextstep.users.domain.NsUser; -public interface Enrollment { - void enroll(NsUser user); - SessionStatus getStatus(); +public class Enrollment { + private final NsUser user; + private EnrollmentStatus enrollmentStatus; + + public Enrollment(NsUser user, EnrollmentStatus enrollmentStatus) { + this.user = user; + this.enrollmentStatus = enrollmentStatus; + } + + public Enrollment(NsUser user) { + this(user, EnrollmentStatus.PENDING_APPROVAL); + } + + public NsUser getUser() { + return user; + } + + public EnrollmentStatus getEnrollmentStatus() { + return enrollmentStatus; + } + + public void approve() { + if (enrollmentStatus == EnrollmentStatus.PENDING_APPROVAL) { + enrollmentStatus = EnrollmentStatus.ENROLLED; + } else { + throw new IllegalStateException("승인 대기 상태의 수강신청만 승인할 수 있습니다."); + } + } + + public void cancel() { + if (enrollmentStatus == EnrollmentStatus.PENDING_APPROVAL || + enrollmentStatus == EnrollmentStatus.ENROLLED) { + enrollmentStatus = EnrollmentStatus.CANCELLED; + } else { + throw new IllegalStateException("승인 대기 또는 수강신청 상태만 취소할 수 있습니다."); + } + } + + public boolean isPendingApproval() { + return enrollmentStatus == EnrollmentStatus.PENDING_APPROVAL; + } + + public boolean isEnrolled() { + return enrollmentStatus == EnrollmentStatus.ENROLLED; + } + + public boolean isCancelled() { + return enrollmentStatus == EnrollmentStatus.CANCELLED; + } } diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentManager.java b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentManager.java index 19515dfe3..0086aa372 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentManager.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentManager.java @@ -1,31 +1,106 @@ package nextstep.courses.domain.session.enrollment; import lombok.Getter; -import nextstep.courses.domain.session.SessionStatus; +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; import nextstep.users.domain.NsUser; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; -@Getter public class EnrollmentManager { - private final List enrolledUsers; - private final SessionStatus status; + private final Map enrollmentStatuses; + @Getter + private final SessionProgressStatus progressStatus; + @Getter + private final SessionRecruitmentStatus recruitmentStatus; - public EnrollmentManager(List enrolledUsers, SessionStatus status) { - this.enrolledUsers = enrolledUsers; - this.status = status; + public EnrollmentManager(SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { + this.enrollmentStatuses = new HashMap<>(); + this.progressStatus = progressStatus; + this.recruitmentStatus = recruitmentStatus; + } + + public EnrollmentManager() { + this(SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + } + + public void addEnrollment(NsUser user) { + enrollmentStatuses.put(user, EnrollmentStatus.PENDING_APPROVAL); + } + + public void approveEnrollment(NsUser user) { + if (!enrollmentStatuses.containsKey(user)) { + throw new IllegalArgumentException("해당 사용자의 수강신청 정보가 없습니다."); + } + + EnrollmentStatus currentStatus = enrollmentStatuses.get(user); + if (currentStatus != EnrollmentStatus.PENDING_APPROVAL) { + throw new IllegalStateException("승인 대기 상태의 수강신청만 승인할 수 있습니다."); + } + + enrollmentStatuses.put(user, EnrollmentStatus.ENROLLED); + } + + public void cancelEnrollment(NsUser user) { + if (!enrollmentStatuses.containsKey(user)) { + throw new IllegalArgumentException("해당 사용자의 수강신청 정보가 없습니다."); + } + + EnrollmentStatus currentStatus = enrollmentStatuses.get(user); + if (currentStatus != EnrollmentStatus.PENDING_APPROVAL && currentStatus != EnrollmentStatus.ENROLLED) { + throw new IllegalStateException("승인 대기 또는 수강신청 상태만 취소할 수 있습니다."); + } + + enrollmentStatuses.put(user, EnrollmentStatus.CANCELLED); + } + + public EnrollmentStatus getEnrollmentStatus(NsUser user) { + return enrollmentStatuses.getOrDefault(user, null); + } + + public List getEnrolledUsers() { + return enrollmentStatuses.entrySet().stream() + .filter(entry -> entry.getValue() == EnrollmentStatus.ENROLLED) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + public List getPendingApprovalUsers() { + return enrollmentStatuses.entrySet().stream() + .filter(entry -> entry.getValue() == EnrollmentStatus.PENDING_APPROVAL) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + public List getCancelledUsers() { + return enrollmentStatuses.entrySet().stream() + .filter(entry -> entry.getValue() == EnrollmentStatus.CANCELLED) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + public List getAllEnrollments() { + List enrollments = new ArrayList<>(); + for (Map.Entry entry : enrollmentStatuses.entrySet()) { + enrollments.add(new Enrollment(entry.getKey(), entry.getValue())); + } + return enrollments; } public void enroll(NsUser user) { validateEnrollment(user); - enrolledUsers.add(user); + addEnrollment(user); } private void validateEnrollment(NsUser user) { if (user == null) { throw new IllegalArgumentException("수강 신청할 사용자가 없습니다."); } - if (!status.isRecruiting()) { + if (!recruitmentStatus.isRecruiting()) { throw new IllegalStateException("수강 신청이 불가능합니다."); } if (hasEnrolledUser(user)) { @@ -34,6 +109,7 @@ private void validateEnrollment(NsUser user) { } private boolean hasEnrolledUser(NsUser user) { - return enrolledUsers.contains(user); + return enrollmentStatuses.containsKey(user); } + } diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatus.java b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatus.java new file mode 100644 index 000000000..c3f673fdf --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatus.java @@ -0,0 +1,18 @@ +package nextstep.courses.domain.session.enrollment; + +public enum EnrollmentStatus { + PENDING_APPROVAL("승인대기"), + ENROLLED("수강신청"), + WAITING("대기"), + CANCELLED("취소"); + + private final String description; + + EnrollmentStatus(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java new file mode 100644 index 000000000..c1ce6b121 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java @@ -0,0 +1,18 @@ +package nextstep.courses.domain.session.enrollment; + +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; +import nextstep.users.domain.NsUser; + +import java.util.List; + +public interface Enrollments { + void enroll(NsUser user); + void approve(NsUser user); + void cancel(NsUser user); + SessionProgressStatus getProgressStatus(); + SessionRecruitmentStatus getRecruitmentStatus(); + List getEnrolledUsers(); + List getPendingApprovalUsers(); + EnrollmentStatus getEnrollmentStatus(NsUser user); +} diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollment.java b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollment.java deleted file mode 100644 index 48872455a..000000000 --- a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollment.java +++ /dev/null @@ -1,27 +0,0 @@ -package nextstep.courses.domain.session.enrollment; - -import nextstep.courses.domain.session.SessionStatus; -import nextstep.users.domain.NsUser; - -import java.util.ArrayList; -import java.util.List; - -public class FreeEnrollment implements Enrollment { - private final EnrollmentManager enrollment; - - public FreeEnrollment(List enrolledUsers, SessionStatus status) { - this.enrollment = new EnrollmentManager(enrolledUsers, status); - } - - public FreeEnrollment() { - this.enrollment = new EnrollmentManager(new ArrayList<>(), SessionStatus.RECRUITING); - } - - public void enroll(NsUser user) { - enrollment.enroll(user); - } - - public SessionStatus getStatus() { - return enrollment.getStatus(); - } -} diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java new file mode 100644 index 000000000..48ac3bf9f --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java @@ -0,0 +1,62 @@ +package nextstep.courses.domain.session.enrollment; + +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; +import nextstep.users.domain.NsUser; + +import java.util.List; + +public class FreeEnrollments implements Enrollments { + private final EnrollmentManager statusManager; + + public FreeEnrollments(List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { + this.statusManager = new EnrollmentManager(progressStatus, recruitmentStatus); + + // Initialize status manager with existing enrolled users + for (NsUser user : enrolledUsers) { + statusManager.addEnrollment(user); + statusManager.approveEnrollment(user); + } + } + + public FreeEnrollments() { + this.statusManager = new EnrollmentManager(); + } + + public void enroll(NsUser user) { + statusManager.enroll(user); + } + + @Override + public void approve(NsUser user) { + statusManager.approveEnrollment(user); + } + + @Override + public void cancel(NsUser user) { + statusManager.cancelEnrollment(user); + } + + public SessionProgressStatus getProgressStatus() { + return statusManager.getProgressStatus(); + } + + public SessionRecruitmentStatus getRecruitmentStatus() { + return statusManager.getRecruitmentStatus(); + } + + @Override + public List getEnrolledUsers() { + return statusManager.getEnrolledUsers(); + } + + @Override + public List getPendingApprovalUsers() { + return statusManager.getPendingApprovalUsers(); + } + + @Override + public EnrollmentStatus getEnrollmentStatus(NsUser user) { + return statusManager.getEnrollmentStatus(user); + } +} diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java deleted file mode 100644 index e874c3e01..000000000 --- a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java +++ /dev/null @@ -1,46 +0,0 @@ -package nextstep.courses.domain.session.enrollment; - -import lombok.Getter; -import nextstep.courses.domain.session.SessionStatus; -import nextstep.users.domain.NsUser; - -import java.util.ArrayList; -import java.util.List; - -public class PaidEnrollment implements Enrollment{ - private final EnrollmentManager enrollment; - - @Getter - private final int maxEnrollment; - - public PaidEnrollment(int maxEnrollment, List enrolledUsers, SessionStatus status) { - validateMaxEnrollment(maxEnrollment); - this.maxEnrollment = maxEnrollment; - this.enrollment = new EnrollmentManager(enrolledUsers, status); - } - - public PaidEnrollment(int maxEnrollment) { - this(maxEnrollment, new ArrayList<>(), SessionStatus.RECRUITING); - } - - public void enroll(NsUser user) { - if (isFull()) { - throw new IllegalStateException("수강 인원이 가득 찼습니다."); - } - enrollment.enroll(user); - } - - public SessionStatus getStatus() { - return enrollment.getStatus(); - } - - private boolean isFull() { - return enrollment.getEnrolledUsers().size() >= maxEnrollment; - } - - private void validateMaxEnrollment(int maxEnrollment) { - if (maxEnrollment <= 0) { - throw new IllegalArgumentException("유료 강의는 최대 수강 인원이 0보다 커야 합니다."); - } - } -} diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java new file mode 100644 index 000000000..170c955f4 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java @@ -0,0 +1,82 @@ +package nextstep.courses.domain.session.enrollment; + +import lombok.Getter; +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; +import nextstep.users.domain.NsUser; + +import java.util.ArrayList; +import java.util.List; + +public class PaidEnrollments implements Enrollments { + private final EnrollmentManager statusManager; + + @Getter + private final int maxEnrollment; + + public PaidEnrollments(int maxEnrollment, List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { + validateMaxEnrollment(maxEnrollment); + this.maxEnrollment = maxEnrollment; + this.statusManager = new EnrollmentManager(progressStatus, recruitmentStatus); + + // Initialize status manager with existing enrolled users + for (NsUser user : enrolledUsers) { + statusManager.addEnrollment(user); + statusManager.approveEnrollment(user); + } + } + + public PaidEnrollments(int maxEnrollment) { + this(maxEnrollment, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + } + + public void enroll(NsUser user) { + if (isFull()) { + throw new IllegalStateException("수강 인원이 가득 찼습니다."); + } + statusManager.enroll(user); + } + + @Override + public void approve(NsUser user) { + statusManager.approveEnrollment(user); + } + + @Override + public void cancel(NsUser user) { + statusManager.cancelEnrollment(user); + } + + public SessionProgressStatus getProgressStatus() { + return statusManager.getProgressStatus(); + } + + public SessionRecruitmentStatus getRecruitmentStatus() { + return statusManager.getRecruitmentStatus(); + } + + @Override + public List getEnrolledUsers() { + return statusManager.getEnrolledUsers(); + } + + @Override + public List getPendingApprovalUsers() { + return statusManager.getPendingApprovalUsers(); + } + + @Override + public EnrollmentStatus getEnrollmentStatus(NsUser user) { + return statusManager.getEnrollmentStatus(user); + } + + private boolean isFull() { + return statusManager.getEnrolledUsers().size() >= maxEnrollment; + } + + private void validateMaxEnrollment(int maxEnrollment) { + if (maxEnrollment <= 0) { + throw new IllegalArgumentException("유료 강의는 최대 수강 인원이 0보다 커야 합니다."); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/session/info/basic/SessionThumbnail.java b/src/main/java/nextstep/courses/domain/session/info/basic/SessionThumbnail.java index 6f9c426d8..f5aa2850b 100644 --- a/src/main/java/nextstep/courses/domain/session/info/basic/SessionThumbnail.java +++ b/src/main/java/nextstep/courses/domain/session/info/basic/SessionThumbnail.java @@ -1,27 +1,36 @@ package nextstep.courses.domain.session.info.basic; import lombok.Getter; -import nextstep.courses.domain.image.ImageFileName; -import nextstep.courses.domain.image.ImageSize; + +import java.util.ArrayList; +import java.util.List; @Getter public class SessionThumbnail { private static final int MAX_FILE_SIZE = 1024 * 1024; // 1MB + private static final int MAX_THUMBNAIL_COUNT = 5; + + private final List thumbnails; - private final long fileSize; - private final ImageFileName fileName; - private final ImageSize size; + public SessionThumbnail() { + this.thumbnails = new ArrayList<>(); + } - public SessionThumbnail(String fullFileName, long fileSize, int width, int height) { + public void addThumbnail(String fullFileName, long fileSize, int width, int height) { + validateThumbnailCount(); validateFileSize(fileSize); - this.fileSize = fileSize; - this.fileName = new ImageFileName(fullFileName); - this.size = new ImageSize(width, height); + thumbnails.add(new ThumbnailInfo(fullFileName, fileSize, width, height)); + } + + private void validateThumbnailCount() { + if (thumbnails.size() >= MAX_THUMBNAIL_COUNT) { + throw new IllegalArgumentException("썸네일은 최대 " + MAX_THUMBNAIL_COUNT + "개까지만 등록할 수 있습니다."); + } } private void validateFileSize(long fileSize) { if (fileSize > MAX_FILE_SIZE) { - throw new IllegalArgumentException("파일 크기는 1MB를 초과할 수 없습니다."); + throw new IllegalArgumentException("썸네일 파일 크기는 1MB를 초과할 수 없습니다."); } } } diff --git a/src/main/java/nextstep/courses/domain/session/info/basic/ThumbnailInfo.java b/src/main/java/nextstep/courses/domain/session/info/basic/ThumbnailInfo.java new file mode 100644 index 000000000..64727c9a9 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/info/basic/ThumbnailInfo.java @@ -0,0 +1,18 @@ +package nextstep.courses.domain.session.info.basic; + +import lombok.Getter; +import nextstep.courses.domain.image.ImageFileName; +import nextstep.courses.domain.image.ImageSize; + +@Getter +public class ThumbnailInfo { + private final long fileSize; + private final ImageFileName fileName; + private final ImageSize size; + + public ThumbnailInfo(String fullFileName, long fileSize, int width, int height) { + this.fileSize = fileSize; + this.fileName = new ImageFileName(fullFileName); + this.size = new ImageSize(width, height); + } +} diff --git a/src/main/java/nextstep/courses/dto/SessionDto.java b/src/main/java/nextstep/courses/dto/SessionDto.java index f75b019b4..a936cc882 100644 --- a/src/main/java/nextstep/courses/dto/SessionDto.java +++ b/src/main/java/nextstep/courses/dto/SessionDto.java @@ -4,10 +4,11 @@ import lombok.Getter; import nextstep.courses.domain.session.Session; import nextstep.courses.domain.session.SessionId; -import nextstep.courses.domain.session.SessionStatus; +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; import nextstep.courses.domain.session.SessionType; -import nextstep.courses.domain.session.enrollment.Enrollment; -import nextstep.courses.domain.session.enrollment.PaidEnrollment; +import nextstep.courses.domain.session.enrollment.Enrollments; +import nextstep.courses.domain.session.enrollment.PaidEnrollments; import nextstep.courses.domain.session.info.SessionInfo; import nextstep.courses.domain.session.info.basic.SessionBasicInfo; import nextstep.courses.domain.session.info.detail.SessionDetailInfo; @@ -23,7 +24,8 @@ public class SessionDto { private final Long courseId; private final String title; private final SessionType sessionType; - private final SessionStatus status; + private final SessionProgressStatus progressStatus; + private final SessionRecruitmentStatus recruitmentStatus; private final LocalDate startDate; private final LocalDate endDate; private final int maximumEnrollment; @@ -33,7 +35,7 @@ public class SessionDto { public static SessionDto of(Session session) { SessionId sessionId = session.getId(); SessionInfo sessionInfo = session.getInfo(); - Enrollment enrollment = session.getEnrollment(); + Enrollments enrollments = session.getEnrollments(); SessionBasicInfo sessionBasicInfo = sessionInfo.getBasicInfo(); SessionDetailInfo sessionDetailInfo = sessionInfo.getDetailInfo(); @@ -41,23 +43,24 @@ public static SessionDto of(Session session) { SessionPeriod sessionPeriod = sessionDetailInfo.getPeriod(); SessionType type = sessionDetailInfo.getType(); - int maxEnrollment = getMaxEnrollment(type, enrollment); + int maxEnrollment = getMaxEnrollment(type, enrollments); return SessionDto.builder() .id(sessionId.getId()) .courseId(sessionId.getCourseId()) .title(sessionBasicInfo.getTitle()) .sessionType(sessionDetailInfo.getType()) - .status(enrollment.getStatus()) + .progressStatus(enrollments.getProgressStatus()) + .recruitmentStatus(enrollments.getRecruitmentStatus()) .startDate(sessionPeriod.getStartDate()) .endDate(sessionPeriod.getEndDate()) .maximumEnrollment(maxEnrollment) .build(); } - private static int getMaxEnrollment(SessionType type, Enrollment enrollment) { + private static int getMaxEnrollment(SessionType type, Enrollments enrollments) { if (type.isPaid()) { - return ((PaidEnrollment) enrollment).getMaxEnrollment(); + return ((PaidEnrollments) enrollments).getMaxEnrollment(); } return 0; } diff --git a/src/main/java/nextstep/courses/infrastructure/ImageRepository.java b/src/main/java/nextstep/courses/infrastructure/ImageRepository.java index f187b8b7c..a4e4fe1f5 100644 --- a/src/main/java/nextstep/courses/infrastructure/ImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/ImageRepository.java @@ -1,9 +1,9 @@ package nextstep.courses.infrastructure; -import nextstep.courses.dto.ImageDto; +import nextstep.courses.domain.session.info.basic.SessionThumbnail; import org.springframework.stereotype.Repository; @Repository public interface ImageRepository { - ImageDto findBySessionId(Long sessionId); + SessionThumbnail findThumbnailBySessionId(Long sessionId); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java index 3d1d7fe34..89480c762 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java @@ -1,5 +1,6 @@ package nextstep.courses.infrastructure; +import nextstep.courses.domain.session.info.basic.SessionThumbnail; import nextstep.courses.dto.ImageDto; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; @@ -7,6 +8,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.List; @Repository("imageRepository") public class JdbcImageRepository implements ImageRepository { @@ -17,7 +19,21 @@ public JdbcImageRepository(JdbcOperations jdbcTemplate) { } @Override - public ImageDto findBySessionId(Long sessionId) { + public SessionThumbnail findThumbnailBySessionId(Long sessionId) { + List imageDtos = findBySessionId(sessionId); + if (imageDtos.isEmpty()) { + throw new IllegalArgumentException("존재하지 않는 이미지입니다."); + } + + SessionThumbnail thumbnail = new SessionThumbnail(); + for (ImageDto imageDto : imageDtos) { + thumbnail.addThumbnail(imageDto.getFileName(), imageDto.getFileSize(), + imageDto.getWidth(), imageDto.getHeight()); + } + return thumbnail; + } + + private List findBySessionId(Long sessionId) { String sql = "SELECT id, session_id, file_name, file_size, width, height FROM image WHERE session_id = ?"; RowMapper rowMapper = (rs, rowNum) -> ImageDto.builder() .id(rs.getLong("id")) @@ -29,7 +45,7 @@ public ImageDto findBySessionId(Long sessionId) { .createdAt(toLocalDateTime(rs.getTimestamp("created_at"))) .updatedAt(toLocalDateTime(rs.getTimestamp("updated_at"))) .build(); - return jdbcTemplate.queryForObject(sql, rowMapper, sessionId); + return jdbcTemplate.query(sql, rowMapper, sessionId); } private LocalDateTime toLocalDateTime(Timestamp timestamp) { diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentRepository.java index 4588c7ed0..b84dcff02 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentRepository.java @@ -1,10 +1,13 @@ package nextstep.courses.infrastructure; +import nextstep.courses.domain.session.enrollment.EnrollmentStatus; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Repository("sessionEnrollmentRepository") public class JdbcSessionEnrollmentRepository implements SessionEnrollmentRepository { @@ -16,18 +19,50 @@ public JdbcSessionEnrollmentRepository(JdbcOperations jdbcTemplate) { @Override public void save(Long sessionId, Long userId) { + save(sessionId, userId, EnrollmentStatus.PENDING_APPROVAL); + } + + @Override + public void save(Long sessionId, Long userId, EnrollmentStatus status) { String sql = "INSERT INTO session_enrollment (session_id, user_id, status, created_at) VALUES (?, ?, ?, ?)"; jdbcTemplate.update(sql, sessionId, userId, - "Y", + status.name(), LocalDateTime.now() ); } + @Override + public void updateStatus(Long sessionId, Long userId, EnrollmentStatus status) { + String sql = "UPDATE session_enrollment SET status = ?, updated_at = ? WHERE session_id = ? AND user_id = ?"; + jdbcTemplate.update(sql, + status.name(), + LocalDateTime.now(), + sessionId, + userId + ); + } + @Override public List findUserIdsBySessionId(Long sessionId) { String sql = "SELECT user_id FROM session_enrollment WHERE session_id = ?"; return jdbcTemplate.queryForList(sql, Long.class, sessionId); } -} \ No newline at end of file + + @Override + public Map findUserStatusesBySessionId(Long sessionId) { + String sql = "SELECT user_id, status FROM session_enrollment WHERE session_id = ?"; + List> rows = jdbcTemplate.queryForList(sql, sessionId); + + Map result = new HashMap<>(); + for (Map row : rows) { + Long userId = (Long) row.get("user_id"); + String statusStr = (String) row.get("status"); + EnrollmentStatus status = EnrollmentStatus.valueOf(statusStr); + result.put(userId, status); + } + + return result; + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 214f58ece..0d154a90c 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,6 +1,7 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.session.SessionStatus; +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; import nextstep.courses.domain.session.SessionType; import nextstep.courses.dto.SessionDto; import org.springframework.jdbc.core.JdbcOperations; @@ -21,7 +22,7 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public Optional findById(Long id) { - String sql = "SELECT id, course_id, title, session_type, status, " + + String sql = "SELECT id, course_id, title, session_type, progress_status, recruitment_status, " + "start_date, end_date, maximum_enrollment, created_at, updated_at " + "FROM session WHERE id = ?"; RowMapper rowMapper = (rs, rowNum) -> SessionDto.builder() @@ -29,7 +30,8 @@ public Optional findById(Long id) { .courseId(rs.getLong("course_id")) .title(rs.getString("title")) .sessionType(SessionType.valueOf(rs.getString("session_type"))) - .status(SessionStatus.valueOf(rs.getString("status"))) + .progressStatus(SessionProgressStatus.valueOf(rs.getString("progress_status"))) + .recruitmentStatus(SessionRecruitmentStatus.valueOf(rs.getString("recruitment_status"))) .startDate(rs.getDate("start_date").toLocalDate()) .endDate(rs.getDate("end_date").toLocalDate()) .maximumEnrollment(rs.getInt("maximum_enrollment")) diff --git a/src/main/java/nextstep/courses/infrastructure/SessionEnrollmentRepository.java b/src/main/java/nextstep/courses/infrastructure/SessionEnrollmentRepository.java index 7171aa0a5..d9532eefe 100644 --- a/src/main/java/nextstep/courses/infrastructure/SessionEnrollmentRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/SessionEnrollmentRepository.java @@ -1,8 +1,14 @@ package nextstep.courses.infrastructure; +import nextstep.courses.domain.session.enrollment.EnrollmentStatus; + import java.util.List; +import java.util.Map; public interface SessionEnrollmentRepository { void save(Long sessionId, Long userId); + void save(Long sessionId, Long userId, EnrollmentStatus status); + void updateStatus(Long sessionId, Long userId, EnrollmentStatus status); List findUserIdsBySessionId(Long sessionId); -} \ No newline at end of file + Map findUserStatusesBySessionId(Long sessionId); +} diff --git a/src/main/java/nextstep/courses/service/ImageService.java b/src/main/java/nextstep/courses/service/ImageService.java new file mode 100644 index 000000000..2cff7a503 --- /dev/null +++ b/src/main/java/nextstep/courses/service/ImageService.java @@ -0,0 +1,17 @@ +package nextstep.courses.service; + +import lombok.RequiredArgsConstructor; +import nextstep.courses.domain.session.info.basic.SessionThumbnail; +import nextstep.courses.infrastructure.ImageRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ImageService { + private final ImageRepository imageRepository; + + public SessionThumbnail findThumbnailBySessionId(Long sessionId) { + return imageRepository.findThumbnailBySessionId(sessionId); + } + +} diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 13e1eb35e..20f2f8426 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -1,22 +1,16 @@ package nextstep.courses.service; import lombok.RequiredArgsConstructor; -import nextstep.courses.domain.session.Session; -import nextstep.courses.domain.session.SessionId; -import nextstep.courses.domain.session.SessionStatus; -import nextstep.courses.domain.session.SessionType; -import nextstep.courses.domain.session.enrollment.Enrollment; -import nextstep.courses.domain.session.enrollment.FreeEnrollment; -import nextstep.courses.domain.session.enrollment.PaidEnrollment; +import nextstep.courses.domain.session.*; +import nextstep.courses.domain.session.enrollment.Enrollments; +import nextstep.courses.domain.session.enrollment.FreeEnrollments; +import nextstep.courses.domain.session.enrollment.PaidEnrollments; import nextstep.courses.domain.session.info.SessionInfo; import nextstep.courses.domain.session.info.basic.SessionBasicInfo; -import nextstep.courses.domain.session.info.basic.SessionThumbnail; import nextstep.courses.domain.session.info.detail.SessionDetailInfo; import nextstep.courses.domain.session.info.detail.SessionPeriod; import nextstep.courses.domain.session.info.detail.SessionPrice; -import nextstep.courses.dto.ImageDto; import nextstep.courses.dto.SessionDto; -import nextstep.courses.infrastructure.ImageRepository; import nextstep.courses.infrastructure.SessionEnrollmentRepository; import nextstep.courses.infrastructure.SessionRepository; import nextstep.payments.domain.Payment; @@ -27,14 +21,13 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.stream.Collectors; @RequiredArgsConstructor @Service public class SessionService { private final SessionRepository sessionRepository; private final SessionEnrollmentRepository sessionEnrollmentRepository; - private final ImageRepository imageRepository; + private final ImageService imageService; private final PaymentService paymentService; private final UserService userService; @@ -65,13 +58,14 @@ private Session findSession(Long sessionId) { } private Session createSessionFromDto(SessionDto sessionDto) { - SessionBasicInfo sessionBasicInfo = new SessionBasicInfo(sessionDto.getTitle(), getThumbnail(sessionDto.getId())); + SessionBasicInfo sessionBasicInfo = new SessionBasicInfo(sessionDto.getTitle(), + imageService.findThumbnailBySessionId(sessionDto.getId())); SessionDetailInfo sessionDetailInfo = getSessionDetailInfo(sessionDto); SessionInfo sessionInfo = new SessionInfo(sessionBasicInfo, sessionDetailInfo); - Enrollment enrollment = getEnrollment(sessionDto); + Enrollments enrollments = getEnrollment(sessionDto); SessionId entityId = new SessionId(sessionDto.getId(), sessionDto.getCourseId()); - return new Session(entityId, sessionInfo, enrollment); + return new Session(entityId, sessionInfo, enrollments); } private SessionDetailInfo getSessionDetailInfo(SessionDto sessionDto) { @@ -80,34 +74,18 @@ private SessionDetailInfo getSessionDetailInfo(SessionDto sessionDto) { return new SessionDetailInfo(sessionPeriod, sessionPrice); } - private SessionThumbnail getThumbnail(Long sessionId) { - ImageDto imageDto = imageRepository.findBySessionId(sessionId); - if (imageDto == null) { - throw new IllegalArgumentException("존재하지 않는 이미지입니다."); - } - - return new SessionThumbnail(imageDto.getFileName(), imageDto.getFileSize(), - imageDto.getWidth(), imageDto.getHeight()); - } - - private Enrollment getEnrollment(SessionDto sessionDto) { + private Enrollments getEnrollment(SessionDto sessionDto) { SessionType sessionType = sessionDto.getSessionType(); List enrolledUserIds = sessionEnrollmentRepository.findUserIdsBySessionId(sessionDto.getId()); - List enrolledUsers = findEnrolledUsersByIds(enrolledUserIds); - SessionStatus sessionStatus = sessionDto.getStatus(); + List enrolledUsers = userService.findEnrolledUsersByIds(enrolledUserIds); + SessionProgressStatus progressStatus = sessionDto.getProgressStatus(); + SessionRecruitmentStatus recruitmentStatus = sessionDto.getRecruitmentStatus(); if (sessionType.isPaid()) { int maximumEnrollment = sessionDto.getMaximumEnrollment(); - return new PaidEnrollment(maximumEnrollment, enrolledUsers, sessionStatus); + return new PaidEnrollments(maximumEnrollment, enrolledUsers, progressStatus, recruitmentStatus); } - return new FreeEnrollment(enrolledUsers, sessionStatus); - } - - private List findEnrolledUsersByIds(List enrolledUserIds) { - return enrolledUserIds.stream() - .map(userId -> userService.findByUserId(userId.toString()) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."))) - .collect(Collectors.toList()); + return new FreeEnrollments(enrolledUsers, progressStatus, recruitmentStatus); } } \ No newline at end of file diff --git a/src/main/java/nextstep/users/service/UserService.java b/src/main/java/nextstep/users/service/UserService.java index 7e7f10541..b33e3e1e7 100644 --- a/src/main/java/nextstep/users/service/UserService.java +++ b/src/main/java/nextstep/users/service/UserService.java @@ -5,7 +5,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Service public class UserService { @@ -19,4 +21,12 @@ public UserService(UserRepository userRepository) { public Optional findByUserId(String id) { return userRepository.findByUserId(id); } + + @Transactional(readOnly = true) + public List findEnrolledUsersByIds(List enrolledUserIds) { + return enrolledUserIds.stream() + .map(userId -> findByUserId(userId.toString()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."))) + .collect(Collectors.toList()); + } } \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 12518bac6..99a8eafe9 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -61,7 +61,8 @@ CREATE TABLE session course_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, session_type VARCHAR(20) NOT NULL, - status VARCHAR(20) NOT NULL, + progress_status VARCHAR(20) NOT NULL, + recruitment_status VARCHAR(20) NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL, maximum_enrollment INT NOT NULL, diff --git a/src/test/java/nextstep/courses/domain/session/SessionStatusTest.java b/src/test/java/nextstep/courses/domain/session/SessionStatusTest.java index cb4a9a324..bab58a038 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionStatusTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionStatusTest.java @@ -5,18 +5,32 @@ import static org.assertj.core.api.Assertions.assertThat; -class SessionStatusTest { +class SessionProgressStatusTest { + @Test + @DisplayName("강의 진행 상태를 확인한다") + void isInProgress() { + // given + SessionProgressStatus preparing = SessionProgressStatus.PREPARING; + SessionProgressStatus inProgress = SessionProgressStatus.IN_PROGRESS; + SessionProgressStatus ended = SessionProgressStatus.CLOSED; + + // when & then + assertThat(preparing.isInProgress()).isFalse(); + assertThat(inProgress.isInProgress()).isTrue(); + assertThat(ended.isInProgress()).isFalse(); + } +} + +class SessionRecruitmentStatusTest { @Test @DisplayName("모집중인 강의인지 확인한다") void isRecruiting() { // given - SessionStatus recruiting = SessionStatus.RECRUITING; - SessionStatus preparing = SessionStatus.PREPARING; - SessionStatus closed = SessionStatus.CLOSED; + SessionRecruitmentStatus recruiting = SessionRecruitmentStatus.RECRUITING; + SessionRecruitmentStatus notRecruiting = SessionRecruitmentStatus.NOT_RECRUITING; // when & then assertThat(recruiting.isRecruiting()).isTrue(); - assertThat(preparing.isRecruiting()).isFalse(); - assertThat(closed.isRecruiting()).isFalse(); + assertThat(notRecruiting.isRecruiting()).isFalse(); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index 43fb6d2cb..0aa79cbe1 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -1,7 +1,7 @@ package nextstep.courses.domain.session; -import nextstep.courses.domain.session.enrollment.FreeEnrollment; -import nextstep.courses.domain.session.enrollment.PaidEnrollment; +import nextstep.courses.domain.session.enrollment.FreeEnrollments; +import nextstep.courses.domain.session.enrollment.PaidEnrollments; import nextstep.courses.domain.session.info.SessionInfo; import nextstep.courses.domain.session.info.basic.SessionBasicInfo; import nextstep.courses.domain.session.info.basic.SessionThumbnail; @@ -10,6 +10,7 @@ import nextstep.courses.domain.session.info.detail.SessionPrice; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -22,7 +23,13 @@ class SessionTest { private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); private static final LocalDate START_DATE = LocalDate.now(); private static final LocalDate END_DATE = START_DATE.plusMonths(1); - public static final SessionThumbnail THUMBNAIL = new SessionThumbnail("image.jpg", 1024, 300, 200); + public static final SessionThumbnail THUMBNAIL = new SessionThumbnail(); + + @BeforeAll + static void setUp() { + // Any setup code can go here if needed + THUMBNAIL.addThumbnail("image.jpg", 1024, 300, 200); + } @Test @DisplayName("강의를 생성한다") @@ -35,7 +42,7 @@ void create() { Session session = new Session( new SessionId(1L, 1L), sessionInfo, - new FreeEnrollment() + new FreeEnrollments() ); assertThat(session.isPaid()).isTrue(); @@ -52,7 +59,7 @@ void createFreeSession() { Session session = new Session( new SessionId(1L, 1L), sessionInfo, - new FreeEnrollment() + new FreeEnrollments() ); assertThat(session.isPaid()).isFalse(); @@ -80,10 +87,9 @@ void enrollFreeSession() { Session session = new Session( new SessionId(1L, 1L), sessionInfo, - new FreeEnrollment() + new FreeEnrollments() ); - session.enroll(USER, null); assertThatThrownBy(() -> session.enroll(USER, null)) .isInstanceOf(IllegalStateException.class); @@ -97,11 +103,34 @@ void enrollFullSession() { NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); session.enroll(USER, payment); + session.approve(USER); + assertThatThrownBy(() -> session.enroll(anotherUser, payment)) .isInstanceOf(IllegalStateException.class); } - private static Session getPaidSession(int maxEnrollment) { + @Test + @DisplayName("세션에 여러 썸네일을 추가할 수 있다") + void addThumbnails() { + SessionThumbnail thumbnail = new SessionThumbnail(); + thumbnail.addThumbnail("test1.jpg", 1024L, 300, 200); + thumbnail.addThumbnail("test2.jpg", 1024L, 300, 200); + + SessionPeriod period = new SessionPeriod(START_DATE, END_DATE); + SessionPrice price = new SessionPrice(SessionType.PAID, 10000); + SessionDetailInfo detailInfo = new SessionDetailInfo(period, price); + SessionBasicInfo basicInfo = new SessionBasicInfo("강의 제목", thumbnail); + SessionInfo sessionInfo = new SessionInfo(basicInfo, detailInfo); + Session session = new Session( + new SessionId(1L, 1L), + sessionInfo, + new FreeEnrollments() + ); + + assertThat(session.getInfo().getBasicInfo().getThumbnail().getThumbnails()).hasSize(2); + } + + private Session getPaidSession(int maxEnrollment) { SessionPeriod period = new SessionPeriod(START_DATE, END_DATE); SessionPrice price = new SessionPrice(SessionType.PAID, 10000); SessionDetailInfo detailInfo = new SessionDetailInfo(period, price); @@ -111,7 +140,7 @@ private static Session getPaidSession(int maxEnrollment) { return new Session( new SessionId(1L, 1L), sessionInfo, - new PaidEnrollment(maxEnrollment) + new PaidEnrollments(maxEnrollment) ); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionThumbnailTest.java b/src/test/java/nextstep/courses/domain/session/SessionThumbnailTest.java index fb5bf006c..177154b46 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionThumbnailTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionThumbnailTest.java @@ -10,33 +10,34 @@ class SessionThumbnailTest { @Test - @DisplayName("정상적인 썸네일을 생성한다") + @DisplayName("썸네일을 생성한다") void create() { - // given - String fullFileName = "test.jpg"; - long fileSize = 500 * 1024; // 500KB - int width = 300; - int height = 200; - - // when - SessionThumbnail thumbnail = new SessionThumbnail(fullFileName, fileSize, width, height); - - // then - assertThat(thumbnail).isNotNull(); + SessionThumbnail thumbnail = new SessionThumbnail(); + thumbnail.addThumbnail("test.jpg", 1024L, 300, 200); + + assertThat(thumbnail.getThumbnails()).hasSize(1); } @Test - @DisplayName("파일 크기가 1MB를 초과하면 예외가 발생한다") - void validateFileSize() { - // given - String fullFileName = "test.jpg"; - long fileSize = 1024 * 1024 + 1; // 1MB + 1byte - int width = 300; - int height = 200; + @DisplayName("썸네일은 최대 5개까지 등록할 수 있다") + void maxThumbnailCount() { + SessionThumbnail thumbnail = new SessionThumbnail(); + for (int i = 0; i < 5; i++) { + thumbnail.addThumbnail("test" + i + ".jpg", 1024L, 300, 200); + } + + assertThatThrownBy(() -> thumbnail.addThumbnail("test6.jpg", 1024L, 300, 200)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("썸네일은 최대 5개까지만 등록할 수 있습니다."); + } - // when & then - assertThatThrownBy(() -> new SessionThumbnail(fullFileName, fileSize, width, height)) + @Test + @DisplayName("썸네일 파일 크기는 1MB를 초과할 수 없다") + void maxFileSize() { + SessionThumbnail thumbnail = new SessionThumbnail(); + + assertThatThrownBy(() -> thumbnail.addThumbnail("test.jpg", 1024 * 1024 + 1, 300, 200)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("파일 크기는 1MB를 초과할 수 없습니다."); + .hasMessage("썸네일 파일 크기는 1MB를 초과할 수 없습니다."); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java new file mode 100644 index 000000000..ec49712ff --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java @@ -0,0 +1,54 @@ +package nextstep.courses.domain.session.enrollment; + +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; +import nextstep.users.domain.NsUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class EnrollmentsManagerTest { + private NsUser user; + + @BeforeEach + void setUp() { + user = new NsUser(1L, "user", "password", "name", "user@email.com"); + } + + @Test + @DisplayName("모집중인 상태에서 수강신청을 하면 성공한다") + void enroll_success_when_recruiting() { + // given + EnrollmentManager enrollmentManager = new EnrollmentManager( + SessionProgressStatus.PREPARING, + SessionRecruitmentStatus.RECRUITING + ); + + // when + enrollmentManager.enroll(user); + + // then + assertThat(enrollmentManager.getEnrolledUsers()).isEmpty(); + assertThat(enrollmentManager.getPendingApprovalUsers()).hasSize(1); + assertThat(enrollmentManager.getPendingApprovalUsers().get(0)).isEqualTo(user); + assertThat(enrollmentManager.getEnrollmentStatus(user)).isEqualTo(EnrollmentStatus.PENDING_APPROVAL); + } + + @Test + @DisplayName("비모집중인 상태에서 수강신청을 하면 실패한다") + void enroll_fail_when_not_recruiting() { + // given + EnrollmentManager enrollmentManager = new EnrollmentManager( + SessionProgressStatus.PREPARING, + SessionRecruitmentStatus.NOT_RECRUITING + ); + + // when & then + assertThatThrownBy(() -> enrollmentManager.enroll(user)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("수강 신청이 불가능합니다."); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java deleted file mode 100644 index f12d0cf52..000000000 --- a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package nextstep.courses.domain.session.enrollment; - -import nextstep.courses.domain.session.SessionStatus; -import nextstep.users.domain.NsUser; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class FreeEnrollmentTest { - private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); - - @Test - @DisplayName("무료 강의의 수강 신청을 생성한다") - void createFreeEnrollment() { - // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); - - // when & then - assertThat(enrollment).isNotNull(); - } - - @Test - @DisplayName("무료 강의에 수강 신청을 한다") - void enrollFreeSession() { - // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); - NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); - - // when - enrollment.enroll(USER); - enrollment.enroll(anotherUser); - } - - @Test - @DisplayName("이미 수강 신청한 사용자는 다시 수강 신청할 수 없다") - void validateDuplicateEnrollment() { - // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); - - // when - enrollment.enroll(USER); - - // then - assertThatThrownBy(() -> enrollment.enroll(USER)) - .isInstanceOf(IllegalStateException.class); - } - - @Test - @DisplayName("수강 신청할 사용자가 없으면 예외가 발생한다") - void validateNullUser() { - // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); - - // when & then - assertThatThrownBy(() -> enrollment.enroll(null)) - .isInstanceOf(IllegalArgumentException.class); - } -} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java new file mode 100644 index 000000000..91d0ea410 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java @@ -0,0 +1,149 @@ +package nextstep.courses.domain.session.enrollment; + +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; +import nextstep.users.domain.NsUser; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatCode; + +class FreeEnrollmentsTest { + private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); + + @Test + @DisplayName("무료 강의의 수강 신청을 생성한다") + void createFreeEnrollment() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // when & then + assertThat(enrollments).isNotNull(); + } + + @Test + @DisplayName("무료 강의에 수강 신청을 한다") + void enrollFreeSession() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); + + // when & then + assertThatCode(() -> { + enrollments.enroll(USER); + enrollments.enroll(anotherUser); + }).doesNotThrowAnyException(); + } + + @Test + @DisplayName("이미 수강 신청한 사용자는 다시 수강 신청할 수 없다") + void validateDuplicateEnrollment() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // when + enrollments.enroll(USER); + + // then + assertThatThrownBy(() -> enrollments.enroll(USER)) + .isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("수강 신청할 사용자가 없으면 예외가 발생한다") + void validateNullUser() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // when & then + assertThatThrownBy(() -> enrollments.enroll(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("수강 신청 시 초기 상태는 승인 대기 상태이다") + void enrollmentInitialStatusIsPendingApproval() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // when + enrollments.enroll(USER); + + // then + assertThat(enrollments.getEnrollmentStatus(USER)).isEqualTo(EnrollmentStatus.PENDING_APPROVAL); + assertThat(enrollments.getPendingApprovalUsers()).hasSize(1); + assertThat(enrollments.getPendingApprovalUsers().get(0)).isEqualTo(USER); + } + + @Test + @DisplayName("승인 대기 상태의 수강 신청을 승인한다") + void approveEnrollment() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + enrollments.enroll(USER); + + // when + enrollments.approve(USER); + + // then + assertThat(enrollments.getEnrollmentStatus(USER)).isEqualTo(EnrollmentStatus.ENROLLED); + assertThat(enrollments.getEnrolledUsers()).hasSize(1); + assertThat(enrollments.getEnrolledUsers().get(0)).isEqualTo(USER); + } + + @Test + @DisplayName("승인 대기 상태가 아닌 수강 신청을 승인하면 예외가 발생한다") + void approveNonPendingEnrollment() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + enrollments.enroll(USER); + enrollments.approve(USER); + + // when & then + assertThatThrownBy(() -> enrollments.approve(USER)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("승인 대기 상태의 수강신청만 승인할 수 있습니다."); + } + + @Test + @DisplayName("수강 신청을 취소한다") + void cancelEnrollment() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + enrollments.enroll(USER); + + // when + enrollments.cancel(USER); + + // then + assertThat(enrollments.getEnrollmentStatus(USER)).isEqualTo(EnrollmentStatus.CANCELLED); + } + + @Test + @DisplayName("승인된 수강 신청도 취소할 수 있다") + void cancelApprovedEnrollment() { + // given + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + enrollments.enroll(USER); + enrollments.approve(USER); + + // when + enrollments.cancel(USER); + + // then + assertThat(enrollments.getEnrollmentStatus(USER)).isEqualTo(EnrollmentStatus.CANCELLED); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java deleted file mode 100644 index f7d17a31f..000000000 --- a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package nextstep.courses.domain.session.enrollment; - -import nextstep.courses.domain.session.SessionStatus; -import nextstep.users.domain.NsUser; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class PaidEnrollmentTest { - private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); - - @Test - @DisplayName("유료 강의의 수강 신청을 생성한다") - void createPaidEnrollment() { - // given - int maxEnrollment = 30; - - // when - Enrollment enrollment = new PaidEnrollment(maxEnrollment, new ArrayList<>(), SessionStatus.RECRUITING); - - // then - assertThat(enrollment).isNotNull(); - } - - @Test - @DisplayName("유료 강의의 최대 수강 인원이 0이면 예외가 발생한다") - void validateMaxEnrollment() { - // given - int maxEnrollment = 0; - - // when & then - assertThatThrownBy(() -> new PaidEnrollment(maxEnrollment, new ArrayList<>(), SessionStatus.RECRUITING)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("유료 강의는 최대 수강 인원이 0보다 커야 합니다."); - } - - @Test - @DisplayName("유료 강의에 수강 신청을 한다") - void enrollPaidSession() { - // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), SessionStatus.RECRUITING); - - // when - enrollment.enroll(USER); - } - - @Test - @DisplayName("수강 인원이 가득 찬 유료 강의는 수강 신청이 불가능하다") - void enrollFullPaidSession() { - // given - Enrollment enrollment = new PaidEnrollment(1, new ArrayList<>(), SessionStatus.RECRUITING); - NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); - - // when - enrollment.enroll(USER); - - // then - assertThatThrownBy(() -> enrollment.enroll(anotherUser)) - .isInstanceOf(IllegalStateException.class); - } - - @Test - @DisplayName("이미 수강 신청한 사용자는 다시 수강 신청할 수 없다") - void validateDuplicateEnrollment() { - // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), SessionStatus.RECRUITING); - - // when - enrollment.enroll(USER); - - // then - assertThatThrownBy(() -> enrollment.enroll(USER)) - .isInstanceOf(IllegalStateException.class); - } - - @Test - @DisplayName("수강 신청할 사용자가 없으면 예외가 발생한다") - void validateNullUser() { - // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), SessionStatus.RECRUITING); - - // when & then - assertThatThrownBy(() -> enrollment.enroll(null)) - .isInstanceOf(IllegalArgumentException.class); - } -} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java new file mode 100644 index 000000000..342993763 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java @@ -0,0 +1,194 @@ +package nextstep.courses.domain.session.enrollment; + +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; +import nextstep.users.domain.NsUser; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.assertj.core.api.Assertions.*; + +class PaidEnrollmentsTest { + private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); + + @Test + @DisplayName("유료 강의의 수강 신청을 생성한다") + void createPaidEnrollment() { + // given + int maxEnrollment = 30; + + // when + Enrollments enrollments = new PaidEnrollments(maxEnrollment, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // then + assertThat(enrollments).isNotNull(); + } + + @Test + @DisplayName("유료 강의의 최대 수강 인원이 0이면 예외가 발생한다") + void validateMaxEnrollment() { + // given + int maxEnrollment = 0; + + // when & then + assertThatThrownBy(() -> new PaidEnrollments(maxEnrollment, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유료 강의는 최대 수강 인원이 0보다 커야 합니다."); + } + + @Test + @DisplayName("유료 강의에 수강 신청을 한다") + void enrollPaidSession() { + // given + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // when & then + assertThatCode(() -> enrollments.enroll(USER)).doesNotThrowAnyException(); + } + + @Test + @DisplayName("수강 인원이 가득 찬 유료 강의는 수강 신청이 불가능하다") + void enrollFullPaidSession() { + // given + Enrollments enrollments = new PaidEnrollments(1, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); + + // when + enrollments.enroll(USER); + enrollments.approve(USER); + + // then + assertThatThrownBy(() -> enrollments.enroll(anotherUser)) + .isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("이미 수강 신청한 사용자는 다시 수강 신청할 수 없다") + void validateDuplicateEnrollment() { + // given + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // when + enrollments.enroll(USER); + + // then + assertThatThrownBy(() -> enrollments.enroll(USER)) + .isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("수강 신청할 사용자가 없으면 예외가 발생한다") + void validateNullUser() { + // given + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // when & then + assertThatThrownBy(() -> enrollments.enroll(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("유료 강의 수강 신청 시 초기 상태는 승인 대기 상태이다") + void enrollmentInitialStatusIsPendingApproval() { + // given + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + + // when + enrollments.enroll(USER); + + // then + assertThat(enrollments.getEnrollmentStatus(USER)).isEqualTo(EnrollmentStatus.PENDING_APPROVAL); + assertThat(enrollments.getPendingApprovalUsers()).hasSize(1); + assertThat(enrollments.getPendingApprovalUsers().get(0)).isEqualTo(USER); + } + + @Test + @DisplayName("유료 강의 승인 대기 상태의 수강 신청을 승인한다") + void approveEnrollment() { + // given + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + enrollments.enroll(USER); + + // when + enrollments.approve(USER); + + // then + assertThat(enrollments.getEnrollmentStatus(USER)).isEqualTo(EnrollmentStatus.ENROLLED); + assertThat(enrollments.getEnrolledUsers()).hasSize(1); + assertThat(enrollments.getEnrolledUsers().get(0)).isEqualTo(USER); + } + + @Test + @DisplayName("유료 강의 승인 대기 상태가 아닌 수강 신청을 승인하면 예외가 발생한다") + void approveNonPendingEnrollment() { + // given + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + enrollments.enroll(USER); + enrollments.approve(USER); + + // when & then + assertThatThrownBy(() -> enrollments.approve(USER)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("승인 대기 상태의 수강신청만 승인할 수 있습니다."); + } + + @Test + @DisplayName("유료 강의 수강 신청을 취소한다") + void cancelEnrollment() { + // given + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + enrollments.enroll(USER); + + // when + enrollments.cancel(USER); + + // then + assertThat(enrollments.getEnrollmentStatus(USER)).isEqualTo(EnrollmentStatus.CANCELLED); + } + + @Test + @DisplayName("유료 강의 승인된 수강 신청도 취소할 수 있다") + void cancelApprovedEnrollment() { + // given + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + enrollments.enroll(USER); + enrollments.approve(USER); + + // when + enrollments.cancel(USER); + + // then + assertThat(enrollments.getEnrollmentStatus(USER)).isEqualTo(EnrollmentStatus.CANCELLED); + } + + @Test + @DisplayName("유료 강의 수강 인원 계산 시 승인된 수강 신청만 포함한다") + void enrollmentCountOnlyIncludesApproved() { + // given + Enrollments enrollments = new PaidEnrollments(2, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + NsUser user2 = new NsUser(2L, "user2", "password", "name", "email"); + NsUser user3 = new NsUser(3L, "user3", "password", "name", "email"); + + // when + enrollments.enroll(USER); + enrollments.enroll(user2); + enrollments.approve(USER); + + // then + assertThatCode(() -> enrollments.enroll(user3)).doesNotThrowAnyException(); + } +} diff --git a/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java index 29d5b215a..19bca64f6 100644 --- a/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java @@ -1,5 +1,7 @@ package nextstep.courses.infrastructure; +import nextstep.courses.domain.session.info.basic.SessionThumbnail; +import nextstep.courses.domain.session.info.basic.ThumbnailInfo; import nextstep.courses.dto.ImageDto; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -7,6 +9,7 @@ import org.springframework.jdbc.core.RowMapper; import java.time.LocalDateTime; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -34,27 +37,25 @@ void findBySessionId() { .sessionId(sessionId) .fileName("test.jpg") .fileSize(1024L) - .width(800) - .height(600) + .width(600) + .height(400) .createdAt(now) .updatedAt(now) .build(); + List expectedImages = List.of(expectedImage); - when(jdbcTemplate.queryForObject(anyString(), any(RowMapper.class), any())) - .thenReturn(expectedImage); + when(jdbcTemplate.query(anyString(), any(RowMapper.class), any())) + .thenReturn(expectedImages); // when - ImageDto actualImage = imageRepository.findBySessionId(sessionId); + SessionThumbnail sessionThumbnail = imageRepository.findThumbnailBySessionId(sessionId); + ThumbnailInfo actualImage = sessionThumbnail.getThumbnails().get(0); // then assertThat(actualImage).isNotNull(); - assertThat(actualImage.getId()).isEqualTo(expectedImage.getId()); - assertThat(actualImage.getSessionId()).isEqualTo(expectedImage.getSessionId()); - assertThat(actualImage.getFileName()).isEqualTo(expectedImage.getFileName()); + assertThat(actualImage.getFileName().getFullFileName()).isEqualTo(expectedImage.getFileName()); assertThat(actualImage.getFileSize()).isEqualTo(expectedImage.getFileSize()); - assertThat(actualImage.getWidth()).isEqualTo(expectedImage.getWidth()); - assertThat(actualImage.getHeight()).isEqualTo(expectedImage.getHeight()); - assertThat(actualImage.getCreatedAt()).isEqualTo(expectedImage.getCreatedAt()); - assertThat(actualImage.getUpdatedAt()).isEqualTo(expectedImage.getUpdatedAt()); + assertThat(actualImage.getSize().getWidth()).isEqualTo(expectedImage.getWidth()); + assertThat(actualImage.getSize().getHeight()).isEqualTo(expectedImage.getHeight()); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentsRepositoryTest.java similarity index 97% rename from src/test/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentRepositoryTest.java rename to src/test/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentsRepositoryTest.java index fdc6904ee..f5e968bc7 100644 --- a/src/test/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/JdbcSessionEnrollmentsRepositoryTest.java @@ -17,7 +17,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class JdbcSessionEnrollmentRepositoryTest { +class JdbcSessionEnrollmentsRepositoryTest { private JdbcSessionEnrollmentRepository repository; private JdbcOperations jdbcTemplate;