From cce9c3b0c5c2dc3b68ba17fd5ef21e2e9cce2dc2 Mon Sep 17 00:00:00 2001 From: melodist Date: Tue, 22 Apr 2025 16:36:13 +0900 Subject: [PATCH 1/9] =?UTF-8?q?Docs:=204=EB=8B=A8=EA=B3=84=20todo=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++-- docs/issues_step4.md | 1 + docs/todo_step4.md | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 docs/issues_step4.md create mode 100644 docs/todo_step4.md diff --git a/README.md b/README.md index 5ee28f1fb..39acda16e 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,11 @@ - [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단계 +- [ ] 3단계 피드백 + - [ ] Entity 도입 + - [ ] Enrollment 검증 테스트 도입 \ No newline at end of file diff --git a/docs/issues_step4.md b/docs/issues_step4.md new file mode 100644 index 000000000..8b1f15266 --- /dev/null +++ b/docs/issues_step4.md @@ -0,0 +1 @@ +# 4단계 - 수강신청(요구사항 변경) \ No newline at end of file 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 From 88944c84764ab2ac6a544b2b89247b2e6433b397 Mon Sep 17 00:00:00 2001 From: melodist Date: Wed, 23 Apr 2025 10:14:13 +0900 Subject: [PATCH 2/9] =?UTF-8?q?Refactor:=20UserService.findByUserIds=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- docs/issues_step4.md | 15 ++++++++++++++- .../nextstep/courses/service/SessionService.java | 9 +-------- .../java/nextstep/users/service/UserService.java | 10 ++++++++++ 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 39acda16e..607ca2ddc 100644 --- a/README.md +++ b/README.md @@ -73,5 +73,8 @@ - [x] Enrollment.hasEnrolledUser 제거 # 4단계 - [ ] 3단계 피드백 - - [ ] Entity 도입 + - [ ] SessionService 리팩토링 + - [x] UserService.findByUserIds 추가 + - [ ] imageRepository 의존성 제거 + - [ ] sessionEnrollmentRepository 의존성 제거 - [ ] Enrollment 검증 테스트 도입 \ No newline at end of file diff --git a/docs/issues_step4.md b/docs/issues_step4.md index 8b1f15266..733372256 100644 --- a/docs/issues_step4.md +++ b/docs/issues_step4.md @@ -1 +1,14 @@ -# 4단계 - 수강신청(요구사항 변경) \ No newline at end of file +# 4단계 - 수강신청(요구사항 변경) +## 3단계 피드백 +### DTO 사용 +- DTO를 사용하지 않는다고 하더라도 도메인 내부의 값을 알기 위한 getter는 필수불가결 +- 그러나 현재 요구사항에서 Session 전체 값을 update할 필요는 없음 +- SessionDto를 Session으로 변환하는 과정에서 다른 Repository에 의존하는 것에는 문제가 있음 + - UserService에서 List 반환 + - imageRepository 의존성 제거 → imageRepository에서 SessionThumbnail 반환 + - sessionEnrollmentRepository 의존성 제거 -> SessionEnrollmentRepository에서 SessionEnrollment 반환 +- Service 간 의존성을 반드시 제거할 필요는 없음 (단, 순환 참조는 발생하면 안됨) +### Repository +- 도메인 객체에 ID는 필요 없는 값 +- 하지만 이것을 분리하여 얻는 장점은 크지 않음 + - 참고: [https://mincanit.tistory.com/74](https://mincanit.tistory.com/74) \ No newline at end of file diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 13e1eb35e..040704575 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -93,7 +93,7 @@ private SessionThumbnail getThumbnail(Long sessionId) { private Enrollment getEnrollment(SessionDto sessionDto) { SessionType sessionType = sessionDto.getSessionType(); List enrolledUserIds = sessionEnrollmentRepository.findUserIdsBySessionId(sessionDto.getId()); - List enrolledUsers = findEnrolledUsersByIds(enrolledUserIds); + List enrolledUsers = userService.findEnrolledUsersByIds(enrolledUserIds); SessionStatus sessionStatus = sessionDto.getStatus(); if (sessionType.isPaid()) { @@ -103,11 +103,4 @@ private Enrollment getEnrollment(SessionDto sessionDto) { 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()); - } } \ 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 From b503c819c1bc5775d35379911502a806c15e6fba Mon Sep 17 00:00:00 2001 From: melodist Date: Wed, 23 Apr 2025 10:23:30 +0900 Subject: [PATCH 3/9] =?UTF-8?q?Refactor:=20ImageRepository=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++--- docs/issues_step4.md | 2 +- .../infrastructure/ImageRepository.java | 4 ++-- .../infrastructure/JdbcImageRepository.java | 13 ++++++++++++- .../courses/service/ImageService.java | 17 +++++++++++++++++ .../courses/service/SessionService.java | 19 +++---------------- .../JdbcImageRepositoryTest.java | 17 +++++++---------- 7 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 src/main/java/nextstep/courses/service/ImageService.java diff --git a/README.md b/README.md index 607ca2ddc..14276985c 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,7 @@ - [x] Enrollment.hasEnrolledUser 제거 # 4단계 - [ ] 3단계 피드백 - - [ ] SessionService 리팩토링 + - [x] SessionService 리팩토링 - [x] UserService.findByUserIds 추가 - - [ ] imageRepository 의존성 제거 - - [ ] sessionEnrollmentRepository 의존성 제거 + - [x] ImageRepository 의존성 제거 - [ ] Enrollment 검증 테스트 도입 \ No newline at end of file diff --git a/docs/issues_step4.md b/docs/issues_step4.md index 733372256..f3d378807 100644 --- a/docs/issues_step4.md +++ b/docs/issues_step4.md @@ -6,7 +6,7 @@ - SessionDto를 Session으로 변환하는 과정에서 다른 Repository에 의존하는 것에는 문제가 있음 - UserService에서 List 반환 - imageRepository 의존성 제거 → imageRepository에서 SessionThumbnail 반환 - - sessionEnrollmentRepository 의존성 제거 -> SessionEnrollmentRepository에서 SessionEnrollment 반환 + - sessionEnrollmentRepository 의존성 제거 -> 단순 CRUD 기능의 계층을 늘릴 필요는 없음 - Service 간 의존성을 반드시 제거할 필요는 없음 (단, 순환 참조는 발생하면 안됨) ### Repository - 도메인 객체에 ID는 필요 없는 값 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..647f2a45a 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; @@ -17,7 +18,17 @@ public JdbcImageRepository(JdbcOperations jdbcTemplate) { } @Override - public ImageDto findBySessionId(Long sessionId) { + public SessionThumbnail findThumbnailBySessionId(Long sessionId) { + ImageDto imageDto = findBySessionId(sessionId); + if (imageDto == null) { + throw new IllegalArgumentException("존재하지 않는 이미지입니다."); + } + + return new SessionThumbnail(imageDto.getFileName(), imageDto.getFileSize(), + imageDto.getWidth(), imageDto.getHeight()); + } + + private ImageDto 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")) 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 040704575..94491528d 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -10,13 +10,10 @@ import nextstep.courses.domain.session.enrollment.PaidEnrollment; 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 +24,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,7 +61,8 @@ 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); @@ -80,16 +77,6 @@ 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) { SessionType sessionType = sessionDto.getSessionType(); List enrolledUserIds = sessionEnrollmentRepository.findUserIdsBySessionId(sessionDto.getId()); diff --git a/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java index 29d5b215a..4aa09ed2c 100644 --- a/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java @@ -1,5 +1,6 @@ package nextstep.courses.infrastructure; +import nextstep.courses.domain.session.info.basic.SessionThumbnail; import nextstep.courses.dto.ImageDto; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,8 +35,8 @@ void findBySessionId() { .sessionId(sessionId) .fileName("test.jpg") .fileSize(1024L) - .width(800) - .height(600) + .width(600) + .height(400) .createdAt(now) .updatedAt(now) .build(); @@ -44,17 +45,13 @@ void findBySessionId() { .thenReturn(expectedImage); // when - ImageDto actualImage = imageRepository.findBySessionId(sessionId); + SessionThumbnail actualImage = imageRepository.findThumbnailBySessionId(sessionId); // 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 From 06fbcbbb3d6bed213b0d69f2cccdd0c38e85e3b0 Mon Sep 17 00:00:00 2001 From: melodist Date: Thu, 24 Apr 2025 08:42:53 +0900 Subject: [PATCH 4/9] =?UTF-8?q?Test:=20Enrollment=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../domain/session/enrollment/FreeEnrollmentTest.java | 9 ++++++--- .../domain/session/enrollment/PaidEnrollmentTest.java | 7 +++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 14276985c..b9b673823 100644 --- a/README.md +++ b/README.md @@ -76,4 +76,4 @@ - [x] SessionService 리팩토링 - [x] UserService.findByUserIds 추가 - [x] ImageRepository 의존성 제거 - - [ ] Enrollment 검증 테스트 도입 \ No newline at end of file + - [x] Enrollment 검증 테스트 도입 \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java index f12d0cf52..373847641 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java @@ -9,6 +9,7 @@ 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 FreeEnrollmentTest { private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); @@ -30,9 +31,11 @@ void enrollFreeSession() { Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); - // when - enrollment.enroll(USER); - enrollment.enroll(anotherUser); + // when & then + assertThatCode(() -> { + enrollment.enroll(USER); + enrollment.enroll(anotherUser); + }).doesNotThrowAnyException(); } @Test diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java index f7d17a31f..34d593a79 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java @@ -7,8 +7,7 @@ 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.*; class PaidEnrollmentTest { private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); @@ -44,8 +43,8 @@ void enrollPaidSession() { // given Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), SessionStatus.RECRUITING); - // when - enrollment.enroll(USER); + // when & then + assertThatCode(() -> enrollment.enroll(USER)).doesNotThrowAnyException(); } @Test From e870d78adbebca04821949a89ae4d0efd7cea50e Mon Sep 17 00:00:00 2001 From: melodist Date: Mon, 28 Apr 2025 08:38:43 +0900 Subject: [PATCH 5/9] =?UTF-8?q?Feat:=20=EA=B0=95=EC=9D=98=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EC=83=81=ED=83=9C=20/=20=EB=AA=A8=EC=A7=91=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++- .../domain/session/SessionProgressStatus.java | 20 +++++++ ...tus.java => SessionRecruitmentStatus.java} | 14 ++--- .../domain/session/enrollment/Enrollment.java | 6 +- .../session/enrollment/EnrollmentManager.java | 13 ++-- .../session/enrollment/FreeEnrollment.java | 17 ++++-- .../session/enrollment/PaidEnrollment.java | 19 +++--- .../java/nextstep/courses/dto/SessionDto.java | 9 ++- .../infrastructure/JdbcSessionRepository.java | 8 ++- .../courses/service/SessionService.java | 12 ++-- src/main/resources/schema.sql | 3 +- .../domain/session/SessionStatusTest.java | 26 ++++++-- .../enrollment/EnrollmentManagerTest.java | 59 +++++++++++++++++++ .../enrollment/FreeEnrollmentTest.java | 15 +++-- .../enrollment/PaidEnrollmentTest.java | 21 ++++--- 15 files changed, 189 insertions(+), 62 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/SessionProgressStatus.java rename src/main/java/nextstep/courses/domain/session/{SessionStatus.java => SessionRecruitmentStatus.java} (55%) create mode 100644 src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentManagerTest.java diff --git a/README.md b/README.md index b9b673823..12f8205d8 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,13 @@ - [x] Enrollment.isFull 리팩토링 - [x] Enrollment.hasEnrolledUser 제거 # 4단계 -- [ ] 3단계 피드백 +- [x] 3단계 피드백 - [x] SessionService 리팩토링 - [x] UserService.findByUserIds 추가 - [x] ImageRepository 의존성 제거 - - [x] Enrollment 검증 테스트 도입 \ No newline at end of file + - [x] Enrollment 검증 테스트 도입 +- [x] 강의 진행 상태 / 모집 상태 분리 +- [ ] 강의 커버 이미지 다수 등록 +- [ ] 수강 신청 변경 + - [ ] 수강 신청 승인 + - [ ] 수강 신청 취소 \ No newline at end of file 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..bf52b562d 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,11 @@ package nextstep.courses.domain.session.enrollment; -import nextstep.courses.domain.session.SessionStatus; +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; import nextstep.users.domain.NsUser; public interface Enrollment { void enroll(NsUser user); - SessionStatus getStatus(); + SessionProgressStatus getProgressStatus(); + SessionRecruitmentStatus getRecruitmentStatus(); } 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..038ecfba1 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentManager.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentManager.java @@ -1,7 +1,8 @@ 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.List; @@ -9,11 +10,13 @@ @Getter public class EnrollmentManager { private final List enrolledUsers; - private final SessionStatus status; + private final SessionProgressStatus progressStatus; + private final SessionRecruitmentStatus recruitmentStatus; - public EnrollmentManager(List enrolledUsers, SessionStatus status) { + public EnrollmentManager(List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { this.enrolledUsers = enrolledUsers; - this.status = status; + this.progressStatus = progressStatus; + this.recruitmentStatus = recruitmentStatus; } public void enroll(NsUser user) { @@ -25,7 +28,7 @@ private void validateEnrollment(NsUser user) { if (user == null) { throw new IllegalArgumentException("수강 신청할 사용자가 없습니다."); } - if (!status.isRecruiting()) { + if (!recruitmentStatus.isRecruiting()) { throw new IllegalStateException("수강 신청이 불가능합니다."); } if (hasEnrolledUser(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 index 48872455a..a05b78688 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollment.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollment.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.session.enrollment; -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; @@ -9,19 +10,23 @@ public class FreeEnrollment implements Enrollment { private final EnrollmentManager enrollment; - public FreeEnrollment(List enrolledUsers, SessionStatus status) { - this.enrollment = new EnrollmentManager(enrolledUsers, status); + public FreeEnrollment(List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { + this.enrollment = new EnrollmentManager(enrolledUsers, progressStatus, recruitmentStatus); } public FreeEnrollment() { - this.enrollment = new EnrollmentManager(new ArrayList<>(), SessionStatus.RECRUITING); + this.enrollment = new EnrollmentManager(new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); } public void enroll(NsUser user) { enrollment.enroll(user); } - public SessionStatus getStatus() { - return enrollment.getStatus(); + public SessionProgressStatus getProgressStatus() { + return enrollment.getProgressStatus(); + } + + public SessionRecruitmentStatus getRecruitmentStatus() { + return enrollment.getRecruitmentStatus(); } } diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java index e874c3e01..8dd975630 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java @@ -1,26 +1,27 @@ 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.List; -public class PaidEnrollment implements Enrollment{ +public class PaidEnrollment implements Enrollment { private final EnrollmentManager enrollment; @Getter private final int maxEnrollment; - public PaidEnrollment(int maxEnrollment, List enrolledUsers, SessionStatus status) { + public PaidEnrollment(int maxEnrollment, List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { validateMaxEnrollment(maxEnrollment); this.maxEnrollment = maxEnrollment; - this.enrollment = new EnrollmentManager(enrolledUsers, status); + this.enrollment = new EnrollmentManager(enrolledUsers, progressStatus, recruitmentStatus); } public PaidEnrollment(int maxEnrollment) { - this(maxEnrollment, new ArrayList<>(), SessionStatus.RECRUITING); + this(maxEnrollment, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); } public void enroll(NsUser user) { @@ -30,8 +31,12 @@ public void enroll(NsUser user) { enrollment.enroll(user); } - public SessionStatus getStatus() { - return enrollment.getStatus(); + public SessionProgressStatus getProgressStatus() { + return enrollment.getProgressStatus(); + } + + public SessionRecruitmentStatus getRecruitmentStatus() { + return enrollment.getRecruitmentStatus(); } private boolean isFull() { diff --git a/src/main/java/nextstep/courses/dto/SessionDto.java b/src/main/java/nextstep/courses/dto/SessionDto.java index f75b019b4..41197ad0d 100644 --- a/src/main/java/nextstep/courses/dto/SessionDto.java +++ b/src/main/java/nextstep/courses/dto/SessionDto.java @@ -4,7 +4,8 @@ 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; @@ -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; @@ -48,7 +50,8 @@ public static SessionDto of(Session session) { .courseId(sessionId.getCourseId()) .title(sessionBasicInfo.getTitle()) .sessionType(sessionDetailInfo.getType()) - .status(enrollment.getStatus()) + .progressStatus(enrollment.getProgressStatus()) + .recruitmentStatus(enrollment.getRecruitmentStatus()) .startDate(sessionPeriod.getStartDate()) .endDate(sessionPeriod.getEndDate()) .maximumEnrollment(maxEnrollment) 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/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 94491528d..1bdce9924 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -1,10 +1,7 @@ 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.*; import nextstep.courses.domain.session.enrollment.Enrollment; import nextstep.courses.domain.session.enrollment.FreeEnrollment; import nextstep.courses.domain.session.enrollment.PaidEnrollment; @@ -81,13 +78,14 @@ private Enrollment getEnrollment(SessionDto sessionDto) { SessionType sessionType = sessionDto.getSessionType(); List enrolledUserIds = sessionEnrollmentRepository.findUserIdsBySessionId(sessionDto.getId()); List enrolledUsers = userService.findEnrolledUsersByIds(enrolledUserIds); - SessionStatus sessionStatus = sessionDto.getStatus(); + SessionProgressStatus progressStatus = sessionDto.getProgressStatus(); + SessionRecruitmentStatus recruitmentStatus = sessionDto.getRecruitmentStatus(); if (sessionType.isPaid()) { int maximumEnrollment = sessionDto.getMaximumEnrollment(); - return new PaidEnrollment(maximumEnrollment, enrolledUsers, sessionStatus); + return new PaidEnrollment(maximumEnrollment, enrolledUsers, progressStatus, recruitmentStatus); } - return new FreeEnrollment(enrolledUsers, sessionStatus); + return new FreeEnrollment(enrolledUsers, progressStatus, recruitmentStatus); } } \ 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/enrollment/EnrollmentManagerTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentManagerTest.java new file mode 100644 index 000000000..c6a60255c --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentManagerTest.java @@ -0,0 +1,59 @@ +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 java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class EnrollmentManagerTest { + private List enrolledUsers; + private NsUser user; + + @BeforeEach + void setUp() { + enrolledUsers = new ArrayList<>(); + user = new NsUser(1L, "user", "password", "name", "user@email.com"); + } + + @Test + @DisplayName("모집중인 상태에서 수강신청을 하면 성공한다") + void enroll_success_when_recruiting() { + // given + EnrollmentManager enrollmentManager = new EnrollmentManager( + enrolledUsers, + SessionProgressStatus.PREPARING, + SessionRecruitmentStatus.RECRUITING + ); + + // when + enrollmentManager.enroll(user); + + // then + assertThat(enrolledUsers).hasSize(1); + assertThat(enrolledUsers.get(0)).isEqualTo(user); + } + + @Test + @DisplayName("비모집중인 상태에서 수강신청을 하면 실패한다") + void enroll_fail_when_not_recruiting() { + // given + EnrollmentManager enrollmentManager = new EnrollmentManager( + enrolledUsers, + SessionProgressStatus.PREPARING, + SessionRecruitmentStatus.NOT_RECRUITING + ); + + // when & then + assertThatThrownBy(() -> enrollmentManager.enroll(user)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("수강 신청이 불가능합니다."); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java index 373847641..10470507f 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.session.enrollment; -import nextstep.courses.domain.session.SessionStatus; +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; @@ -18,7 +19,8 @@ class FreeEnrollmentTest { @DisplayName("무료 강의의 수강 신청을 생성한다") void createFreeEnrollment() { // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when & then assertThat(enrollment).isNotNull(); @@ -28,7 +30,8 @@ void createFreeEnrollment() { @DisplayName("무료 강의에 수강 신청을 한다") void enrollFreeSession() { // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); // when & then @@ -42,7 +45,8 @@ void enrollFreeSession() { @DisplayName("이미 수강 신청한 사용자는 다시 수강 신청할 수 없다") void validateDuplicateEnrollment() { // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when enrollment.enroll(USER); @@ -56,7 +60,8 @@ void validateDuplicateEnrollment() { @DisplayName("수강 신청할 사용자가 없으면 예외가 발생한다") void validateNullUser() { // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when & then assertThatThrownBy(() -> enrollment.enroll(null)) diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java index 34d593a79..d1bbeaf8a 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.session.enrollment; -import nextstep.courses.domain.session.SessionStatus; +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; @@ -19,7 +20,8 @@ void createPaidEnrollment() { int maxEnrollment = 30; // when - Enrollment enrollment = new PaidEnrollment(maxEnrollment, new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new PaidEnrollment(maxEnrollment, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // then assertThat(enrollment).isNotNull(); @@ -32,7 +34,8 @@ void validateMaxEnrollment() { int maxEnrollment = 0; // when & then - assertThatThrownBy(() -> new PaidEnrollment(maxEnrollment, new ArrayList<>(), SessionStatus.RECRUITING)) + assertThatThrownBy(() -> new PaidEnrollment(maxEnrollment, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("유료 강의는 최대 수강 인원이 0보다 커야 합니다."); } @@ -41,7 +44,8 @@ void validateMaxEnrollment() { @DisplayName("유료 강의에 수강 신청을 한다") void enrollPaidSession() { // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when & then assertThatCode(() -> enrollment.enroll(USER)).doesNotThrowAnyException(); @@ -51,7 +55,8 @@ void enrollPaidSession() { @DisplayName("수강 인원이 가득 찬 유료 강의는 수강 신청이 불가능하다") void enrollFullPaidSession() { // given - Enrollment enrollment = new PaidEnrollment(1, new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new PaidEnrollment(1, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); // when @@ -66,7 +71,8 @@ void enrollFullPaidSession() { @DisplayName("이미 수강 신청한 사용자는 다시 수강 신청할 수 없다") void validateDuplicateEnrollment() { // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when enrollment.enroll(USER); @@ -80,7 +86,8 @@ void validateDuplicateEnrollment() { @DisplayName("수강 신청할 사용자가 없으면 예외가 발생한다") void validateNullUser() { // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), SessionStatus.RECRUITING); + Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), + SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when & then assertThatThrownBy(() -> enrollment.enroll(null)) From 334d1d49f476fddc1671b19d8330adac1390c7cc Mon Sep 17 00:00:00 2001 From: melodist Date: Mon, 28 Apr 2025 14:52:59 +0900 Subject: [PATCH 6/9] =?UTF-8?q?Feat:=20=EA=B0=95=EC=9D=98=20=EC=BB=A4?= =?UTF-8?q?=EB=B2=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=8B=A4=EC=88=98=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../session/info/basic/SessionThumbnail.java | 29 +++++++----- .../session/info/basic/ThumbnailInfo.java | 18 ++++++++ .../infrastructure/JdbcImageRepository.java | 17 ++++--- .../courses/domain/session/SessionTest.java | 31 ++++++++++++- .../domain/session/SessionThumbnailTest.java | 45 ++++++++++--------- .../JdbcImageRepositoryTest.java | 10 +++-- 7 files changed, 108 insertions(+), 44 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/info/basic/ThumbnailInfo.java diff --git a/README.md b/README.md index 12f8205d8..e448915a8 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ - [x] ImageRepository 의존성 제거 - [x] Enrollment 검증 테스트 도입 - [x] 강의 진행 상태 / 모집 상태 분리 -- [ ] 강의 커버 이미지 다수 등록 +- [x] 강의 커버 이미지 다수 등록 - [ ] 수강 신청 변경 - [ ] 수강 신청 승인 - [ ] 수강 신청 취소 \ No newline at end of file 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/infrastructure/JdbcImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java index 647f2a45a..89480c762 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java @@ -8,6 +8,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.List; @Repository("imageRepository") public class JdbcImageRepository implements ImageRepository { @@ -19,16 +20,20 @@ public JdbcImageRepository(JdbcOperations jdbcTemplate) { @Override public SessionThumbnail findThumbnailBySessionId(Long sessionId) { - ImageDto imageDto = findBySessionId(sessionId); - if (imageDto == null) { + List imageDtos = findBySessionId(sessionId); + if (imageDtos.isEmpty()) { throw new IllegalArgumentException("존재하지 않는 이미지입니다."); } - return new SessionThumbnail(imageDto.getFileName(), imageDto.getFileSize(), - imageDto.getWidth(), imageDto.getHeight()); + SessionThumbnail thumbnail = new SessionThumbnail(); + for (ImageDto imageDto : imageDtos) { + thumbnail.addThumbnail(imageDto.getFileName(), imageDto.getFileSize(), + imageDto.getWidth(), imageDto.getHeight()); + } + return thumbnail; } - private ImageDto findBySessionId(Long sessionId) { + 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")) @@ -40,7 +45,7 @@ private 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/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index 43fb6d2cb..3ebafabe7 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -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("강의를 생성한다") @@ -83,7 +90,6 @@ void enrollFreeSession() { new FreeEnrollment() ); - session.enroll(USER, null); assertThatThrownBy(() -> session.enroll(USER, null)) .isInstanceOf(IllegalStateException.class); @@ -101,6 +107,27 @@ void enrollFullSession() { .isInstanceOf(IllegalStateException.class); } + @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 FreeEnrollment() + ); + + assertThat(session.getInfo().getBasicInfo().getThumbnail().getThumbnails()).hasSize(2); + } + private static Session getPaidSession(int maxEnrollment) { SessionPeriod period = new SessionPeriod(START_DATE, END_DATE); SessionPrice price = new SessionPrice(SessionType.PAID, 10000); 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/infrastructure/JdbcImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java index 4aa09ed2c..19bca64f6 100644 --- a/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/JdbcImageRepositoryTest.java @@ -1,6 +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; @@ -8,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; @@ -40,12 +42,14 @@ void findBySessionId() { .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 - SessionThumbnail actualImage = imageRepository.findThumbnailBySessionId(sessionId); + SessionThumbnail sessionThumbnail = imageRepository.findThumbnailBySessionId(sessionId); + ThumbnailInfo actualImage = sessionThumbnail.getThumbnails().get(0); // then assertThat(actualImage).isNotNull(); From f41196cfcc631a698cbdf25bbe5afe134be5031a Mon Sep 17 00:00:00 2001 From: melodist Date: Tue, 29 Apr 2025 10:43:03 +0900 Subject: [PATCH 7/9] =?UTF-8?q?Feat:=20=EC=88=98=EA=B0=95=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80=20-=20Enrol?= =?UTF-8?q?lment=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++ docs/issues_step4.md | 6 +++- .../courses/domain/session/Session.java | 10 +++---- .../domain/session/enrollment/Enrollment.java | 14 ++++++---- .../session/enrollment/EnrollmentStatus.java | 13 +++++++++ .../session/enrollment/Enrollments.java | 11 ++++++++ ...eeEnrollment.java => FreeEnrollments.java} | 6 ++-- ...idEnrollment.java => PaidEnrollments.java} | 6 ++-- .../java/nextstep/courses/dto/SessionDto.java | 16 +++++------ .../courses/service/SessionService.java | 16 +++++------ .../courses/domain/session/SessionTest.java | 14 +++++----- ...rTest.java => EnrollmentsManagerTest.java} | 2 +- ...mentTest.java => FreeEnrollmentsTest.java} | 22 +++++++-------- ...mentTest.java => PaidEnrollmentsTest.java} | 28 +++++++++---------- ...JdbcSessionEnrollmentsRepositoryTest.java} | 2 +- 15 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatus.java create mode 100644 src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java rename src/main/java/nextstep/courses/domain/session/enrollment/{FreeEnrollment.java => FreeEnrollments.java} (79%) rename src/main/java/nextstep/courses/domain/session/enrollment/{PaidEnrollment.java => PaidEnrollments.java} (85%) rename src/test/java/nextstep/courses/domain/session/enrollment/{EnrollmentManagerTest.java => EnrollmentsManagerTest.java} (98%) rename src/test/java/nextstep/courses/domain/session/enrollment/{FreeEnrollmentTest.java => FreeEnrollmentsTest.java} (75%) rename src/test/java/nextstep/courses/domain/session/enrollment/{PaidEnrollmentTest.java => PaidEnrollmentsTest.java} (73%) rename src/test/java/nextstep/courses/infrastructure/{JdbcSessionEnrollmentRepositoryTest.java => JdbcSessionEnrollmentsRepositoryTest.java} (97%) diff --git a/README.md b/README.md index e448915a8..7a41c8351 100644 --- a/README.md +++ b/README.md @@ -80,5 +80,8 @@ - [x] 강의 진행 상태 / 모집 상태 분리 - [x] 강의 커버 이미지 다수 등록 - [ ] 수강 신청 변경 + - [ ] 수강 신청 상태 추가 (미승인/승인/취소) + - [x] Enrollment 추가 + - [ ] Enrollments 리팩토링 - [ ] 수강 신청 승인 - [ ] 수강 신청 취소 \ No newline at end of file diff --git a/docs/issues_step4.md b/docs/issues_step4.md index f3d378807..1231efdd5 100644 --- a/docs/issues_step4.md +++ b/docs/issues_step4.md @@ -11,4 +11,8 @@ ### Repository - 도메인 객체에 ID는 필요 없는 값 - 하지만 이것을 분리하여 얻는 장점은 크지 않음 - - 참고: [https://mincanit.tistory.com/74](https://mincanit.tistory.com/74) \ No newline at end of file + - 참고: [https://mincanit.tistory.com/74](https://mincanit.tistory.com/74) +## 수강 신청 변경 +### 수강 신청 상태 추가 +- 기존 EnrollmentManager에서는 List를 사용하여 수강신청을 관리 + - 수강 신청 상태 - 사용자를 묶는 객체 추가 diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index 59490479b..7ee377fef 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,7 @@ public void enroll(NsUser user, Payment payment) { info.validatePayment(payment); } - enrollment.enroll(user); + enrollments.enroll(user); } public boolean isPaid() { 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 bf52b562d..476b030d0 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollment.java @@ -1,11 +1,13 @@ package nextstep.courses.domain.session.enrollment; -import nextstep.courses.domain.session.SessionProgressStatus; -import nextstep.courses.domain.session.SessionRecruitmentStatus; import nextstep.users.domain.NsUser; -public interface Enrollment { - void enroll(NsUser user); - SessionProgressStatus getProgressStatus(); - SessionRecruitmentStatus getRecruitmentStatus(); +public class Enrollment { + private final NsUser user; + private final EnrollmentStatus enrollmentStatus; + + public Enrollment(NsUser user, EnrollmentStatus enrollmentStatus) { + this.user = user; + this.enrollmentStatus = enrollmentStatus; + } } 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..4ea7b8780 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatus.java @@ -0,0 +1,13 @@ +package nextstep.courses.domain.session.enrollment; + +public enum EnrollmentStatus { + ENROLLED("수강신청"), + WAITING("대기"), + CANCELLED("취소"); + + private final String description; + + EnrollmentStatus(String description) { + this.description = 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..dfb11d1a5 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java @@ -0,0 +1,11 @@ +package nextstep.courses.domain.session.enrollment; + +import nextstep.courses.domain.session.SessionProgressStatus; +import nextstep.courses.domain.session.SessionRecruitmentStatus; +import nextstep.users.domain.NsUser; + +public interface Enrollments { + void enroll(NsUser user); + SessionProgressStatus getProgressStatus(); + SessionRecruitmentStatus getRecruitmentStatus(); +} diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollment.java b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java similarity index 79% rename from src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollment.java rename to src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java index a05b78688..45cbc5cf5 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollment.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java @@ -7,14 +7,14 @@ import java.util.ArrayList; import java.util.List; -public class FreeEnrollment implements Enrollment { +public class FreeEnrollments implements Enrollments { private final EnrollmentManager enrollment; - public FreeEnrollment(List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { + public FreeEnrollments(List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { this.enrollment = new EnrollmentManager(enrolledUsers, progressStatus, recruitmentStatus); } - public FreeEnrollment() { + public FreeEnrollments() { this.enrollment = new EnrollmentManager(new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); } diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java similarity index 85% rename from src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java rename to src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java index 8dd975630..e620d001c 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollment.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java @@ -8,19 +8,19 @@ import java.util.ArrayList; import java.util.List; -public class PaidEnrollment implements Enrollment { +public class PaidEnrollments implements Enrollments { private final EnrollmentManager enrollment; @Getter private final int maxEnrollment; - public PaidEnrollment(int maxEnrollment, List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { + public PaidEnrollments(int maxEnrollment, List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { validateMaxEnrollment(maxEnrollment); this.maxEnrollment = maxEnrollment; this.enrollment = new EnrollmentManager(enrolledUsers, progressStatus, recruitmentStatus); } - public PaidEnrollment(int maxEnrollment) { + public PaidEnrollments(int maxEnrollment) { this(maxEnrollment, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); } diff --git a/src/main/java/nextstep/courses/dto/SessionDto.java b/src/main/java/nextstep/courses/dto/SessionDto.java index 41197ad0d..a936cc882 100644 --- a/src/main/java/nextstep/courses/dto/SessionDto.java +++ b/src/main/java/nextstep/courses/dto/SessionDto.java @@ -7,8 +7,8 @@ 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; @@ -35,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(); @@ -43,24 +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()) - .progressStatus(enrollment.getProgressStatus()) - .recruitmentStatus(enrollment.getRecruitmentStatus()) + .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/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 1bdce9924..20f2f8426 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -2,9 +2,9 @@ import lombok.RequiredArgsConstructor; import nextstep.courses.domain.session.*; -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.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.detail.SessionDetailInfo; @@ -63,9 +63,9 @@ private Session createSessionFromDto(SessionDto sessionDto) { 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) { @@ -74,7 +74,7 @@ private SessionDetailInfo getSessionDetailInfo(SessionDto sessionDto) { return new SessionDetailInfo(sessionPeriod, sessionPrice); } - private Enrollment getEnrollment(SessionDto sessionDto) { + private Enrollments getEnrollment(SessionDto sessionDto) { SessionType sessionType = sessionDto.getSessionType(); List enrolledUserIds = sessionEnrollmentRepository.findUserIdsBySessionId(sessionDto.getId()); List enrolledUsers = userService.findEnrolledUsersByIds(enrolledUserIds); @@ -83,9 +83,9 @@ private Enrollment getEnrollment(SessionDto sessionDto) { if (sessionType.isPaid()) { int maximumEnrollment = sessionDto.getMaximumEnrollment(); - return new PaidEnrollment(maximumEnrollment, enrolledUsers, progressStatus, recruitmentStatus); + return new PaidEnrollments(maximumEnrollment, enrolledUsers, progressStatus, recruitmentStatus); } - return new FreeEnrollment(enrolledUsers, progressStatus, recruitmentStatus); + return new FreeEnrollments(enrolledUsers, progressStatus, recruitmentStatus); } } \ 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 3ebafabe7..45b2bdc05 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; @@ -42,7 +42,7 @@ void create() { Session session = new Session( new SessionId(1L, 1L), sessionInfo, - new FreeEnrollment() + new FreeEnrollments() ); assertThat(session.isPaid()).isTrue(); @@ -59,7 +59,7 @@ void createFreeSession() { Session session = new Session( new SessionId(1L, 1L), sessionInfo, - new FreeEnrollment() + new FreeEnrollments() ); assertThat(session.isPaid()).isFalse(); @@ -87,7 +87,7 @@ void enrollFreeSession() { Session session = new Session( new SessionId(1L, 1L), sessionInfo, - new FreeEnrollment() + new FreeEnrollments() ); session.enroll(USER, null); @@ -122,7 +122,7 @@ void addThumbnails() { Session session = new Session( new SessionId(1L, 1L), sessionInfo, - new FreeEnrollment() + new FreeEnrollments() ); assertThat(session.getInfo().getBasicInfo().getThumbnail().getThumbnails()).hasSize(2); @@ -138,7 +138,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/enrollment/EnrollmentManagerTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java similarity index 98% rename from src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentManagerTest.java rename to src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java index c6a60255c..1e253d7f8 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentManagerTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java @@ -13,7 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -class EnrollmentManagerTest { +class EnrollmentsManagerTest { private List enrolledUsers; private NsUser user; diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java similarity index 75% rename from src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java rename to src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java index 10470507f..539a2c08a 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java @@ -12,32 +12,32 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatCode; -class FreeEnrollmentTest { +class FreeEnrollmentsTest { private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); @Test @DisplayName("무료 강의의 수강 신청을 생성한다") void createFreeEnrollment() { // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when & then - assertThat(enrollment).isNotNull(); + assertThat(enrollments).isNotNull(); } @Test @DisplayName("무료 강의에 수강 신청을 한다") void enrollFreeSession() { // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); // when & then assertThatCode(() -> { - enrollment.enroll(USER); - enrollment.enroll(anotherUser); + enrollments.enroll(USER); + enrollments.enroll(anotherUser); }).doesNotThrowAnyException(); } @@ -45,14 +45,14 @@ void enrollFreeSession() { @DisplayName("이미 수강 신청한 사용자는 다시 수강 신청할 수 없다") void validateDuplicateEnrollment() { // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when - enrollment.enroll(USER); + enrollments.enroll(USER); // then - assertThatThrownBy(() -> enrollment.enroll(USER)) + assertThatThrownBy(() -> enrollments.enroll(USER)) .isInstanceOf(IllegalStateException.class); } @@ -60,11 +60,11 @@ void validateDuplicateEnrollment() { @DisplayName("수강 신청할 사용자가 없으면 예외가 발생한다") void validateNullUser() { // given - Enrollment enrollment = new FreeEnrollment(new ArrayList<>(), + Enrollments enrollments = new FreeEnrollments(new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when & then - assertThatThrownBy(() -> enrollment.enroll(null)) + assertThatThrownBy(() -> enrollments.enroll(null)) .isInstanceOf(IllegalArgumentException.class); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java similarity index 73% rename from src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java rename to src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java index d1bbeaf8a..a0a4bcad8 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java @@ -10,7 +10,7 @@ import static org.assertj.core.api.Assertions.*; -class PaidEnrollmentTest { +class PaidEnrollmentsTest { private static final NsUser USER = new NsUser(1L, "user", "password", "name", "email"); @Test @@ -20,11 +20,11 @@ void createPaidEnrollment() { int maxEnrollment = 30; // when - Enrollment enrollment = new PaidEnrollment(maxEnrollment, new ArrayList<>(), + Enrollments enrollments = new PaidEnrollments(maxEnrollment, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // then - assertThat(enrollment).isNotNull(); + assertThat(enrollments).isNotNull(); } @Test @@ -34,7 +34,7 @@ void validateMaxEnrollment() { int maxEnrollment = 0; // when & then - assertThatThrownBy(() -> new PaidEnrollment(maxEnrollment, new ArrayList<>(), + assertThatThrownBy(() -> new PaidEnrollments(maxEnrollment, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("유료 강의는 최대 수강 인원이 0보다 커야 합니다."); @@ -44,26 +44,26 @@ void validateMaxEnrollment() { @DisplayName("유료 강의에 수강 신청을 한다") void enrollPaidSession() { // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when & then - assertThatCode(() -> enrollment.enroll(USER)).doesNotThrowAnyException(); + assertThatCode(() -> enrollments.enroll(USER)).doesNotThrowAnyException(); } @Test @DisplayName("수강 인원이 가득 찬 유료 강의는 수강 신청이 불가능하다") void enrollFullPaidSession() { // given - Enrollment enrollment = new PaidEnrollment(1, new ArrayList<>(), + Enrollments enrollments = new PaidEnrollments(1, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); NsUser anotherUser = new NsUser(2L, "user2", "password", "name", "email"); // when - enrollment.enroll(USER); + enrollments.enroll(USER); // then - assertThatThrownBy(() -> enrollment.enroll(anotherUser)) + assertThatThrownBy(() -> enrollments.enroll(anotherUser)) .isInstanceOf(IllegalStateException.class); } @@ -71,14 +71,14 @@ void enrollFullPaidSession() { @DisplayName("이미 수강 신청한 사용자는 다시 수강 신청할 수 없다") void validateDuplicateEnrollment() { // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when - enrollment.enroll(USER); + enrollments.enroll(USER); // then - assertThatThrownBy(() -> enrollment.enroll(USER)) + assertThatThrownBy(() -> enrollments.enroll(USER)) .isInstanceOf(IllegalStateException.class); } @@ -86,11 +86,11 @@ void validateDuplicateEnrollment() { @DisplayName("수강 신청할 사용자가 없으면 예외가 발생한다") void validateNullUser() { // given - Enrollment enrollment = new PaidEnrollment(30, new ArrayList<>(), + Enrollments enrollments = new PaidEnrollments(30, new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); // when & then - assertThatThrownBy(() -> enrollment.enroll(null)) + assertThatThrownBy(() -> enrollments.enroll(null)) .isInstanceOf(IllegalArgumentException.class); } } \ 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; From 84ec26393d12fd1778b0568ed72757ea1b0cf936 Mon Sep 17 00:00:00 2001 From: melodist Date: Wed, 30 Apr 2025 22:15:25 +0900 Subject: [PATCH 8/9] =?UTF-8?q?Feat:=20EnrollmentStatusManager=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/issues_step4.md | 3 + .../domain/session/enrollment/Enrollment.java | 43 +++++++- .../session/enrollment/EnrollmentStatus.java | 5 + .../enrollment/EnrollmentStatusManager.java | 80 ++++++++++++++ .../session/enrollment/Enrollments.java | 7 ++ .../session/enrollment/FreeEnrollments.java | 35 ++++++ .../session/enrollment/PaidEnrollments.java | 36 ++++++- .../JdbcSessionEnrollmentRepository.java | 39 ++++++- .../SessionEnrollmentRepository.java | 8 +- .../enrollment/FreeEnrollmentsTest.java | 81 +++++++++++++- .../enrollment/PaidEnrollmentsTest.java | 100 +++++++++++++++++- 11 files changed, 430 insertions(+), 7 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatusManager.java diff --git a/docs/issues_step4.md b/docs/issues_step4.md index 1231efdd5..1925717fd 100644 --- a/docs/issues_step4.md +++ b/docs/issues_step4.md @@ -16,3 +16,6 @@ ### 수강 신청 상태 추가 - 기존 EnrollmentManager에서는 List를 사용하여 수강신청을 관리 - 수강 신청 상태 - 사용자를 묶는 객체 추가 + - EnrollmentManager는 수강 신청 상태와 관계 없이 List 도 필요 + - List 대신 Map를 갖도록 변경하자 + - 이러면 별도의 Enrollment 객체는 필요하지 않음 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 476b030d0..c600a5bd6 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollment.java @@ -4,10 +4,51 @@ public class Enrollment { private final NsUser user; - private final EnrollmentStatus enrollmentStatus; + 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/EnrollmentStatus.java b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatus.java index 4ea7b8780..c3f673fdf 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatus.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatus.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.session.enrollment; public enum EnrollmentStatus { + PENDING_APPROVAL("승인대기"), ENROLLED("수강신청"), WAITING("대기"), CANCELLED("취소"); @@ -10,4 +11,8 @@ public enum EnrollmentStatus { EnrollmentStatus(String description) { this.description = description; } + + public String getDescription() { + return description; + } } diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatusManager.java b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatusManager.java new file mode 100644 index 000000000..7b2bf5007 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatusManager.java @@ -0,0 +1,80 @@ +package nextstep.courses.domain.session.enrollment; + +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; + +public class EnrollmentStatusManager { + private final Map enrollmentStatuses; + + public EnrollmentStatusManager() { + this.enrollmentStatuses = new HashMap<>(); + } + + 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; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java index dfb11d1a5..c1ce6b121 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/Enrollments.java @@ -4,8 +4,15 @@ 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/FreeEnrollments.java b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java index 45cbc5cf5..f50e15552 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java @@ -9,17 +9,37 @@ public class FreeEnrollments implements Enrollments { private final EnrollmentManager enrollment; + private final EnrollmentStatusManager statusManager; public FreeEnrollments(List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { this.enrollment = new EnrollmentManager(enrolledUsers, progressStatus, recruitmentStatus); + this.statusManager = new EnrollmentStatusManager(); + + // Initialize status manager with existing enrolled users + for (NsUser user : enrolledUsers) { + statusManager.addEnrollment(user); + statusManager.approveEnrollment(user); + } } public FreeEnrollments() { this.enrollment = new EnrollmentManager(new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); + this.statusManager = new EnrollmentStatusManager(); } public void enroll(NsUser user) { enrollment.enroll(user); + statusManager.addEnrollment(user); + } + + @Override + public void approve(NsUser user) { + statusManager.approveEnrollment(user); + } + + @Override + public void cancel(NsUser user) { + statusManager.cancelEnrollment(user); } public SessionProgressStatus getProgressStatus() { @@ -29,4 +49,19 @@ public SessionProgressStatus getProgressStatus() { public SessionRecruitmentStatus getRecruitmentStatus() { return enrollment.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/PaidEnrollments.java b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java index e620d001c..4f86b3add 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java @@ -10,6 +10,7 @@ public class PaidEnrollments implements Enrollments { private final EnrollmentManager enrollment; + private final EnrollmentStatusManager statusManager; @Getter private final int maxEnrollment; @@ -18,6 +19,13 @@ public PaidEnrollments(int maxEnrollment, List enrolledUsers, SessionPro validateMaxEnrollment(maxEnrollment); this.maxEnrollment = maxEnrollment; this.enrollment = new EnrollmentManager(enrolledUsers, progressStatus, recruitmentStatus); + this.statusManager = new EnrollmentStatusManager(); + + // Initialize status manager with existing enrolled users + for (NsUser user : enrolledUsers) { + statusManager.addEnrollment(user); + statusManager.approveEnrollment(user); + } } public PaidEnrollments(int maxEnrollment) { @@ -29,6 +37,17 @@ public void enroll(NsUser user) { throw new IllegalStateException("수강 인원이 가득 찼습니다."); } enrollment.enroll(user); + statusManager.addEnrollment(user); + } + + @Override + public void approve(NsUser user) { + statusManager.approveEnrollment(user); + } + + @Override + public void cancel(NsUser user) { + statusManager.cancelEnrollment(user); } public SessionProgressStatus getProgressStatus() { @@ -39,8 +58,23 @@ public SessionRecruitmentStatus getRecruitmentStatus() { return enrollment.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 enrollment.getEnrolledUsers().size() >= maxEnrollment; + return statusManager.getEnrolledUsers().size() >= maxEnrollment; } private void validateMaxEnrollment(int maxEnrollment) { 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/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/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java index 539a2c08a..91d0ea410 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/FreeEnrollmentsTest.java @@ -67,4 +67,83 @@ void validateNullUser() { assertThatThrownBy(() -> enrollments.enroll(null)) .isInstanceOf(IllegalArgumentException.class); } -} \ No newline at end of file + + @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/PaidEnrollmentsTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java index a0a4bcad8..342993763 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/PaidEnrollmentsTest.java @@ -61,6 +61,7 @@ void enrollFullPaidSession() { // when enrollments.enroll(USER); + enrollments.approve(USER); // then assertThatThrownBy(() -> enrollments.enroll(anotherUser)) @@ -93,4 +94,101 @@ void validateNullUser() { assertThatThrownBy(() -> enrollments.enroll(null)) .isInstanceOf(IllegalArgumentException.class); } -} \ No newline at end of file + + @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(); + } +} From 34fbb91fdb9573bf044c729c6ffb58104a98733a Mon Sep 17 00:00:00 2001 From: melodist Date: Thu, 1 May 2025 23:39:54 +0900 Subject: [PATCH 9/9] =?UTF-8?q?Feat:=20=EC=88=98=EA=B0=95=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=8A=B9=EC=9D=B8/=EC=B7=A8=EC=86=8C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +-- .../courses/domain/session/Session.java | 4 + .../session/enrollment/EnrollmentManager.java | 85 +++++++++++++++++-- .../enrollment/EnrollmentStatusManager.java | 80 ----------------- .../session/enrollment/FreeEnrollments.java | 17 ++-- .../session/enrollment/PaidEnrollments.java | 13 ++- .../courses/domain/session/SessionTest.java | 4 +- .../enrollment/EnrollmentsManagerTest.java | 15 ++-- 8 files changed, 107 insertions(+), 121 deletions(-) delete mode 100644 src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatusManager.java diff --git a/README.md b/README.md index 7a41c8351..64618d5bf 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,9 @@ - [x] Enrollment 검증 테스트 도입 - [x] 강의 진행 상태 / 모집 상태 분리 - [x] 강의 커버 이미지 다수 등록 -- [ ] 수강 신청 변경 - - [ ] 수강 신청 상태 추가 (미승인/승인/취소) +- [x] 수강 신청 변경 + - [x] 수강 신청 상태 추가 (미승인/승인/취소) - [x] Enrollment 추가 - - [ ] Enrollments 리팩토링 - - [ ] 수강 신청 승인 - - [ ] 수강 신청 취소 \ No newline at end of file + - [x] Enrollments 리팩토링 + - [x] 수강 신청 승인 + - [x] 수강 신청 취소 \ 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 7ee377fef..aac308562 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -27,6 +27,10 @@ public void enroll(NsUser user, Payment payment) { enrollments.enroll(user); } + public void approve(NsUser user) { + enrollments.approve(user); + } + public boolean isPaid() { return info.isPaid(); } 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 038ecfba1..0086aa372 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentManager.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentManager.java @@ -5,23 +5,95 @@ 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 Map enrollmentStatuses; + @Getter private final SessionProgressStatus progressStatus; + @Getter private final SessionRecruitmentStatus recruitmentStatus; - public EnrollmentManager(List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { - this.enrolledUsers = enrolledUsers; + 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) { @@ -37,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/EnrollmentStatusManager.java b/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatusManager.java deleted file mode 100644 index 7b2bf5007..000000000 --- a/src/main/java/nextstep/courses/domain/session/enrollment/EnrollmentStatusManager.java +++ /dev/null @@ -1,80 +0,0 @@ -package nextstep.courses.domain.session.enrollment; - -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; - -public class EnrollmentStatusManager { - private final Map enrollmentStatuses; - - public EnrollmentStatusManager() { - this.enrollmentStatuses = new HashMap<>(); - } - - 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; - } -} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java index f50e15552..48ac3bf9f 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/FreeEnrollments.java @@ -4,16 +4,13 @@ import nextstep.courses.domain.session.SessionRecruitmentStatus; import nextstep.users.domain.NsUser; -import java.util.ArrayList; import java.util.List; public class FreeEnrollments implements Enrollments { - private final EnrollmentManager enrollment; - private final EnrollmentStatusManager statusManager; + private final EnrollmentManager statusManager; public FreeEnrollments(List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { - this.enrollment = new EnrollmentManager(enrolledUsers, progressStatus, recruitmentStatus); - this.statusManager = new EnrollmentStatusManager(); + this.statusManager = new EnrollmentManager(progressStatus, recruitmentStatus); // Initialize status manager with existing enrolled users for (NsUser user : enrolledUsers) { @@ -23,13 +20,11 @@ public FreeEnrollments(List enrolledUsers, SessionProgressStatus progres } public FreeEnrollments() { - this.enrollment = new EnrollmentManager(new ArrayList<>(), SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING); - this.statusManager = new EnrollmentStatusManager(); + this.statusManager = new EnrollmentManager(); } public void enroll(NsUser user) { - enrollment.enroll(user); - statusManager.addEnrollment(user); + statusManager.enroll(user); } @Override @@ -43,11 +38,11 @@ public void cancel(NsUser user) { } public SessionProgressStatus getProgressStatus() { - return enrollment.getProgressStatus(); + return statusManager.getProgressStatus(); } public SessionRecruitmentStatus getRecruitmentStatus() { - return enrollment.getRecruitmentStatus(); + return statusManager.getRecruitmentStatus(); } @Override diff --git a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java index 4f86b3add..170c955f4 100644 --- a/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java +++ b/src/main/java/nextstep/courses/domain/session/enrollment/PaidEnrollments.java @@ -9,8 +9,7 @@ import java.util.List; public class PaidEnrollments implements Enrollments { - private final EnrollmentManager enrollment; - private final EnrollmentStatusManager statusManager; + private final EnrollmentManager statusManager; @Getter private final int maxEnrollment; @@ -18,8 +17,7 @@ public class PaidEnrollments implements Enrollments { public PaidEnrollments(int maxEnrollment, List enrolledUsers, SessionProgressStatus progressStatus, SessionRecruitmentStatus recruitmentStatus) { validateMaxEnrollment(maxEnrollment); this.maxEnrollment = maxEnrollment; - this.enrollment = new EnrollmentManager(enrolledUsers, progressStatus, recruitmentStatus); - this.statusManager = new EnrollmentStatusManager(); + this.statusManager = new EnrollmentManager(progressStatus, recruitmentStatus); // Initialize status manager with existing enrolled users for (NsUser user : enrolledUsers) { @@ -36,8 +34,7 @@ public void enroll(NsUser user) { if (isFull()) { throw new IllegalStateException("수강 인원이 가득 찼습니다."); } - enrollment.enroll(user); - statusManager.addEnrollment(user); + statusManager.enroll(user); } @Override @@ -51,11 +48,11 @@ public void cancel(NsUser user) { } public SessionProgressStatus getProgressStatus() { - return enrollment.getProgressStatus(); + return statusManager.getProgressStatus(); } public SessionRecruitmentStatus getRecruitmentStatus() { - return enrollment.getRecruitmentStatus(); + return statusManager.getRecruitmentStatus(); } @Override diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index 45b2bdc05..0aa79cbe1 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -103,6 +103,8 @@ 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); } @@ -128,7 +130,7 @@ void addThumbnails() { assertThat(session.getInfo().getBasicInfo().getThumbnail().getThumbnails()).hasSize(2); } - private static Session getPaidSession(int maxEnrollment) { + 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); diff --git a/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java b/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java index 1e253d7f8..ec49712ff 100644 --- a/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java +++ b/src/test/java/nextstep/courses/domain/session/enrollment/EnrollmentsManagerTest.java @@ -7,19 +7,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; class EnrollmentsManagerTest { - private List enrolledUsers; private NsUser user; @BeforeEach void setUp() { - enrolledUsers = new ArrayList<>(); user = new NsUser(1L, "user", "password", "name", "user@email.com"); } @@ -28,7 +23,6 @@ void setUp() { void enroll_success_when_recruiting() { // given EnrollmentManager enrollmentManager = new EnrollmentManager( - enrolledUsers, SessionProgressStatus.PREPARING, SessionRecruitmentStatus.RECRUITING ); @@ -37,8 +31,10 @@ void enroll_success_when_recruiting() { enrollmentManager.enroll(user); // then - assertThat(enrolledUsers).hasSize(1); - assertThat(enrolledUsers.get(0)).isEqualTo(user); + 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 @@ -46,7 +42,6 @@ void enroll_success_when_recruiting() { void enroll_fail_when_not_recruiting() { // given EnrollmentManager enrollmentManager = new EnrollmentManager( - enrolledUsers, SessionProgressStatus.PREPARING, SessionRecruitmentStatus.NOT_RECRUITING ); @@ -56,4 +51,4 @@ void enroll_fail_when_not_recruiting() { .isInstanceOf(IllegalStateException.class) .hasMessage("수강 신청이 불가능합니다."); } -} \ No newline at end of file +}