From a5e70745ea5bb3eccdafc9953281f560f880c59b Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Sun, 20 Apr 2025 18:45:13 +0900 Subject: [PATCH 1/9] docs: set up requirement --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00ce3d2e4..1041e5199 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,10 @@ - boolean validateFileSize() - boolean validateFileType() - boolean validateRatio() - \ No newline at end of file + + +## ๐Ÿš€ 4๋‹จ๊ณ„ - ์ˆ˜๊ฐ•์‹ ์ฒญ(์š”๊ตฌ์‚ฌํ•ญ ๋ณ€๊ฒฝ) +- [] SessionStatus ์ง„ํ–‰์ƒํƒœ์™€ ๋ชจ์ง‘์ƒํƒœ๋กœ ๋ถ„๋ฆฌ +- [] coverImage ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ธฐ๋Šฅ ์ถ”๊ฐ€ +- [] ์„ ๋ฐœ ์—ฌ๋ถ€ ํ™•์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ +- [] ์ˆ˜๊ฐ• ์Šน์ธ ๋ฐ ์ˆ˜๊ฐ• ์ทจ์†Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ \ No newline at end of file From 65672481e4169445b8f138fc22d2a75b1151408f Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Sun, 20 Apr 2025 19:00:25 +0900 Subject: [PATCH 2/9] feat: add RecruitmentStatus to `Session` --- README.md | 2 +- .../courses/domain/RecruitmentStatus.java | 6 ++ .../java/nextstep/courses/domain/Session.java | 17 +++-- .../courses/domain/SessionStatus.java | 2 +- .../infrastructure/JdbcSessionRepository.java | 15 ++-- src/main/resources/schema.sql | 1 + .../nextstep/courses/domain/SessionTest.java | 74 ++++++++++++++++++- .../nextstep/courses/domain/SessionsTest.java | 3 +- .../infrastructure/SessionRepositoryTest.java | 8 +- 9 files changed, 102 insertions(+), 26 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/RecruitmentStatus.java diff --git a/README.md b/README.md index 1041e5199..1555bb1a8 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ ## ๐Ÿš€ 4๋‹จ๊ณ„ - ์ˆ˜๊ฐ•์‹ ์ฒญ(์š”๊ตฌ์‚ฌํ•ญ ๋ณ€๊ฒฝ) -- [] SessionStatus ์ง„ํ–‰์ƒํƒœ์™€ ๋ชจ์ง‘์ƒํƒœ๋กœ ๋ถ„๋ฆฌ +- [x] SessionStatus ์ง„ํ–‰์ƒํƒœ์™€ ๋ชจ์ง‘์ƒํƒœ๋กœ ๋ถ„๋ฆฌ - [] coverImage ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ธฐ๋Šฅ ์ถ”๊ฐ€ - [] ์„ ๋ฐœ ์—ฌ๋ถ€ ํ™•์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ - [] ์ˆ˜๊ฐ• ์Šน์ธ ๋ฐ ์ˆ˜๊ฐ• ์ทจ์†Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/RecruitmentStatus.java b/src/main/java/nextstep/courses/domain/RecruitmentStatus.java new file mode 100644 index 000000000..77e1512c2 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/RecruitmentStatus.java @@ -0,0 +1,6 @@ +package nextstep.courses.domain; + +public enum RecruitmentStatus { + NOT_RECRUITING, + RECRUITING +} diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index e7f925551..085b6c062 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -3,7 +3,6 @@ import nextstep.payments.domain.Payment; import java.time.LocalDateTime; -import java.util.regex.Pattern; public class Session { private CapacityInfo capacityInfo; @@ -12,15 +11,16 @@ public class Session { private int id; private Long tuition; private Image coverImage; + private RecruitmentStatus recruitmentStatus; private SessionStatus sessionStatus; private JoinStrategy joinStrategy; - public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Image coverImage, SessionStatus sessionStatus) { - this(title, id, startDate, endDate, tuition, currentCount, capacity, coverImage, sessionStatus, tuition == 0 ? new FreeJoinStrategy() : new PaidJoinStrategy()); + public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Image coverImage, SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus) { + this(title, id, startDate, endDate, tuition, currentCount, capacity, coverImage, sessionStatus, recruitmentStatus, tuition == 0 ? new FreeJoinStrategy() : new PaidJoinStrategy()); } - public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Image coverImage, SessionStatus sessionStatus, JoinStrategy joinStrategy) { + public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Image coverImage, SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus, JoinStrategy joinStrategy) { this.title = title; this.id = id; this.sessionPeriod = new SessionPeriod(startDate, endDate); @@ -28,6 +28,7 @@ public Session(String title, int id, LocalDateTime startDate, LocalDateTime endD this.capacityInfo = new CapacityInfo(currentCount, capacity); this.coverImage = coverImage; this.sessionStatus = sessionStatus; + this.recruitmentStatus = recruitmentStatus; this.joinStrategy = joinStrategy; } @@ -36,7 +37,7 @@ boolean joinable(Payment pay) { } public boolean recruiting() { - return sessionStatus == SessionStatus.RECRUITING; + return sessionStatus == SessionStatus.ONGOING && recruitmentStatus == RecruitmentStatus.RECRUITING; } public boolean underCapacity() { @@ -91,7 +92,11 @@ public Image getCoverImage() { return coverImage; } - public SessionStatus getStatus() { + public SessionStatus getSessionStatus() { return sessionStatus; } + + public RecruitmentStatus getRecruitmentStatus() { + return recruitmentStatus; + } } diff --git a/src/main/java/nextstep/courses/domain/SessionStatus.java b/src/main/java/nextstep/courses/domain/SessionStatus.java index d4ce621fa..43cdd62a8 100644 --- a/src/main/java/nextstep/courses/domain/SessionStatus.java +++ b/src/main/java/nextstep/courses/domain/SessionStatus.java @@ -2,6 +2,6 @@ public enum SessionStatus { PREPARING, - RECRUITING, + ONGOING, CLOSED } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 1b692b2ac..e04fb39b0 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,9 +1,6 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Image; -import nextstep.courses.domain.Session; -import nextstep.courses.domain.SessionRepository; -import nextstep.courses.domain.SessionStatus; +import nextstep.courses.domain.*; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; @@ -22,8 +19,8 @@ public int save(Session session, Long courseId) { String sql = "insert into session " + "(title, start_date, end_date, tuition, current_count, capacity, " + "image_file_size, image_file_type, image_url, image_width, image_height, " + - "status, course_id) " + - "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + "status, recruitment_status, course_id) " + + "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; return jdbcTemplate.update(sql, session.getTitle(), @@ -37,7 +34,8 @@ public int save(Session session, Long courseId) { session.getCoverImage().getImageUrl(), session.getCoverImage().getWidth(), session.getCoverImage().getHeight(), - session.getStatus().name(), + session.getSessionStatus().name(), + session.getRecruitmentStatus().name(), courseId ); } @@ -65,7 +63,8 @@ private RowMapper sessionRowMapper() { rs.getInt("image_width"), rs.getInt("image_height") ), - SessionStatus.valueOf(rs.getString("status")) + SessionStatus.valueOf(rs.getString("status")), + RecruitmentStatus.valueOf(rs.getString("recruitment_status")) ); } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 04723cb30..a78c7b01d 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -27,6 +27,7 @@ CREATE TABLE session image_height INT, status VARCHAR(50) NOT NULL, + recruitment_status VARCHAR(50) NOT NULL, -- FK to course course_id BIGINT NOT NULL, diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index 75a9e4de7..eba2a0e63 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -24,7 +24,8 @@ void freeSession_joinable_whenRecruiting() { 0, // currentCount 0, // capacity (๋ฌด์ œํ•œ์ด์ง€๋งŒ ๊ทธ๋ƒฅ 0์œผ๋กœ ๋‘ ) validImage, - SessionStatus.RECRUITING, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, new FreeJoinStrategy() ); @@ -44,6 +45,7 @@ void freeSession_notJoinable_whenNotRecruiting() { 0, validImage, SessionStatus.PREPARING, + RecruitmentStatus.RECRUITING, new FreeJoinStrategy() ); @@ -62,7 +64,8 @@ void paidSession_joinable_whenRecruiting_underCapacity_andPaidCorrectly() { 29, // currentCount 30, // capacity validImage, - SessionStatus.RECRUITING, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, new PaidJoinStrategy() ); @@ -81,7 +84,8 @@ void paidSession_notJoinable_whenWrongAmount() { 10, 30, validImage, - SessionStatus.RECRUITING, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, new PaidJoinStrategy() ); @@ -100,10 +104,72 @@ void paidSession_notJoinable_whenOverCapacity() { 30, 30, validImage, - SessionStatus.RECRUITING, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, new PaidJoinStrategy() ); assertThat(session.joinable(new Payment(10000L))).isFalse(); } + + @Test + @DisplayName("๊ฐ•์˜ ์ƒํƒœ๊ฐ€ PREPARING์ด๋ฉด ๋ชจ์ง‘์ค‘์ด์–ด๋„ ์ˆ˜๊ฐ• ์‹ ์ฒญ ๋ถˆ๊ฐ€") + void notJoinable_whenLecturePreparing_evenIfRecruiting() { + Session session = new Session( + "๊ฐ•์˜ ์ค€๋น„ ์ค‘", + 1, + LocalDateTime.now(), + LocalDateTime.now().plusDays(7), + 0L, + 0, + 0, + validImage, + SessionStatus.PREPARING, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + + assertThat(session.joinable(new Payment())).isFalse(); + } + + @Test + @DisplayName("๊ฐ•์˜ ์ƒํƒœ๊ฐ€ CLOSED์ด๋ฉด ๋ชจ์ง‘์ค‘์ด์–ด๋„ ์ˆ˜๊ฐ• ์‹ ์ฒญ ๋ถˆ๊ฐ€") + void notJoinable_whenLectureClosed_evenIfRecruiting() { + Session session = new Session( + "์ข…๋ฃŒ๋œ ๊ฐ•์˜", + 1, + LocalDateTime.now().minusDays(10), + LocalDateTime.now().minusDays(3), + 0L, + 0, + 0, + validImage, + SessionStatus.CLOSED, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + + assertThat(session.joinable(new Payment())).isFalse(); + } + + @Test + @DisplayName("๋ชจ์ง‘ ์ƒํƒœ๊ฐ€ NOT_RECRUITING์ด๋ฉด ๊ฐ•์˜๊ฐ€ Ongoing์ด์–ด๋„ ์ˆ˜๊ฐ• ์‹ ์ฒญ ๋ถˆ๊ฐ€") + void notJoinable_whenNotRecruiting() { + Session session = new Session( + "๋ชจ์ง‘ ๋น„ํ™œ์„ฑ ๊ฐ•์˜", + 1, + LocalDateTime.now(), + LocalDateTime.now().plusDays(7), + 0L, + 0, + 0, + validImage, + SessionStatus.ONGOING, + RecruitmentStatus.NOT_RECRUITING, + new FreeJoinStrategy() + ); + + assertThat(session.joinable(new Payment())).isFalse(); + } + } diff --git a/src/test/java/nextstep/courses/domain/SessionsTest.java b/src/test/java/nextstep/courses/domain/SessionsTest.java index 7b2ea5065..4a9ff3a25 100644 --- a/src/test/java/nextstep/courses/domain/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/SessionsTest.java @@ -22,7 +22,8 @@ private Session createSessionWithId(int id) { 0, 0, image, - SessionStatus.RECRUITING, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, new FreeJoinStrategy() ); } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 94523c758..a2219b00e 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -1,9 +1,6 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Image; -import nextstep.courses.domain.Session; -import nextstep.courses.domain.SessionRepository; -import nextstep.courses.domain.SessionStatus; +import nextstep.courses.domain.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -43,7 +40,8 @@ void crud() { 0, 20, image, - SessionStatus.RECRUITING + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING ); // when From 4cc11b9542d0a7c23403cb60995804ac3f854e13 Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Mon, 21 Apr 2025 19:43:48 +0900 Subject: [PATCH 3/9] feat: add support for multiple cover images --- README.md | 2 +- .../java/nextstep/courses/domain/Image.java | 8 ++ .../java/nextstep/courses/domain/Images.java | 16 +++ .../java/nextstep/courses/domain/Session.java | 18 +-- .../courses/domain/SessionRepository.java | 2 +- .../infrastructure/JdbcSessionRepository.java | 114 ++++++++++++------ src/main/resources/schema.sql | 40 +++--- .../nextstep/courses/domain/ImagesTest.java | 44 +++++++ .../nextstep/courses/domain/SessionTest.java | 19 +-- .../nextstep/courses/domain/SessionsTest.java | 4 +- .../infrastructure/SessionRepositoryTest.java | 24 ++-- 11 files changed, 210 insertions(+), 81 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/Images.java create mode 100644 src/test/java/nextstep/courses/domain/ImagesTest.java diff --git a/README.md b/README.md index 1555bb1a8..3477a100e 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,6 @@ ## ๐Ÿš€ 4๋‹จ๊ณ„ - ์ˆ˜๊ฐ•์‹ ์ฒญ(์š”๊ตฌ์‚ฌํ•ญ ๋ณ€๊ฒฝ) - [x] SessionStatus ์ง„ํ–‰์ƒํƒœ์™€ ๋ชจ์ง‘์ƒํƒœ๋กœ ๋ถ„๋ฆฌ -- [] coverImage ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ธฐ๋Šฅ ์ถ”๊ฐ€ +- [x] coverImage ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ธฐ๋Šฅ ์ถ”๊ฐ€ - [] ์„ ๋ฐœ ์—ฌ๋ถ€ ํ™•์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ - [] ์ˆ˜๊ฐ• ์Šน์ธ ๋ฐ ์ˆ˜๊ฐ• ์ทจ์†Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/Image.java b/src/main/java/nextstep/courses/domain/Image.java index 4454a011f..c981bdf7c 100644 --- a/src/main/java/nextstep/courses/domain/Image.java +++ b/src/main/java/nextstep/courses/domain/Image.java @@ -6,11 +6,18 @@ public class Image { private static final Set ALLOWED_FILE_FORMAT = new HashSet<>(List.of("gif", "jpg", "jpeg", "png", "svg")); + private Long id; private final File file; private String imageUrl; private int width; private int height; + // DB์šฉ ์ƒ์„ฑ์ž (id ํฌํ•จ, ๊ฒ€์ฆ ์ƒ๋žต ๊ฐ€๋Šฅ) + public Image(Long id, float fileSize, String fileType, String imageUrl, int width, int height) { + this(fileSize, fileType, imageUrl, width, height); + this.id = id; + } + public Image(float fileSize, String fileType, String imageUrl, int width, int height) { this.file = new File(ALLOWED_FILE_FORMAT, fileSize, fileType); this.imageUrl = imageUrl; @@ -19,6 +26,7 @@ public Image(float fileSize, String fileType, String imageUrl, int width, int he validate(); } + private void validate() { validateFileSize(); validateFileType(); diff --git a/src/main/java/nextstep/courses/domain/Images.java b/src/main/java/nextstep/courses/domain/Images.java new file mode 100644 index 000000000..ef8daae52 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Images.java @@ -0,0 +1,16 @@ +package nextstep.courses.domain; + +import java.util.Collections; +import java.util.List; + +public class Images { + private List images; + + public Images(List images) { + this.images = List.copyOf(images); + } + + public List getImages() { + return Collections.unmodifiableList(images); + } +} diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index 085b6c062..84d7886de 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -10,23 +10,23 @@ public class Session { private String title; private int id; private Long tuition; - private Image coverImage; + private Images coverImages; private RecruitmentStatus recruitmentStatus; private SessionStatus sessionStatus; private JoinStrategy joinStrategy; - public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Image coverImage, SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus) { - this(title, id, startDate, endDate, tuition, currentCount, capacity, coverImage, sessionStatus, recruitmentStatus, tuition == 0 ? new FreeJoinStrategy() : new PaidJoinStrategy()); + public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Images coverImages, SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus) { + this(title, id, startDate, endDate, tuition, currentCount, capacity, coverImages, sessionStatus, recruitmentStatus, tuition == 0 ? new FreeJoinStrategy() : new PaidJoinStrategy()); } - public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Image coverImage, SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus, JoinStrategy joinStrategy) { + public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Images coverImages, SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus, JoinStrategy joinStrategy) { this.title = title; this.id = id; this.sessionPeriod = new SessionPeriod(startDate, endDate); this.tuition = tuition; this.capacityInfo = new CapacityInfo(currentCount, capacity); - this.coverImage = coverImage; + this.coverImages = coverImages; this.sessionStatus = sessionStatus; this.recruitmentStatus = recruitmentStatus; this.joinStrategy = joinStrategy; @@ -88,8 +88,12 @@ public int getCapacity() { return capacityInfo.getCapacity(); } - public Image getCoverImage() { - return coverImage; + public Images getCoverImages() { + return coverImages; + } + + public Image getMainCoverImage() { + return coverImages.getImages().get(0); } public SessionStatus getSessionStatus() { diff --git a/src/main/java/nextstep/courses/domain/SessionRepository.java b/src/main/java/nextstep/courses/domain/SessionRepository.java index f1d74ae1d..d84e70ff8 100644 --- a/src/main/java/nextstep/courses/domain/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/SessionRepository.java @@ -2,6 +2,6 @@ public interface SessionRepository { int save(Session session, Long courseId); - + void saveImage(int sessionId, Image image); Session findById(Long id); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index e04fb39b0..6b854afc7 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -5,6 +5,8 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public class JdbcSessionRepository implements SessionRepository { @@ -16,30 +18,57 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public int save(Session session, Long courseId) { - String sql = "insert into session " + + String sql = "INSERT INTO session " + "(title, start_date, end_date, tuition, current_count, capacity, " + - "image_file_size, image_file_type, image_url, image_width, image_height, " + "status, recruitment_status, course_id) " + - "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - return jdbcTemplate.update(sql, - session.getTitle(), - session.getStartDate(), - session.getEndDate(), - session.getTuition(), - session.getCurrentCount(), - session.getCapacity(), - session.getCoverImage().getSize(), - session.getCoverImage().getType(), - session.getCoverImage().getImageUrl(), - session.getCoverImage().getWidth(), - session.getCoverImage().getHeight(), - session.getSessionStatus().name(), - session.getRecruitmentStatus().name(), - courseId + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + var keyHolder = new org.springframework.jdbc.support.GeneratedKeyHolder(); + + jdbcTemplate.update(connection -> { + var ps = connection.prepareStatement(sql, new String[]{"id"}); + ps.setString(1, session.getTitle()); + ps.setTimestamp(2, java.sql.Timestamp.valueOf(session.getStartDate())); + ps.setTimestamp(3, java.sql.Timestamp.valueOf(session.getEndDate())); + ps.setLong(4, session.getTuition()); + ps.setInt(5, session.getCurrentCount()); + ps.setInt(6, session.getCapacity()); + ps.setString(7, session.getSessionStatus().name()); + ps.setString(8, session.getRecruitmentStatus().name()); + ps.setLong(9, courseId); + return ps; + }, keyHolder); + + var generatedId = keyHolder.getKey(); + if (generatedId == null) { + throw new IllegalStateException("Session ์ €์žฅ ์‹คํŒจ - id ์ƒ์„ฑ ์‹คํŒจ"); + } + + int sessionId = generatedId.intValue(); + + // ์ด๋ฏธ์ง€ ์ €์žฅ + for (Image image : session.getCoverImages().getImages()) { + saveImage(sessionId, image); + } + + return sessionId; + } + + + @Override + public void saveImage(int sessionId, Image image){ + String sql = "INSERT INTO image (session_id, url, file_type, file_size, width, height) VALUES (?, ?, ?, ?, ?, ?)"; + jdbcTemplate.update(sql, + sessionId, + image.getImageUrl(), + image.getType(), + image.getSize(), + image.getWidth(), + image.getHeight() ); } + @Override public Session findById(Long id) { String sql = "select * from session where id = ?"; @@ -48,23 +77,34 @@ public Session findById(Long id) { } private RowMapper sessionRowMapper() { - return (rs, rowNum) -> new Session( - rs.getString("title"), - rs.getInt("id"), - rs.getTimestamp("start_date").toLocalDateTime(), - rs.getTimestamp("end_date").toLocalDateTime(), - rs.getLong("tuition"), - rs.getInt("current_count"), - rs.getInt("capacity"), - new Image( - rs.getFloat("image_file_size"), - rs.getString("image_file_type"), - rs.getString("image_url"), - rs.getInt("image_width"), - rs.getInt("image_height") - ), - SessionStatus.valueOf(rs.getString("status")), - RecruitmentStatus.valueOf(rs.getString("recruitment_status")) - ); + return (rs, rowNum) -> { + int sessionId = rs.getInt("id"); + + List images = jdbcTemplate.query( + "SELECT * FROM image WHERE session_id = ?", + (irs, irow) -> new Image( + irs.getLong("id"), + irs.getFloat("file_size"), + irs.getString("file_type"), + irs.getString("url"), + irs.getInt("width"), + irs.getInt("height") + ), + sessionId + ); + + return new Session( + rs.getString("title"), + sessionId, + rs.getTimestamp("start_date").toLocalDateTime(), + rs.getTimestamp("end_date").toLocalDateTime(), + rs.getLong("tuition"), + rs.getInt("current_count"), + rs.getInt("capacity"), + new Images(images), + SessionStatus.valueOf(rs.getString("status")), + RecruitmentStatus.valueOf(rs.getString("recruitment_status")) + ); + }; } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index a78c7b01d..d54a6733b 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -11,29 +11,35 @@ create table course CREATE TABLE session ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(255) NOT NULL, - start_date DATETIME NOT NULL, - end_date DATETIME NOT NULL, - tuition BIGINT NOT NULL, - current_count INT NOT NULL, - capacity INT NOT NULL, + id BIGINT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + start_date DATETIME NOT NULL, + end_date DATETIME NOT NULL, + tuition BIGINT NOT NULL, + current_count INT NOT NULL, + capacity INT NOT NULL, - -- Embedded Image - image_file_size FLOAT, - image_file_type VARCHAR(50), - image_url VARCHAR(500), - image_width INT, - image_height INT, - - status VARCHAR(50) NOT NULL, - recruitment_status VARCHAR(50) NOT NULL, + status VARCHAR(50) NOT NULL, + recruitment_status VARCHAR(50) NOT NULL, -- FK to course - course_id BIGINT NOT NULL, + course_id BIGINT NOT NULL, FOREIGN KEY (course_id) REFERENCES course (id) ); +CREATE TABLE image +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + session_id BIGINT NOT NULL, + url VARCHAR(500) NOT NULL, + file_type VARCHAR(50), + file_size FLOAT, + width INT, + height INT, + FOREIGN KEY (session_id) REFERENCES session (id) +); + + create table ns_user ( id bigint generated by default as identity, diff --git a/src/test/java/nextstep/courses/domain/ImagesTest.java b/src/test/java/nextstep/courses/domain/ImagesTest.java new file mode 100644 index 000000000..f06660988 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/ImagesTest.java @@ -0,0 +1,44 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class ImagesTest { + + @Test + void ์ƒ์„ฑ_๋ฐ_์กฐํšŒ() { + Image image1 = new Image(100, "jpg", "url1", 300, 200); + Image image2 = new Image(100, "png", "url2", 300, 200); + + Images images = new Images(List.of(image1, image2)); + + assertThat(images.getImages()).hasSize(2) + .containsExactly(image1, image2); + } + + @Test + void ์ƒ์„ฑ์ž์—์„œ_๋ณต์‚ฌ๋œ_๋ฆฌ์ŠคํŠธ๋Š”_์™ธ๋ถ€_๋ฆฌ์ŠคํŠธ_๋ณ€๊ฒฝ_์˜ํ–ฅ์„_๋ฐ›์ง€_์•Š๋Š”๋‹ค() { + List original = new ArrayList<>(); + original.add(new Image(100, "jpg", "url1", 300, 200)); + + Images images = new Images(original); + + original.add(new Image(100, "png", "url2", 300, 200)); + + assertThat(images.getImages()).hasSize(1); + } + + @Test + void getImages๋กœ_๊ฐ€์ ธ์˜จ_๋ฆฌ์ŠคํŠธ๋Š”_์ˆ˜์ •ํ• _์ˆ˜_์—†๋‹ค() { + Images images = new Images(List.of(new Image(100, "jpg", "url1", 300, 200))); + + List retrieved = images.getImages(); + + assertThatThrownBy(() -> retrieved.add(new Image(100, "png", "url2", 300, 200))) + .isInstanceOf(UnsupportedOperationException.class); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index eba2a0e63..ad5c8020c 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -5,12 +5,13 @@ import org.junit.jupiter.api.Test; import java.time.LocalDateTime; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; class SessionTest { - private final Image validImage = new Image(500f, "png", "cdn.com", 600, 400); + private final Images validImages = new Images(List.of(validImage)); @Test @DisplayName("๋ชจ์ง‘์ค‘ ์ƒํƒœ์˜ ๋ฌด๋ฃŒ ๊ฐ•์˜๋Š” ์ˆ˜๊ฐ• ์‹ ์ฒญ ๊ฐ€๋Šฅํ•˜๋‹ค") @@ -23,7 +24,7 @@ void freeSession_joinable_whenRecruiting() { 0L, // tuition 0, // currentCount 0, // capacity (๋ฌด์ œํ•œ์ด์ง€๋งŒ ๊ทธ๋ƒฅ 0์œผ๋กœ ๋‘ ) - validImage, + validImages, SessionStatus.ONGOING, RecruitmentStatus.RECRUITING, new FreeJoinStrategy() @@ -43,7 +44,7 @@ void freeSession_notJoinable_whenNotRecruiting() { 0L, 0, 0, - validImage, + validImages, SessionStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeJoinStrategy() @@ -63,7 +64,7 @@ void paidSession_joinable_whenRecruiting_underCapacity_andPaidCorrectly() { 10000L, // tuition 29, // currentCount 30, // capacity - validImage, + validImages, SessionStatus.ONGOING, RecruitmentStatus.RECRUITING, new PaidJoinStrategy() @@ -83,7 +84,7 @@ void paidSession_notJoinable_whenWrongAmount() { 10000L, 10, 30, - validImage, + validImages, SessionStatus.ONGOING, RecruitmentStatus.RECRUITING, new PaidJoinStrategy() @@ -103,7 +104,7 @@ void paidSession_notJoinable_whenOverCapacity() { 10000L, 30, 30, - validImage, + validImages, SessionStatus.ONGOING, RecruitmentStatus.RECRUITING, new PaidJoinStrategy() @@ -123,7 +124,7 @@ void notJoinable_whenLecturePreparing_evenIfRecruiting() { 0L, 0, 0, - validImage, + validImages, SessionStatus.PREPARING, RecruitmentStatus.RECRUITING, new FreeJoinStrategy() @@ -143,7 +144,7 @@ void notJoinable_whenLectureClosed_evenIfRecruiting() { 0L, 0, 0, - validImage, + validImages, SessionStatus.CLOSED, RecruitmentStatus.RECRUITING, new FreeJoinStrategy() @@ -163,7 +164,7 @@ void notJoinable_whenNotRecruiting() { 0L, 0, 0, - validImage, + validImages, SessionStatus.ONGOING, RecruitmentStatus.NOT_RECRUITING, new FreeJoinStrategy() diff --git a/src/test/java/nextstep/courses/domain/SessionsTest.java b/src/test/java/nextstep/courses/domain/SessionsTest.java index 4a9ff3a25..327f4ebfc 100644 --- a/src/test/java/nextstep/courses/domain/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/SessionsTest.java @@ -9,8 +9,8 @@ import static org.assertj.core.api.Assertions.*; class SessionsTest { - private final Image image = new Image(500f, "png", "cdn.com", 600, 400); + private final Images images = new Images(List.of(image)); private Session createSessionWithId(int id) { return new Session( @@ -21,7 +21,7 @@ private Session createSessionWithId(int id) { 0L, 0, 0, - image, + images, SessionStatus.ONGOING, RecruitmentStatus.RECRUITING, new FreeJoinStrategy() diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index a2219b00e..ad4442989 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -10,6 +10,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import java.time.LocalDateTime; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -31,30 +32,39 @@ void setUp() { void crud() { // given Image image = new Image(100.0f, "png", "https://example.com/image.png", 300, 200); + Images images = new Images(List.of(image)); Session session = new Session( "๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„", - 1, + 0, // ์ €์žฅ ์ „์— id๋Š” ๋ฌด์˜๋ฏธํ•จ LocalDateTime.now(), LocalDateTime.now().plusDays(30), 10000L, 0, 20, - image, + images, SessionStatus.ONGOING, RecruitmentStatus.RECRUITING ); // when - int count = sessionRepository.save(session, 1L); // courseId = 1L - assertThat(count).isEqualTo(1); - - Session saved = sessionRepository.findById(1L); + int savedSessionId = sessionRepository.save(session, 1L); // courseId = 1L + Session saved = sessionRepository.findById((long) savedSessionId); // then assertThat(saved.getTitle()).isEqualTo(session.getTitle()); assertThat(saved.getTuition()).isEqualTo(session.getTuition()); assertThat(saved.getCapacity()).isEqualTo(session.getCapacity()); - assertThat(saved.getCoverImage().getImageUrl()).isEqualTo(session.getCoverImage().getImageUrl()); + + // ์ด๋ฏธ์ง€ ๊ฒ€์ฆ + List savedImages = saved.getCoverImages().getImages(); + assertThat(savedImages).hasSize(1); + + Image savedImage = savedImages.get(0); + assertThat(savedImage.getImageUrl()).isEqualTo(image.getImageUrl()); + assertThat(savedImage.getSize()).isEqualTo(image.getSize()); + assertThat(savedImage.getType()).isEqualTo(image.getType()); + assertThat(savedImage.getWidth()).isEqualTo(image.getWidth()); + assertThat(savedImage.getHeight()).isEqualTo(image.getHeight()); LOGGER.debug("Session: {}", saved); } From e1db975f09c3aa48e2b90c71b1f45815b88873da Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Tue, 22 Apr 2025 13:22:38 +0900 Subject: [PATCH 4/9] feat: add domain model `Enrollment` --- .../nextstep/courses/domain/Enrollment.java | 39 ++++++++++ .../courses/domain/EnrollmentStatus.java | 5 ++ .../courses/domain/EnrollmentTest.java | 77 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/main/java/nextstep/courses/domain/Enrollment.java create mode 100644 src/main/java/nextstep/courses/domain/EnrollmentStatus.java create mode 100644 src/test/java/nextstep/courses/domain/EnrollmentTest.java diff --git a/src/main/java/nextstep/courses/domain/Enrollment.java b/src/main/java/nextstep/courses/domain/Enrollment.java new file mode 100644 index 000000000..a9cc05f56 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Enrollment.java @@ -0,0 +1,39 @@ +package nextstep.courses.domain; + +public class Enrollment { + private Member student; + private Session session; + private EnrollmentStatus status; // PENDING, APPROVED, REJECTED + + public Enrollment(Member student, Session session) { + this.student = student; + this.session = session; + this.status = EnrollmentStatus.PENDING; + } + + public void approve() { + if (this.status == EnrollmentStatus.APPROVED) { + throw new IllegalStateException("์ด๋ฏธ ์Šน์ธ๋œ ์ˆ˜๊ฐ• ์‹ ์ฒญ์ž…๋‹ˆ๋‹ค."); + } + + this.status = EnrollmentStatus.APPROVED; + session.accept(); + } + + public void reject() { + this.status = EnrollmentStatus.REJECTED; + } + + public boolean isApproved() { + return status == EnrollmentStatus.APPROVED; + } + + public Member getStudent() { + return student; + } + + public EnrollmentStatus getStatus() { + return status; + } +} + diff --git a/src/main/java/nextstep/courses/domain/EnrollmentStatus.java b/src/main/java/nextstep/courses/domain/EnrollmentStatus.java new file mode 100644 index 000000000..9e04b16b2 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/EnrollmentStatus.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain; + +public enum EnrollmentStatus { + PENDING, APPROVED, REJECTED; +} diff --git a/src/test/java/nextstep/courses/domain/EnrollmentTest.java b/src/test/java/nextstep/courses/domain/EnrollmentTest.java new file mode 100644 index 000000000..44ffc03df --- /dev/null +++ b/src/test/java/nextstep/courses/domain/EnrollmentTest.java @@ -0,0 +1,77 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class EnrollmentTest { + + private final Member member = new Member(1L, "ํ™๊ธธ๋™", "hong@example.com"); + private final Image validImage = new Image(500f, "png", "cdn.com", 600, 400); + private final Images validImages = new Images(List.of(validImage)); + + private Session createDummySession() { + return new Session( + "๋”๋ฏธ ๊ฐ•์˜", + 1, + LocalDateTime.now(), + LocalDateTime.now().plusDays(7), + 0L, + 0, + 10, + validImages, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + } + + @Test + @DisplayName("Enrollment๋Š” ์ƒ์„ฑ ์‹œ PENDING ์ƒํƒœ์ด๋‹ค") + void created_enrollment_has_pending_status() { + Session session = createDummySession(); + Enrollment enrollment = new Enrollment(member, session); + + assertThat(enrollment.getStatus()).isEqualTo(EnrollmentStatus.PENDING); + } + + @Test + @DisplayName("Enrollment๋ฅผ ์Šน์ธํ•˜๋ฉด APPROVED ์ƒํƒœ๊ฐ€ ๋œ๋‹ค") + void approve_sets_status_to_approved() { + Session session = createDummySession(); + Enrollment enrollment = new Enrollment(member, session); + + enrollment.approve(); + + assertThat(enrollment.getStatus()).isEqualTo(EnrollmentStatus.APPROVED); + } + + @Test + @DisplayName("Enrollment๋ฅผ ๊ฑฐ์ ˆํ•˜๋ฉด REJECTED ์ƒํƒœ๊ฐ€ ๋œ๋‹ค") + void reject_sets_status_to_rejected() { + Session session = createDummySession(); + Enrollment enrollment = new Enrollment(member, session); + + enrollment.reject(); + + assertThat(enrollment.getStatus()).isEqualTo(EnrollmentStatus.REJECTED); + } + + @Test + @DisplayName("์ด๋ฏธ ์Šน์ธ๋œ Enrollment๋Š” ๋‹ค์‹œ ์Šน์ธํ•  ์ˆ˜ ์—†๋‹ค") + void approving_twice_should_throw() { + Session session = createDummySession(); + Enrollment enrollment = new Enrollment(member, session); + + enrollment.approve(); + + assertThatThrownBy(enrollment::approve) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("์ด๋ฏธ ์Šน์ธ๋œ ์ˆ˜๊ฐ• ์‹ ์ฒญ์ž…๋‹ˆ๋‹ค."); + } +} From 1ce727a2bd461f64ac6ae89a374c45c6aa2fba66 Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Tue, 22 Apr 2025 13:24:18 +0900 Subject: [PATCH 5/9] feat: add domain model `Enrollments` --- .../nextstep/courses/domain/Enrollments.java | 29 +++++ .../courses/domain/EnrollmentsTest.java | 100 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/main/java/nextstep/courses/domain/Enrollments.java create mode 100644 src/test/java/nextstep/courses/domain/EnrollmentsTest.java diff --git a/src/main/java/nextstep/courses/domain/Enrollments.java b/src/main/java/nextstep/courses/domain/Enrollments.java new file mode 100644 index 000000000..a0a320137 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Enrollments.java @@ -0,0 +1,29 @@ +package nextstep.courses.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Enrollments { + private final List values = new ArrayList<>(); + + public void addEnrollment(Enrollment enrollment) { + values.add(enrollment); + } + + public boolean isEnrolledBy(Member student) { + return values.stream() + .anyMatch(e -> e.getStudent().equals(student)); + } + + public Enrollment findByMember(Member student) { + return values.stream() + .filter(e -> e.getStudent().equals(student)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("ํ•ด๋‹น ํšŒ์›์˜ ์ˆ˜๊ฐ• ์‹ ์ฒญ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")); + } + + public List getValues() { + return Collections.unmodifiableList(values); + } +} diff --git a/src/test/java/nextstep/courses/domain/EnrollmentsTest.java b/src/test/java/nextstep/courses/domain/EnrollmentsTest.java new file mode 100644 index 000000000..f460731d1 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/EnrollmentsTest.java @@ -0,0 +1,100 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class EnrollmentsTest { + + private final Member member1 = new Member(1L, "ํ™๊ธธ๋™", "hong@example.com"); + private final Member member2 = new Member(2L, "์ด๋ชฝ๋ฃก", "lee@example.com"); + + private final Image validImage = new Image(500f, "png", "cdn.com", 600, 400); + private final Images validImages = new Images(List.of(validImage)); + + private Session createSession() { + return new Session( + "๊ฐ•์˜", + 1, + LocalDateTime.now(), + LocalDateTime.now().plusDays(7), + 0L, + 0, + 10, + validImages, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + } + + @Test + @DisplayName("Enrollment๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฆฌ์ŠคํŠธ์— ํฌํ•จ๋œ๋‹ค") + void addEnrollment_stores_enrollment() { + Enrollments enrollments = new Enrollments(); + Session session = createSession(); + Enrollment enrollment = new Enrollment(member1, session); + + enrollments.addEnrollment(enrollment); + + assertThat(enrollments.getValues()).contains(enrollment); + } + + @Test + @DisplayName("ํ•ด๋‹น ๋ฉค๋ฒ„๊ฐ€ ์ˆ˜๊ฐ• ์‹ ์ฒญํ•œ ๊ฒฝ์šฐ isEnrolledBy()๋Š” true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค") + void isEnrolledBy_returns_true_for_existing_member() { + Enrollments enrollments = new Enrollments(); + Session session = createSession(); + enrollments.addEnrollment(new Enrollment(member1, session)); + + assertThat(enrollments.isEnrolledBy(member1)).isTrue(); + } + + @Test + @DisplayName("ํ•ด๋‹น ๋ฉค๋ฒ„๊ฐ€ ์ˆ˜๊ฐ• ์‹ ์ฒญํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ isEnrolledBy()๋Š” false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค") + void isEnrolledBy_returns_false_for_non_enrolled_member() { + Enrollments enrollments = new Enrollments(); + assertThat(enrollments.isEnrolledBy(member1)).isFalse(); + } + + @Test + @DisplayName("findByMember()๋Š” ์ˆ˜๊ฐ• ์‹ ์ฒญํ•œ ๋ฉค๋ฒ„์˜ Enrollment๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค") + void findByMember_returns_enrollment() { + Enrollments enrollments = new Enrollments(); + Session session = createSession(); + Enrollment enrollment = new Enrollment(member1, session); + enrollments.addEnrollment(enrollment); + + Enrollment found = enrollments.findByMember(member1); + + assertThat(found).isEqualTo(enrollment); + } + + @Test + @DisplayName("findByMember()๋Š” ์ˆ˜๊ฐ• ์‹ ์ฒญํ•˜์ง€ ์•Š์€ ๋ฉค๋ฒ„์ผ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค") + void findByMember_throws_for_non_existing_member() { + Enrollments enrollments = new Enrollments(); + + assertThatThrownBy(() -> enrollments.findByMember(member2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("ํ•ด๋‹น ํšŒ์›์˜ ์ˆ˜๊ฐ• ์‹ ์ฒญ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); + } + + @Test + @DisplayName("getValues()๋Š” ๋ถˆ๋ณ€ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค") + void getValues_returns_unmodifiable_list() { + Enrollments enrollments = new Enrollments(); + Session session = createSession(); + enrollments.addEnrollment(new Enrollment(member1, session)); + + List values = enrollments.getValues(); + + assertThatThrownBy(() -> values.add(new Enrollment(member2, session))) + .isInstanceOf(UnsupportedOperationException.class); + } +} From 06ea9e5ef11e61470deecce2a9a5bcf706c5d657 Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Tue, 22 Apr 2025 13:25:43 +0900 Subject: [PATCH 6/9] feat: add domain model `Member` --- .../java/nextstep/courses/domain/Member.java | 41 +++++++++++++++++ .../nextstep/courses/domain/MemberTest.java | 46 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/main/java/nextstep/courses/domain/Member.java create mode 100644 src/test/java/nextstep/courses/domain/MemberTest.java diff --git a/src/main/java/nextstep/courses/domain/Member.java b/src/main/java/nextstep/courses/domain/Member.java new file mode 100644 index 000000000..03a131dfd --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Member.java @@ -0,0 +1,41 @@ +package nextstep.courses.domain; + +import java.util.Objects; + +public class Member { + private final Long id; + private final String name; + private final String email; + + public Member(Long id, String name, String email) { + this.id = id; + this.name = name; + this.email = email; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + // Member ์‹๋ณ„ ๋น„๊ต (์˜ˆ: Set ์‚ฌ์šฉ ์‹œ ํ•„์š”) + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Member)) return false; + Member member = (Member) o; + return Objects.equals(id, member.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/MemberTest.java b/src/test/java/nextstep/courses/domain/MemberTest.java new file mode 100644 index 000000000..dc05f66b9 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/MemberTest.java @@ -0,0 +1,46 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MemberTest { + + @Test + @DisplayName("Member๋Š” ์ƒ์„ฑ ์‹œ id, name, email ๊ฐ’์„ ๊ฐ€์ง„๋‹ค") + void member_creation_and_getters() { + Member member = new Member(1L, "ํ™๊ธธ๋™", "hong@example.com"); + + assertThat(member.getId()).isEqualTo(1L); + assertThat(member.getName()).isEqualTo("ํ™๊ธธ๋™"); + assertThat(member.getEmail()).isEqualTo("hong@example.com"); + } + + @Test + @DisplayName("id๊ฐ€ ๊ฐ™์€ ๋‘ Member๋Š” equals๋กœ ๊ฐ™๋‹ค๊ณ  ํŒ๋‹จํ•œ๋‹ค") + void members_with_same_id_are_equal() { + Member member1 = new Member(1L, "ํ™๊ธธ๋™", "hong@example.com"); + Member member2 = new Member(1L, "์ด๋ฆ„๋ฌด๊ด€", "๋‹ค๋ฅธ์ด๋ฉ”์ผ@example.com"); + + assertThat(member1).isEqualTo(member2); + } + + @Test + @DisplayName("id๊ฐ€ ๋‹ค๋ฅธ ๋‘ Member๋Š” equals๋กœ ๋‹ค๋ฅด๋‹ค๊ณ  ํŒ๋‹จํ•œ๋‹ค") + void members_with_different_id_are_not_equal() { + Member member1 = new Member(1L, "ํ™๊ธธ๋™", "hong@example.com"); + Member member2 = new Member(2L, "ํ™๊ธธ๋™", "hong@example.com"); + + assertThat(member1).isNotEqualTo(member2); + } + + @Test + @DisplayName("id๊ฐ€ ๊ฐ™์œผ๋ฉด hashCode๋„ ๋™์ผํ•˜๋‹ค") + void members_with_same_id_have_same_hashcode() { + Member member1 = new Member(1L, "ํ™๊ธธ๋™", "hong@example.com"); + Member member2 = new Member(1L, "๋‹ค๋ฅธ์ด๋ฆ„", "๋‹ค๋ฅธ์ด๋ฉ”์ผ@example.com"); + + assertThat(member1.hashCode()).isEqualTo(member2.hashCode()); + } +} From 5c95f8e061d3784a69f2b81e3cec37dab06890a8 Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Tue, 22 Apr 2025 13:28:35 +0900 Subject: [PATCH 7/9] feat: add enrollment to `Session` --- README.md | 3 +- .../java/nextstep/courses/domain/Session.java | 25 ++++- .../courses/service/EnrollService.java | 30 ++++-- .../nextstep/courses/domain/SessionTest.java | 96 +++++++++++++++++++ 4 files changed, 143 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3477a100e..c3a1b8e36 100644 --- a/README.md +++ b/README.md @@ -73,5 +73,4 @@ ## ๐Ÿš€ 4๋‹จ๊ณ„ - ์ˆ˜๊ฐ•์‹ ์ฒญ(์š”๊ตฌ์‚ฌํ•ญ ๋ณ€๊ฒฝ) - [x] SessionStatus ์ง„ํ–‰์ƒํƒœ์™€ ๋ชจ์ง‘์ƒํƒœ๋กœ ๋ถ„๋ฆฌ - [x] coverImage ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ธฐ๋Šฅ ์ถ”๊ฐ€ -- [] ์„ ๋ฐœ ์—ฌ๋ถ€ ํ™•์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ -- [] ์ˆ˜๊ฐ• ์Šน์ธ ๋ฐ ์ˆ˜๊ฐ• ์ทจ์†Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ \ No newline at end of file +- [x] ์ˆ˜๊ฐ• ์Šน์ธ ๋ฐ ์ˆ˜๊ฐ• ์ทจ์†Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index 84d7886de..b04fbbce9 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -14,6 +14,7 @@ public class Session { private RecruitmentStatus recruitmentStatus; private SessionStatus sessionStatus; private JoinStrategy joinStrategy; + private final Enrollments enrollments = new Enrollments(); public Session(String title, int id, LocalDateTime startDate, LocalDateTime endDate, Long tuition, int currentCount, int capacity, Images coverImages, SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus) { @@ -52,14 +53,32 @@ public boolean hasId(long id) { return this.id == id; } - public void enroll(Payment pay) { + public void enroll(Payment pay, Member member) { if (!joinable(pay)) { throw new IllegalStateException("์ˆ˜๊ฐ• ์‹ ์ฒญ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); } + if (enrollments.isEnrolledBy(member)) { + throw new IllegalStateException("์ด๋ฏธ ์ˆ˜๊ฐ• ์‹ ์ฒญํ•œ ํšŒ์›์ž…๋‹ˆ๋‹ค."); + } + + enrollments.addEnrollment(new Enrollment(member, this)); + } + + public void approveEnrollment(Member member) { + Enrollment enrollment = enrollments.findByMember(member); + enrollment.approve(); + } + + public void accept(){ this.capacityInfo.increaseCurrentCount(); } + public void rejectEnrollment(Member member) { + Enrollment enrollment = enrollments.findByMember(member); + enrollment.reject(); + } + public String getTitle() { return title; } @@ -103,4 +122,8 @@ public SessionStatus getSessionStatus() { public RecruitmentStatus getRecruitmentStatus() { return recruitmentStatus; } + + public Enrollments getEnrollments() { + return enrollments; + } } diff --git a/src/main/java/nextstep/courses/service/EnrollService.java b/src/main/java/nextstep/courses/service/EnrollService.java index 0f68163e3..cc9bc514f 100644 --- a/src/main/java/nextstep/courses/service/EnrollService.java +++ b/src/main/java/nextstep/courses/service/EnrollService.java @@ -1,9 +1,6 @@ package nextstep.courses.service; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; -import nextstep.courses.domain.Session; -import nextstep.courses.domain.Sessions; +import nextstep.courses.domain.*; import nextstep.payments.domain.Payment; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,14 +9,31 @@ @Service("enrollService") public class EnrollService { + @Resource(name = "courseRepository") private CourseRepository courseRepository; @Transactional - public void enrollCourse(long courseId, long sessionId, Payment payment) { + public void enrollCourse(long courseId, long sessionId, Payment payment, Member member) { + Session session = findSession(courseId, sessionId); + session.enroll(payment, member); // ์กฐ๊ฑด ๋งŒ์กฑํ•ด์•ผ๋งŒ Enrollment(PENDING) ์ƒ์„ฑ + } + + @Transactional + public void approveEnrollment(long courseId, long sessionId, Member member) { + Session session = findSession(courseId, sessionId); + session.approveEnrollment(member); + } + + @Transactional + public void rejectEnrollment(long courseId, long sessionId, Member member) { + Session session = findSession(courseId, sessionId); + session.rejectEnrollment(member); + } + + private Session findSession(long courseId, long sessionId) { Course course = courseRepository.findById(courseId); - Sessions sessions = course.getSessions(); - Session session = sessions.findById(sessionId); - session.enroll(payment); + return course.getSessions().findById(sessionId); } } + diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index ad5c8020c..3af277e05 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -8,10 +8,12 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; class SessionTest { private final Image validImage = new Image(500f, "png", "cdn.com", 600, 400); private final Images validImages = new Images(List.of(validImage)); + private final Member member = new Member(1L, "ํ™๊ธธ๋™", "hong@example.com"); @Test @DisplayName("๋ชจ์ง‘์ค‘ ์ƒํƒœ์˜ ๋ฌด๋ฃŒ ๊ฐ•์˜๋Š” ์ˆ˜๊ฐ• ์‹ ์ฒญ ๊ฐ€๋Šฅํ•˜๋‹ค") @@ -173,4 +175,98 @@ void notJoinable_whenNotRecruiting() { assertThat(session.joinable(new Payment())).isFalse(); } + @Test + @DisplayName("์ˆ˜๊ฐ• ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋ฉด ์ˆ˜๊ฐ• ์‹ ์ฒญ์ด PENDING ์ƒํƒœ๋กœ ๋“ฑ๋ก๋œ๋‹ค") + void enroll_success_creates_pending_enrollment() { + Session session = new Session("๊ฐ•์˜", 1, + LocalDateTime.now(), LocalDateTime.now().plusDays(7), + 0L, 0, 10, + validImages, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + + session.enroll(new Payment(), member); + + Enrollment enrollment = session.getEnrollments().findByMember(member); + assertThat(enrollment.getStatus()).isEqualTo(EnrollmentStatus.PENDING); + } + + @Test + @DisplayName("๋™์ผํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์ค‘๋ณต ์‹ ์ฒญ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ") + void duplicate_enrollment_should_throw() { + Session session = new Session("๊ฐ•์˜", 1, + LocalDateTime.now(), LocalDateTime.now().plusDays(7), + 0L, 0, 10, + validImages, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + + session.enroll(new Payment(), member); + + assertThatThrownBy(() -> session.enroll(new Payment(), member)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("์ด๋ฏธ ์ˆ˜๊ฐ• ์‹ ์ฒญํ•œ ํšŒ์›์ž…๋‹ˆ๋‹ค."); + } + + @Test + @DisplayName("๊ฐ•์‚ฌ๊ฐ€ ์ˆ˜๊ฐ• ์‹ ์ฒญ์„ ์Šน์ธํ•˜๋ฉด APPROVED ์ƒํƒœ๊ฐ€ ๋˜๊ณ  ์ •์›์ด ์ฆ๊ฐ€ํ•œ๋‹ค") + void approve_enrollment_increases_capacity() { + Session session = new Session("๊ฐ•์˜", 1, + LocalDateTime.now(), LocalDateTime.now().plusDays(7), + 0L, 0, 10, + validImages, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + + session.enroll(new Payment(), member); + session.approveEnrollment(member); + + Enrollment enrollment = session.getEnrollments().findByMember(member); + assertThat(enrollment.getStatus()).isEqualTo(EnrollmentStatus.APPROVED); + assertThat(session.getCurrentCount()).isEqualTo(1); + } + + @Test + @DisplayName("๊ฐ•์‚ฌ๊ฐ€ ์ˆ˜๊ฐ• ์‹ ์ฒญ์„ ๊ฑฐ์ ˆํ•˜๋ฉด REJECTED ์ƒํƒœ๊ฐ€ ๋œ๋‹ค") + void reject_enrollment_sets_rejected_status() { + Session session = new Session("๊ฐ•์˜", 1, + LocalDateTime.now(), LocalDateTime.now().plusDays(7), + 0L, 0, 10, + validImages, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + + session.enroll(new Payment(), member); + session.rejectEnrollment(member); + + Enrollment enrollment = session.getEnrollments().findByMember(member); + assertThat(enrollment.getStatus()).isEqualTo(EnrollmentStatus.REJECTED); + } + + @Test + @DisplayName("์Šน์ธํ•˜์ง€ ์•Š์œผ๋ฉด ์ˆ˜๊ฐ• ์‹ ์ฒญ์€ APPROVED ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋‹ค") + void pending_enrollment_not_approved_by_default() { + Session session = new Session("๊ฐ•์˜", 1, + LocalDateTime.now(), LocalDateTime.now().plusDays(7), + 0L, 0, 10, + validImages, + SessionStatus.ONGOING, + RecruitmentStatus.RECRUITING, + new FreeJoinStrategy() + ); + + session.enroll(new Payment(), member); + + Enrollment enrollment = session.getEnrollments().findByMember(member); + assertThat(enrollment.isApproved()).isFalse(); + } + } From 987d58b4429a6f7a3c3b8188ad606c7bcec7cc2e Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Tue, 22 Apr 2025 13:28:55 +0900 Subject: [PATCH 8/9] db: add Member, Enrollment table --- src/main/resources/schema.sql | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index d54a6733b..ca3e49c42 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -27,6 +27,28 @@ CREATE TABLE session FOREIGN KEY (course_id) REFERENCES course (id) ); +CREATE TABLE member +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE +); + +CREATE TABLE enrollment +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + session_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + status VARCHAR(50) NOT NULL, -- PENDING, APPROVED, REJECTED + + -- FK ์„ค์ • + FOREIGN KEY (session_id) REFERENCES session (id), + FOREIGN KEY (member_id) REFERENCES member (id), + + UNIQUE (session_id, member_id) -- ์ค‘๋ณต ์ˆ˜๊ฐ• ์‹ ์ฒญ ๋ฐฉ์ง€ +); + + CREATE TABLE image ( id BIGINT AUTO_INCREMENT PRIMARY KEY, From 504f5a5eda30c89e61cb90095c80f778ca2b05ea Mon Sep 17 00:00:00 2001 From: "kass.fresh" Date: Tue, 22 Apr 2025 13:49:54 +0900 Subject: [PATCH 9/9] feat: implement `JdbcEnrollmentRepository` --- .../nextstep/courses/domain/Enrollment.java | 4 + .../JdbcEnrollmentRepository.java | 60 ++++++++++++++ .../EnrollmentRepositoryTest.java | 81 +++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java create mode 100644 src/test/java/nextstep/courses/infrastructure/EnrollmentRepositoryTest.java diff --git a/src/main/java/nextstep/courses/domain/Enrollment.java b/src/main/java/nextstep/courses/domain/Enrollment.java index a9cc05f56..599fcb251 100644 --- a/src/main/java/nextstep/courses/domain/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/Enrollment.java @@ -35,5 +35,9 @@ public Member getStudent() { public EnrollmentStatus getStatus() { return status; } + + public Session getSession() { + return session; + } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java new file mode 100644 index 000000000..524c22739 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java @@ -0,0 +1,60 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.Enrollment; +import nextstep.courses.domain.EnrollmentStatus; +import nextstep.courses.domain.Member; +import nextstep.courses.domain.Session; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class JdbcEnrollmentRepository { + + private final JdbcOperations jdbc; + private final JdbcSessionRepository jdbcSessionRepository; + + public JdbcEnrollmentRepository(JdbcOperations jdbc, JdbcSessionRepository jdbcSessionRepository) { + this.jdbc = jdbc; + this.jdbcSessionRepository = jdbcSessionRepository; + } + + public void save(Long sessionId, Long memberId, EnrollmentStatus status) { + String sql = "INSERT INTO enrollment (session_id, member_id, status) VALUES (?, ?, ?)"; + jdbc.update(sql, sessionId, memberId, status.name()); + } + + public void updateStatus(Long sessionId, Long memberId, EnrollmentStatus status) { + String sql = "UPDATE enrollment SET status = ? WHERE session_id = ? AND member_id = ?"; + jdbc.update(sql, status.name(), sessionId, memberId); + } + + public List findBySessionId(Long sessionId) { + String sql = + "SELECT e.*, m.id as member_id, m.name, m.email " + + "FROM enrollment e " + + "JOIN member m ON e.member_id = m.id " + + "WHERE e.session_id = ?"; + + + return jdbc.query(sql, (rs, rowNum) -> { + Member member = new Member( + rs.getLong("member_id"), + rs.getString("name"), + rs.getString("email") + ); + + Session session = jdbcSessionRepository.findById(sessionId); + Enrollment enrollment = new Enrollment(member, session); + EnrollmentStatus status = EnrollmentStatus.valueOf(rs.getString("status")); + if (status == EnrollmentStatus.APPROVED) { + enrollment.approve(); + } else if (status == EnrollmentStatus.REJECTED) { + enrollment.reject(); + } + + return enrollment; + }, sessionId); + } +} diff --git a/src/test/java/nextstep/courses/infrastructure/EnrollmentRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/EnrollmentRepositoryTest.java new file mode 100644 index 000000000..457d02761 --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/EnrollmentRepositoryTest.java @@ -0,0 +1,81 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +class JdbcEnrollmentRepositoryTest { + + @Autowired + private JdbcTemplate jdbc; + + private JdbcSessionRepository sessionRepository; + private JdbcEnrollmentRepository enrollmentRepository; + + @BeforeEach + void setUp() { + sessionRepository = new JdbcSessionRepository(jdbc); + enrollmentRepository = new JdbcEnrollmentRepository(jdbc, sessionRepository); + + // ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… + jdbc.update("INSERT INTO member (id, name, email) VALUES (?, ?, ?)", + 1L, "ํ™๊ธธ๋™", "hong@example.com"); + + jdbc.update("INSERT INTO session (id, title, start_date, end_date, tuition, current_count, capacity, status, recruitment_status, course_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + 1L, "๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„", + LocalDateTime.now(), LocalDateTime.now().plusDays(30), + 10000L, 0, 20, + "ONGOING", "RECRUITING", 1L + ); + + // image insert ์—†์ด๋„ session์€ ์ƒ์„ฑ๋จ + } + + @Test + @DisplayName("Enrollment ์ €์žฅ ํ›„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค (Session ํฌํ•จ)") + void save_and_find_with_session() { + // when + enrollmentRepository.save(1L, 1L, EnrollmentStatus.PENDING); + + // then + List result = enrollmentRepository.findBySessionId(1L); + assertThat(result).hasSize(1); + + Enrollment enrollment = result.get(0); + assertThat(enrollment.getStudent().getId()).isEqualTo(1L); + assertThat(enrollment.getStatus()).isEqualTo(EnrollmentStatus.PENDING); + + // โœ… Session๊นŒ์ง€ ์ œ๋Œ€๋กœ ์—ฐ๊ฒฐ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ + Session session = enrollment.getSession(); + assertThat(session).isNotNull(); + assertThat(session.getTitle()).isEqualTo("๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„"); + assertThat(session.getTuition()).isEqualTo(10000L); + } + + @Test + @DisplayName("Enrollment ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค") + void update_status() { + // given + enrollmentRepository.save(1L, 1L, EnrollmentStatus.PENDING); + + // when + enrollmentRepository.updateStatus(1L, 1L, EnrollmentStatus.APPROVED); + + // then + List result = enrollmentRepository.findBySessionId(1L); + Enrollment enrollment = result.get(0); + + assertThat(enrollment.getStatus()).isEqualTo(EnrollmentStatus.APPROVED); + } +}