diff --git "a/docs/step4\354\232\224\352\265\254\354\202\254\355\225\255.md" "b/docs/step4\354\232\224\352\265\254\354\202\254\355\225\255.md" new file mode 100644 index 000000000..191aaf7f2 --- /dev/null +++ "b/docs/step4\354\232\224\352\265\254\354\202\254\355\225\255.md" @@ -0,0 +1,30 @@ +# 변경된 기능 요구사항 정리 + +## 1 강의 상태 분리 +### 기존 +- 강의 진행 상태와 모집 상태가 결합되어 있음 +- 모집중 상태일 때만 수강신청 가능 + +### 변경점 +- [ ] 강의 진행 상태(ProgressStatus): 준비중, 진행중, 종료 +- [ ] 모집 상태(RecruitmentStatus): 비모집중, 모집중 +- [ ] 강의가 진행중 이여도 모집중이라면 수강신청 가능 + +## 2 강의 커버 이미지 여러개 +### 기존 +- Session은 하나의 SessionImage만 가짐 +- session테이블에 image_id가 FK 존재 + +### 변경점 +- [ ] Session은 하나 이상의 커버 이미지를 가질 수 있음 +- [ ] DB에 데이터가 존재한다는 가정하에 진행해야 하므로 session테이블의 image_id는 대표이미지로 변경 +- [ ] 등록되는 여러개의 이미지는 별도 테이블로 추가로 관리해야할듯 + +## 3 수강 승인기능 추가 +### 기존 +- 모집중이고 결제금액과 수강료가 일치하고 최대 수강인원을 넘지 않으면 수강 가능 + +### 변경점 +- [ ] 수강신청을 받으면 대기 상태가 되고 +- [ ] 강사가 선발된 인원에 대해 승인 (승인된 인원만 수강 가능) +- [ ] 강사가 선발되지 않은 인원 수강 취소 diff --git a/src/main/java/nextstep/courses/domain/session/Enrollment.java b/src/main/java/nextstep/courses/domain/session/Enrollment.java index ed61ab572..ea6c45d6a 100644 --- a/src/main/java/nextstep/courses/domain/session/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/session/Enrollment.java @@ -3,29 +3,28 @@ import nextstep.payments.domain.Payment; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class Enrollment { private final Long sessionId; - private final SessionStatus status; + private final RecruitmentStatus recruitmentStatus; private final SessionType sessionType; private final List enrolledStudents; - public Enrollment(SessionStatus status, SessionType sessionType) { - this(null, status, sessionType, Collections.emptyList()); + public Enrollment(RecruitmentStatus recruitmentStatus, SessionType sessionType) { + this(null, recruitmentStatus, sessionType, Collections.emptyList()); } - public Enrollment(Long sessionId, SessionStatus status, SessionType sessionType, List enrolledStudents) { + + public Enrollment(Long sessionId, RecruitmentStatus recruitmentStatus, SessionType sessionType, List enrolledStudents) { this.sessionId = sessionId; - this.status = status; + this.recruitmentStatus = recruitmentStatus; this.sessionType = sessionType; this.enrolledStudents = enrolledStudents; } public EnrolledStudent enroll(Long nsUserId, Payment payment) { - if (!status.canEnroll()) { + if (!recruitmentStatus.canEnroll()) { throw new IllegalStateException("모집중인 강의만 수강 신청할 수 있다"); } if (!sessionType.isValidPayment(payment)) { @@ -35,14 +34,33 @@ public EnrolledStudent enroll(Long nsUserId, Payment payment) { if (sessionType.isOverCapacity(enrolledStudents.size())) { throw new IllegalStateException("최대 수강 인원을 초과했습니다."); } - return new EnrolledStudent(sessionId,nsUserId); + return new EnrolledStudent(sessionId, nsUserId); } - public SessionStatus getStatus() { - return status; + public EnrollmentApplication apply(Long nsUserId, Payment payment) { + if (!recruitmentStatus.canEnroll()) { + throw new IllegalStateException("모집중인 강의만 수강 신청할 수 있습니다."); + } + if (!sessionType.isValidPayment(payment)) { + throw new IllegalArgumentException("결제 금액이 수강료와 일치하지 않습니다."); + } + if (sessionType.isOverCapacity(enrolledStudents.size())) { + throw new IllegalStateException("최대 수강 인원을 초과했습니다."); + } + return new EnrollmentApplication(sessionId, nsUserId, payment); + } + + public EnrolledStudent approve(EnrollmentApplication application, Long adminId) { + application.approve(adminId); + return new EnrolledStudent(application.getSessionId(), application.getNsUserId()); } + public SessionType getSessionType() { return sessionType; } + + public RecruitmentStatus getRecruitmentStatus() { + return recruitmentStatus; + } } diff --git a/src/main/java/nextstep/courses/domain/session/EnrollmentApplication.java b/src/main/java/nextstep/courses/domain/session/EnrollmentApplication.java new file mode 100644 index 000000000..9cf81105b --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/EnrollmentApplication.java @@ -0,0 +1,67 @@ +package nextstep.courses.domain.session; + +import nextstep.payments.domain.Payment; + +import java.time.LocalDateTime; + +public class EnrollmentApplication { + private final Long sessionId; + private final Long nsUserId; + private final Payment payment; + private EnrollmentStatus status; + private LocalDateTime approvedAt; + private Long approvedBy; + + public EnrollmentApplication(Long sessionId, Long nsUserId, Payment payment) { + this(sessionId, nsUserId, payment, EnrollmentStatus.PENDING, LocalDateTime.now(), null); + } + + public EnrollmentApplication(Long sessionId, Long nsUserId, Payment payment, EnrollmentStatus status, LocalDateTime approvedAt, Long approvedBy) { + this.sessionId = sessionId; + this.nsUserId = nsUserId; + this.payment = payment; + this.status = status; + this.approvedAt = approvedAt; + this.approvedBy = approvedBy; + } + + public void approve(Long adminId) { + if (!status.isPending()) { + throw new IllegalStateException("대기 중인 신청만 승인 가능합니다."); + } + this.status = EnrollmentStatus.APPROVED; + this.approvedAt = LocalDateTime.now(); + this.approvedBy = adminId; + } + + public void cancel() { + if (!status.isPending()) { + throw new IllegalStateException("대기 중인 신청만 취소 가능합니다."); + } + this.status = EnrollmentStatus.CANCELLED; + } + + public Long getSessionId() { + return sessionId; + } + + public Long getNsUserId() { + return nsUserId; + } + + public Payment getPayment() { + return payment; + } + + public EnrollmentStatus getStatus() { + return status; + } + + public LocalDateTime getApprovedAt() { + return approvedAt; + } + + public Long getApprovedBy() { + return approvedBy; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/EnrollmentRepository.java b/src/main/java/nextstep/courses/domain/session/EnrollmentRepository.java index b0d5eda83..9c476e72d 100644 --- a/src/main/java/nextstep/courses/domain/session/EnrollmentRepository.java +++ b/src/main/java/nextstep/courses/domain/session/EnrollmentRepository.java @@ -3,8 +3,11 @@ import java.util.List; public interface EnrollmentRepository { - void save (EnrolledStudent enrolledStudent); + void save(EnrolledStudent enrolledStudent); List findBySessionId(Long sessionId); + void saveApplication(EnrollmentApplication application); + + void updateApplication(EnrollmentApplication application); } diff --git a/src/main/java/nextstep/courses/domain/session/EnrollmentStatus.java b/src/main/java/nextstep/courses/domain/session/EnrollmentStatus.java new file mode 100644 index 000000000..9f4ca5842 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/EnrollmentStatus.java @@ -0,0 +1,34 @@ +package nextstep.courses.domain.session; + +import java.util.Arrays; + +public enum EnrollmentStatus { + PENDING("대기중"), + APPROVED("승인됨"), + CANCELLED("취소됨"); + + private final String value; + + EnrollmentStatus(String value) { + this.value = value; + } + + public static EnrollmentStatus from(String description) { + return Arrays.stream(values()) + .filter(status -> status.value.equals(description)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("잘못된 수강신청 상태입니다: " + description)); + } + + public boolean isApproved() { + return this == APPROVED; + } + + public boolean isPending() { + return this == PENDING; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/ProgressStatus.java b/src/main/java/nextstep/courses/domain/session/ProgressStatus.java new file mode 100644 index 000000000..167270ee9 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/ProgressStatus.java @@ -0,0 +1,26 @@ +package nextstep.courses.domain.session; + +import java.util.Arrays; + +public enum ProgressStatus { + PREPARING("준비중"), + IN_PROGRESS("진행중"), + CLOSED("종료"); + + private final String value; + + ProgressStatus(String value) { + this.value = value; + } + + public static ProgressStatus from(String description) { + return Arrays.stream(values()) + .filter(status -> status.value.equals(description)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("잘못된 진행 상태입니다: " + description)); + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/RecruitmentStatus.java b/src/main/java/nextstep/courses/domain/session/RecruitmentStatus.java new file mode 100644 index 000000000..de0512b29 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/RecruitmentStatus.java @@ -0,0 +1,29 @@ +package nextstep.courses.domain.session; + +import java.util.Arrays; + +public enum RecruitmentStatus { + NOT_RECRUITING("비모집중"), + RECRUITING("모집중"); + + private final String value; + + RecruitmentStatus(String value) { + this.value = value; + } + + public static RecruitmentStatus from(String description) { + return Arrays.stream(values()) + .filter(status -> status.value.equals(description)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("잘못된 모집 상태입니다: " + description)); + } + + public boolean canEnroll() { + return this == RECRUITING; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index 33b85cf2a..98a1036d1 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -1,58 +1,50 @@ package nextstep.courses.domain.session; import nextstep.courses.domain.session.image.SessionImage; -import nextstep.payments.domain.Payment; +import nextstep.courses.domain.session.image.SessionImages; import java.time.LocalDate; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class Session { private final Long id; - private final int cohort; - private final SessionPeriod period; - private final SessionImage coverImage; - private final Enrollment enrollment; - - public Session(LocalDate startDate, LocalDate endDate, SessionImage coverImage, String status) { - this(new SessionPeriod(startDate, endDate), coverImage, SessionStatus.from(status), new FreeSessionType()); - } - - public Session(LocalDate startDate, LocalDate endDate, SessionImage image, String status, int maximumCapacity, long fee) { - this(new SessionPeriod(startDate, endDate), image, SessionStatus.from(status), new PaidSessionType(maximumCapacity, fee)); - } - - public Session(SessionPeriod period, SessionImage coverImage, SessionStatus status, SessionType sessionType) { - this(1, period, coverImage, new Enrollment(status, sessionType)); - } - - public Session(int cohort, LocalDate startDate, LocalDate endDate, SessionImage image) { - this(cohort, new SessionPeriod(startDate, endDate), image, new Enrollment(SessionStatus.PREPARING, new FreeSessionType())); + private final SessionInfo sessionInfo; + private final ProgressStatus progressStatus; + private final RecruitmentStatus recruitmentStatus; + private final SessionType sessionType; + + public Session(LocalDate startDate, LocalDate endDate, SessionImage image) { + this(new SessionInfo(1, startDate, endDate, image), + ProgressStatus.PREPARING, + RecruitmentStatus.NOT_RECRUITING, + new FreeSessionType()); } - public Session(int cohort, LocalDate startDate, LocalDate endDate, SessionImage image, Enrollment enrollment) { - this(cohort, new SessionPeriod(startDate, endDate), image, enrollment); + public Session(int cohort, LocalDate startDate, LocalDate endDate, SessionImage image, + ProgressStatus progressStatus, RecruitmentStatus recruitmentStatus, SessionType type) { + this(null, new SessionInfo(cohort, startDate, endDate, image), progressStatus, recruitmentStatus, type); } - public Session(int cohort, SessionPeriod period, SessionImage coverImage, Enrollment enrollment) { - this(null, cohort, period, coverImage, enrollment); + public Session(Long id, int cohort, LocalDate startDate, LocalDate endDate, SessionImage image, + ProgressStatus progressStatus, RecruitmentStatus recruitmentStatus, SessionType type) { + this(id, new SessionInfo(cohort, startDate, endDate, image), progressStatus, recruitmentStatus, type); } - public Session(long id, int cohort, LocalDate startDate, LocalDate endDate, SessionImage image, Enrollment enrollment) { - this(id, cohort, new SessionPeriod(startDate, endDate), image, enrollment); + public Session(SessionInfo info, ProgressStatus progressStatus, RecruitmentStatus recruitmentStatus, SessionType type) { + this(null, info, progressStatus, recruitmentStatus, type); } - public Session(Long id, int cohort, SessionPeriod period, SessionImage coverImage, Enrollment enrollment) { + public Session(Long id, SessionInfo sessionInfo, ProgressStatus progressStatus, RecruitmentStatus recruitmentStatus, SessionType sessionType) { this.id = id; - this.cohort = cohort; - this.period = period; - this.coverImage = coverImage; - this.enrollment = enrollment; + this.sessionInfo = sessionInfo; + this.progressStatus = progressStatus; + this.recruitmentStatus = recruitmentStatus; + this.sessionType = sessionType; } + public Enrollment createEnrollment(List currentStudents) { - return new Enrollment(id, enrollment.getStatus(), enrollment.getSessionType(), currentStudents); + return new Enrollment(id, recruitmentStatus, sessionType, currentStudents); } public Long getId() { @@ -60,23 +52,35 @@ public Long getId() { } public int getCohort() { - return cohort; + return sessionInfo.getCohort(); } public SessionImage getImage() { - return coverImage; + return sessionInfo.getImage(); + } + + public SessionImages getImages() { + return sessionInfo.getImages(); + } + + public SessionType getSessionType() { + return sessionType; + } + + public ProgressStatus getProgressStatus() { + return progressStatus; } - public Enrollment getEnrollment() { - return enrollment; + public RecruitmentStatus getRecruitmentStatus() { + return recruitmentStatus; } public LocalDate getStartDate() { - return period.getStartDate(); + return sessionInfo.getStartDate(); } public LocalDate getEndDate() { - return period.getEndDate(); + return sessionInfo.getEndDate(); } } 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..73eda7ddd --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionInfo.java @@ -0,0 +1,46 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.domain.session.image.SessionImage; +import nextstep.courses.domain.session.image.SessionImages; + +import java.time.LocalDate; + +public class SessionInfo { + private final int cohort; + private final SessionPeriod period; + private final SessionImages images; + + public SessionInfo(int cohort, LocalDate startDate, LocalDate endDate, SessionImage image) { + this(cohort, new SessionPeriod(startDate, endDate), new SessionImages(image)); + } + public SessionInfo(int cohort, LocalDate startDate, LocalDate endDate, SessionImages images) { + this(cohort, new SessionPeriod(startDate, endDate), images); + } + + + public SessionInfo(int cohort, SessionPeriod period, SessionImages images) { + this.cohort = cohort; + this.period = period; + this.images = images; + } + + public int getCohort() { + return cohort; + } + + public LocalDate getStartDate() { + return period.getStartDate(); + } + + public LocalDate getEndDate() { + return period.getEndDate(); + } + + public SessionImage getImage() { + return images.getFirstImage(); + } + + public SessionImages getImages() { + return images; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/SessionType.java b/src/main/java/nextstep/courses/domain/session/SessionType.java index 16c025049..a6e1aabd0 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionType.java +++ b/src/main/java/nextstep/courses/domain/session/SessionType.java @@ -3,10 +3,7 @@ import nextstep.payments.domain.Payment; public interface SessionType { - - public boolean isFree(); - - public boolean isOverCapacity(int currentEnrollmentCount); - - public boolean isValidPayment(Payment payment); + public boolean isFree(); + public boolean isOverCapacity(int currentEnrollmentCount); + public boolean isValidPayment(Payment payment); } diff --git a/src/main/java/nextstep/courses/domain/session/image/SessionImages.java b/src/main/java/nextstep/courses/domain/session/image/SessionImages.java new file mode 100644 index 000000000..b82061c8b --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/image/SessionImages.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.session.image; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SessionImages { + private final List images; + + public SessionImages(SessionImage image) { + this(List.of(image)); + } + + public SessionImages(List images) { + if (images == null || images.isEmpty()) { + throw new IllegalArgumentException("커버 이미지는 최소 1개 이상이어야 합니다."); + } + this.images = new ArrayList<>(images); + } + + public int size() { + return images.size(); + } + + public SessionImage getFirstImage() { + return images.get(0); + } + + public List getImages() { + return Collections.unmodifiableList(images); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java index c91173cfd..f947ad4cf 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java @@ -1,6 +1,7 @@ package nextstep.courses.infrastructure; import nextstep.courses.domain.session.EnrolledStudent; +import nextstep.courses.domain.session.EnrollmentApplication; import nextstep.courses.domain.session.EnrollmentRepository; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.stereotype.Repository; @@ -32,4 +33,31 @@ public List findBySessionId(Long sessionId) { rs.getLong("ns_user_id") ), sessionId); } + + @Override + public void saveApplication(EnrollmentApplication application) { + String sql = "insert into session_enrollment (session_id, ns_user_id, enrolled_at, enrollment_status) values(?, ?, ?, ?)"; + jdbcTemplate.update(sql, + application.getSessionId(), + application.getNsUserId(), + Timestamp.valueOf(LocalDateTime.now()), + application.getStatus().getValue()); + } + + @Override + public void updateApplication(EnrollmentApplication application) { + String sql = "update session_enrollment set enrollment_status = ?, approved_at = ?, approved_by = ? " + + "where session_id = ? and ns_user_id = ?"; + + Timestamp approvedAt = application.getApprovedAt() != null + ? Timestamp.valueOf(application.getApprovedAt()) + : null; + + jdbcTemplate.update(sql, + application.getStatus().getValue(), + approvedAt, + application.getApprovedBy(), + application.getSessionId(), + application.getNsUserId()); + } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index e16e825eb..e6c5f686d 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,14 +1,16 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.session.Enrollment; import nextstep.courses.domain.session.FreeSessionType; import nextstep.courses.domain.session.PaidSessionType; +import nextstep.courses.domain.session.ProgressStatus; +import nextstep.courses.domain.session.RecruitmentStatus; import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionInfo; import nextstep.courses.domain.session.SessionRepository; -import nextstep.courses.domain.session.SessionStatus; import nextstep.courses.domain.session.SessionType; import nextstep.courses.domain.session.Sessions; import nextstep.courses.domain.session.image.SessionImage; +import nextstep.courses.domain.session.image.SessionImages; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; @@ -31,66 +33,98 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public void save(Long courseId, Session session) { + Long sessionId = saveSession(courseId, session); + saveCoverImages(sessionId, session.getImages()); + } + + private Long saveSession(Long courseId, Session session) { Long imageId = saveSessionImage(session.getImage()); + String sql = "insert into session (course_id, cohort, start_date, end_date, image_id, status, progress_status, recruitment_status, session_type, max_capacity, fee, created_at)" + + "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); - Enrollment enrollment = session.getEnrollment(); - SessionStatus status = enrollment.getStatus(); - SessionType type = enrollment.getSessionType(); + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"}); + ps.setLong(1, courseId); + ps.setInt(2, session.getCohort()); + ps.setDate(3, Date.valueOf(session.getStartDate())); + ps.setDate(4, Date.valueOf(session.getEndDate())); + ps.setLong(5, imageId); + ps.setString(6, getStatusValue(session)); + ps.setString(7, session.getProgressStatus().getValue()); + ps.setString(8, session.getRecruitmentStatus().getValue()); + ps.setString(9, getSessionTypeString(session.getSessionType())); + ps.setObject(10, getMaxCapacity(session.getSessionType()), java.sql.Types.INTEGER); + ps.setObject(11, getFee(session.getSessionType()), java.sql.Types.BIGINT); + ps.setTimestamp(12, Timestamp.valueOf(LocalDateTime.now())); + return ps; + }, keyHolder); - String sessionTypeStr = type.isFree() ? "FREE" : "PAID"; - Integer maxCapacity = null; - Long fee = null; + return Objects.requireNonNull(keyHolder.getKey()).longValue(); + } - if (!type.isFree()) { - PaidSessionType paidType = (PaidSessionType) type; - maxCapacity = paidType.getMaxCapacity(); - fee = paidType.getFee(); + private void saveCoverImages(Long sessionId, SessionImages images) { + for (SessionImage image : images.getImages()) { + Long imageId = saveSessionImage(image); + String sql = "insert into session_cover_images (session_id, session_image_id) values(?, ?)"; + jdbcTemplate.update(sql, sessionId, imageId); } + } + + private String getStatusValue(Session session) { + return session.getRecruitmentStatus().canEnroll() ? "모집중" : session.getProgressStatus().getValue(); - String sql = "insert into session (course_id, cohort, start_date, end_date, image_id, status, session_type, max_capacity, fee, created_at) " + - "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - jdbcTemplate.update(sql, - courseId, - session.getCohort(), - Date.valueOf(session.getStartDate()), - Date.valueOf(session.getEndDate()), - imageId, - status.getValue(), - sessionTypeStr, - maxCapacity, - fee, - Timestamp.valueOf(LocalDateTime.now())); } + private String getSessionTypeString(SessionType sessionType) { + return sessionType.isFree() ? "FREE" : "PAID"; + } + + + private Integer getMaxCapacity(SessionType sessionType) { + if (sessionType.isFree()) { + return null; + } + return ((PaidSessionType) sessionType).getMaxCapacity(); + } + + private Long getFee(SessionType sessionType) { + if (sessionType.isFree()) { + return null; + } + return ((PaidSessionType) sessionType).getFee(); + } + @Override public Sessions findByCourseId(Long courseId) { - String sql = "select s.id, s.cohort, s.start_date, s.end_date, s.status, s.session_type, s.max_capacity, s.fee, " + - "i.file_size, i.image_type, i.width, i.height " + + String sql = "select s.id, s.cohort, s.start_date, s.end_date, s.image_id, s.progress_status, s.recruitment_status " + + "s.session_type, s.max_capacity, s.fee, " + "from session s " + - "join session_image i on s.image_id = i.id " + "where s.course_id = ? " + "order by s.cohort"; List sessionList = jdbcTemplate.query(sql, (rs, rowNum) -> { - SessionImage image = new SessionImage( - rs.getLong("file_size"), - rs.getString("image_type"), - rs.getInt("width"), - rs.getInt("height")); - SessionStatus status = SessionStatus.from(rs.getString("status")); - SessionType type = "FREE".equals(rs.getString("session_type")) - ? new FreeSessionType() - : new PaidSessionType(rs.getInt("max_capacity"), rs.getLong("fee")); + Long sessionId = rs.getLong("id"); + Long imageId = rs.getLong("image_id"); + SessionImages images = findSessionImages(sessionId, imageId); - return new Session( + SessionInfo info = new SessionInfo( rs.getInt("cohort"), rs.getDate("start_date").toLocalDate(), rs.getDate("end_date").toLocalDate(), - image, - new Enrollment(status, type)); + images); + + SessionType type = "FREE".equals(rs.getString("session_type")) + ? new FreeSessionType() + : new PaidSessionType(rs.getInt("max_capacity"), rs.getLong("fee")); + + ProgressStatus progressStatus = ProgressStatus.from(rs.getString("progress_status")); + RecruitmentStatus recruitmentStatus = RecruitmentStatus.from(rs.getString("recruitment_status")); + + return new Session(info, progressStatus, recruitmentStatus, type); }, courseId); return new Sessions(sessionList); @@ -98,33 +132,29 @@ public Sessions findByCourseId(Long courseId) { @Override public Session findById(Long sessionId) { - String sql = "select s.id, s.cohort, s.start_date, s.end_date, s.status, s.session_type, s.max_capacity, s.fee, " + - "i.file_size, i.image_type, i.width, i.height " + + String sql = "select s.id, s.cohort, s.start_date, s.end_date, s.image_id, s.session_type, s.max_capacity, s.fee, " + "from session s " + - "join session_image i on s.image_id = i.id " + "where s.id = ?"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { - SessionImage image = new SessionImage( - rs.getLong("file_size"), - rs.getString("image_type"), - rs.getInt("width"), - rs.getInt("height")); + Long id = rs.getLong("id"); + Long imageId = rs.getLong("image_id"); + SessionImages images = findSessionImages(id, imageId); + + SessionInfo info = new SessionInfo( + rs.getInt("cohort"), + rs.getDate("start_date").toLocalDate(), + rs.getDate("end_date").toLocalDate(), + images); + + ProgressStatus progressStatus = ProgressStatus.from(rs.getString("progress_status")); + RecruitmentStatus recruitmentStatus = RecruitmentStatus.from(rs.getString("recruitment_status")); - SessionStatus status = SessionStatus.from(rs.getString("status")); SessionType type = "FREE".equals(rs.getString("session_type")) ? new FreeSessionType() : new PaidSessionType(rs.getInt("max_capacity"), rs.getLong("fee")); - Enrollment enrollment = new Enrollment(status, type); - - return new Session( - rs.getLong("id"), - rs.getInt("cohort"), - rs.getDate("start_date").toLocalDate(), - rs.getDate("end_date").toLocalDate(), - image, - enrollment); + return new Session(id, info, progressStatus, recruitmentStatus, type); }, sessionId); } @@ -149,6 +179,39 @@ private Long saveSessionImage(SessionImage image) { return Objects.requireNonNull(keyHolder.getKey()).longValue(); } + private SessionImages findSessionImages(Long sessionId, Long imageId) { + String sql = "select si.file_size, si.image_type, si.width, si.height " + + "from session_cover_images sci " + + "join session_image si on sci.session_image_id = si.id " + + "where sci.session_id = ? " + + "order by sci.id"; + + List imageList = jdbcTemplate.query(sql, + (rs, rowNum) -> new SessionImage( + rs.getLong("file_size"), + rs.getString("image_type"), + rs.getInt("width"), + rs.getInt("height")), + sessionId); + + if (!imageList.isEmpty()) { + return new SessionImages(imageList); + } + + return new SessionImages(findSessionImageById(imageId)); + } + + private SessionImage findSessionImageById(Long imageId) { + String sql = "select file_size, image_type, width, height from session_image where id = ?"; + return jdbcTemplate.queryForObject(sql, + (rs, rowNum) -> new SessionImage( + rs.getLong("file_size"), + rs.getString("image_type"), + rs.getInt("width"), + rs.getInt("height")), + imageId); + } + private Long findSessionImageId(SessionImage image) { String sql = "select id from session_image where file_size = ? and image_type = ? and width = ? and height = ?"; List results = jdbcTemplate.query(sql, diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index f7a3799a7..e2c354ac6 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -2,6 +2,7 @@ import nextstep.courses.domain.session.EnrolledStudent; import nextstep.courses.domain.session.Enrollment; +import nextstep.courses.domain.session.EnrollmentApplication; import nextstep.courses.domain.session.EnrollmentRepository; import nextstep.courses.domain.session.Session; import nextstep.courses.domain.session.SessionRepository; @@ -30,4 +31,27 @@ public void enroll(Long sessionId, Long nsUserId, Payment payment) { enrollmentRepository.save(enroll); } + + public void applyForEnrollment(Long sessionId, Long nsUserId, Payment payment) { + Session session = sessionRepository.findById(sessionId); + List students = enrollmentRepository.findBySessionId(sessionId); + + Enrollment enrollment = session.createEnrollment(students); + EnrollmentApplication application = enrollment.apply(nsUserId, payment); + + enrollmentRepository.saveApplication(application); + } + + public void approveEnrollment(Long sessionId, Long nsUserId, Long instructorId) { + Session session = sessionRepository.findById(sessionId); + List students = enrollmentRepository.findBySessionId(sessionId); + + Enrollment enrollment = session.createEnrollment(students); + EnrollmentApplication application = new EnrollmentApplication(sessionId, nsUserId, null); + EnrolledStudent student = enrollment.approve(application, instructorId); + + enrollmentRepository.updateApplication(application); + enrollmentRepository.save(student); + } + } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 5adaefd5d..122dd15aa 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,50 +1,55 @@ -create table course ( - id bigint generated by default as identity, - title varchar(255) not null, - creator_id bigint not null, - created_at timestamp not null, +create table course +( + id bigint generated by default as identity, + title varchar(255) not null, + creator_id bigint not null, + created_at timestamp not null, updated_at timestamp, primary key (id) ); -create table ns_user ( - id bigint generated by default as identity, - user_id varchar(20) not null, - password varchar(20) not null, - name varchar(20) not null, - email varchar(50), - created_at timestamp not null, +create table ns_user +( + id bigint generated by default as identity, + user_id varchar(20) not null, + password varchar(20) not null, + name varchar(20) not null, + email varchar(50), + created_at timestamp not null, updated_at timestamp, primary key (id) ); -create table question ( - id bigint generated by default as identity, - created_at timestamp not null, +create table question +( + id bigint generated by default as identity, + created_at timestamp not null, updated_at timestamp, - contents clob, - deleted boolean not null, - title varchar(100) not null, - writer_id bigint, + contents clob, + deleted boolean not null, + title varchar(100) not null, + writer_id bigint, primary key (id) ); -create table answer ( - id bigint generated by default as identity, - created_at timestamp not null, - updated_at timestamp, - contents clob, - deleted boolean not null, +create table answer +( + id bigint generated by default as identity, + created_at timestamp not null, + updated_at timestamp, + contents clob, + deleted boolean not null, question_id bigint, - writer_id bigint, + writer_id bigint, primary key (id) ); -create table delete_history ( - id bigint not null, - content_id bigint, - content_type varchar(255), - created_date timestamp, +create table delete_history +( + id bigint not null, + content_id bigint, + content_type varchar(255), + created_date timestamp, deleted_by_id bigint, primary key (id) ); @@ -63,18 +68,20 @@ create table session_image create table session ( - id bigint generated by default as identity, - course_id bigint not null, - cohort int not null, - start_date date not null, - end_date date not null, - image_id bigint not null, - status varchar(20) not null, - session_type varchar(20) not null, - max_capacity int, - fee bigint, - created_at timestamp not null, - updated_at timestamp, + id bigint generated by default as identity, + course_id bigint not null, + cohort int not null, + start_date date not null, + end_date date not null, + image_id bigint not null, + status varchar(20) not null, + progress_status varchar(20), + recruitment_status varchar(20), + session_type varchar(20) not null, + max_capacity int, + fee bigint, + created_at timestamp not null, + updated_at timestamp, primary key (id), foreign key (course_id) references course (id), foreign key (image_id) references session_image (id) @@ -86,8 +93,21 @@ create table session_enrollment session_id bigint not null, ns_user_id bigint not null, enrolled_at timestamp not null, + enrollment_status varchar(20), + approved_at timestamp, + approved_by bigint, primary key (id), foreign key (session_id) references session (id), foreign key (ns_user_id) references ns_user (id) ); + +create table session_cover_images +( + id bigint generated by default as identity, + session_id bigint not null, + session_image_id bigint not null, + primary key (id), + foreign key (session_id) references session (id), + foreign key (session_image_id) references session_image (id) +); diff --git a/src/test/java/nextstep/courses/domain/CourseTest.java b/src/test/java/nextstep/courses/domain/CourseTest.java index 56dbbed5d..213ed54fb 100644 --- a/src/test/java/nextstep/courses/domain/CourseTest.java +++ b/src/test/java/nextstep/courses/domain/CourseTest.java @@ -1,9 +1,9 @@ package nextstep.courses.domain; -import nextstep.courses.domain.session.Enrollment; import nextstep.courses.domain.session.FreeSessionType; +import nextstep.courses.domain.session.ProgressStatus; +import nextstep.courses.domain.session.RecruitmentStatus; import nextstep.courses.domain.session.Session; -import nextstep.courses.domain.session.SessionStatus; import nextstep.courses.domain.session.Sessions; import nextstep.courses.domain.session.image.SessionImage; import org.junit.jupiter.api.Test; @@ -12,7 +12,6 @@ import java.util.ArrayList; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; class CourseTest { private static final LocalDate START_DATE = LocalDate.of(2025, 11, 3); @@ -22,8 +21,8 @@ class CourseTest { @Test public void 세션을_가진_과정_생성() { - Session session1 = new Session(1, START_DATE, END_DATE, IMAGE); - Session session2 = new Session(2, START_DATE, END_DATE, IMAGE); + Session session1 = new Session(1, START_DATE, END_DATE, IMAGE, ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); + Session session2 = new Session(2, START_DATE, END_DATE, IMAGE, ProgressStatus.PREPARING, RecruitmentStatus.NOT_RECRUITING, new FreeSessionType()); Sessions sessions = new Sessions(new ArrayList<>()); sessions.add(session1); sessions.add(session2); @@ -37,16 +36,16 @@ class CourseTest { @Test public void 과정에_새로운_기수_추가() { - Session session1 = new Session(1, START_DATE, END_DATE, IMAGE); + Session session1 = new Session(1, START_DATE, END_DATE, IMAGE,ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING,new FreeSessionType()); Sessions sessions = new Sessions(new ArrayList<>()); sessions.add(session1); Course course = new Course("JPA의사실과 오해", 1L, sessions); - Session session2 = new Session(2, START_DATE, END_DATE, IMAGE); + Session session2 = new Session(2, START_DATE, END_DATE, IMAGE,ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING,new FreeSessionType()); course.addSession(session2); assertThat(course.getSessions().size()).isEqualTo(2); } - + } diff --git a/src/test/java/nextstep/courses/domain/session/EnrollmentApplicationTest.java b/src/test/java/nextstep/courses/domain/session/EnrollmentApplicationTest.java new file mode 100644 index 000000000..9781c35ce --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/EnrollmentApplicationTest.java @@ -0,0 +1,59 @@ +package nextstep.courses.domain.session; + +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class EnrollmentApplicationTest { + @Test + public void 신청서를_생성한다() { + Payment payment = new Payment("결제번호-1", 1L, 1L, 50_000L); + + EnrollmentApplication application = new EnrollmentApplication(1L, 1L, payment); + + assertThat(application.getSessionId()).isEqualTo(1L); + assertThat(application.getNsUserId()).isEqualTo(1L); + assertThat(application.getStatus()).isEqualTo(EnrollmentStatus.PENDING); + } + + @Test + public void 신청서를_승인한다() { + EnrollmentApplication application = new EnrollmentApplication(1L, 1L, null); + + application.approve(100L); + + assertThat(application.getStatus()).isEqualTo(EnrollmentStatus.APPROVED); + assertThat(application.getApprovedBy()).isEqualTo(100L); + } + + @Test + public void 신청서를_취소한다() { + EnrollmentApplication application = new EnrollmentApplication(1L, 1L, null); + + application.cancel(); + + assertThat(application.getStatus()).isEqualTo(EnrollmentStatus.CANCELLED); + } + + @Test + public void 이미_승인된_신청서는_다시_승인_불가() { + EnrollmentApplication application = new EnrollmentApplication(1L, 1L, null); + application.approve(100L); + + assertThatThrownBy(() -> application.approve(100L)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("대기 중인 신청만 승인 가능합니다"); + } + + @Test + public void 이미_취소된_신청서는_승인_불가() { + EnrollmentApplication application = new EnrollmentApplication(1L, 1L, null); + application.cancel(); + + assertThatThrownBy(() -> application.approve(100L)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("대기 중인 신청만 승인 가능합니다"); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/EnrollmentStatusTest.java b/src/test/java/nextstep/courses/domain/session/EnrollmentStatusTest.java new file mode 100644 index 000000000..1cddd08a4 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/EnrollmentStatusTest.java @@ -0,0 +1,45 @@ +package nextstep.courses.domain.session; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class EnrollmentStatusTest { + + @Test + public void 설명으로_상태_조회한다() { + EnrollmentStatus pending = EnrollmentStatus.from("대기중"); + + assertThat(pending).isEqualTo(EnrollmentStatus.PENDING); + } + + @Test + public void 잘못된_설명으로_조회시_예외발생() { + assertThatThrownBy(() -> EnrollmentStatus.from("잘못된상태")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("잘못된 수강신청 상태입니다"); + } + + @ParameterizedTest + @CsvSource({ + "APPROVED, true", + "PENDING, false", + "CANCELLED, false" + }) + public void 승인_여부를_확인한다(EnrollmentStatus status, boolean isApproved) { + assertThat(status.isApproved()).isEqualTo(isApproved); + } + + @ParameterizedTest + @CsvSource({ + "PENDING, true", + "APPROVED, false", + "CANCELLED, false" + }) + public void 대기중_상태를_확인한다(EnrollmentStatus status, boolean isPending) { + assertThat(status.isPending()).isEqualTo(isPending); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java index 1796627b5..57db351cf 100644 --- a/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java +++ b/src/test/java/nextstep/courses/domain/session/EnrollmentTest.java @@ -12,7 +12,7 @@ public class EnrollmentTest { @Test public void 모집중_상태일때_수강신청_가능() { - Enrollment enrollment = new Enrollment(1L, SessionStatus.RECRUITING, new FreeSessionType(), Collections.emptyList()); + Enrollment enrollment = new Enrollment(1L, RecruitmentStatus.RECRUITING, new FreeSessionType(), Collections.emptyList()); EnrolledStudent student = enrollment.enroll(1L, null); @@ -22,7 +22,7 @@ public class EnrollmentTest { @Test public void 준비중_상태일때_수강신청_불가() { - Enrollment enrollment = new Enrollment(SessionStatus.PREPARING, new FreeSessionType()); + Enrollment enrollment = new Enrollment(RecruitmentStatus.NOT_RECRUITING, new FreeSessionType()); assertThatThrownBy(() -> { enrollment.enroll(1L, null); @@ -33,7 +33,7 @@ public class EnrollmentTest { @Test public void 유료_강의_결제금액_검증() { SessionType type = new PaidSessionType(10, 100_000L); - Enrollment enrollment = new Enrollment(1L, SessionStatus.RECRUITING, type, Collections.emptyList()); + Enrollment enrollment = new Enrollment(1L, RecruitmentStatus.RECRUITING, type, Collections.emptyList()); assertThatThrownBy(() -> enrollment.enroll(1L, new Payment("결제번호-1", 1L, 1L, 50_000L))) .isInstanceOf(IllegalArgumentException.class) @@ -50,7 +50,7 @@ public class EnrollmentTest { new EnrolledStudent(1L, 2L) ); - Enrollment enrollment = new Enrollment(1L, SessionStatus.RECRUITING, type, currentStudent); + Enrollment enrollment = new Enrollment(1L, RecruitmentStatus.RECRUITING, type, currentStudent); assertThatThrownBy(() -> { enrollment.enroll(3L, new Payment("결제번호-1", 1L, 3L, fee)); @@ -58,4 +58,26 @@ public class EnrollmentTest { .hasMessageContaining("최대 수강 인원을 초과"); } + @Test + public void 신청서_생성() { + Enrollment enrollment = new Enrollment(1L, RecruitmentStatus.RECRUITING, new FreeSessionType(), Collections.emptyList()); + + EnrollmentApplication application = enrollment.apply(1L, null); + + assertThat(application.getSessionId()).isEqualTo(1L); + assertThat(application.getNsUserId()).isEqualTo(1L); + assertThat(application.getStatus()).isEqualTo(EnrollmentStatus.PENDING); + } + + @Test + public void 신청서_승인하여_수강생_등록() { + Enrollment enrollment = new Enrollment(1L, RecruitmentStatus.RECRUITING, new FreeSessionType(), Collections.emptyList()); + EnrollmentApplication application = enrollment.apply(1L, null); + + EnrolledStudent student = enrollment.approve(application, 100L); + + assertThat(student.getSessionId()).isEqualTo(1L); + assertThat(student.getNsUserId()).isEqualTo(1L); + assertThat(application.getStatus()).isEqualTo(EnrollmentStatus.APPROVED); + } } diff --git a/src/test/java/nextstep/courses/domain/session/ProgressStatusTest.java b/src/test/java/nextstep/courses/domain/session/ProgressStatusTest.java new file mode 100644 index 000000000..b91628226 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/ProgressStatusTest.java @@ -0,0 +1,23 @@ +package nextstep.courses.domain.session; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ProgressStatusTest { + @Test + void 각상태별_생성확인() { + assertThat(ProgressStatus.PREPARING.getValue()).isEqualTo("준비중"); + assertThat(ProgressStatus.IN_PROGRESS.getValue()).isEqualTo("진행중"); + assertThat(ProgressStatus.CLOSED.getValue()).isEqualTo("종료"); + } + + @Test + void 잘못된_상태_문자열이면_예외() { + assertThatThrownBy(() -> { + ProgressStatus.from("모집중"); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("잘못된 진행 상태"); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/RecruitmentStatusTest.java b/src/test/java/nextstep/courses/domain/session/RecruitmentStatusTest.java new file mode 100644 index 000000000..a83d01891 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/RecruitmentStatusTest.java @@ -0,0 +1,33 @@ +package nextstep.courses.domain.session; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class RecruitmentStatusTest { + @Test + void 각상태별_생성확인() { + assertThat(RecruitmentStatus.NOT_RECRUITING.getValue()).isEqualTo("비모집중"); + assertThat(RecruitmentStatus.RECRUITING.getValue()).isEqualTo("모집중"); + } + + @Test + void 잘못된_상태_문자열이면_예외() { + assertThatThrownBy(() -> { + RecruitmentStatus.from("준비중"); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("잘못된 모집 상태"); + } + + @ParameterizedTest + @CsvSource({ + "NOT_RECRUITING, false", + "RECRUITING, true" + }) + void 상태별_수강신청_가능여부(RecruitmentStatus status, boolean canEnroll) { + assertThat(status.canEnroll()).isEqualTo(canEnroll); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index 4f8ff4e7d..814b92aeb 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -1,14 +1,12 @@ package nextstep.courses.domain.session; import nextstep.courses.domain.session.image.SessionImage; -import nextstep.payments.domain.Payment; import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SessionTest { private static final LocalDate START_DATE = LocalDate.of(2025, 11, 3); @@ -17,7 +15,7 @@ public class SessionTest { @Test public void 정상적인_강의_생성() { - Session session = new Session(START_DATE, END_DATE, IMAGE, "준비중"); + Session session = new Session(START_DATE, END_DATE, IMAGE); assertThat(session).isNotNull(); } @@ -26,7 +24,7 @@ public class SessionTest { public void 기수_정보를_가진_강의_생성() { int cohort = 1; - Session session = new Session(cohort, START_DATE, END_DATE, IMAGE); + Session session = new Session(cohort, START_DATE, END_DATE, IMAGE, ProgressStatus.IN_PROGRESS, RecruitmentStatus.RECRUITING, new FreeSessionType()); assertThat(session.getCohort()).isEqualTo(cohort); } @@ -34,7 +32,8 @@ public class SessionTest { @Test public void 모집중_상태일때_수강신청_가능() { - Session session = new Session(1L, 1, START_DATE, END_DATE, IMAGE, new Enrollment(SessionStatus.RECRUITING, new FreeSessionType())); + Session session = new Session(1L, 1, START_DATE, END_DATE, IMAGE, + ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); Enrollment enrollment = session.createEnrollment(Collections.emptyList()); EnrolledStudent student = enrollment.enroll(1L, null); @@ -43,4 +42,29 @@ public class SessionTest { } + @Test + void 진행중이면서_모집중일때_수강신청_가능() { + Session session = new Session(1L, 1, START_DATE, END_DATE, IMAGE, + ProgressStatus.IN_PROGRESS, RecruitmentStatus.RECRUITING, new FreeSessionType()); + + Enrollment enrollment = session.createEnrollment(java.util.Collections.emptyList()); + EnrolledStudent student = enrollment.enroll(1L, null); + + assertThat(student.getNsUserId()).isEqualTo(1L); + assertThat(student.getSessionId()).isEqualTo(1L); + } + + @Test + void 기존_SessionStatus만있어도_생성_가능() { + LocalDate startDate = LocalDate.of(2024, 1, 1); + LocalDate endDate = LocalDate.of(2024, 3, 31); + SessionImage image = new SessionImage(500_000L, "png", 900, 600); + + Session session = new Session(1L, 1, startDate, endDate, image, + ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); + + assertThat(session.getRecruitmentStatus()).isEqualTo(RecruitmentStatus.RECRUITING); + } + + } diff --git a/src/test/java/nextstep/courses/domain/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/session/SessionsTest.java index 7e1abc292..08ea8f920 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionsTest.java @@ -15,8 +15,8 @@ class SessionsTest { @Test public void 세션_목록_생성() { - Session session1 = new Session(1, START_DATE, END_DATE, IMAGE); - Session session2 = new Session(2, START_DATE, END_DATE, IMAGE); + Session session1 = new Session(1, START_DATE, END_DATE, IMAGE, ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); + Session session2 = new Session(2, START_DATE, END_DATE, IMAGE, ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); Sessions sessions = new Sessions(List.of(session1, session2)); @@ -27,10 +27,10 @@ class SessionsTest { public void 세션_추가() { SessionImage image = new SessionImage(500_000L, "png", 900, 600); - Session session1 = new Session(1, START_DATE, END_DATE, image); + Session session1 = new Session(1, START_DATE, END_DATE, image, ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); Sessions sessions = new Sessions(List.of(session1)); - Session session2 = new Session(2, START_DATE, END_DATE, image); + Session session2 = new Session(2, START_DATE, END_DATE, image, ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); sessions.add(session2); assertThat(sessions.size()).isEqualTo(2); @@ -38,8 +38,8 @@ class SessionsTest { @Test public void 특정_기수_세션_조회() { - Session session1 = new Session(1, START_DATE, END_DATE, IMAGE); - Session session2 = new Session(2, START_DATE, END_DATE, IMAGE); + Session session1 = new Session(1, START_DATE, END_DATE, IMAGE, ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); + Session session2 = new Session(2, START_DATE, END_DATE, IMAGE, ProgressStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeSessionType()); Sessions sessions = new Sessions(List.of(session1, session2)); Session found = sessions.findByCohort(2); diff --git a/src/test/java/nextstep/courses/domain/session/image/SessionImagesTest.java b/src/test/java/nextstep/courses/domain/session/image/SessionImagesTest.java new file mode 100644 index 000000000..ebb579719 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/image/SessionImagesTest.java @@ -0,0 +1,40 @@ +package nextstep.courses.domain.session.image; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class SessionImagesTest { + @Test + public void 커버_이미지_목록_생성() { + SessionImage image1 = new SessionImage(100_000L, "png", 300, 200); + SessionImage image2 = new SessionImage(100_000L, "jpg", 300, 200); + + SessionImages images = new SessionImages(Arrays.asList(image1, image2)); + + assertThat(images).isNotNull(); + assertThat(images.size()).isEqualTo(2); + } + + @Test + public void 빈_목록으로_생성_불가() { + assertThatThrownBy(() -> { + new SessionImages(Collections.emptyList()); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("커버 이미지는 최소 1개 이상이어야 합니다"); + } + + @Test + public void 대표_이미지_조회() { + SessionImage image1 = new SessionImage(100_000L, "png", 300, 200); + SessionImage image2 = new SessionImage(100_000L, "jpg", 300, 200); + + SessionImages images = new SessionImages(Arrays.asList(image1, image2)); + + assertThat(images.getFirstImage()).isEqualTo(image1); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/session/service/CourseServiceTest.java index 157dd887b..4a6c4b32b 100644 --- a/src/test/java/nextstep/courses/domain/session/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/session/service/CourseServiceTest.java @@ -2,12 +2,8 @@ import nextstep.courses.domain.Course; import nextstep.courses.domain.CourseRepository; -import nextstep.courses.domain.session.Enrollment; -import nextstep.courses.domain.session.FreeSessionType; import nextstep.courses.domain.session.Session; -import nextstep.courses.domain.session.SessionPeriod; import nextstep.courses.domain.session.SessionRepository; -import nextstep.courses.domain.session.SessionStatus; import nextstep.courses.domain.session.Sessions; import nextstep.courses.domain.session.image.SessionImage; import nextstep.courses.infrastructure.JdbcCourseRepository; @@ -20,7 +16,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -40,18 +35,6 @@ void setUp() { courseService = new CourseService(courseRepository, sessionRepository); } - @Test - void sessions를_가진_course_저장하고_조회한다() { - Course course = createCourseTestFixture(); - - Long savedCourseId = courseService.save(course); - - Course savedCourse = courseService.findById(savedCourseId); - assertThat(savedCourse.getTitle()).isEqualTo("TDD, 클린 코드 with Java"); - assertThat(savedCourse.getSessions()).isNotNull(); - assertThat(savedCourse.getSessions().size()).isEqualTo(2); - } - @Test void 같은_이미지를_사용하는_세션들은_이미지를_재사용한다() { Course course = createCourseTestFixture(); @@ -67,10 +50,10 @@ private static Course createCourseTestFixture() { LocalDate endDate = LocalDate.of(2025, 12, 18); SessionImage image = new SessionImage(300_000L, "png", 600, 400); - Session session1 = new Session(1, new SessionPeriod(startDate, endDate), image, new Enrollment(SessionStatus.RECRUITING, new FreeSessionType())); - Session session2 = new Session(2, new SessionPeriod(startDate, endDate), image, new Enrollment(SessionStatus.RECRUITING, new FreeSessionType())); + Session session1 = new Session(startDate, endDate, image); + Session session2 = new Session(startDate, endDate, image); - Sessions sessions = new Sessions(new ArrayList<>(List.of(session1, session2))); + Sessions sessions = new Sessions(List.of(session1, session2)); return new Course("TDD, 클린 코드 with Java", 1L, sessions); } }