From a9323539a9a4aa4a9c97453e473fdc0159294e31 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sat, 25 Nov 2023 16:50:06 +0900 Subject: [PATCH 01/62] =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=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 | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4985061663..5cc0d8f342 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ -# 학습 관리 시스템(Learning Management System) -## 진행 방법 -* 학습 관리 시스템의 수강신청 요구사항을 파악한다. -* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다. -* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다. -* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다. +## 학습 관리 시스템(Learning Management System) -## 온라인 코드 리뷰 과정 -* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview) \ No newline at end of file +### 질문 삭제하기 요구사항 +- 질문 데이터를 완전히 삭제하는 것이 아니라 데이터의 상태를 삭제 상태(deleted - boolean type)로 변경한다. +- 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능하다. +- 답변이 없는 경우 삭제가 가능하다. +- 질문자와 답변글의 모든답변자가 같은경우 삭제가가능하다. +- 질문을 삭제할 때 답변 또한 삭제해야 하며, 답변의 삭제 또한 삭제 상태(deleted)를 변경 +한다. +- 질문자와 답변자가 다른경우 답변을 삭제할 수 없다. +- 질문과 답변 삭제 이력에 대한 정보를 DeleteHistory를 활용해 남긴다. + +### 리팩터링 요구사항 +- nextstep.qna.service.QnaService의 deleteQuestion()는 앞의 질문 삭제 기능을 구현한 코드이다. 이 메소드는 단위 테스트하기 어려운 코드와 단위 테스트 가능한 코드가 섞여 있다. +- QnaService의 deleteQuestion() 메서드에 단위 테스트 가능한 코드(핵심 비지니스 로직)를 도메인 모델 객체에 구현한다. +- QnaService의 비지니스 로직을 도메인 모델로 이동하는 리팩터링을 진행할 때 TDD로 구현한다. +- QnaService의 deleteQuestion() 메서드에 대한 단위 테스트는 src/test/java 폴더 nextstep.qna.service.QnaServiceTest이다. 도메인 모델로 로직을 이동한 후에도 QnaServiceTest의 모든 테스트는 통과해야 한다. \ No newline at end of file From e69ca97c68575f5af9dded80187dd8a9d1e775f1 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 28 Nov 2023 22:53:51 +0900 Subject: [PATCH 02/62] =?UTF-8?q?[refactor]=20=EC=A7=88=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=EB=A5=BC=20Question=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99=ED=95=98?= =?UTF-8?q?=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 질문, 답변 삭제 상태 변경을 Service Layer에서 했습니다. 도메인에 책임을 질 수 있도록 Question 도메인으로 이동합니다 --- .../java/nextstep/qna/domain/Question.java | 22 ++++++++++++ .../java/nextstep/qna/service/QnAService.java | 11 ++---- .../java/nextstep/qna/domain/AnswerTest.java | 2 ++ .../nextstep/qna/domain/QuestionTest.java | 36 +++++++++++++++++++ 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index b623c52c76..fa3189afd6 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -1,5 +1,6 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.users.domain.NsUser; import java.time.LocalDateTime; @@ -68,6 +69,10 @@ public void addAnswer(Answer answer) { answers.add(answer); } + public void setAnswers(List answers) { + this.answers = answers; + } + public boolean isOwner(NsUser loginUser) { return writer.equals(loginUser); } @@ -89,4 +94,21 @@ public List getAnswers() { public String toString() { return "Question [id=" + getId() + ", title=" + title + ", contents=" + contents + ", writer=" + writer + "]"; } + + public Question delete(NsUser loginUser) throws CannotDeleteException { + this.setDeleted(true); + + List deletedAnswers = new ArrayList<>(); + for (Answer answer : this.answers) { + if (!answer.isOwner(loginUser)) { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } + + answer.setDeleted(true); + deletedAnswers.add(answer); + } + + this.setAnswers(deletedAnswers); + return this; + } } diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 5741c84d65..6cc4e8b7cf 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -30,18 +30,11 @@ public void deleteQuestion(NsUser loginUser, long questionId) throws CannotDelet throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); } - List answers = question.getAnswers(); - for (Answer answer : answers) { - if (!answer.isOwner(loginUser)) { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - } - } + question = question.delete(loginUser); List deleteHistories = new ArrayList<>(); - question.setDeleted(true); deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); - for (Answer answer : answers) { - answer.setDeleted(true); + for (Answer answer : question.getAnswers()) { deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); } deleteHistoryService.saveAll(deleteHistories); diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index 8e80ffb429..98bc478713 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -5,4 +5,6 @@ public class AnswerTest { public static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); public static final Answer A2 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q1, "Answers Contents2"); + public static final Answer A3 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q2, "Answers Contents3"); + public static final Answer A4 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q2, "Answers Contents4"); } diff --git a/src/test/java/nextstep/qna/domain/QuestionTest.java b/src/test/java/nextstep/qna/domain/QuestionTest.java index 3b87823963..1cd45f9e81 100644 --- a/src/test/java/nextstep/qna/domain/QuestionTest.java +++ b/src/test/java/nextstep/qna/domain/QuestionTest.java @@ -1,8 +1,44 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static nextstep.qna.domain.AnswerTest.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class QuestionTest { public static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); public static final Question Q2 = new Question(NsUserTest.SANJIGI, "title2", "contents2"); + + @BeforeEach + void setUp() { + Q1.addAnswer(A1); + Q1.addAnswer(A2); + + Q2.addAnswer(A3); + Q2.addAnswer(A4); + } + + @Test + @DisplayName("질문 작성자와 답변 작성자가 같으면 데이터를 삭제 상태로 변경한다") + void delete_success() throws CannotDeleteException { + Question question = Q2.delete(NsUserTest.SANJIGI); + + assertThat(question.isDeleted()).isTrue(); + for (Answer answer : question.getAnswers()) { + assertThat(answer.isDeleted()).isTrue(); + } + } + + @Test + @DisplayName("질문 작성자와 답변 작성자가 다르면 삭제할 수 없다는 예외를 던진다") + void delete_different_questionWriter_AnswerWriter_throwsException() { + assertThatThrownBy( + () -> Q1.delete(NsUserTest.JAVAJIGI) + ).isInstanceOf(CannotDeleteException.class); + } } From 0796c549a033a25762f6678c2e558e3ff095f7f9 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 28 Nov 2023 23:02:48 +0900 Subject: [PATCH 03/62] =?UTF-8?q?[refactor]=20Question=20=EB=8B=B5?= =?UTF-8?q?=EB=B3=80=20=EC=82=AD=EC=A0=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=EC=9D=84=20Answer=EB=A1=9C=20=EC=98=AE=EA=B2=A8=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 질문 작성자와 답변 작성자가 같은지 검사하는 로직을 Answer에게 넘겨 자신의 책임을 다할 수 있도록 개선합니다 --- src/main/java/nextstep/qna/domain/Answer.java | 6 ++++- .../java/nextstep/qna/domain/Question.java | 14 ++---------- .../java/nextstep/qna/domain/AnswerTest.java | 22 +++++++++++++++++++ 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index cf681811e7..375f803b02 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -1,5 +1,6 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.qna.NotFoundException; import nextstep.qna.UnAuthorizedException; import nextstep.users.domain.NsUser; @@ -47,7 +48,10 @@ public Long getId() { return id; } - public Answer setDeleted(boolean deleted) { + public Answer setDeleted(NsUser loginUser, boolean deleted) throws CannotDeleteException { + if (!isOwner(loginUser)) { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } this.deleted = deleted; return this; } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index fa3189afd6..d67f87f47f 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -69,10 +69,6 @@ public void addAnswer(Answer answer) { answers.add(answer); } - public void setAnswers(List answers) { - this.answers = answers; - } - public boolean isOwner(NsUser loginUser) { return writer.equals(loginUser); } @@ -98,17 +94,11 @@ public String toString() { public Question delete(NsUser loginUser) throws CannotDeleteException { this.setDeleted(true); - List deletedAnswers = new ArrayList<>(); for (Answer answer : this.answers) { - if (!answer.isOwner(loginUser)) { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - } - - answer.setDeleted(true); - deletedAnswers.add(answer); + Answer deletedAnswer = answer.setDeleted(loginUser, true); + this.addAnswer(deletedAnswer); } - this.setAnswers(deletedAnswers); return this; } } diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index 98bc478713..ba4745040e 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -1,10 +1,32 @@ package nextstep.qna.domain; +import nextstep.qna.CannotDeleteException; import nextstep.users.domain.NsUserTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AnswerTest { public static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); public static final Answer A2 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q1, "Answers Contents2"); public static final Answer A3 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q2, "Answers Contents3"); public static final Answer A4 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q2, "Answers Contents4"); + + @Test + @DisplayName("질문 작성자와 답변 작성자가 같으면 답변을 삭제 상태로 변경한다") + void setDeleted_success() throws CannotDeleteException { + Answer deletedAnswer = A1.setDeleted(NsUserTest.JAVAJIGI, true); + + assertThat(deletedAnswer.isDeleted()).isTrue(); + } + + @Test + @DisplayName("질문 작성자와 답변 작성자가 다르면 삭제할 수 없다는 예외를 던진다") + void setDeleted_different_questionWriter_AnswerWriter_throwsException() { + assertThatThrownBy( + () -> A1.setDeleted(NsUserTest.SANJIGI, true) + ).isInstanceOf(CannotDeleteException.class); + } } From 452ffbf04acc8ba6102264d2433b0c8c2bc825ea Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 28 Nov 2023 23:22:22 +0900 Subject: [PATCH 04/62] =?UTF-8?q?[refactor]=20=EC=A7=88=EB=AC=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A5=BC=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 질문을 조회하는 로직을 별도의 메소드로 분리합니다. 삭제된 질문과 답변들은 DB에 이력을 남기므로 DeleteHistory 리스트를 만들기 위해 메서드로 나누어 조합합니다 --- .../java/nextstep/qna/service/QnAService.java | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 6cc4e8b7cf..99784fd301 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -25,18 +25,44 @@ public class QnAService { @Transactional public void deleteQuestion(NsUser loginUser, long questionId) throws CannotDeleteException { + Question question = getQuestion(loginUser, questionId); + + question = question.delete(loginUser); + + List deleteHistories = deletedQuestionAndAnswers(question); + deleteHistoryService.saveAll(deleteHistories); + } + + private Question getQuestion(NsUser loginUser, long questionId) throws CannotDeleteException { Question question = questionRepository.findById(questionId).orElseThrow(NotFoundException::new); if (!question.isOwner(loginUser)) { throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); } + return question; + } - question = question.delete(loginUser); + private List deletedQuestionAndAnswers(Question question) { + List deleteHistories = new ArrayList<>(); + DeleteHistory deleteHistoryFromQuestion = deleteHistoryFromQuestion(question); + List deleteHistoriesFromAnswers = deleteHistoryFromAnswers(question.getAnswers()); + + deleteHistories.add(deleteHistoryFromQuestion); + deleteHistories.addAll(deleteHistoriesFromAnswers); + return deleteHistories; + } + private DeleteHistory deleteHistoryFromQuestion(Question question) { + return new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now()); + } + + private List deleteHistoryFromAnswers(List answers) { List deleteHistories = new ArrayList<>(); - deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); - for (Answer answer : question.getAnswers()) { - deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); + for (Answer answer : answers) { + DeleteHistory deletedAnswerHistory = + new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now()); + deleteHistories.add(deletedAnswerHistory); } - deleteHistoryService.saveAll(deleteHistories); + + return deleteHistories; } } From d80f72fe2dd01c3ed44946746588b023ca2da307 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Wed, 29 Nov 2023 18:01:37 +0900 Subject: [PATCH 05/62] =?UTF-8?q?[refactor]=20=EC=82=AD=EC=A0=9C=EC=9D=B4?= =?UTF-8?q?=EB=A0=A5=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 삭제이력 생성을 서비스가 아닌 도메인에서 하도록 개선합니다 --- .../nextstep/qna/domain/DeleteHistory.java | 18 ++++++++++++++++++ .../java/nextstep/qna/service/QnAService.java | 6 ++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 43c37e5e5c..0cca0fe56b 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -26,6 +26,24 @@ public DeleteHistory(ContentType contentType, Long contentId, NsUser deletedBy, this.createdDate = createdDate; } + public static DeleteHistory from(Question question) { + return new DeleteHistory( + ContentType.QUESTION, + question.getId(), + question.getWriter(), + LocalDateTime.now() + ); + } + + public static DeleteHistory from(Answer answer) { + return new DeleteHistory( + ContentType.QUESTION, + answer.getId(), + answer.getWriter(), + LocalDateTime.now() + ); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 99784fd301..85a40a30ca 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -52,15 +52,13 @@ private List deletedQuestionAndAnswers(Question question) { } private DeleteHistory deleteHistoryFromQuestion(Question question) { - return new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now()); + return DeleteHistory.from(question); } private List deleteHistoryFromAnswers(List answers) { List deleteHistories = new ArrayList<>(); for (Answer answer : answers) { - DeleteHistory deletedAnswerHistory = - new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now()); - deleteHistories.add(deletedAnswerHistory); + deleteHistories.add(DeleteHistory.from(answer)); } return deleteHistories; From 83f217b2ffbf7c739642e9b20cf35db35bb0f599 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Wed, 29 Nov 2023 20:08:53 +0900 Subject: [PATCH 06/62] =?UTF-8?q?[refactor]=20=EC=A7=88=EB=AC=B8,=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80,=20=EC=82=AD=EC=A0=9C=20=EC=9D=B4=EB=A0=A5?= =?UTF-8?q?=20=EA=B0=81=EC=9E=90=EC=9D=98=20=EC=B1=85=EC=9E=84=EC=9D=84=20?= =?UTF-8?q?=EB=B6=80=EC=97=AC=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 질문은 로그인 한 사용자가 삭제하는지, 답변은 로그인한 사용자가 삭제하는지 각자 책임을 지도록 개선합니다 삭제 이력은 질문과 답변을 변환하는 정적 팩터리 메서드를 추가합니다 --- .../nextstep/qna/CannotDeleteException.java | 2 +- src/main/java/nextstep/qna/domain/Answer.java | 25 ++++++--- .../nextstep/qna/domain/AnswerRepository.java | 2 + .../nextstep/qna/domain/DeleteHistory.java | 14 ++++- .../java/nextstep/qna/domain/Question.java | 25 +++++---- .../qna/domain/QuestionRepository.java | 2 + .../infrastructure/JdbcAnswerRepository.java | 5 ++ .../JdbcQuestionRepository.java | 5 ++ .../java/nextstep/qna/service/QnAService.java | 51 ++++++++++--------- .../java/nextstep/qna/domain/AnswerTest.java | 17 +++---- .../nextstep/qna/domain/QuestionTest.java | 30 ++++------- .../nextstep/qna/service/QnaServiceTest.java | 19 +++++-- 12 files changed, 122 insertions(+), 75 deletions(-) diff --git a/src/main/java/nextstep/qna/CannotDeleteException.java b/src/main/java/nextstep/qna/CannotDeleteException.java index a8d9d2832e..492369d2f7 100644 --- a/src/main/java/nextstep/qna/CannotDeleteException.java +++ b/src/main/java/nextstep/qna/CannotDeleteException.java @@ -1,6 +1,6 @@ package nextstep.qna; -public class CannotDeleteException extends Exception { +public class CannotDeleteException extends RuntimeException { private static final long serialVersionUID = 1L; public CannotDeleteException(String message) { diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index 375f803b02..4cae2705e7 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -48,12 +48,8 @@ public Long getId() { return id; } - public Answer setDeleted(NsUser loginUser, boolean deleted) throws CannotDeleteException { - if (!isOwner(loginUser)) { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - } - this.deleted = deleted; - return this; + private void setDeleted() { + this.deleted = true; } public boolean isDeleted() { @@ -76,6 +72,23 @@ public void toQuestion(Question question) { this.question = question; } + public Answer delete(NsUser loginUser) { + validate(loginUser); + + this.setDeleted(); + return this; + } + + private void validate(NsUser loginUser) { + checkLoginUserIsWriter(loginUser); + } + + private void checkLoginUserIsWriter(NsUser loginUser) { + if (!isOwner(loginUser)) { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } + } + @Override public String toString() { return "Answer [id=" + getId() + ", writer=" + writer + ", contents=" + contents + "]"; diff --git a/src/main/java/nextstep/qna/domain/AnswerRepository.java b/src/main/java/nextstep/qna/domain/AnswerRepository.java index bb894f84cd..3d3e203467 100644 --- a/src/main/java/nextstep/qna/domain/AnswerRepository.java +++ b/src/main/java/nextstep/qna/domain/AnswerRepository.java @@ -4,4 +4,6 @@ public interface AnswerRepository { List findByQuestion(Long questionId); + + void update(Long answerId, Answer answer); } diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 0cca0fe56b..4706cbae26 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -3,6 +3,8 @@ import nextstep.users.domain.NsUser; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class DeleteHistory { @@ -35,9 +37,19 @@ public static DeleteHistory from(Question question) { ); } + public static List from(List answers) { + List deleteHistories = new ArrayList<>(); + + for (Answer answer : answers) { + deleteHistories.add(from(answer)); + } + + return deleteHistories; + } + public static DeleteHistory from(Answer answer) { return new DeleteHistory( - ContentType.QUESTION, + ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now() diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index d67f87f47f..da13cb579d 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -73,9 +73,8 @@ public boolean isOwner(NsUser loginUser) { return writer.equals(loginUser); } - public Question setDeleted(boolean deleted) { - this.deleted = deleted; - return this; + private void setDeleted() { + this.deleted = true; } public boolean isDeleted() { @@ -91,14 +90,20 @@ public String toString() { return "Question [id=" + getId() + ", title=" + title + ", contents=" + contents + ", writer=" + writer + "]"; } - public Question delete(NsUser loginUser) throws CannotDeleteException { - this.setDeleted(true); - - for (Answer answer : this.answers) { - Answer deletedAnswer = answer.setDeleted(loginUser, true); - this.addAnswer(deletedAnswer); - } + public Question delete(NsUser loginUser) { + validate(loginUser); + this.setDeleted(); return this; } + + private void validate(NsUser loginUser) { + checkLoginUserIsWriter(loginUser); + } + + private void checkLoginUserIsWriter(NsUser loginUser) { + if (!isOwner(loginUser)) { + throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); + } + } } diff --git a/src/main/java/nextstep/qna/domain/QuestionRepository.java b/src/main/java/nextstep/qna/domain/QuestionRepository.java index ec354bb3f8..0d96b5af59 100644 --- a/src/main/java/nextstep/qna/domain/QuestionRepository.java +++ b/src/main/java/nextstep/qna/domain/QuestionRepository.java @@ -4,4 +4,6 @@ public interface QuestionRepository { Optional findById(Long id); + + void update(Long questionId, Question question); } diff --git a/src/main/java/nextstep/qna/infrastructure/JdbcAnswerRepository.java b/src/main/java/nextstep/qna/infrastructure/JdbcAnswerRepository.java index 7c27c6dd86..d77374626f 100644 --- a/src/main/java/nextstep/qna/infrastructure/JdbcAnswerRepository.java +++ b/src/main/java/nextstep/qna/infrastructure/JdbcAnswerRepository.java @@ -12,4 +12,9 @@ public class JdbcAnswerRepository implements AnswerRepository { public List findByQuestion(Long questionId) { return null; } + + @Override + public void update(Long answerId, Answer answer) { + + } } diff --git a/src/main/java/nextstep/qna/infrastructure/JdbcQuestionRepository.java b/src/main/java/nextstep/qna/infrastructure/JdbcQuestionRepository.java index a6e4062dca..122194437c 100644 --- a/src/main/java/nextstep/qna/infrastructure/JdbcQuestionRepository.java +++ b/src/main/java/nextstep/qna/infrastructure/JdbcQuestionRepository.java @@ -12,4 +12,9 @@ public class JdbcQuestionRepository implements QuestionRepository { public Optional findById(Long id) { return Optional.empty(); } + + @Override + public void update(Long questionId, Question question) { + + } } diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 85a40a30ca..2de61e8c2b 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -8,7 +8,6 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -24,43 +23,47 @@ public class QnAService { private DeleteHistoryService deleteHistoryService; @Transactional - public void deleteQuestion(NsUser loginUser, long questionId) throws CannotDeleteException { - Question question = getQuestion(loginUser, questionId); + public void deleteQuestion(NsUser loginUser, long questionId) { + Question question = questionToBeDeleted(loginUser, questionId); + questionRepository.update(questionId, question); - question = question.delete(loginUser); + List answers = answersToBeDeleted(loginUser, questionId); + for (Answer answer : answers) { + answerRepository.update(answer.getId(), answer); + } - List deleteHistories = deletedQuestionAndAnswers(question); + List deleteHistories = deleteHistoriesOf(question, answers); deleteHistoryService.saveAll(deleteHistories); } - private Question getQuestion(NsUser loginUser, long questionId) throws CannotDeleteException { - Question question = questionRepository.findById(questionId).orElseThrow(NotFoundException::new); - if (!question.isOwner(loginUser)) { - throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); - } + private Question questionToBeDeleted(NsUser loginUser, long questionId) { + Question question = getQuestion(questionId); + question = question.delete(loginUser); return question; } - private List deletedQuestionAndAnswers(Question question) { - List deleteHistories = new ArrayList<>(); - DeleteHistory deleteHistoryFromQuestion = deleteHistoryFromQuestion(question); - List deleteHistoriesFromAnswers = deleteHistoryFromAnswers(question.getAnswers()); + private Question getQuestion(long questionId) throws CannotDeleteException { + return questionRepository.findById(questionId).orElseThrow(NotFoundException::new); + } - deleteHistories.add(deleteHistoryFromQuestion); - deleteHistories.addAll(deleteHistoriesFromAnswers); - return deleteHistories; + private List answersToBeDeleted(NsUser loginUser, long questionId) { + List deletedAnswers = new ArrayList<>(); + for (Answer answer : getAnswers(questionId)) { + answer = answer.delete(loginUser); + deletedAnswers.add(answer); + } + + return deletedAnswers; } - private DeleteHistory deleteHistoryFromQuestion(Question question) { - return DeleteHistory.from(question); + private List getAnswers(long questionId) { + return answerRepository.findByQuestion(questionId); } - private List deleteHistoryFromAnswers(List answers) { + private List deleteHistoriesOf(Question question, List answers) { List deleteHistories = new ArrayList<>(); - for (Answer answer : answers) { - deleteHistories.add(DeleteHistory.from(answer)); - } - + deleteHistories.add(DeleteHistory.from(question)); + deleteHistories.addAll(DeleteHistory.from(answers)); return deleteHistories; } } diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index ba4745040e..37869d647f 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -9,24 +9,23 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AnswerTest { - public static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); - public static final Answer A2 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q1, "Answers Contents2"); - public static final Answer A3 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q2, "Answers Contents3"); - public static final Answer A4 = new Answer(NsUserTest.SANJIGI, QuestionTest.Q2, "Answers Contents4"); + private static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); + private static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, Q1, "Answers Contents1"); + private static final Answer A2 = new Answer(NsUserTest.SANJIGI, Q1, "Answers Contents2"); @Test - @DisplayName("질문 작성자와 답변 작성자가 같으면 답변을 삭제 상태로 변경한다") + @DisplayName("답변 작성자가 로그인한 사용자면 답변을 삭제 상태로 변경하고 해당 답변을 반환한다") void setDeleted_success() throws CannotDeleteException { - Answer deletedAnswer = A1.setDeleted(NsUserTest.JAVAJIGI, true); + Answer deletedAnswer = A1.delete(NsUserTest.JAVAJIGI); assertThat(deletedAnswer.isDeleted()).isTrue(); } @Test - @DisplayName("질문 작성자와 답변 작성자가 다르면 삭제할 수 없다는 예외를 던진다") - void setDeleted_different_questionWriter_AnswerWriter_throwsException() { + @DisplayName("질문 작성자가 로그인한 사용자가 아니면 삭제할 수 없다는 예외를 던진다") + void setDeleted_different_answerWriter_loginUser_throwsException() { assertThatThrownBy( - () -> A1.setDeleted(NsUserTest.SANJIGI, true) + () -> A2.delete(NsUserTest.JAVAJIGI) ).isInstanceOf(CannotDeleteException.class); } } diff --git a/src/test/java/nextstep/qna/domain/QuestionTest.java b/src/test/java/nextstep/qna/domain/QuestionTest.java index 1cd45f9e81..ce23f996c3 100644 --- a/src/test/java/nextstep/qna/domain/QuestionTest.java +++ b/src/test/java/nextstep/qna/domain/QuestionTest.java @@ -6,39 +6,29 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.util.List; + import static nextstep.qna.domain.AnswerTest.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class QuestionTest { - public static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); - public static final Question Q2 = new Question(NsUserTest.SANJIGI, "title2", "contents2"); - - @BeforeEach - void setUp() { - Q1.addAnswer(A1); - Q1.addAnswer(A2); - - Q2.addAnswer(A3); - Q2.addAnswer(A4); - } + private static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); + private static final Question Q2 = new Question(NsUserTest.SANJIGI, "title2", "contents2"); @Test - @DisplayName("질문 작성자와 답변 작성자가 같으면 데이터를 삭제 상태로 변경한다") + @DisplayName("질문 작성자가 로그인한 사용자면 질문을 삭제 상태로 변경하고 해당 질문을 반환한다") void delete_success() throws CannotDeleteException { - Question question = Q2.delete(NsUserTest.SANJIGI); + Question deletedQuestion = Q1.delete(NsUserTest.JAVAJIGI); - assertThat(question.isDeleted()).isTrue(); - for (Answer answer : question.getAnswers()) { - assertThat(answer.isDeleted()).isTrue(); - } + assertThat(deletedQuestion.isDeleted()).isTrue(); } @Test - @DisplayName("질문 작성자와 답변 작성자가 다르면 삭제할 수 없다는 예외를 던진다") - void delete_different_questionWriter_AnswerWriter_throwsException() { + @DisplayName("질문 작성자가 로그인한 사용자가 아니면 삭제할 수 없다는 예외를 던진다") + void delete_different_questionWriter_loginUser_throwsException() { assertThatThrownBy( - () -> Q1.delete(NsUserTest.JAVAJIGI) + () -> Q2.delete(NsUserTest.JAVAJIGI) ).isInstanceOf(CannotDeleteException.class); } } diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index e1e943c23a..fcf6762f10 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -11,6 +11,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -22,28 +23,36 @@ @ExtendWith(MockitoExtension.class) public class QnaServiceTest { + private static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); + @Mock private QuestionRepository questionRepository; @Mock private DeleteHistoryService deleteHistoryService; + @Mock + private AnswerRepository answerRepository; + @InjectMocks private QnAService qnAService; private Question question; + private List answers = new ArrayList<>(); private Answer answer; @BeforeEach public void setUp() throws Exception { question = new Question(1L, NsUserTest.JAVAJIGI, "title1", "contents1"); - answer = new Answer(11L, NsUserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); + answer = new Answer(11L, NsUserTest.JAVAJIGI, Q1, "Answers Contents1"); + answers.add(answer); question.addAnswer(answer); } @Test public void delete_성공() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); + when(answerRepository.findByQuestion(question.getId())).thenReturn(answers); assertThat(question.isDeleted()).isFalse(); qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId()); @@ -64,6 +73,7 @@ public void setUp() throws Exception { @Test public void delete_성공_질문자_답변자_같음() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); + when(answerRepository.findByQuestion(question.getId())).thenReturn(answers); qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId()); @@ -82,9 +92,10 @@ public void setUp() throws Exception { } private void verifyDeleteHistories() { - List deleteHistories = Arrays.asList( - new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now()), - new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); + List deleteHistories = new ArrayList<>(); + deleteHistories.add(new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now())); + deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); + verify(deleteHistoryService).saveAll(deleteHistories); } } From 7f079679b32e34beeb5060b0899ad373d35840f5 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Wed, 29 Nov 2023 20:11:19 +0900 Subject: [PATCH 07/62] =?UTF-8?q?[refactor]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=ED=9E=88=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=83=81=EC=88=98=EB=A5=BC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Q1은 이번 테스트에서 필요하지 않는 상수이므로 제거합니다 --- src/test/java/nextstep/qna/service/QnaServiceTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index fcf6762f10..8472c68a0f 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -23,8 +23,6 @@ @ExtendWith(MockitoExtension.class) public class QnaServiceTest { - private static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); - @Mock private QuestionRepository questionRepository; @@ -44,7 +42,7 @@ public class QnaServiceTest { @BeforeEach public void setUp() throws Exception { question = new Question(1L, NsUserTest.JAVAJIGI, "title1", "contents1"); - answer = new Answer(11L, NsUserTest.JAVAJIGI, Q1, "Answers Contents1"); + answer = new Answer(11L, NsUserTest.JAVAJIGI, question, "Answers Contents1"); answers.add(answer); question.addAnswer(answer); } From 53416b6ab667e0222a743455b287ffd3d735e1e6 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Wed, 29 Nov 2023 21:10:53 +0900 Subject: [PATCH 08/62] =?UTF-8?q?[refactor]=20=EC=83=9D=EC=84=B1=EC=8B=9C?= =?UTF-8?q?=EA=B0=84,=20=EC=88=98=EC=A0=95=EC=8B=9C=EA=B0=84=EC=9D=84=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 생성시간, 수정시간을 별도의 클래스로 분리합니다 --- src/main/java/nextstep/qna/domain/Answer.java | 4 +-- .../java/nextstep/qna/domain/Question.java | 4 +-- .../java/nextstep/qna/domain/TimeStamped.java | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 src/main/java/nextstep/qna/domain/TimeStamped.java diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index 4cae2705e7..bdd5f9a919 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -18,9 +18,7 @@ public class Answer { private boolean deleted = false; - private LocalDateTime createdDate = LocalDateTime.now(); - - private LocalDateTime updatedDate; + private TimeStamped timeStamped; public Answer() { } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index da13cb579d..8706391b78 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -20,9 +20,7 @@ public class Question { private boolean deleted = false; - private LocalDateTime createdDate = LocalDateTime.now(); - - private LocalDateTime updatedDate; + private TimeStamped timeStamped; public Question() { } diff --git a/src/main/java/nextstep/qna/domain/TimeStamped.java b/src/main/java/nextstep/qna/domain/TimeStamped.java new file mode 100644 index 0000000000..0374e63a58 --- /dev/null +++ b/src/main/java/nextstep/qna/domain/TimeStamped.java @@ -0,0 +1,25 @@ +package nextstep.qna.domain; + +import java.time.LocalDateTime; + +public class TimeStamped { + private LocalDateTime createdDate = LocalDateTime.now(); + + private LocalDateTime updatedDate; + + public LocalDateTime getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(LocalDateTime createdDate) { + this.createdDate = createdDate; + } + + public LocalDateTime getUpdatedDate() { + return updatedDate; + } + + public void setUpdatedDate(LocalDateTime updatedDate) { + this.updatedDate = updatedDate; + } +} From 86fff7cd03c158c6d7d20ecbf4dd9692ef0e8275 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 30 Nov 2023 00:50:36 +0900 Subject: [PATCH 09/62] =?UTF-8?q?[refactor]=20=EC=A7=88=EB=AC=B8=EC=9D=98?= =?UTF-8?q?=20=EB=8B=B5=EB=B3=80=EC=9D=84=20=EC=9D=BC=EA=B8=89=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 질문 리스트를 일급 컬렉션으로 개선합니다 --- .../java/nextstep/qna/domain/Answers.java | 37 +++++++++++++++++++ .../nextstep/qna/domain/DeleteHistory.java | 2 +- .../java/nextstep/qna/domain/Question.java | 4 +- .../java/nextstep/qna/service/QnAService.java | 14 +++---- 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 src/main/java/nextstep/qna/domain/Answers.java diff --git a/src/main/java/nextstep/qna/domain/Answers.java b/src/main/java/nextstep/qna/domain/Answers.java new file mode 100644 index 0000000000..61d18ba466 --- /dev/null +++ b/src/main/java/nextstep/qna/domain/Answers.java @@ -0,0 +1,37 @@ +package nextstep.qna.domain; + +import nextstep.users.domain.NsUser; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class Answers implements Iterable { + private final List answers; + + public Answers() { + this(new ArrayList<>()); + } + + public Answers(List answers) { + this.answers = answers; + } + + public Answers delete(NsUser loginUser) { + List deletedAnswers = new ArrayList<>(); + for (Answer answer : answers) { + deletedAnswers.add(answer.delete(loginUser)); + } + + return new Answers(deletedAnswers); + } + + public void add(Answer answer) { + this.answers.add(answer); + } + + @Override + public Iterator iterator() { + return this.answers.iterator(); + } +} diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 4706cbae26..46a9ee5e21 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -37,7 +37,7 @@ public static DeleteHistory from(Question question) { ); } - public static List from(List answers) { + public static List from(Answers answers) { List deleteHistories = new ArrayList<>(); for (Answer answer : answers) { diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 8706391b78..ea23267891 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -16,7 +16,7 @@ public class Question { private NsUser writer; - private List answers = new ArrayList<>(); + private Answers answers = new Answers(); private boolean deleted = false; @@ -79,7 +79,7 @@ public boolean isDeleted() { return deleted; } - public List getAnswers() { + public Answers getAnswers() { return answers; } diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 2de61e8c2b..6094acd7a4 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -27,7 +27,7 @@ public void deleteQuestion(NsUser loginUser, long questionId) { Question question = questionToBeDeleted(loginUser, questionId); questionRepository.update(questionId, question); - List answers = answersToBeDeleted(loginUser, questionId); + Answers answers = answersToBeDeleted(loginUser, questionId); for (Answer answer : answers) { answerRepository.update(answer.getId(), answer); } @@ -46,13 +46,9 @@ private Question getQuestion(long questionId) throws CannotDeleteException { return questionRepository.findById(questionId).orElseThrow(NotFoundException::new); } - private List answersToBeDeleted(NsUser loginUser, long questionId) { - List deletedAnswers = new ArrayList<>(); - for (Answer answer : getAnswers(questionId)) { - answer = answer.delete(loginUser); - deletedAnswers.add(answer); - } - + private Answers answersToBeDeleted(NsUser loginUser, long questionId) { + Answers answers = new Answers(getAnswers(questionId)); + Answers deletedAnswers = answers.delete(loginUser); return deletedAnswers; } @@ -60,7 +56,7 @@ private List getAnswers(long questionId) { return answerRepository.findByQuestion(questionId); } - private List deleteHistoriesOf(Question question, List answers) { + private List deleteHistoriesOf(Question question, Answers answers) { List deleteHistories = new ArrayList<>(); deleteHistories.add(DeleteHistory.from(question)); deleteHistories.addAll(DeleteHistory.from(answers)); From 3dfd1cb91994af3ffcc495903f2d49548a19f4ca Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 4 Dec 2023 21:06:30 +0900 Subject: [PATCH 10/62] =?UTF-8?q?[refactor]=20=EB=8B=B5=EB=B3=80=EC=9D=84?= =?UTF-8?q?=20=EC=A7=88=EB=AC=B8=20=EB=8F=84=EB=A9=94=EC=9D=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B4=80=EB=A6=AC=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 질문 서비스 레이어에서 답변과 질문 도메인을 나눠서 관리하고 있지만 연관관계를 고려하여 질문 도메인에서 관리하도록 개선합니다 --- .../java/nextstep/qna/domain/Question.java | 1 + .../java/nextstep/qna/service/QnAService.java | 27 +++---------------- .../nextstep/qna/domain/QuestionTest.java | 9 ++++++- .../nextstep/qna/service/QnaServiceTest.java | 1 - 4 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index ea23267891..353e730a32 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -92,6 +92,7 @@ public Question delete(NsUser loginUser) { validate(loginUser); this.setDeleted(); + Answers deletedAnswers = answers.delete(loginUser); return this; } diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 6094acd7a4..afebd99ad4 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -24,38 +24,17 @@ public class QnAService { @Transactional public void deleteQuestion(NsUser loginUser, long questionId) { - Question question = questionToBeDeleted(loginUser, questionId); - questionRepository.update(questionId, question); - - Answers answers = answersToBeDeleted(loginUser, questionId); - for (Answer answer : answers) { - answerRepository.update(answer.getId(), answer); - } + Question question = getQuestion(questionId); + question.delete(loginUser); - List deleteHistories = deleteHistoriesOf(question, answers); + List deleteHistories = deleteHistoriesOf(question, question.getAnswers()); deleteHistoryService.saveAll(deleteHistories); } - private Question questionToBeDeleted(NsUser loginUser, long questionId) { - Question question = getQuestion(questionId); - question = question.delete(loginUser); - return question; - } - private Question getQuestion(long questionId) throws CannotDeleteException { return questionRepository.findById(questionId).orElseThrow(NotFoundException::new); } - private Answers answersToBeDeleted(NsUser loginUser, long questionId) { - Answers answers = new Answers(getAnswers(questionId)); - Answers deletedAnswers = answers.delete(loginUser); - return deletedAnswers; - } - - private List getAnswers(long questionId) { - return answerRepository.findByQuestion(questionId); - } - private List deleteHistoriesOf(Question question, Answers answers) { List deleteHistories = new ArrayList<>(); deleteHistories.add(DeleteHistory.from(question)); diff --git a/src/test/java/nextstep/qna/domain/QuestionTest.java b/src/test/java/nextstep/qna/domain/QuestionTest.java index ce23f996c3..2481e62cb3 100644 --- a/src/test/java/nextstep/qna/domain/QuestionTest.java +++ b/src/test/java/nextstep/qna/domain/QuestionTest.java @@ -14,14 +14,21 @@ public class QuestionTest { private static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); + private static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, Q1, "Answers Contents1"); + private static final Answer A2 = new Answer(NsUserTest.SANJIGI, Q1, "Answers Contents2"); + private static final Question Q2 = new Question(NsUserTest.SANJIGI, "title2", "contents2"); @Test @DisplayName("질문 작성자가 로그인한 사용자면 질문을 삭제 상태로 변경하고 해당 질문을 반환한다") void delete_success() throws CannotDeleteException { Question deletedQuestion = Q1.delete(NsUserTest.JAVAJIGI); - assertThat(deletedQuestion.isDeleted()).isTrue(); + + Answers answers = deletedQuestion.getAnswers(); + for (Answer answer : answers) { + assertThat(answer.isDeleted()).isTrue(); + } } @Test diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index 8472c68a0f..5fc189b3a1 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -71,7 +71,6 @@ public void setUp() throws Exception { @Test public void delete_성공_질문자_답변자_같음() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); - when(answerRepository.findByQuestion(question.getId())).thenReturn(answers); qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId()); From 55a8469729bd73afd573dd7449de8fd0ea7b23f1 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 4 Dec 2023 21:23:00 +0900 Subject: [PATCH 11/62] =?UTF-8?q?[refactor]=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=A0=A5=EC=9D=98=20=EC=9D=98=EC=A1=B4=EC=9D=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에는 삭제 이력이 질문과 답변을 의존했습니다. 하지만 질문과 답변의 도메인 정보를 get으로 꺼내했으므로 반대의 의존성을 만들어 노출을 최소화합니다 --- src/main/java/nextstep/qna/domain/Answer.java | 9 ++++++ .../java/nextstep/qna/domain/Answers.java | 8 ++++++ .../nextstep/qna/domain/DeleteHistory.java | 28 ------------------- .../java/nextstep/qna/domain/Question.java | 18 +++++++++++- .../java/nextstep/qna/service/QnAService.java | 12 ++------ .../nextstep/qna/service/QnaServiceTest.java | 1 - 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index bdd5f9a919..edbac2fb30 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -42,6 +42,15 @@ public Answer(Long id, NsUser writer, Question question, String contents) { this.contents = contents; } + public DeleteHistory toDeleteHistory() { + return new DeleteHistory( + ContentType.ANSWER, + this.id, + this.writer, + LocalDateTime.now() + ); + } + public Long getId() { return id; } diff --git a/src/main/java/nextstep/qna/domain/Answers.java b/src/main/java/nextstep/qna/domain/Answers.java index 61d18ba466..ebfdf23723 100644 --- a/src/main/java/nextstep/qna/domain/Answers.java +++ b/src/main/java/nextstep/qna/domain/Answers.java @@ -30,6 +30,14 @@ public void add(Answer answer) { this.answers.add(answer); } + public List toDeleteHistories() { + List deleteHistories = new ArrayList<>(); + for (Answer answer : answers) { + deleteHistories.add(answer.toDeleteHistory()); + } + return deleteHistories; + } + @Override public Iterator iterator() { return this.answers.iterator(); diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 46a9ee5e21..51d818b0c9 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -28,34 +28,6 @@ public DeleteHistory(ContentType contentType, Long contentId, NsUser deletedBy, this.createdDate = createdDate; } - public static DeleteHistory from(Question question) { - return new DeleteHistory( - ContentType.QUESTION, - question.getId(), - question.getWriter(), - LocalDateTime.now() - ); - } - - public static List from(Answers answers) { - List deleteHistories = new ArrayList<>(); - - for (Answer answer : answers) { - deleteHistories.add(from(answer)); - } - - return deleteHistories; - } - - public static DeleteHistory from(Answer answer) { - return new DeleteHistory( - ContentType.ANSWER, - answer.getId(), - answer.getWriter(), - LocalDateTime.now() - ); - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 353e730a32..a9505c0ab9 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -92,7 +92,7 @@ public Question delete(NsUser loginUser) { validate(loginUser); this.setDeleted(); - Answers deletedAnswers = answers.delete(loginUser); + this.answers = answers.delete(loginUser); return this; } @@ -105,4 +105,20 @@ private void checkLoginUserIsWriter(NsUser loginUser) { throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); } } + + public List toDeleteHistories() { + List deleteHistories = new ArrayList<>(); + deleteHistories.add(toDeleteHistory()); + deleteHistories.addAll(answers.toDeleteHistories()); + return deleteHistories; + } + + private DeleteHistory toDeleteHistory() { + return new DeleteHistory( + ContentType.QUESTION, + this.id, + this.writer, + LocalDateTime.now() + ); + } } diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index afebd99ad4..edf490ba4e 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -25,20 +25,14 @@ public class QnAService { @Transactional public void deleteQuestion(NsUser loginUser, long questionId) { Question question = getQuestion(questionId); - question.delete(loginUser); + question = question.delete(loginUser); + questionRepository.update(questionId, question); - List deleteHistories = deleteHistoriesOf(question, question.getAnswers()); + List deleteHistories = question.toDeleteHistories(); deleteHistoryService.saveAll(deleteHistories); } private Question getQuestion(long questionId) throws CannotDeleteException { return questionRepository.findById(questionId).orElseThrow(NotFoundException::new); } - - private List deleteHistoriesOf(Question question, Answers answers) { - List deleteHistories = new ArrayList<>(); - deleteHistories.add(DeleteHistory.from(question)); - deleteHistories.addAll(DeleteHistory.from(answers)); - return deleteHistories; - } } diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index 5fc189b3a1..3fba161701 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -50,7 +50,6 @@ public void setUp() throws Exception { @Test public void delete_성공() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); - when(answerRepository.findByQuestion(question.getId())).thenReturn(answers); assertThat(question.isDeleted()).isFalse(); qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId()); From e83f398f6dd8cada1a97f2001d5b06c42ef8aa8c Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 4 Dec 2023 23:14:55 +0900 Subject: [PATCH 12/62] =?UTF-8?q?[refactor]=20=EC=A7=88=EB=AC=B8=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=97=90=EC=84=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=9D=B4=EB=A0=A5=EC=9D=84=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 질문 도메인이 삭제이력을 관리할 수 있도록 합니다. 따라서 도메인에서 질문과 답변을 삭제이력으로 만드는 작업을 합니다 --- src/main/java/nextstep/qna/domain/Answer.java | 4 ++-- src/main/java/nextstep/qna/domain/Answers.java | 5 +++-- src/main/java/nextstep/qna/domain/Question.java | 12 +++++++----- .../java/nextstep/qna/service/QnAService.java | 11 +++++++---- .../java/nextstep/qna/service/QnaServiceTest.java | 15 +++++++++------ 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/main/java/nextstep/qna/domain/Answer.java b/src/main/java/nextstep/qna/domain/Answer.java index edbac2fb30..999ab85e51 100644 --- a/src/main/java/nextstep/qna/domain/Answer.java +++ b/src/main/java/nextstep/qna/domain/Answer.java @@ -42,12 +42,12 @@ public Answer(Long id, NsUser writer, Question question, String contents) { this.contents = contents; } - public DeleteHistory toDeleteHistory() { + public DeleteHistory toDeleteHistory(LocalDateTime time) { return new DeleteHistory( ContentType.ANSWER, this.id, this.writer, - LocalDateTime.now() + time ); } diff --git a/src/main/java/nextstep/qna/domain/Answers.java b/src/main/java/nextstep/qna/domain/Answers.java index ebfdf23723..8bb18ad38b 100644 --- a/src/main/java/nextstep/qna/domain/Answers.java +++ b/src/main/java/nextstep/qna/domain/Answers.java @@ -2,6 +2,7 @@ import nextstep.users.domain.NsUser; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -30,10 +31,10 @@ public void add(Answer answer) { this.answers.add(answer); } - public List toDeleteHistories() { + public List toDeleteHistories(LocalDateTime time) { List deleteHistories = new ArrayList<>(); for (Answer answer : answers) { - deleteHistories.add(answer.toDeleteHistory()); + deleteHistories.add(answer.toDeleteHistory(time)); } return deleteHistories; } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index a9505c0ab9..5ff4cf9fd8 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -106,19 +106,21 @@ private void checkLoginUserIsWriter(NsUser loginUser) { } } - public List toDeleteHistories() { + public List toDeleteHistories(NsUser loginUser, LocalDateTime date) { + validate(loginUser); + List deleteHistories = new ArrayList<>(); - deleteHistories.add(toDeleteHistory()); - deleteHistories.addAll(answers.toDeleteHistories()); + deleteHistories.add(toDeleteHistory(date)); + deleteHistories.addAll(answers.toDeleteHistories(date)); return deleteHistories; } - private DeleteHistory toDeleteHistory() { + private DeleteHistory toDeleteHistory(LocalDateTime date) { return new DeleteHistory( ContentType.QUESTION, this.id, this.writer, - LocalDateTime.now() + date ); } } diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index edf490ba4e..03b1759c3f 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -2,13 +2,16 @@ import nextstep.qna.CannotDeleteException; import nextstep.qna.NotFoundException; -import nextstep.qna.domain.*; +import nextstep.qna.domain.AnswerRepository; +import nextstep.qna.domain.DeleteHistory; +import nextstep.qna.domain.Question; +import nextstep.qna.domain.QuestionRepository; import nextstep.users.domain.NsUser; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; -import java.util.ArrayList; +import java.time.LocalDateTime; import java.util.List; @Service("qnaService") @@ -23,12 +26,12 @@ public class QnAService { private DeleteHistoryService deleteHistoryService; @Transactional - public void deleteQuestion(NsUser loginUser, long questionId) { + public void deleteQuestion(NsUser loginUser, long questionId, LocalDateTime date) { Question question = getQuestion(questionId); question = question.delete(loginUser); questionRepository.update(questionId, question); - List deleteHistories = question.toDeleteHistories(); + List deleteHistories = question.toDeleteHistories(loginUser, date); deleteHistoryService.saveAll(deleteHistories); } diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index 3fba161701..79f7ab3813 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -10,6 +10,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -38,6 +39,8 @@ public class QnaServiceTest { private Question question; private List answers = new ArrayList<>(); private Answer answer; + private LocalDateTime date = LocalDateTime.of(2023, 12, 4, 23, 0); + @BeforeEach public void setUp() throws Exception { @@ -52,7 +55,7 @@ public void setUp() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); assertThat(question.isDeleted()).isFalse(); - qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId()); + qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId(), date); assertThat(question.isDeleted()).isTrue(); verifyDeleteHistories(); @@ -63,7 +66,7 @@ public void setUp() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); assertThatThrownBy(() -> { - qnAService.deleteQuestion(NsUserTest.SANJIGI, question.getId()); + qnAService.deleteQuestion(NsUserTest.SANJIGI, question.getId(), date); }).isInstanceOf(CannotDeleteException.class); } @@ -71,7 +74,7 @@ public void setUp() throws Exception { public void delete_성공_질문자_답변자_같음() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); - qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId()); + qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId(), date); assertThat(question.isDeleted()).isTrue(); assertThat(answer.isDeleted()).isTrue(); @@ -83,14 +86,14 @@ public void setUp() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); assertThatThrownBy(() -> { - qnAService.deleteQuestion(NsUserTest.SANJIGI, question.getId()); + qnAService.deleteQuestion(NsUserTest.SANJIGI, question.getId(), date); }).isInstanceOf(CannotDeleteException.class); } private void verifyDeleteHistories() { List deleteHistories = new ArrayList<>(); - deleteHistories.add(new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now())); - deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); + deleteHistories.add(new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), date)); + deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), date)); verify(deleteHistoryService).saveAll(deleteHistories); } From 6a649b11eb479b812baf147da53cf482086c700d Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 4 Dec 2023 23:24:37 +0900 Subject: [PATCH 13/62] =?UTF-8?q?[feat]=20=EC=82=AD=EC=A0=9C=EC=9D=B4?= =?UTF-8?q?=EB=A0=A5=20=EC=9D=BC=EA=B8=89=20=EC=BB=AC=EB=A0=89=EC=85=98?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 삭제이력을 리스트로 관리하지 않고 일급 컬렉션으로 관리합니다 삭제 이력에 질문과 답변을 추가하는 행위와 삭제이력 상태를 관리합니다 --- .../java/nextstep/qna/domain/Answers.java | 4 +- .../nextstep/qna/domain/DeleteHistories.java | 46 +++++++++++++++++++ .../qna/domain/DeleteHistoryRepository.java | 2 +- .../java/nextstep/qna/domain/Question.java | 6 +-- .../JdbcDeleteHistoryRepository.java | 3 +- .../qna/service/DeleteHistoryService.java | 3 +- .../java/nextstep/qna/service/QnAService.java | 7 +-- .../nextstep/qna/service/QnaServiceTest.java | 4 +- 8 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 src/main/java/nextstep/qna/domain/DeleteHistories.java diff --git a/src/main/java/nextstep/qna/domain/Answers.java b/src/main/java/nextstep/qna/domain/Answers.java index 8bb18ad38b..c97f7d5594 100644 --- a/src/main/java/nextstep/qna/domain/Answers.java +++ b/src/main/java/nextstep/qna/domain/Answers.java @@ -31,8 +31,8 @@ public void add(Answer answer) { this.answers.add(answer); } - public List toDeleteHistories(LocalDateTime time) { - List deleteHistories = new ArrayList<>(); + public DeleteHistories toDeleteHistories(LocalDateTime time) { + DeleteHistories deleteHistories = new DeleteHistories(); for (Answer answer : answers) { deleteHistories.add(answer.toDeleteHistory(time)); } diff --git a/src/main/java/nextstep/qna/domain/DeleteHistories.java b/src/main/java/nextstep/qna/domain/DeleteHistories.java new file mode 100644 index 0000000000..071d627298 --- /dev/null +++ b/src/main/java/nextstep/qna/domain/DeleteHistories.java @@ -0,0 +1,46 @@ +package nextstep.qna.domain; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +public class DeleteHistories implements Iterable { + private final List deleteHistories; + + public DeleteHistories() { + this(new ArrayList<>()); + } + + public DeleteHistories(List deleteHistories) { + this.deleteHistories = deleteHistories; + } + + public void add(DeleteHistories inputDeleteHistories) { + for(DeleteHistory deleteHistory : inputDeleteHistories) { + this.add(deleteHistory); + }; + } + + public void add(DeleteHistory deleteHistory) { + this.deleteHistories.add(deleteHistory); + } + + @Override + public Iterator iterator() { + return this.deleteHistories.iterator(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DeleteHistories that = (DeleteHistories) o; + return Objects.equals(deleteHistories, that.deleteHistories); + } + + @Override + public int hashCode() { + return Objects.hash(deleteHistories); + } +} diff --git a/src/main/java/nextstep/qna/domain/DeleteHistoryRepository.java b/src/main/java/nextstep/qna/domain/DeleteHistoryRepository.java index e8671add77..54f9fe446c 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistoryRepository.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistoryRepository.java @@ -4,5 +4,5 @@ public interface DeleteHistoryRepository { - void saveAll(List deleteHistories); + void saveAll(DeleteHistories deleteHistories); } diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 5ff4cf9fd8..2d0ed31ae1 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -106,12 +106,12 @@ private void checkLoginUserIsWriter(NsUser loginUser) { } } - public List toDeleteHistories(NsUser loginUser, LocalDateTime date) { + public DeleteHistories toDeleteHistories(NsUser loginUser, LocalDateTime date) { validate(loginUser); - List deleteHistories = new ArrayList<>(); + DeleteHistories deleteHistories = new DeleteHistories(); deleteHistories.add(toDeleteHistory(date)); - deleteHistories.addAll(answers.toDeleteHistories(date)); + deleteHistories.add(answers.toDeleteHistories(date)); return deleteHistories; } diff --git a/src/main/java/nextstep/qna/infrastructure/JdbcDeleteHistoryRepository.java b/src/main/java/nextstep/qna/infrastructure/JdbcDeleteHistoryRepository.java index 7ff0188875..2dc6f9e13a 100644 --- a/src/main/java/nextstep/qna/infrastructure/JdbcDeleteHistoryRepository.java +++ b/src/main/java/nextstep/qna/infrastructure/JdbcDeleteHistoryRepository.java @@ -1,5 +1,6 @@ package nextstep.qna.infrastructure; +import nextstep.qna.domain.DeleteHistories; import nextstep.qna.domain.DeleteHistory; import nextstep.qna.domain.DeleteHistoryRepository; import org.springframework.stereotype.Repository; @@ -9,7 +10,7 @@ @Repository("deleteHistoryRepository") public class JdbcDeleteHistoryRepository implements DeleteHistoryRepository { @Override - public void saveAll(List deleteHistories) { + public void saveAll(DeleteHistories deleteHistories) { } } diff --git a/src/main/java/nextstep/qna/service/DeleteHistoryService.java b/src/main/java/nextstep/qna/service/DeleteHistoryService.java index 7599dca96b..9cc42cb817 100644 --- a/src/main/java/nextstep/qna/service/DeleteHistoryService.java +++ b/src/main/java/nextstep/qna/service/DeleteHistoryService.java @@ -1,5 +1,6 @@ package nextstep.qna.service; +import nextstep.qna.domain.DeleteHistories; import nextstep.qna.domain.DeleteHistory; import nextstep.qna.domain.DeleteHistoryRepository; import org.springframework.stereotype.Service; @@ -15,7 +16,7 @@ public class DeleteHistoryService { private DeleteHistoryRepository deleteHistoryRepository; @Transactional(propagation = Propagation.REQUIRES_NEW) - public void saveAll(List deleteHistories) { + public void saveAll(DeleteHistories deleteHistories) { deleteHistoryRepository.saveAll(deleteHistories); } } diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 03b1759c3f..6c2b646d12 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -2,10 +2,7 @@ import nextstep.qna.CannotDeleteException; import nextstep.qna.NotFoundException; -import nextstep.qna.domain.AnswerRepository; -import nextstep.qna.domain.DeleteHistory; -import nextstep.qna.domain.Question; -import nextstep.qna.domain.QuestionRepository; +import nextstep.qna.domain.*; import nextstep.users.domain.NsUser; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,7 +28,7 @@ public void deleteQuestion(NsUser loginUser, long questionId, LocalDateTime date question = question.delete(loginUser); questionRepository.update(questionId, question); - List deleteHistories = question.toDeleteHistories(loginUser, date); + DeleteHistories deleteHistories = question.toDeleteHistories(loginUser, date); deleteHistoryService.saveAll(deleteHistories); } diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index 79f7ab3813..07f4e075d0 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -10,10 +10,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -91,7 +89,7 @@ public void setUp() throws Exception { } private void verifyDeleteHistories() { - List deleteHistories = new ArrayList<>(); + DeleteHistories deleteHistories = new DeleteHistories(); deleteHistories.add(new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), date)); deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), date)); From b7e8fc71ed8e7563b4f7e02de4c36d276e542038 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 5 Dec 2023 19:34:44 +0900 Subject: [PATCH 14/62] =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기능 요구사항을 정리합니다 --- README.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5cc0d8f342..01d209f85d 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,23 @@ -## 학습 관리 시스템(Learning Management System) +# 학습 관리 시스템(Learning Management System) -### 질문 삭제하기 요구사항 -- 질문 데이터를 완전히 삭제하는 것이 아니라 데이터의 상태를 삭제 상태(deleted - boolean type)로 변경한다. -- 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능하다. -- 답변이 없는 경우 삭제가 가능하다. -- 질문자와 답변글의 모든답변자가 같은경우 삭제가가능하다. -- 질문을 삭제할 때 답변 또한 삭제해야 하며, 답변의 삭제 또한 삭제 상태(deleted)를 변경 -한다. -- 질문자와 답변자가 다른경우 답변을 삭제할 수 없다. -- 질문과 답변 삭제 이력에 대한 정보를 DeleteHistory를 활용해 남긴다. +## 수강 신청 기능 요구사항 +- 과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다. +- 강의는 시작일과 종료일을 가진다. +- 강의는 강의 커버 이미지 정보를 가진다. + - 이미지 크기는 1MB 이하여야 한다. + - 이미지 타입은 gif, jpg(jpeg 포함),, png, svg만 허용한다. + - 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. +- 강의는 무료 강의와 유료 강의로 나뉜다. + - 무료 강의는 최대 수강 인원 제한이 없다. + - 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. + - 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. +- 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. +- 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. +- 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다. +- 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다. -### 리팩터링 요구사항 -- nextstep.qna.service.QnaService의 deleteQuestion()는 앞의 질문 삭제 기능을 구현한 코드이다. 이 메소드는 단위 테스트하기 어려운 코드와 단위 테스트 가능한 코드가 섞여 있다. -- QnaService의 deleteQuestion() 메서드에 단위 테스트 가능한 코드(핵심 비지니스 로직)를 도메인 모델 객체에 구현한다. -- QnaService의 비지니스 로직을 도메인 모델로 이동하는 리팩터링을 진행할 때 TDD로 구현한다. -- QnaService의 deleteQuestion() 메서드에 대한 단위 테스트는 src/test/java 폴더 nextstep.qna.service.QnaServiceTest이다. 도메인 모델로 로직을 이동한 후에도 QnaServiceTest의 모든 테스트는 통과해야 한다. \ No newline at end of file +## 프로그래밍 요구사항 +- DB 테이블 설계 없이 도메인 모델부터 구현한다. +- 도메인 모델은 TDD로 구현한다. + - 단, Service 클래스는 단위 테스트가 없어도 된다. +- 다음 동영상을 참고해 DB 테이블보다 도메인 모델을 먼저 설계하고 구현한다. \ No newline at end of file From 32b07f32dc4e748aab5c78a2d59cdf6911e66897 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 5 Dec 2023 20:25:26 +0900 Subject: [PATCH 15/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=EC=99=80=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=EC=9D=84=20=EC=83=9D=EC=84=B1=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의와 이미지 도메인을 생성합니다. 유효성 검증만 도메인 데이터 변경 함수를 추가해야 합니다 --- .../java/nextstep/courses/domain/Course.java | 3 + .../java/nextstep/courses/domain/Image.java | 40 +++++++++++ .../java/nextstep/courses/domain/Session.java | 69 +++++++++++++++++++ .../nextstep/courses/domain/ImageTest.java | 20 ++++++ .../nextstep/courses/domain/SessionTest.java | 22 ++++++ 5 files changed, 154 insertions(+) create mode 100644 src/main/java/nextstep/courses/domain/Image.java create mode 100644 src/main/java/nextstep/courses/domain/Session.java create mode 100644 src/test/java/nextstep/courses/domain/ImageTest.java create mode 100644 src/test/java/nextstep/courses/domain/SessionTest.java diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/Course.java index 0f69716043..0c0e2d2b09 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/Course.java @@ -1,6 +1,7 @@ package nextstep.courses.domain; import java.time.LocalDateTime; +import java.util.List; public class Course { private Long id; @@ -9,6 +10,8 @@ public class Course { private Long creatorId; + private List sessions; + private LocalDateTime createdAt; private LocalDateTime updatedAt; diff --git a/src/main/java/nextstep/courses/domain/Image.java b/src/main/java/nextstep/courses/domain/Image.java new file mode 100644 index 0000000000..85dfc79476 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Image.java @@ -0,0 +1,40 @@ +package nextstep.courses.domain; + +import java.time.LocalDateTime; + +public class Image { + private Long id; + + private int size; + + private Type type; + + private int width; + + private int height; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + public enum Type { + GIF, JPG, JPEG, PNG, SVG + } + + public Image() { + } + + public Image(int size, Type type, int width, int height) { + this(0L, size, type, width, height, LocalDateTime.now(), null); + } + + public Image(Long id, int size, Type type, int width, int height, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.size = size; + this.type = type; + this.width = width; + this.height = height; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java new file mode 100644 index 0000000000..3deb8a6730 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -0,0 +1,69 @@ +package nextstep.courses.domain; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class Session { + private Long id; + + private Image image; + + private LocalDate startDate; + + private LocalDate endDate; + + private Type type; + + private int quota; + + private int apply = 0; + + private Status status = Status.READY; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + public enum Type { + FREE("무료"), + CHARGE("유료"); + + Type(String description) { + this.description = description; + } + + private final String description; + } + + public Session() { + } + + public enum Status { + READY("준비중"), + RECRUIT("모집중"), + END("종료"); + + Status(String description) { + this.description = description; + } + + private final String description; + } + + public Session(Image image, LocalDate startDate, LocalDate endDate, Type type, int quota) { + this(0L, image, startDate, endDate, type, quota, LocalDateTime.now(), null); + } + + public Session(Long id, Image image, LocalDate startDate, + LocalDate endDate, Type type, int quota, + LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.image = image; + this.startDate = startDate; + this.endDate = endDate; + this.type = type; + this.quota = quota; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } +} diff --git a/src/test/java/nextstep/courses/domain/ImageTest.java b/src/test/java/nextstep/courses/domain/ImageTest.java new file mode 100644 index 0000000000..2c6c5c206b --- /dev/null +++ b/src/test/java/nextstep/courses/domain/ImageTest.java @@ -0,0 +1,20 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +public class ImageTest { + @Test + void success() { + Image image = new Image( + 1000L, + 1000, + Image.Type.GIF, + 300, + 200, + LocalDateTime.now(), + LocalDateTime.now() + ); + } +} diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java new file mode 100644 index 0000000000..3f4b3e3599 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -0,0 +1,22 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class SessionTest { + @Test + void success() { + Session session = new Session( + 1L, + new Image(1000, Image.Type.JPEG, 300, 200), + LocalDate.now(), + LocalDate.now(), + Session.Type.FREE, + 100, + LocalDateTime.now(), + LocalDateTime.now() + ); + } +} From 861343bf458123b0876ddfaa955ea5ad805f30e2 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 5 Dec 2023 20:58:20 +0900 Subject: [PATCH 16/62] =?UTF-8?q?[feat]=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=9C=A0=ED=9A=A8=EC=84=B1=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이미지의 크기, 확장자, 비율을 검사하는 유효성을 추가합니다 --- .../java/nextstep/courses/domain/Image.java | 74 ++++++++++++++++--- .../nextstep/courses/domain/ImageTest.java | 2 +- .../nextstep/courses/domain/SessionTest.java | 2 +- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/Image.java b/src/main/java/nextstep/courses/domain/Image.java index 85dfc79476..cb13df352b 100644 --- a/src/main/java/nextstep/courses/domain/Image.java +++ b/src/main/java/nextstep/courses/domain/Image.java @@ -1,39 +1,91 @@ package nextstep.courses.domain; import java.time.LocalDateTime; +import java.util.Arrays; public class Image { + public static final int MB = 1024 * 1024; + public static final int WIDTH_MIN = 300; + public static final int HEIGHT_MIN = 200; + public static final double WIDTH_HEIGHT_RATIO = 1.5; + private Long id; - private int size; + private int imageSize; private Type type; - private int width; + private int imageWidth; - private int height; + private int imageHeight; private LocalDateTime createdAt; private LocalDateTime updatedAt; public enum Type { - GIF, JPG, JPEG, PNG, SVG + GIF("gif"), + JPG("jpg"), + JPEG("jpeg"), + PNG("png"), + SVG("svg"); + + Type(String description) { + this.description = description; + } + + private final String description; + + public static Type find(String name) { + return Arrays.stream(values()) + .filter(imageType -> imageType.description.equals(name)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException( + String.format("허용하는 확장자는 다음과 같습니다.\n %s", descriptions()) + ) + ); + } + + public static String descriptions() { + StringBuilder sb = new StringBuilder(); + for (Type type : values()) { + sb.append(type.description).append(", "); + } + sb.setLength(sb.length() - 2); + + return sb.toString(); + } } public Image() { } - public Image(int size, Type type, int width, int height) { - this(0L, size, type, width, height, LocalDateTime.now(), null); + public Image(int imageSize, String type, int imageWidth, int imageHeight) { + this(0L, imageSize, type, imageWidth, imageHeight, LocalDateTime.now(), null); } - public Image(Long id, int size, Type type, int width, int height, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Image(Long id, int imageSize, String type, int imageWidth, int imageHeight, + LocalDateTime createdAt, LocalDateTime updatedAt) { + if (imageSize > 1 * MB) { + throw new IllegalArgumentException("사진 크기는 1MB를 넘을 수 없습니다."); + } + + if(imageWidth < WIDTH_MIN || imageHeight < HEIGHT_MIN) { + throw new IllegalArgumentException( + String.format("가로 픽셀은 %d, 세로 픽셀은 %d 이상이어야 합니다.", WIDTH_MIN, HEIGHT_MIN) + ); + } + + if((double) imageWidth / imageHeight != WIDTH_HEIGHT_RATIO) { + throw new IllegalArgumentException("가로 세로 비율은 3:2여야 합니다."); + } + this.id = id; - this.size = size; - this.type = type; - this.width = width; - this.height = height; + this.imageSize = imageSize; + this.type = Type.find(type); + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; this.createdAt = createdAt; this.updatedAt = updatedAt; } diff --git a/src/test/java/nextstep/courses/domain/ImageTest.java b/src/test/java/nextstep/courses/domain/ImageTest.java index 2c6c5c206b..afffae34e3 100644 --- a/src/test/java/nextstep/courses/domain/ImageTest.java +++ b/src/test/java/nextstep/courses/domain/ImageTest.java @@ -10,7 +10,7 @@ void success() { Image image = new Image( 1000L, 1000, - Image.Type.GIF, + "gif", 300, 200, LocalDateTime.now(), diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index 3f4b3e3599..c388f153a5 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -10,7 +10,7 @@ public class SessionTest { void success() { Session session = new Session( 1L, - new Image(1000, Image.Type.JPEG, 300, 200), + new Image(1000, "jpeg", 300, 200), LocalDate.now(), LocalDate.now(), Session.Type.FREE, From e0f9865e98cc68958fafc2de46d3ddb1d5b49856 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 5 Dec 2023 21:45:07 +0900 Subject: [PATCH 17/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=EC=97=90=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 모집중인 경우만 강의신청이 가능하며, 유료 강의의 경우 수강 인원을 초과 할 수 없고 결제가 완료되어야만 신청이 가능합니다 --- .../java/nextstep/courses/domain/Session.java | 38 +++++++++++++++++-- .../nextstep/payments/domain/Payment.java | 4 ++ .../nextstep/courses/domain/SessionTest.java | 1 + 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index 3deb8a6730..e8f9e6536e 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -1,11 +1,15 @@ package nextstep.courses.domain; +import nextstep.payments.domain.Payment; + import java.time.LocalDate; import java.time.LocalDateTime; public class Session { private Long id; + private Long paymentId; + private Image image; private LocalDate startDate; @@ -14,9 +18,11 @@ public class Session { private Type type; + private Long amount; + private int quota; - private int apply = 0; + private int applyCount = 0; private Status status = Status.READY; @@ -50,20 +56,44 @@ public enum Status { private final String description; } - public Session(Image image, LocalDate startDate, LocalDate endDate, Type type, int quota) { - this(0L, image, startDate, endDate, type, quota, LocalDateTime.now(), null); + public Session(Image image, LocalDate startDate, LocalDate endDate, + Type type, Long amount, int quota) { + this(0L, image, startDate, endDate, type, amount, quota, LocalDateTime.now(), null); } public Session(Long id, Image image, LocalDate startDate, - LocalDate endDate, Type type, int quota, + LocalDate endDate, Type type, Long amount, int quota, LocalDateTime createdAt, LocalDateTime updatedAt) { + if(image == null) { + throw new IllegalArgumentException("이미지를 추가해야 합니다"); + } + this.id = id; this.image = image; this.startDate = startDate; this.endDate = endDate; this.type = type; + this.amount = amount; this.quota = quota; this.createdAt = createdAt; this.updatedAt = updatedAt; } + + void applySession(Payment payment) { + if (this.status != Status.RECRUIT) { + throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능합니다."); + } + + if (this.type == Type.CHARGE) { + if (this.applyCount + 1 == quota) { + throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); + } + + if(!payment.isPaid(paymentId, amount)) { + throw new IllegalArgumentException("결제를 진행해 주세요."); + } + } + + this.applyCount += 1; + } } diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 57d833f851..0530892df8 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -26,4 +26,8 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.amount = amount; this.createdAt = LocalDateTime.now(); } + + public boolean isPaid(Long paymentId, Long amount) { + return Long.parseLong(id) == paymentId && this.amount.equals(amount); + } } diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index c388f153a5..8de337f4a8 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -14,6 +14,7 @@ void success() { LocalDate.now(), LocalDate.now(), Session.Type.FREE, + 1000L, 100, LocalDateTime.now(), LocalDateTime.now() From 3ddfe50c7d394dba4a313b98de57bf03c619ae47 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 5 Dec 2023 23:21:02 +0900 Subject: [PATCH 18/62] =?UTF-8?q?[test]=20=EA=B0=95=EC=9D=98=EC=99=80=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의와 이미지의 도메인 유효성 테스트를 추가합니다 --- .../java/nextstep/courses/domain/Session.java | 23 +++--- .../nextstep/courses/domain/ImageTest.java | 49 ++++++++++--- .../nextstep/courses/domain/SessionTest.java | 71 +++++++++++++++---- 3 files changed, 112 insertions(+), 31 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index e8f9e6536e..9632f7886b 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -22,9 +22,9 @@ public class Session { private int quota; - private int applyCount = 0; + private int applyCount; - private Status status = Status.READY; + private Status status; private LocalDateTime createdAt; @@ -58,12 +58,13 @@ public enum Status { public Session(Image image, LocalDate startDate, LocalDate endDate, Type type, Long amount, int quota) { - this(0L, image, startDate, endDate, type, amount, quota, LocalDateTime.now(), null); + this(0L, image, startDate, endDate, type, amount, quota, 0, + Status.READY, LocalDateTime.now(), null); } - public Session(Long id, Image image, LocalDate startDate, - LocalDate endDate, Type type, Long amount, int quota, - LocalDateTime createdAt, LocalDateTime updatedAt) { + public Session(Long id, Image image, LocalDate startDate, LocalDate endDate, + Type type, Long amount, int quota, int applyCount, + Status status, LocalDateTime createdAt, LocalDateTime updatedAt) { if(image == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); } @@ -75,13 +76,19 @@ public Session(Long id, Image image, LocalDate startDate, this.type = type; this.amount = amount; this.quota = quota; + this.applyCount = applyCount; + this.status = status; this.createdAt = createdAt; this.updatedAt = updatedAt; } + public int applyCount() { + return this.applyCount; + } + void applySession(Payment payment) { if (this.status != Status.RECRUIT) { - throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능합니다."); + throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); } if (this.type == Type.CHARGE) { @@ -89,7 +96,7 @@ void applySession(Payment payment) { throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); } - if(!payment.isPaid(paymentId, amount)) { + if(payment == null || !payment.isPaid(paymentId, amount)) { throw new IllegalArgumentException("결제를 진행해 주세요."); } } diff --git a/src/test/java/nextstep/courses/domain/ImageTest.java b/src/test/java/nextstep/courses/domain/ImageTest.java index afffae34e3..2df47f65b9 100644 --- a/src/test/java/nextstep/courses/domain/ImageTest.java +++ b/src/test/java/nextstep/courses/domain/ImageTest.java @@ -1,20 +1,47 @@ package nextstep.courses.domain; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ImageTest { @Test - void success() { - Image image = new Image( - 1000L, - 1000, - "gif", - 300, - 200, - LocalDateTime.now(), - LocalDateTime.now() - ); + @DisplayName("이미지는 1 MB 를 초과하면 사이즈가 크다는 예외를 반환한다.") + void newObject_over1MBSize_throwsException() { + assertThatThrownBy( + () -> new Image(2 * Image.MB, "gif", 300, 200) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이미지는 가로 픽셀이 300 미만이면 길이가 작아는 예외를 반환한다.") + void newObject_lessThanMinWidthSize_throwsException() { + assertThatThrownBy( + () -> new Image(Image.MB, "gif", Image.WIDTH_MIN - 1, Image.HEIGHT_MIN) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이미지는 세로 픽셀이 200 미만이면 길이가 작아는 예외를 반환한다.") + void newObject_lessThanMinHeightSize_throwsException() { + assertThatThrownBy( + () -> new Image(Image.MB, "gif", Image.WIDTH_MIN, Image.HEIGHT_MIN - 1) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이미지는 가로 세로 비율이 3:2가 아니면 비율이 틀리다는 예외를 반환한다.") + void newObject_inValidWidthHeightRatio_throwsException() { + assertThatThrownBy( + () -> new Image(Image.MB, "gif", 600, 500) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void newObject_inValidType_throwsException() { + assertThatThrownBy( + () -> new Image(Image.MB, "pdf", Image.WIDTH_MIN, Image.HEIGHT_MIN) + ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index 8de337f4a8..b375c8b6ef 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -1,23 +1,70 @@ package nextstep.courses.domain; +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.LocalDateTime; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + public class SessionTest { + private static final Image IMAGE = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + private static final Payment PAYMENT = new Payment("1", 1L, 1L, 1000L); + private static final LocalDate localDate = LocalDate.of(2023, 12, 5); + private static final LocalDateTime localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + + @Test + @DisplayName("강의는 이미지가 없으면 이미지를 추가하라는 예외를 반환한다.") + void newObject_imageNull_throwsException() { + assertThatThrownBy( + () -> new Session(null, localDate, localDate, Session.Type.FREE, 1000L, 10) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("applySession은 수강신청을 하면 수강 신청 인원이 1 증가한다.") + void applySession_success() { + Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, + 1000L, 10, 1, Session.Status.RECRUIT, localDateTime, localDateTime); + + session.applySession(PAYMENT); + + assertThat(session.applyCount()).isEqualTo(2); + } + @Test - void success() { - Session session = new Session( - 1L, - new Image(1000, "jpeg", 300, 200), - LocalDate.now(), - LocalDate.now(), - Session.Type.FREE, - 1000L, - 100, - LocalDateTime.now(), - LocalDateTime.now() - ); + @DisplayName("applySession은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") + void applySession_notRecruitStatus_throwsException() { + Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, + 1000L, 10, 0, Session.Status.READY, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.applySession(PAYMENT) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("applySession은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다..") + void applySession_chargeSession_overQuota_throwsException() { + Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, + 1000L, 10, 9, Session.Status.RECRUIT, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.applySession(PAYMENT) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("applySession은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다..") + void applySession_chargeSession_notPaid_throwsException() { + Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, + 1000L, 10, 9, Session.Status.RECRUIT, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.applySession(null) + ).isInstanceOf(IllegalArgumentException.class); } } From 335ebead827a0b67e039b789f9f2aae7be2ce8e2 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 7 Dec 2023 00:31:11 +0900 Subject: [PATCH 19/62] =?UTF-8?q?[refactor]=20=EA=B0=95=EC=9D=98=EC=9D=98?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=EB=A5=BC=20=EA=B0=9C=EC=84=A0=ED=95=98?= =?UTF-8?q?=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의는 여러명의 수강이 있을 수 있으므로 수강생 리스트를 추가하고 결제정보도 여러개가 될 수 있기 때문에 결제정보 필드를 제거합니다 --- .../courses/domain/{ => course}/Course.java | 4 +- .../domain/{ => course}/CourseRepository.java | 2 +- .../domain/{ => course/image}/Image.java | 2 +- .../domain/{ => course/session}/Session.java | 31 ++++++++----- .../infrastructure/JdbcCourseRepository.java | 4 +- .../nextstep/payments/domain/Payment.java | 7 ++- .../java/nextstep/users/domain/NsUser.java | 4 ++ .../courses/domain/course/CourseTest.java | 4 ++ .../domain/{ => course/image}/ImageTest.java | 2 +- .../{ => course/session}/SessionTest.java | 45 +++++++++++++------ .../infrastructure/CourseRepositoryTest.java | 4 +- 11 files changed, 73 insertions(+), 36 deletions(-) rename src/main/java/nextstep/courses/domain/{ => course}/Course.java (92%) rename src/main/java/nextstep/courses/domain/{ => course}/CourseRepository.java (71%) rename src/main/java/nextstep/courses/domain/{ => course/image}/Image.java (98%) rename src/main/java/nextstep/courses/domain/{ => course/session}/Session.java (75%) create mode 100644 src/test/java/nextstep/courses/domain/course/CourseTest.java rename src/test/java/nextstep/courses/domain/{ => course/image}/ImageTest.java (97%) rename src/test/java/nextstep/courses/domain/{ => course/session}/SessionTest.java (61%) diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java similarity index 92% rename from src/main/java/nextstep/courses/domain/Course.java rename to src/main/java/nextstep/courses/domain/course/Course.java index 0c0e2d2b09..9fb31300c0 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -1,4 +1,6 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course; + +import nextstep.courses.domain.course.session.Session; import java.time.LocalDateTime; import java.util.List; diff --git a/src/main/java/nextstep/courses/domain/CourseRepository.java b/src/main/java/nextstep/courses/domain/course/CourseRepository.java similarity index 71% rename from src/main/java/nextstep/courses/domain/CourseRepository.java rename to src/main/java/nextstep/courses/domain/course/CourseRepository.java index 6aaeb638d1..28180d25e0 100644 --- a/src/main/java/nextstep/courses/domain/CourseRepository.java +++ b/src/main/java/nextstep/courses/domain/course/CourseRepository.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course; public interface CourseRepository { int save(Course course); diff --git a/src/main/java/nextstep/courses/domain/Image.java b/src/main/java/nextstep/courses/domain/course/image/Image.java similarity index 98% rename from src/main/java/nextstep/courses/domain/Image.java rename to src/main/java/nextstep/courses/domain/course/image/Image.java index cb13df352b..da9729be53 100644 --- a/src/main/java/nextstep/courses/domain/Image.java +++ b/src/main/java/nextstep/courses/domain/course/image/Image.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course.image; import java.time.LocalDateTime; import java.util.Arrays; diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java similarity index 75% rename from src/main/java/nextstep/courses/domain/Session.java rename to src/main/java/nextstep/courses/domain/course/session/Session.java index 9632f7886b..fd169978d3 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -1,15 +1,18 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course.session; +import nextstep.courses.domain.course.image.Image; import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUser; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; public class Session { private Long id; - private Long paymentId; - private Image image; private LocalDate startDate; @@ -22,7 +25,7 @@ public class Session { private int quota; - private int applyCount; + private List users = new ArrayList<>(); private Status status; @@ -30,6 +33,10 @@ public class Session { private LocalDateTime updatedAt; + public boolean isSame(Long sessionId) { + return Objects.equals(this.id, sessionId); + } + public enum Type { FREE("무료"), CHARGE("유료"); @@ -58,12 +65,12 @@ public enum Status { public Session(Image image, LocalDate startDate, LocalDate endDate, Type type, Long amount, int quota) { - this(0L, image, startDate, endDate, type, amount, quota, 0, + this(0L, image, startDate, endDate, type, amount, quota, new ArrayList(), Status.READY, LocalDateTime.now(), null); } public Session(Long id, Image image, LocalDate startDate, LocalDate endDate, - Type type, Long amount, int quota, int applyCount, + Type type, Long amount, int quota, List users, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) { if(image == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); @@ -76,31 +83,31 @@ public Session(Long id, Image image, LocalDate startDate, LocalDate endDate, this.type = type; this.amount = amount; this.quota = quota; - this.applyCount = applyCount; + this.users = users; this.status = status; this.createdAt = createdAt; this.updatedAt = updatedAt; } public int applyCount() { - return this.applyCount; + return this.users.size(); } - void applySession(Payment payment) { + void applySession(NsUser loginUser, Payment payment) { if (this.status != Status.RECRUIT) { throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); } if (this.type == Type.CHARGE) { - if (this.applyCount + 1 == quota) { + if (applyCount() + 1 > quota) { throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); } - if(payment == null || !payment.isPaid(paymentId, amount)) { + if (payment == null || !payment.isPaid(loginUser, this)) { throw new IllegalArgumentException("결제를 진행해 주세요."); } } - this.applyCount += 1; + this.users.add(loginUser); } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index f9122cbe33..7489afc31d 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -1,7 +1,7 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 0530892df8..e1eca7b51e 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -1,5 +1,8 @@ package nextstep.payments.domain; +import nextstep.courses.domain.course.session.Session; +import nextstep.users.domain.NsUser; + import java.time.LocalDateTime; public class Payment { @@ -27,7 +30,7 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.createdAt = LocalDateTime.now(); } - public boolean isPaid(Long paymentId, Long amount) { - return Long.parseLong(id) == paymentId && this.amount.equals(amount); + public boolean isPaid(NsUser nsUser, Session session) { + return nsUser.isSame(this.nsUserId) && session.isSame(this.sessionId); } } diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java index 62ec5138cd..dfe07870fc 100755 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -117,6 +117,10 @@ public boolean isGuestUser() { return false; } + public boolean isSame(Long nsUserId) { + return Objects.equals(this.id, nsUserId); + } + private static class GuestNsUser extends NsUser { @Override public boolean isGuestUser() { diff --git a/src/test/java/nextstep/courses/domain/course/CourseTest.java b/src/test/java/nextstep/courses/domain/course/CourseTest.java new file mode 100644 index 0000000000..ef655e7294 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/CourseTest.java @@ -0,0 +1,4 @@ +package nextstep.courses.domain.course; + +public class CourseTest { +} diff --git a/src/test/java/nextstep/courses/domain/ImageTest.java b/src/test/java/nextstep/courses/domain/course/image/ImageTest.java similarity index 97% rename from src/test/java/nextstep/courses/domain/ImageTest.java rename to src/test/java/nextstep/courses/domain/course/image/ImageTest.java index 2df47f65b9..9826c7ed7b 100644 --- a/src/test/java/nextstep/courses/domain/ImageTest.java +++ b/src/test/java/nextstep/courses/domain/course/image/ImageTest.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course.image; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java similarity index 61% rename from src/test/java/nextstep/courses/domain/SessionTest.java rename to src/test/java/nextstep/courses/domain/course/session/SessionTest.java index b375c8b6ef..ecf0cc1339 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -1,20 +1,37 @@ -package nextstep.courses.domain; +package nextstep.courses.domain.course.session; +import nextstep.courses.domain.course.image.Image; import nextstep.payments.domain.Payment; +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.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SessionTest { private static final Image IMAGE = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); - private static final Payment PAYMENT = new Payment("1", 1L, 1L, 1000L); + private static final Payment PAYMENT = new Payment("1", 1L, 3L, 1000L); private static final LocalDate localDate = LocalDate.of(2023, 12, 5); private static final LocalDateTime localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); + private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); + private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net"); + + private List nsUsers; + + @BeforeEach + void setUp() { + this.nsUsers = new ArrayList<>(); + this.nsUsers.add(JAVAJIGI); + this.nsUsers.add(SANJIGI); + } @Test @DisplayName("강의는 이미지가 없으면 이미지를 추가하라는 예외를 반환한다.") @@ -28,43 +45,43 @@ void newObject_imageNull_throwsException() { @DisplayName("applySession은 수강신청을 하면 수강 신청 인원이 1 증가한다.") void applySession_success() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, - 1000L, 10, 1, Session.Status.RECRUIT, localDateTime, localDateTime); + 1000L, 10, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); - session.applySession(PAYMENT); + session.applySession(APPLE, PAYMENT); - assertThat(session.applyCount()).isEqualTo(2); + assertThat(session.applyCount()).isEqualTo(3); } @Test @DisplayName("applySession은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void applySession_notRecruitStatus_throwsException() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, - 1000L, 10, 0, Session.Status.READY, localDateTime, localDateTime); + 1000L, 10, nsUsers, Session.Status.READY, localDateTime, localDateTime); assertThatThrownBy( - () -> session.applySession(PAYMENT) + () -> session.applySession(APPLE, PAYMENT) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("applySession은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다..") + @DisplayName("applySession은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") void applySession_chargeSession_overQuota_throwsException() { - Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, - 1000L, 10, 9, Session.Status.RECRUIT, localDateTime, localDateTime); + Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.CHARGE, + 1000L, 2, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( - () -> session.applySession(PAYMENT) + () -> session.applySession(APPLE, PAYMENT) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("applySession은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다..") void applySession_chargeSession_notPaid_throwsException() { - Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, - 1000L, 10, 9, Session.Status.RECRUIT, localDateTime, localDateTime); + Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.CHARGE, + 1000L, 10, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( - () -> session.applySession(null) + () -> session.applySession(APPLE, null) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index f087fc0ad2..530375d3e1 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -1,7 +1,7 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; From f68108256046fdf372c4d14cbcf807fdef21a516 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 7 Dec 2023 18:02:42 +0900 Subject: [PATCH 20/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 신청 서비스레이어를 생성합니다. 강의 유효성 검사는 강의 클래스 테스트에서 진행했으므로 정상적인 강의 신청만 테스트합니다 --- .../domain/course/session/Session.java | 6 +- .../course/session/SessionRepository.java | 7 ++ .../infrastructure/JdbcSessionRepository.java | 15 +++++ .../courses/service/SessionService.java | 25 +++++++ .../course/service/SessionServiceTest.java | 65 +++++++++++++++++++ .../domain/course/session/SessionTest.java | 26 ++++---- 6 files changed, 131 insertions(+), 13 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/session/SessionRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java create mode 100644 src/main/java/nextstep/courses/service/SessionService.java create mode 100644 src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index fd169978d3..00c3883dc6 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -89,11 +89,15 @@ public Session(Long id, Image image, LocalDate startDate, LocalDate endDate, this.updatedAt = updatedAt; } + public Long getId() { + return this.id; + } + public int applyCount() { return this.users.size(); } - void applySession(NsUser loginUser, Payment payment) { + public void apply(NsUser loginUser, Payment payment) { if (this.status != Status.RECRUIT) { throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java new file mode 100644 index 0000000000..73aa8558db --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -0,0 +1,7 @@ +package nextstep.courses.domain.course.session; + +import java.util.Optional; + +public interface SessionRepository { + Optional findById(Long id); +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java new file mode 100644 index 0000000000..016b959e55 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -0,0 +1,15 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.session.Session; +import nextstep.courses.domain.course.session.SessionRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository("sessionRepository") +public class JdbcSessionRepository implements SessionRepository { + @Override + public Optional findById(Long id) { + return Optional.empty(); + } +} diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java new file mode 100644 index 0000000000..e57d06772d --- /dev/null +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -0,0 +1,25 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.course.session.Session; +import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.payments.domain.Payment; +import nextstep.qna.NotFoundException; +import nextstep.users.domain.NsUser; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Service("sessionService") +public class SessionService { + @Resource(name = "sessionRepository") + private SessionRepository sessionRepository; + + public void applySession(NsUser loginUser, long sessionId, Payment payment) { + Session session = getSession(sessionId); + session.apply(loginUser, payment); + } + + private Session getSession(long sessionId) { + return sessionRepository.findById(sessionId).orElseThrow(NotFoundException::new); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java new file mode 100644 index 0000000000..37c5e74d32 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -0,0 +1,65 @@ +package nextstep.courses.domain.course.service; + +import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.session.Session; +import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.service.SessionService; +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class SessionServiceTest { + private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); + private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net"); + + private Image image; + private Payment payment; + private LocalDate localDate; + private LocalDateTime localDateTime; + private List users = new ArrayList<>(); + private Session session; + + @Mock + private SessionRepository sessionRepository; + + @InjectMocks + private SessionService sessionService; + + @BeforeEach + public void setUp() { + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + payment = new Payment("1", 1L, 3L, 1000L); + localDate = LocalDate.of(2023, 12, 5); + localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + users.add(JAVAJIGI); + session = new Session(1L, image, localDate, localDate, Session.Type.FREE, + 1000L, 10, users, Session.Status.RECRUIT, localDateTime, localDateTime); + } + + @Test + @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") + void apply_success() { + when(sessionRepository.findById(session.getId())).thenReturn(Optional.of(session)); + + assertThat(session.applyCount()).isEqualTo(1); + sessionService.applySession(APPLE, session.getId(), payment); + + assertThat(session.applyCount()).isEqualTo(2); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index ecf0cc1339..143de9c290 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -42,46 +42,48 @@ void newObject_imageNull_throwsException() { } @Test - @DisplayName("applySession은 수강신청을 하면 수강 신청 인원이 1 증가한다.") - void applySession_success() { + @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") + void apply_success() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, 1000L, 10, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); - session.applySession(APPLE, PAYMENT); + assertThat(session.applyCount()).isEqualTo(2); + + session.apply(APPLE, PAYMENT); assertThat(session.applyCount()).isEqualTo(3); } @Test - @DisplayName("applySession은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") - void applySession_notRecruitStatus_throwsException() { + @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") + void apply_notRecruitStatus_throwsException() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, 1000L, 10, nsUsers, Session.Status.READY, localDateTime, localDateTime); assertThatThrownBy( - () -> session.applySession(APPLE, PAYMENT) + () -> session.apply(APPLE, PAYMENT) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("applySession은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") - void applySession_chargeSession_overQuota_throwsException() { + @DisplayName("수강 신청은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") + void apply_chargeSession_overQuota_throwsException() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.CHARGE, 1000L, 2, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( - () -> session.applySession(APPLE, PAYMENT) + () -> session.apply(APPLE, PAYMENT) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("applySession은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다..") - void applySession_chargeSession_notPaid_throwsException() { + @DisplayName("수강 신청은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다.") + void apply_chargeSession_notPaid_throwsException() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.CHARGE, 1000L, 10, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( - () -> session.applySession(APPLE, null) + () -> session.apply(APPLE, null) ).isInstanceOf(IllegalArgumentException.class); } } From 95dcaa483636dc1bcd01dbfc513ab11124856071 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 7 Dec 2023 18:35:37 +0900 Subject: [PATCH 21/62] =?UTF-8?q?[refactor]=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=EC=9D=84=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이미지, 강의 도메인 생성자의 유효성 검사를 메서드로 분리합니다 --- .../courses/domain/course/image/Image.java | 66 ++++++------------- .../courses/domain/course/image/Type.java | 38 +++++++++++ .../domain/course/session/Session.java | 33 +++++++--- 3 files changed, 82 insertions(+), 55 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/image/Type.java diff --git a/src/main/java/nextstep/courses/domain/course/image/Image.java b/src/main/java/nextstep/courses/domain/course/image/Image.java index da9729be53..93547a644d 100644 --- a/src/main/java/nextstep/courses/domain/course/image/Image.java +++ b/src/main/java/nextstep/courses/domain/course/image/Image.java @@ -1,7 +1,6 @@ package nextstep.courses.domain.course.image; import java.time.LocalDateTime; -import java.util.Arrays; public class Image { public static final int MB = 1024 * 1024; @@ -23,41 +22,6 @@ public class Image { private LocalDateTime updatedAt; - public enum Type { - GIF("gif"), - JPG("jpg"), - JPEG("jpeg"), - PNG("png"), - SVG("svg"); - - Type(String description) { - this.description = description; - } - - private final String description; - - public static Type find(String name) { - return Arrays.stream(values()) - .filter(imageType -> imageType.description.equals(name)) - .findAny() - .orElseThrow( - () -> new IllegalArgumentException( - String.format("허용하는 확장자는 다음과 같습니다.\n %s", descriptions()) - ) - ); - } - - public static String descriptions() { - StringBuilder sb = new StringBuilder(); - for (Type type : values()) { - sb.append(type.description).append(", "); - } - sb.setLength(sb.length() - 2); - - return sb.toString(); - } - } - public Image() { } @@ -67,26 +31,36 @@ public Image(int imageSize, String type, int imageWidth, int imageHeight) { public Image(Long id, int imageSize, String type, int imageWidth, int imageHeight, LocalDateTime createdAt, LocalDateTime updatedAt) { + checkImageSizeIsValid(imageSize); + checkWidthAndHeightSizeIsValid(imageWidth, imageHeight); + checkWidthAndHeightRatioIsValid(imageWidth, imageHeight); + + this.id = id; + this.imageSize = imageSize; + this.type = Type.find(type); + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + private static void checkImageSizeIsValid(int imageSize) { if (imageSize > 1 * MB) { throw new IllegalArgumentException("사진 크기는 1MB를 넘을 수 없습니다."); } + } - if(imageWidth < WIDTH_MIN || imageHeight < HEIGHT_MIN) { + private static void checkWidthAndHeightSizeIsValid(int imageWidth, int imageHeight) { + if (imageWidth < WIDTH_MIN || imageHeight < HEIGHT_MIN) { throw new IllegalArgumentException( String.format("가로 픽셀은 %d, 세로 픽셀은 %d 이상이어야 합니다.", WIDTH_MIN, HEIGHT_MIN) ); } + } - if((double) imageWidth / imageHeight != WIDTH_HEIGHT_RATIO) { + private static void checkWidthAndHeightRatioIsValid(int imageWidth, int imageHeight) { + if ((double) imageWidth / imageHeight != WIDTH_HEIGHT_RATIO) { throw new IllegalArgumentException("가로 세로 비율은 3:2여야 합니다."); } - - this.id = id; - this.imageSize = imageSize; - this.type = Type.find(type); - this.imageWidth = imageWidth; - this.imageHeight = imageHeight; - this.createdAt = createdAt; - this.updatedAt = updatedAt; } } diff --git a/src/main/java/nextstep/courses/domain/course/image/Type.java b/src/main/java/nextstep/courses/domain/course/image/Type.java new file mode 100644 index 0000000000..97cffe394f --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/image/Type.java @@ -0,0 +1,38 @@ +package nextstep.courses.domain.course.image; + +import java.util.Arrays; + +public enum Type { + GIF("gif"), + JPG("jpg"), + JPEG("jpeg"), + PNG("png"), + SVG("svg"); + + Type(String description) { + this.description = description; + } + + private final String description; + + public static Type find(String name) { + return Arrays.stream(values()) + .filter(imageType -> imageType.description.equals(name)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException( + String.format("허용하는 확장자는 다음과 같습니다.\n %s", descriptions()) + ) + ); + } + + public static String descriptions() { + StringBuilder sb = new StringBuilder(); + for (Type type : values()) { + sb.append(type.description).append(", "); + } + sb.setLength(sb.length() - 2); + + return sb.toString(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 00c3883dc6..e7268f6810 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -98,20 +98,35 @@ public int applyCount() { } public void apply(NsUser loginUser, Payment payment) { + checkStatusOnRecruit(); + + if (typeCharged()) { + checkApplySizeIsValid(); + checkPaymentIsPaid(loginUser, payment); + } + + this.users.add(loginUser); + } + + private boolean typeCharged() { + return this.type == Type.CHARGE; + } + + private void checkStatusOnRecruit() { if (this.status != Status.RECRUIT) { throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); } + } - if (this.type == Type.CHARGE) { - if (applyCount() + 1 > quota) { - throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); - } - - if (payment == null || !payment.isPaid(loginUser, this)) { - throw new IllegalArgumentException("결제를 진행해 주세요."); - } + private void checkApplySizeIsValid() { + if (applyCount() + 1 > quota) { + throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); } + } - this.users.add(loginUser); + private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { + if (payment == null || !payment.isPaid(loginUser, this)) { + throw new IllegalArgumentException("결제를 진행해 주세요."); + } } } From e75bbd3e94ce7830aba7922d4ff8b274dd35e29c Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 7 Dec 2023 18:52:16 +0900 Subject: [PATCH 22/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=EC=9E=90=EB=A5=BC=20=EC=9D=BC=EA=B8=89=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=EC=9C=BC=EB=A1=9C=20=EA=B0=90=EC=8B=B8?= =?UTF-8?q?=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 신청자들을 일급 컬렉션으로 감싸도록 개선합니다 --- .../courses/domain/course/Course.java | 3 +- .../domain/course/session/Applicants.java | 32 +++++++++++++++++++ .../domain/course/session/Session.java | 12 +++---- .../domain/course/session/Sessions.java | 29 +++++++++++++++++ .../course/service/SessionServiceTest.java | 9 +++--- .../domain/course/session/SessionTest.java | 18 +++++------ .../domain/course/session/SessionsTest.java | 20 ++++++++++++ 7 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/session/Applicants.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/Sessions.java create mode 100644 src/test/java/nextstep/courses/domain/course/session/SessionsTest.java diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 9fb31300c0..eb28d83cdd 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course; import nextstep.courses.domain.course.session.Session; +import nextstep.courses.domain.course.session.Sessions; import java.time.LocalDateTime; import java.util.List; @@ -12,7 +13,7 @@ public class Course { private Long creatorId; - private List sessions; + private Sessions sessions; private LocalDateTime createdAt; diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java new file mode 100644 index 0000000000..9a6fe6b8af --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/Applicants.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.course.session; + +import nextstep.users.domain.NsUser; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class Applicants implements Iterable { + private final List applicants; + + public Applicants() { + this(new ArrayList()); + } + + public Applicants(List applicants) { + this.applicants = applicants; + } + + public int size() { + return this.applicants.size(); + } + + public void add(NsUser applicant) { + this.applicants.add(applicant); + } + + @Override + public Iterator iterator() { + return this.applicants.iterator(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index e7268f6810..aa62a598f2 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -25,7 +25,7 @@ public class Session { private int quota; - private List users = new ArrayList<>(); + private Applicants applicants = new Applicants(); private Status status; @@ -65,12 +65,12 @@ public enum Status { public Session(Image image, LocalDate startDate, LocalDate endDate, Type type, Long amount, int quota) { - this(0L, image, startDate, endDate, type, amount, quota, new ArrayList(), + this(0L, image, startDate, endDate, type, amount, quota, new Applicants(), Status.READY, LocalDateTime.now(), null); } public Session(Long id, Image image, LocalDate startDate, LocalDate endDate, - Type type, Long amount, int quota, List users, + Type type, Long amount, int quota, Applicants applicants, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) { if(image == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); @@ -83,7 +83,7 @@ public Session(Long id, Image image, LocalDate startDate, LocalDate endDate, this.type = type; this.amount = amount; this.quota = quota; - this.users = users; + this.applicants = applicants; this.status = status; this.createdAt = createdAt; this.updatedAt = updatedAt; @@ -94,7 +94,7 @@ public Long getId() { } public int applyCount() { - return this.users.size(); + return this.applicants.size(); } public void apply(NsUser loginUser, Payment payment) { @@ -105,7 +105,7 @@ public void apply(NsUser loginUser, Payment payment) { checkPaymentIsPaid(loginUser, payment); } - this.users.add(loginUser); + this.applicants.add(loginUser); } private boolean typeCharged() { diff --git a/src/main/java/nextstep/courses/domain/course/session/Sessions.java b/src/main/java/nextstep/courses/domain/course/session/Sessions.java new file mode 100644 index 0000000000..551a2d3e75 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/Sessions.java @@ -0,0 +1,29 @@ +package nextstep.courses.domain.course.session; + +import java.util.Iterator; +import java.util.List; + +public class Sessions implements Iterable { + private final List sessions; + + public Sessions(List sessions) { + validate(sessions); + + this.sessions = sessions; + } + + private void validate(List sessions) { + checkSessionsSizeIsValid(sessions); + } + + private void checkSessionsSizeIsValid(List sessions) { + if(sessions == null) { + throw new IllegalArgumentException("강의는 빈 값이 아니어야 합니다."); + } + } + + @Override + public Iterator iterator() { + return null; + } +} diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 37c5e74d32..f1d6a3fc8e 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course.service; import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.session.Applicants; import nextstep.courses.domain.course.session.Session; import nextstep.courses.domain.course.session.SessionRepository; import nextstep.courses.service.SessionService; @@ -16,8 +17,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -32,7 +31,7 @@ public class SessionServiceTest { private Payment payment; private LocalDate localDate; private LocalDateTime localDateTime; - private List users = new ArrayList<>(); + private Applicants applicants = new Applicants(); private Session session; @Mock @@ -47,9 +46,9 @@ public void setUp() { payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - users.add(JAVAJIGI); + applicants.add(JAVAJIGI); session = new Session(1L, image, localDate, localDate, Session.Type.FREE, - 1000L, 10, users, Session.Status.RECRUIT, localDateTime, localDateTime); + 1000L, 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 143de9c290..393fb90b89 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -9,8 +9,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -24,13 +22,13 @@ public class SessionTest { private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net"); - private List nsUsers; + private Applicants applicants; @BeforeEach void setUp() { - this.nsUsers = new ArrayList<>(); - this.nsUsers.add(JAVAJIGI); - this.nsUsers.add(SANJIGI); + this.applicants = new Applicants(); + this.applicants.add(JAVAJIGI); + this.applicants.add(SANJIGI); } @Test @@ -45,7 +43,7 @@ void newObject_imageNull_throwsException() { @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, - 1000L, 10, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); + 1000L, 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThat(session.applyCount()).isEqualTo(2); @@ -58,7 +56,7 @@ void apply_success() { @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, - 1000L, 10, nsUsers, Session.Status.READY, localDateTime, localDateTime); + 1000L, 10, applicants, Session.Status.READY, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, PAYMENT) @@ -69,7 +67,7 @@ void apply_notRecruitStatus_throwsException() { @DisplayName("수강 신청은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_overQuota_throwsException() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.CHARGE, - 1000L, 2, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); + 1000L, 2, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, PAYMENT) @@ -80,7 +78,7 @@ void apply_chargeSession_overQuota_throwsException() { @DisplayName("수강 신청은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_notPaid_throwsException() { Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.CHARGE, - 1000L, 10, nsUsers, Session.Status.RECRUIT, localDateTime, localDateTime); + 1000L, 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, null) diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java new file mode 100644 index 0000000000..4c319ec1db --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -0,0 +1,20 @@ +package nextstep.courses.domain.course.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class SessionsTest { + @ParameterizedTest + @NullSource + @DisplayName("Sessions 은 빈 값이 주어지면 예외를 던진다.") + void newObject_empty_throwsException(List sessions) { + assertThatThrownBy( + () -> new Sessions(sessions) + ).isInstanceOf(IllegalArgumentException.class); + } +} From c30d2944f383369c22e69a42e1e57fb00f516908 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 7 Dec 2023 18:54:53 +0900 Subject: [PATCH 23/62] =?UTF-8?q?[test]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이미지 타입 조회 시, 존재하지 않는 타입의 경우 예외를 던지는 테스트를 추가합니다 --- .../courses/domain/course/image/TypeTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/java/nextstep/courses/domain/course/image/TypeTest.java diff --git a/src/test/java/nextstep/courses/domain/course/image/TypeTest.java b/src/test/java/nextstep/courses/domain/course/image/TypeTest.java new file mode 100644 index 0000000000..0aff843b75 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/image/TypeTest.java @@ -0,0 +1,17 @@ +package nextstep.courses.domain.course.image; + +import nextstep.courses.domain.course.session.Sessions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class TypeTest { + @Test + @DisplayName("find는 존재하지 않는 값을 입력하면 찾을 수 없다는 예외를 던진다.") + void find_notExistedName_throwsException() { + assertThatThrownBy( + () -> Type.find("abcd") + ).isInstanceOf(IllegalArgumentException.class); + } +} From fd6e1047198d99ed9cb1d02f8010258f3005e2ff Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 7 Dec 2023 19:16:33 +0900 Subject: [PATCH 24/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=EA=B3=BC=20=EC=A2=85=EB=A3=8C=EB=82=A0=EC=A7=9C?= =?UTF-8?q?=EB=A5=BC=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 시작과 종료날짜를 클래스로 분리하여 강의기간 생성과 유효성 검사를 책임질 수 있도록 합니다 --- .../courses/domain/course/Course.java | 2 - .../domain/course/session/Duration.java | 32 ++++++++ .../domain/course/session/Session.java | 76 +++++++++---------- .../course/service/SessionServiceTest.java | 7 +- .../domain/course/session/DurationTest.java | 42 ++++++++++ .../domain/course/session/SessionTest.java | 52 ++++++++----- .../domain/course/session/SessionsTest.java | 2 +- 7 files changed, 151 insertions(+), 62 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/session/Duration.java create mode 100644 src/test/java/nextstep/courses/domain/course/session/DurationTest.java diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index eb28d83cdd..9181ecdbf5 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -1,10 +1,8 @@ package nextstep.courses.domain.course; -import nextstep.courses.domain.course.session.Session; import nextstep.courses.domain.course.session.Sessions; import java.time.LocalDateTime; -import java.util.List; public class Course { private Long id; diff --git a/src/main/java/nextstep/courses/domain/course/session/Duration.java b/src/main/java/nextstep/courses/domain/course/session/Duration.java new file mode 100644 index 0000000000..2a5b29778d --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/Duration.java @@ -0,0 +1,32 @@ +package nextstep.courses.domain.course.session; + +import java.time.LocalDate; + +public class Duration { + private LocalDate startDate; + + private LocalDate endDate; + + public Duration(LocalDate startDate, LocalDate endDate) { + validate(startDate, endDate); + this.startDate = startDate; + this.endDate = endDate; + } + + private void validate(LocalDate startDate, LocalDate endDate) { + checkStartDateOrEndDateNull(startDate, endDate); + checkDurationIsValid(startDate, endDate); + } + + private void checkStartDateOrEndDateNull(LocalDate startDate, LocalDate endDate) { + if (startDate == null || endDate == null) { + throw new IllegalArgumentException("시작 날짜와 종료 일자를 모두 입력해주세요."); + } + } + + private void checkDurationIsValid(LocalDate startDate, LocalDate endDate) { + if (startDate.isAfter(endDate)) { + throw new IllegalArgumentException("시작 날짜가 종료 날짜보다 늦을 수 없습니다."); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index aa62a598f2..e7ca441e90 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -4,10 +4,7 @@ import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; -import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; public class Session { @@ -15,9 +12,7 @@ public class Session { private Image image; - private LocalDate startDate; - - private LocalDate endDate; + private Duration duration; private Type type; @@ -33,53 +28,29 @@ public class Session { private LocalDateTime updatedAt; - public boolean isSame(Long sessionId) { - return Objects.equals(this.id, sessionId); - } - - public enum Type { - FREE("무료"), - CHARGE("유료"); - - Type(String description) { - this.description = description; - } - - private final String description; - } - public Session() { } - public enum Status { - READY("준비중"), - RECRUIT("모집중"), - END("종료"); - - Status(String description) { - this.description = description; - } - - private final String description; - } - - public Session(Image image, LocalDate startDate, LocalDate endDate, + public Session(Image image, Duration duration, Type type, Long amount, int quota) { - this(0L, image, startDate, endDate, type, amount, quota, new Applicants(), + this(0L, image, duration, type, amount, quota, new Applicants(), Status.READY, LocalDateTime.now(), null); } - public Session(Long id, Image image, LocalDate startDate, LocalDate endDate, + public Session(Long id, Image image, Duration duration, Type type, Long amount, int quota, Applicants applicants, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) { - if(image == null) { + if (image == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); } + if (duration == null) { + throw new IllegalArgumentException("기간을 추가해야 합니다."); + } + this.id = id; this.image = image; - this.startDate = startDate; - this.endDate = endDate; + this.duration = duration; this.type = type; this.amount = amount; this.quota = quota; @@ -89,6 +60,10 @@ public Session(Long id, Image image, LocalDate startDate, LocalDate endDate, this.updatedAt = updatedAt; } + public boolean isSame(Long sessionId) { + return Objects.equals(this.id, sessionId); + } + public Long getId() { return this.id; } @@ -129,4 +104,27 @@ private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { throw new IllegalArgumentException("결제를 진행해 주세요."); } } + + public enum Type { + FREE("무료"), + CHARGE("유료"); + + private final String description; + + Type(String description) { + this.description = description; + } + } + + public enum Status { + READY("준비중"), + RECRUIT("모집중"), + END("종료"); + + private final String description; + + Status(String description) { + this.description = description; + } + } } diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index f1d6a3fc8e..9ba41fbd4e 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -2,6 +2,7 @@ import nextstep.courses.domain.course.image.Image; import nextstep.courses.domain.course.session.Applicants; +import nextstep.courses.domain.course.session.Duration; import nextstep.courses.domain.course.session.Session; import nextstep.courses.domain.course.session.SessionRepository; import nextstep.courses.service.SessionService; @@ -32,6 +33,7 @@ public class SessionServiceTest { private LocalDate localDate; private LocalDateTime localDateTime; private Applicants applicants = new Applicants(); + private Duration duration; private Session session; @Mock @@ -47,8 +49,9 @@ public void setUp() { localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); applicants.add(JAVAJIGI); - session = new Session(1L, image, localDate, localDate, Session.Type.FREE, - 1000L, 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + duration = new Duration(localDate, localDate); + session = new Session(1L, image, duration, Session.Type.FREE, 1000L, + 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/session/DurationTest.java b/src/test/java/nextstep/courses/domain/course/session/DurationTest.java new file mode 100644 index 0000000000..9b0e19ff18 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/DurationTest.java @@ -0,0 +1,42 @@ +package nextstep.courses.domain.course.session; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class DurationTest { + private LocalDate localDate; + + @BeforeEach + void setUp() { + localDate = LocalDate.of(2023, 12, 5); + } + + @Test + @DisplayName("Duration 은 시작 혹은 종료 날짜에 빈 값이 주어지면 예외를 던진다.") + void newObject_nullAndEmpty_throwsException() { + assertThatThrownBy( + () -> new Duration(null, null) + ).isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy( + () -> new Duration(localDate, null) + ).isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy( + () -> new Duration(null, localDate) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("Duration 은 시작 날짜가 종료날짜보다 늦으면 예외를 던진다.") + void newObject_startDateIsAfterBeforeDate_throwsException() { + assertThatThrownBy( + () -> new Duration(localDate.plusDays(1), localDate) + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 393fb90b89..3acc79ccb2 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -14,40 +14,56 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SessionTest { - private static final Image IMAGE = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); - private static final Payment PAYMENT = new Payment("1", 1L, 3L, 1000L); - private static final LocalDate localDate = LocalDate.of(2023, 12, 5); - private static final LocalDateTime localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net"); - private Applicants applicants; + private Image image; + private Payment payment; + private LocalDate localDate; + private LocalDateTime localDateTime; + private Applicants applicants = new Applicants(); + private Duration duration; + private Session session; @BeforeEach void setUp() { - this.applicants = new Applicants(); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + payment = new Payment("1", 1L, 3L, 1000L); + localDate = LocalDate.of(2023, 12, 5); + localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); this.applicants.add(JAVAJIGI); this.applicants.add(SANJIGI); + duration = new Duration(localDate, localDate); + session = new Session(1L, image, duration, Session.Type.FREE, 1000L, + 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); } @Test @DisplayName("강의는 이미지가 없으면 이미지를 추가하라는 예외를 반환한다.") void newObject_imageNull_throwsException() { assertThatThrownBy( - () -> new Session(null, localDate, localDate, Session.Type.FREE, 1000L, 10) + () -> new Session(null, duration, Session.Type.FREE, 1000L, 10) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("강의는 기간이 없으면 기간을 추가하라는 예외를 반환한다.") + void newObject_durationNull_throwsException() { + assertThatThrownBy( + () -> new Session(image, null, Session.Type.FREE, 1000L, 10) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { - Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, - 1000L, 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + Session session = new Session(1L, image, duration, Session.Type.FREE, 1000L, + 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThat(session.applyCount()).isEqualTo(2); - session.apply(APPLE, PAYMENT); + session.apply(APPLE, payment); assertThat(session.applyCount()).isEqualTo(3); } @@ -55,30 +71,30 @@ void apply_success() { @Test @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { - Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.FREE, - 1000L, 10, applicants, Session.Status.READY, localDateTime, localDateTime); + Session session = new Session(1L, image, duration, Session.Type.FREE, 1000L, + 10, applicants, Session.Status.READY, localDateTime, localDateTime); assertThatThrownBy( - () -> session.apply(APPLE, PAYMENT) + () -> session.apply(APPLE, payment) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("수강 신청은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_overQuota_throwsException() { - Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.CHARGE, - 1000L, 2, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, + 2, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( - () -> session.apply(APPLE, PAYMENT) + () -> session.apply(APPLE, payment) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("수강 신청은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_notPaid_throwsException() { - Session session = new Session(1L, IMAGE, localDate, localDate, Session.Type.CHARGE, - 1000L, 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, + 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, null) diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index 4c319ec1db..f22cc7ce37 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -12,7 +12,7 @@ public class SessionsTest { @ParameterizedTest @NullSource @DisplayName("Sessions 은 빈 값이 주어지면 예외를 던진다.") - void newObject_empty_throwsException(List sessions) { + void newObject_null_throwsException(List sessions) { assertThatThrownBy( () -> new Sessions(sessions) ).isInstanceOf(IllegalArgumentException.class); From ac45f24353be55ceb7b6e278524b15a60c85e6fb Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 7 Dec 2023 20:11:17 +0900 Subject: [PATCH 25/62] =?UTF-8?q?[style]=20=EA=B0=95=EC=9D=98=EC=9D=98=20e?= =?UTF-8?q?num=20=EC=9C=84=EC=B9=98=EB=A5=BC=20=EC=A1=B0=EC=A0=95=ED=95=98?= =?UTF-8?q?=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변수 바로 아래에 내부 enum을 위치시키도록 조정합니다 --- .../domain/course/session/Session.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index e7ca441e90..eaae9f608c 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -20,7 +20,7 @@ public class Session { private int quota; - private Applicants applicants = new Applicants(); + private Applicants applicants; private Status status; @@ -28,6 +28,29 @@ public class Session { private LocalDateTime updatedAt; + public enum Type { + FREE("무료"), + CHARGE("유료"); + + private final String description; + + Type(String description) { + this.description = description; + } + } + + public enum Status { + READY("준비중"), + RECRUIT("모집중"), + END("종료"); + + private final String description; + + Status(String description) { + this.description = description; + } + } + public Session() { } @@ -104,27 +127,4 @@ private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { throw new IllegalArgumentException("결제를 진행해 주세요."); } } - - public enum Type { - FREE("무료"), - CHARGE("유료"); - - private final String description; - - Type(String description) { - this.description = description; - } - } - - public enum Status { - READY("준비중"), - RECRUIT("모집중"), - END("종료"); - - private final String description; - - Status(String description) { - this.description = description; - } - } } From 740212cfdd07b5afd48322f327cf15c4f1209380 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Fri, 8 Dec 2023 18:58:39 +0900 Subject: [PATCH 26/62] =?UTF-8?q?[feat]=20=EA=B3=BC=EC=A0=95=EC=97=90=20?= =?UTF-8?q?=EA=B0=95=EC=9D=98=20=EC=B6=94=EA=B0=80=EB=A5=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 과정에 강의가 새롭게 추가되므로 강의를 추가하는 로직을 만듭니다 --- .../courses/domain/course/Course.java | 16 ++++- .../domain/course/session/Sessions.java | 15 +++- .../courses/service/CourseService.java | 23 +++++++ .../course/service/CourseServiceTest.java | 68 +++++++++++++++++++ 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/main/java/nextstep/courses/service/CourseService.java create mode 100644 src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 9181ecdbf5..6913282e93 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.course; +import nextstep.courses.domain.course.session.Session; import nextstep.courses.domain.course.session.Sessions; import java.time.LocalDateTime; @@ -11,7 +12,7 @@ public class Course { private Long creatorId; - private Sessions sessions; + private Sessions sessions = new Sessions(); private LocalDateTime createdAt; @@ -30,6 +31,19 @@ public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, Lo this.creatorId = creatorId; this.createdAt = createdAt; this.updatedAt = updatedAt; + this.sessions = new Sessions(); + } + + public int sessionSize() { + return this.sessions.size(); + } + + public void addSession(Session session) { + this.sessions.add(session); + } + + public Long getId() { + return id; } public String getTitle() { diff --git a/src/main/java/nextstep/courses/domain/course/session/Sessions.java b/src/main/java/nextstep/courses/domain/course/session/Sessions.java index 551a2d3e75..0879f6bf5d 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Sessions.java +++ b/src/main/java/nextstep/courses/domain/course/session/Sessions.java @@ -1,11 +1,16 @@ package nextstep.courses.domain.course.session; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Sessions implements Iterable { private final List sessions; + public Sessions() { + this(new ArrayList<>()); + } + public Sessions(List sessions) { validate(sessions); @@ -22,8 +27,16 @@ private void checkSessionsSizeIsValid(List sessions) { } } + public int size() { + return this.sessions.size(); + } + + public void add(Session session) { + this.sessions.add(session); + } + @Override public Iterator iterator() { - return null; + return this.sessions.iterator(); } } diff --git a/src/main/java/nextstep/courses/service/CourseService.java b/src/main/java/nextstep/courses/service/CourseService.java new file mode 100644 index 0000000000..07e590a346 --- /dev/null +++ b/src/main/java/nextstep/courses/service/CourseService.java @@ -0,0 +1,23 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.domain.course.session.Session; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Service("courseService") +public class CourseService { + @Resource(name = "courseRepository") + private CourseRepository courseRepository; + + public void addSession(long courseId, Session session) { + Course course = getCourse(courseId); + course.addSession(session); + } + + private Course getCourse(long courseId) { + return courseRepository.findById(courseId); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java new file mode 100644 index 0000000000..1fe8be0efb --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -0,0 +1,68 @@ +package nextstep.courses.domain.course.service; + +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.session.Applicants; +import nextstep.courses.domain.course.session.Duration; +import nextstep.courses.domain.course.session.Session; +import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.service.CourseService; +import nextstep.courses.service.SessionService; +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CourseServiceTest { + private Image image; + private Payment payment; + private LocalDate localDate; + private LocalDateTime localDateTime; + private Duration duration; + private Course course; + private Session session; + + @BeforeEach + void setUp() { + course = new Course("math", 1L); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + payment = new Payment("1", 1L, 3L, 1000L); + localDate = LocalDate.of(2023, 12, 5); + localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + duration = new Duration(localDate, localDate); + session = new Session(1L, image, duration, Session.Type.FREE, 1000L, + 10, new Applicants(), Session.Status.RECRUIT, localDateTime, localDateTime); + } + + @Mock + private SessionRepository sessionRepository; + + @Mock + private CourseRepository courseRepository; + + @InjectMocks + private CourseService courseService; + + @Test + @DisplayName("주어진 강의를 과정에 추가하면 과정에 강의가 추가된다.") + void addSession_success() { + when(courseRepository.findById(course.getId())).thenReturn(course); + assertThat(course.sessionSize()).isEqualTo(0); + + courseService.addSession(course.getId(), session); + assertThat(course.sessionSize()).isEqualTo(1); + } +} From 967ae5b003e8efee01b150fac11529e71355165a Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Fri, 8 Dec 2023 19:12:09 +0900 Subject: [PATCH 27/62] =?UTF-8?q?[feat]=20=EC=A4=91=EB=B3=B5=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 과정에 강의가 중복 추가되거나, 강의에 수강생이 중복 신청되는 경우 이미 추가가 되었다는 예외를 추가합니다 --- .../domain/course/session/Applicants.java | 7 ++++ .../domain/course/session/Sessions.java | 7 ++++ .../domain/course/session/ApplicantsTest.java | 30 ++++++++++++++++ .../domain/course/session/SessionsTest.java | 35 +++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java index 9a6fe6b8af..e143f43ef6 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Applicants.java +++ b/src/main/java/nextstep/courses/domain/course/session/Applicants.java @@ -22,9 +22,16 @@ public int size() { } public void add(NsUser applicant) { + checkApplicantAlreadyExisted(applicant); this.applicants.add(applicant); } + private void checkApplicantAlreadyExisted(NsUser applicant) { + if (this.applicants.contains(applicant)) { + throw new IllegalArgumentException("이미 강의를 신청하였습니다."); + } + } + @Override public Iterator iterator() { return this.applicants.iterator(); diff --git a/src/main/java/nextstep/courses/domain/course/session/Sessions.java b/src/main/java/nextstep/courses/domain/course/session/Sessions.java index 0879f6bf5d..3e5d36a0f0 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Sessions.java +++ b/src/main/java/nextstep/courses/domain/course/session/Sessions.java @@ -32,9 +32,16 @@ public int size() { } public void add(Session session) { + checkSessionAlreadyExisted(session); this.sessions.add(session); } + private void checkSessionAlreadyExisted(Session session) { + if (this.sessions.contains(session)) { + throw new IllegalArgumentException("이미 강의를 추가하였습니다."); + } + } + @Override public Iterator iterator() { return this.sessions.iterator(); diff --git a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java new file mode 100644 index 0000000000..c64f5facc6 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java @@ -0,0 +1,30 @@ +package nextstep.courses.domain.course.session; + +import nextstep.users.domain.NsUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ApplicantsTest { + private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); + private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); + + Applicants applicants; + + @BeforeEach + void setUp() { + applicants = new Applicants(); + applicants.add(JAVAJIGI); + applicants.add(SANJIGI); + } + + @Test + @DisplayName("add 는 이미 수강생이 강의를 신청하였으면 예외를 던진다.") + void add_alreadyExistedApplicant_throwsException() { + assertThatThrownBy( + () -> applicants.add(JAVAJIGI) + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index f22cc7ce37..221c332636 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -1,14 +1,41 @@ package nextstep.courses.domain.course.session; +import nextstep.courses.domain.course.image.Image; +import nextstep.payments.domain.Payment; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SessionsTest { + private Sessions sessions; + private Image image; + private Payment payment; + private LocalDate localDate; + private LocalDateTime localDateTime; + private Duration duration; + private Session session; + + @BeforeEach + void setUp() { + sessions = new Sessions(); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + payment = new Payment("1", 1L, 3L, 1000L); + localDate = LocalDate.of(2023, 12, 5); + localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + duration = new Duration(localDate, localDate); + session = new Session(1L, image, duration, Session.Type.FREE, 1000L, + 10, new Applicants(), Session.Status.RECRUIT, localDateTime, localDateTime); + sessions.add(session); + } + @ParameterizedTest @NullSource @DisplayName("Sessions 은 빈 값이 주어지면 예외를 던진다.") @@ -17,4 +44,12 @@ void newObject_null_throwsException(List sessions) { () -> new Sessions(sessions) ).isInstanceOf(IllegalArgumentException.class); } + + @Test + @DisplayName("add 는 이미 강의가 추가되었으면 예외를 던진다.") + void add_alreadyExistedSession_throwsException() { + assertThatThrownBy( + () -> sessions.add(session) + ).isInstanceOf(IllegalArgumentException.class); + } } From 51396ae161181ce6afdd6bf4f2dd13ab3cd26717 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Fri, 8 Dec 2023 19:29:45 +0900 Subject: [PATCH 28/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=EA=B3=BC=20=EC=A7=80=EB=B6=88=EB=82=B4=EC=97=AD?= =?UTF-8?q?=EC=9D=B4=20=EB=8B=A4=EB=A5=B8=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 금액과 지불내역이 다르면 수강신청을 할 수 없습니다. 해당 유효성과 테스트를 추가합니다 --- .../courses/domain/course/session/Session.java | 6 +++++- src/main/java/nextstep/payments/domain/Payment.java | 4 +++- .../courses/domain/course/session/SessionTest.java | 13 +++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index eaae9f608c..4ffde6fbaf 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -83,7 +83,11 @@ public Session(Long id, Image image, Duration duration, this.updatedAt = updatedAt; } - public boolean isSame(Long sessionId) { + public boolean sameAmount(Long amount) { + return Objects.equals(this.amount, amount); + } + + public boolean sameId(Long sessionId) { return Objects.equals(this.id, sessionId); } diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index e1eca7b51e..a1a15689cf 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -31,6 +31,8 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { } public boolean isPaid(NsUser nsUser, Session session) { - return nsUser.isSame(this.nsUserId) && session.isSame(this.sessionId); + return nsUser.isSame(this.nsUserId) + && session.sameId(this.sessionId) + && session.sameAmount(this.amount); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 3acc79ccb2..918509569d 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -20,6 +20,7 @@ public class SessionTest { private Image image; private Payment payment; + private Payment differentPayment; private LocalDate localDate; private LocalDateTime localDateTime; private Applicants applicants = new Applicants(); @@ -30,6 +31,7 @@ public class SessionTest { void setUp() { image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); payment = new Payment("1", 1L, 3L, 1000L); + differentPayment = new Payment("1", 1L, 3L, 500L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); this.applicants.add(JAVAJIGI); @@ -100,4 +102,15 @@ void apply_chargeSession_notPaid_throwsException() { () -> session.apply(APPLE, null) ).isInstanceOf(IllegalArgumentException.class); } + + @Test + @DisplayName("수강 신청은 수강 금액과 지불 금액이 다르면 신청할 수 없다는 예외를 던진다.") + void apply_chargeSession_differentAmount_throwsException() { + Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, + 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.apply(APPLE, differentPayment) + ).isInstanceOf(IllegalArgumentException.class); + } } From 7bc23b2ca6998b552207d0d8ebfc50c7a4ab97a7 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Fri, 8 Dec 2023 20:16:01 +0900 Subject: [PATCH 29/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=88=98=EA=B0=95=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=9C=A0=ED=9A=A8=EC=84=B1=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 준비와 모집은 시작일 전에 가능하며, 강의 종료는 종료일 이후에 가능하다는 유효성을 추가합니다 --- .../domain/course/session/Duration.java | 8 +++ .../domain/course/session/Session.java | 29 +++++++++++ .../domain/course/session/SessionTest.java | 52 +++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/src/main/java/nextstep/courses/domain/course/session/Duration.java b/src/main/java/nextstep/courses/domain/course/session/Duration.java index 2a5b29778d..590429ae98 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Duration.java +++ b/src/main/java/nextstep/courses/domain/course/session/Duration.java @@ -29,4 +29,12 @@ private void checkDurationIsValid(LocalDate startDate, LocalDate endDate) { throw new IllegalArgumentException("시작 날짜가 종료 날짜보다 늦을 수 없습니다."); } } + + public boolean startDateIsSameOrBefore(LocalDate date) { + return this.startDate == date || this.startDate.isBefore(date); + } + + public boolean endDateIsSameOrAfter(LocalDate date) { + return this.endDate == date || this.endDate.isAfter(date); + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 4ffde6fbaf..57a34d2a28 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -4,6 +4,7 @@ import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Objects; @@ -131,4 +132,32 @@ private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { throw new IllegalArgumentException("결제를 진행해 주세요."); } } + + public void changeOnReady(LocalDate date) { + checkStartDateIsSameOrBefore(date); + this.status = Status.READY; + } + + public void changeOnRecruit(LocalDate date) { + checkStartDateIsSameOrBefore(date); + this.status = Status.RECRUIT; + } + + private void checkStartDateIsSameOrBefore(LocalDate date) { + if (duration.startDateIsSameOrBefore(date)) { + throw new IllegalArgumentException("강의 시작일 이전에 변경 가능합니다."); + } + } + + public void changeOnEnd(LocalDate date) { + checkEndDateIsSameOrAfter(date); + this.status = Status.END; + } + + private void checkEndDateIsSameOrAfter(LocalDate date) { + if (duration.endDateIsSameOrAfter(date)) { + throw new IllegalArgumentException("강의 종료일 이후 변경 가능합니다."); + } + } + } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 918509569d..1f089ab984 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -17,6 +17,10 @@ public class SessionTest { private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net"); + private static final LocalDate DATE_2023_12_5 = LocalDate.of(2023, 12, 5); + private static final LocalDate DATE_2023_12_6 = LocalDate.of(2023, 12, 6); + private static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); + private static final LocalDate DATE_2023_12_12 = LocalDate.of(2023, 12, 12); private Image image; private Payment payment; @@ -113,4 +117,52 @@ void apply_chargeSession_differentAmount_throwsException() { () -> session.apply(APPLE, differentPayment) ).isInstanceOf(IllegalArgumentException.class); } + + @Test + @DisplayName("changeOnReady는 강의 시작날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") + void changeOnReady_startDateIsBeforeOrSame_throwsException() { + Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); + Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, + 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.changeOnReady(DATE_2023_12_5) + ).isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy( + () -> session.changeOnReady(DATE_2023_12_6) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("changeOnRecruit는 강의 시작날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") + void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { + Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); + Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, + 10, applicants, Session.Status.READY, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.changeOnRecruit(DATE_2023_12_5) + ).isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy( + () -> session.changeOnRecruit(DATE_2023_12_6) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("changeOnRecruit는 강의 종료 날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") + void changeOnEnd_EndDateIsSameOrAfter_throwsException() { + Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); + Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, + 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.changeOnEnd(DATE_2023_12_6) + ).isInstanceOf(IllegalArgumentException.class); + + assertThatThrownBy( + () -> session.changeOnEnd(DATE_2023_12_12) + ).isInstanceOf(IllegalArgumentException.class); + } } From 82773e1c421e34ddc0679ae60d1bd05e4a8920d4 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sat, 9 Dec 2023 10:13:18 +0900 Subject: [PATCH 30/62] =?UTF-8?q?[feat]=20=EC=88=98=EA=B0=95=EC=83=9D=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EC=97=90=20=EC=A0=95=EC=9B=90=20?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수강생 클래스에 단지 수강생 뿐 아니라 정원 수를 같이 관리하도록 개선합니다. 따라서 지원에 따른 정원 검사를 해당 클래스에서 책임을 가지도록 합니다 --- .../courses/domain/course/Course.java | 2 +- .../domain/course/session/Applicants.java | 22 +++++++++-- .../domain/course/session/Session.java | 16 ++------ .../course/service/CourseServiceTest.java | 2 +- .../course/service/SessionServiceTest.java | 9 +++-- .../domain/course/session/ApplicantsTest.java | 39 ++++++++++++++++--- .../domain/course/session/SessionTest.java | 31 +++++++++------ .../domain/course/session/SessionsTest.java | 2 +- 8 files changed, 82 insertions(+), 41 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 6913282e93..4a8d9a5d9a 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -12,7 +12,7 @@ public class Course { private Long creatorId; - private Sessions sessions = new Sessions(); + private Sessions sessions; private LocalDateTime createdAt; diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java index e143f43ef6..4479c3e6aa 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Applicants.java +++ b/src/main/java/nextstep/courses/domain/course/session/Applicants.java @@ -9,20 +9,24 @@ public class Applicants implements Iterable { private final List applicants; - public Applicants() { - this(new ArrayList()); + private final int quota; + + public Applicants(int quota) { + this(new ArrayList<>(), quota); } - public Applicants(List applicants) { + public Applicants(List applicants, int quota) { this.applicants = applicants; + this.quota = quota; } public int size() { return this.applicants.size(); } - public void add(NsUser applicant) { + public void addApplicant(NsUser applicant, Session.Type type) { checkApplicantAlreadyExisted(applicant); + checkChargeAndApplySizeIsValid(type); this.applicants.add(applicant); } @@ -32,6 +36,16 @@ private void checkApplicantAlreadyExisted(NsUser applicant) { } } + private void checkChargeAndApplySizeIsValid(Session.Type type) { + if (type == Session.Type.CHARGE && this.isFull()) { + throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); + } + } + + public boolean isFull() { + return this. applicants.size() == this.quota; + } + @Override public Iterator iterator() { return this.applicants.iterator(); diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 57a34d2a28..b7c22d5cdf 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -19,8 +19,6 @@ public class Session { private Long amount; - private int quota; - private Applicants applicants; private Status status; @@ -57,12 +55,12 @@ public Session() { public Session(Image image, Duration duration, Type type, Long amount, int quota) { - this(0L, image, duration, type, amount, quota, new Applicants(), + this(0L, image, duration, type, amount, new Applicants(quota), Status.READY, LocalDateTime.now(), null); } public Session(Long id, Image image, Duration duration, - Type type, Long amount, int quota, Applicants applicants, + Type type, Long amount, Applicants applicants, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) { if (image == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); @@ -77,7 +75,6 @@ public Session(Long id, Image image, Duration duration, this.duration = duration; this.type = type; this.amount = amount; - this.quota = quota; this.applicants = applicants; this.status = status; this.createdAt = createdAt; @@ -104,11 +101,10 @@ public void apply(NsUser loginUser, Payment payment) { checkStatusOnRecruit(); if (typeCharged()) { - checkApplySizeIsValid(); checkPaymentIsPaid(loginUser, payment); } - this.applicants.add(loginUser); + this.applicants.addApplicant(loginUser, type); } private boolean typeCharged() { @@ -121,12 +117,6 @@ private void checkStatusOnRecruit() { } } - private void checkApplySizeIsValid() { - if (applyCount() + 1 > quota) { - throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); - } - } - private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { if (payment == null || !payment.isPaid(loginUser, this)) { throw new IllegalArgumentException("결제를 진행해 주세요."); diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 1fe8be0efb..341f8228f4 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -44,7 +44,7 @@ void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - 10, new Applicants(), Session.Status.RECRUIT, localDateTime, localDateTime); + new Applicants(10), Session.Status.RECRUIT, localDateTime, localDateTime); } @Mock diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 9ba41fbd4e..cf633674a8 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -32,7 +32,8 @@ public class SessionServiceTest { private Payment payment; private LocalDate localDate; private LocalDateTime localDateTime; - private Applicants applicants = new Applicants(); + private int quota; + private Applicants applicants; private Duration duration; private Session session; @@ -48,10 +49,12 @@ public void setUp() { payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - applicants.add(JAVAJIGI); + quota = 10; + applicants = new Applicants(quota); + applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, localDateTime, localDateTime); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java index c64f5facc6..096c7941a7 100644 --- a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java @@ -5,26 +5,53 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ApplicantsTest { private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); + private static final NsUser APPLE = new NsUser(3L, "sanjigi", "password", "name", "apple@slipp.net"); Applicants applicants; + private int qouta; @BeforeEach void setUp() { - applicants = new Applicants(); - applicants.add(JAVAJIGI); - applicants.add(SANJIGI); + qouta = 2; + applicants = new Applicants(qouta); + applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); } @Test - @DisplayName("add 는 이미 수강생이 강의를 신청하였으면 예외를 던진다.") - void add_alreadyExistedApplicant_throwsException() { + @DisplayName("addApplicant 는 이미 수강생이 강의를 신청 하였으면 예외를 던진다.") + void addApplicant_alreadyExistedApplicant_throwsException() { assertThatThrownBy( - () -> applicants.add(JAVAJIGI) + () -> applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE) ).isInstanceOf(IllegalArgumentException.class); } + + @Test + @DisplayName("addApplicant 는 유료강의 수강 정원이 찼으면 예외를 던진다.") + void addApplicant_alreadyFull_throwsException() { + applicants = new Applicants(2); + applicants.addApplicant(SANJIGI, Session.Type.CHARGE); + applicants.addApplicant(APPLE, Session.Type.CHARGE); + + assertThatThrownBy( + () -> applicants.addApplicant(SANJIGI, Session.Type.CHARGE) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("isFull 은 수강생 수와 정원이 같으면 true를 적으면 false를 반환한다.") + void isFull_sameOrOverQuota_throwsException() { + applicants = new Applicants(2); + applicants.addApplicant(SANJIGI, Session.Type.CHARGE); + + assertThat(applicants.isFull()).isFalse(); + + applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); + assertThat(applicants.isFull()).isTrue(); + } } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 1f089ab984..00da482402 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -27,7 +27,8 @@ public class SessionTest { private Payment differentPayment; private LocalDate localDate; private LocalDateTime localDateTime; - private Applicants applicants = new Applicants(); + private int quota; + private Applicants applicants; private Duration duration; private Session session; @@ -38,11 +39,13 @@ void setUp() { differentPayment = new Payment("1", 1L, 3L, 500L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - this.applicants.add(JAVAJIGI); - this.applicants.add(SANJIGI); + quota = 10; + applicants = new Applicants(quota); + this.applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); + this.applicants.addApplicant(SANJIGI, Session.Type.CHARGE); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, localDateTime, localDateTime); } @Test @@ -65,7 +68,7 @@ void newObject_durationNull_throwsException() { @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { Session session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThat(session.applyCount()).isEqualTo(2); @@ -78,7 +81,7 @@ void apply_success() { @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { Session session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - 10, applicants, Session.Status.READY, localDateTime, localDateTime); + applicants, Session.Status.READY, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, payment) @@ -88,8 +91,12 @@ void apply_notRecruitStatus_throwsException() { @Test @DisplayName("수강 신청은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_overQuota_throwsException() { + applicants = new Applicants(2); + applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); + applicants.addApplicant(SANJIGI, Session.Type.CHARGE); + Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - 2, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, payment) @@ -100,7 +107,7 @@ void apply_chargeSession_overQuota_throwsException() { @DisplayName("수강 신청은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_notPaid_throwsException() { Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, null) @@ -111,7 +118,7 @@ void apply_chargeSession_notPaid_throwsException() { @DisplayName("수강 신청은 수강 금액과 지불 금액이 다르면 신청할 수 없다는 예외를 던진다.") void apply_chargeSession_differentAmount_throwsException() { Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, differentPayment) @@ -123,7 +130,7 @@ void apply_chargeSession_differentAmount_throwsException() { void changeOnReady_startDateIsBeforeOrSame_throwsException() { Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnReady(DATE_2023_12_5) @@ -139,7 +146,7 @@ void changeOnReady_startDateIsBeforeOrSame_throwsException() { void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - 10, applicants, Session.Status.READY, localDateTime, localDateTime); + applicants, Session.Status.READY, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnRecruit(DATE_2023_12_5) @@ -155,7 +162,7 @@ void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { void changeOnEnd_EndDateIsSameOrAfter_throwsException() { Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - 10, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnEnd(DATE_2023_12_6) diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index 221c332636..a2e33dd5f4 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -32,7 +32,7 @@ void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - 10, new Applicants(), Session.Status.RECRUIT, localDateTime, localDateTime); + new Applicants(10), Session.Status.RECRUIT, localDateTime, localDateTime); sessions.add(session); } From 33d3d6b0d123181f86eaa9151232fa16cb992944 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sat, 9 Dec 2023 10:17:25 +0900 Subject: [PATCH 31/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EB=B3=80=EA=B2=BD=EC=9D=84=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A0=88=EC=9D=B4=EC=96=B4=EC=97=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 준비중, 모집중, 종료 3가지 각가 상태로 변경 가능한 기능을 서비스 레이어에 추가합니다 --- .../nextstep/courses/service/SessionService.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index e57d06772d..14ab39aa15 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -8,6 +8,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.time.LocalDate; @Service("sessionService") public class SessionService { @@ -19,6 +20,21 @@ public void applySession(NsUser loginUser, long sessionId, Payment payment) { session.apply(loginUser, payment); } + public void changeOnReady(long sessionId, LocalDate date) { + Session session = getSession(sessionId); + session.changeOnReady(date); + } + + public void changeOnRecruit(long sessionId, LocalDate date) { + Session session = getSession(sessionId); + session.changeOnRecruit(date); + } + + public void changeOnEnd(long sessionId, LocalDate date) { + Session session = getSession(sessionId); + session.changeOnEnd(date); + } + private Session getSession(long sessionId) { return sessionRepository.findById(sessionId).orElseThrow(NotFoundException::new); } From 1a558deeb1a8de7e5ebe1f49774d707daa304bcb Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sat, 9 Dec 2023 22:14:28 +0900 Subject: [PATCH 32/62] =?UTF-8?q?[refactor]=20=EC=88=98=EA=B0=95=EC=83=9D?= =?UTF-8?q?=EA=B3=BC=20=EA=B0=95=EC=9D=98=EC=9D=98=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=EC=9D=84=20=EC=A0=9C=EA=B1=B0=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의와 수강생이 서로 의존성을 가지고 잇지만 강의가 수강생에 의존하는 것이 더 자연스러운 흐름이므로 수강생이 강의의 존재를 모르도록 개선합니다. 추후 일급 컬렉션을 보장하기 위해 정원 수를 분리해야 합니다 --- .../courses/domain/course/session/Applicants.java | 13 +++++++++---- .../courses/domain/course/session/Session.java | 4 +++- .../domain/course/service/SessionServiceTest.java | 2 +- .../domain/course/session/ApplicantsTest.java | 14 +++++++------- .../courses/domain/course/session/SessionTest.java | 8 ++++---- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java index 4479c3e6aa..457e232ea1 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Applicants.java +++ b/src/main/java/nextstep/courses/domain/course/session/Applicants.java @@ -24,9 +24,14 @@ public int size() { return this.applicants.size(); } - public void addApplicant(NsUser applicant, Session.Type type) { + public void addChargedApplicant(NsUser applicant) { + checkApplicantAlreadyExisted(applicant); + checkChargeAndApplySizeIsValid(); + this.applicants.add(applicant); + } + + public void addFreeApplicant(NsUser applicant) { checkApplicantAlreadyExisted(applicant); - checkChargeAndApplySizeIsValid(type); this.applicants.add(applicant); } @@ -36,8 +41,8 @@ private void checkApplicantAlreadyExisted(NsUser applicant) { } } - private void checkChargeAndApplySizeIsValid(Session.Type type) { - if (type == Session.Type.CHARGE && this.isFull()) { + private void checkChargeAndApplySizeIsValid() { + if (this.isFull()) { throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); } } diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index b7c22d5cdf..b509a4bfe6 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -102,9 +102,11 @@ public void apply(NsUser loginUser, Payment payment) { if (typeCharged()) { checkPaymentIsPaid(loginUser, payment); + this.applicants.addChargedApplicant(loginUser); + return; } - this.applicants.addApplicant(loginUser, type); + this.applicants.addFreeApplicant(loginUser); } private boolean typeCharged() { diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index cf633674a8..860b3b5116 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -51,7 +51,7 @@ public void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); quota = 10; applicants = new Applicants(quota); - applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); + applicants.addFreeApplicant(JAVAJIGI); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); diff --git a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java index 096c7941a7..dc1029f81a 100644 --- a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java @@ -20,14 +20,14 @@ public class ApplicantsTest { void setUp() { qouta = 2; applicants = new Applicants(qouta); - applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); + applicants.addChargedApplicant(JAVAJIGI); } @Test @DisplayName("addApplicant 는 이미 수강생이 강의를 신청 하였으면 예외를 던진다.") void addApplicant_alreadyExistedApplicant_throwsException() { assertThatThrownBy( - () -> applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE) + () -> applicants.addChargedApplicant(JAVAJIGI) ).isInstanceOf(IllegalArgumentException.class); } @@ -35,11 +35,11 @@ void addApplicant_alreadyExistedApplicant_throwsException() { @DisplayName("addApplicant 는 유료강의 수강 정원이 찼으면 예외를 던진다.") void addApplicant_alreadyFull_throwsException() { applicants = new Applicants(2); - applicants.addApplicant(SANJIGI, Session.Type.CHARGE); - applicants.addApplicant(APPLE, Session.Type.CHARGE); + applicants.addChargedApplicant(SANJIGI); + applicants.addChargedApplicant(APPLE); assertThatThrownBy( - () -> applicants.addApplicant(SANJIGI, Session.Type.CHARGE) + () -> applicants.addChargedApplicant(SANJIGI) ).isInstanceOf(IllegalArgumentException.class); } @@ -47,11 +47,11 @@ void addApplicant_alreadyFull_throwsException() { @DisplayName("isFull 은 수강생 수와 정원이 같으면 true를 적으면 false를 반환한다.") void isFull_sameOrOverQuota_throwsException() { applicants = new Applicants(2); - applicants.addApplicant(SANJIGI, Session.Type.CHARGE); + applicants.addFreeApplicant(SANJIGI); assertThat(applicants.isFull()).isFalse(); - applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); + applicants.addFreeApplicant(JAVAJIGI); assertThat(applicants.isFull()).isTrue(); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 00da482402..c34190f366 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -41,8 +41,8 @@ void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); quota = 10; applicants = new Applicants(quota); - this.applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); - this.applicants.addApplicant(SANJIGI, Session.Type.CHARGE); + this.applicants.addChargedApplicant(JAVAJIGI); + this.applicants.addChargedApplicant(SANJIGI); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); @@ -92,8 +92,8 @@ void apply_notRecruitStatus_throwsException() { @DisplayName("수강 신청은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_overQuota_throwsException() { applicants = new Applicants(2); - applicants.addApplicant(JAVAJIGI, Session.Type.CHARGE); - applicants.addApplicant(SANJIGI, Session.Type.CHARGE); + applicants.addChargedApplicant(JAVAJIGI); + applicants.addChargedApplicant(SANJIGI); Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, applicants, Session.Status.RECRUIT, localDateTime, localDateTime); From 328ed9fabdafb02eb3890737fb61279b573ec6dc Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sat, 9 Dec 2023 22:44:26 +0900 Subject: [PATCH 33/62] =?UTF-8?q?[refactor]=20=EA=B3=BC=EC=A0=95=EC=9D=98?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20=EC=86=8D=EC=84=B1=EC=9D=84=20=EC=83=81?= =?UTF-8?q?=EC=86=8D=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0=ED=95=98?= =?UTF-8?q?=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 생성자, 생성일시, 수정일시를 공통 클래스로 분리합니다 --- .../nextstep/courses/domain/BaseEntity.java | 29 +++++++++++++ .../courses/domain/course/Course.java | 43 ++++++------------- .../infrastructure/JdbcCourseRepository.java | 13 +++--- .../course/service/CourseServiceTest.java | 2 +- .../infrastructure/CourseRepositoryTest.java | 2 +- 5 files changed, 51 insertions(+), 38 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/BaseEntity.java diff --git a/src/main/java/nextstep/courses/domain/BaseEntity.java b/src/main/java/nextstep/courses/domain/BaseEntity.java new file mode 100644 index 0000000000..9c3b681541 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/BaseEntity.java @@ -0,0 +1,29 @@ +package nextstep.courses.domain; + +import java.time.LocalDateTime; + +public class BaseEntity { + private Long creatorId; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + public BaseEntity(Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.creatorId = creatorId; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getCreatorId() { + return creatorId; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } +} diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 4a8d9a5d9a..0aebb57ad4 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -1,36 +1,30 @@ package nextstep.courses.domain.course; +import nextstep.courses.domain.BaseEntity; import nextstep.courses.domain.course.session.Session; import nextstep.courses.domain.course.session.Sessions; import java.time.LocalDateTime; -public class Course { +public class Course extends BaseEntity { private Long id; private String title; - private Long creatorId; + private int sequence; private Sessions sessions; - private LocalDateTime createdAt; - - private LocalDateTime updatedAt; - - public Course() { - } - - public Course(String title, Long creatorId) { - this(0L, title, creatorId, LocalDateTime.now(), null); + public Course(String title, int sequence, Long creatorId) { + this(0L, title, sequence, creatorId, LocalDateTime.now(), null); } - public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Course(Long id, String title, int sequence, Long creatorId, + LocalDateTime createdAt, LocalDateTime updatedAt) { + super(creatorId, createdAt, updatedAt); this.id = id; this.title = title; - this.creatorId = creatorId; - this.createdAt = createdAt; - this.updatedAt = updatedAt; + this.sequence = sequence; this.sessions = new Sessions(); } @@ -50,22 +44,11 @@ public String getTitle() { return title; } - public Long getCreatorId() { - return creatorId; - } - - public LocalDateTime getCreatedAt() { - return createdAt; + public int getSequence() { + return this.sequence; } - @Override - public String toString() { - return "Course{" + - "id=" + id + - ", title='" + title + '\'' + - ", creatorId=" + creatorId + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - '}'; + public Long getCreatorId() { + return this.getCreatorId(); } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index 7489afc31d..6dcd7c323e 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -19,19 +19,20 @@ public JdbcCourseRepository(JdbcOperations jdbcTemplate) { @Override public int save(Course course) { - String sql = "insert into course (title, creator_id, created_at) values(?, ?, ?)"; - return jdbcTemplate.update(sql, course.getTitle(), course.getCreatorId(), course.getCreatedAt()); + String sql = "insert into course (title, sequence, creator_id, created_at) values(?, ?, ?)"; + return jdbcTemplate.update(sql, course.getTitle(), course.getSequence(), course.getCreatorId(), course.getCreatedAt()); } @Override public Course findById(Long id) { - String sql = "select id, title, creator_id, created_at, updated_at from course where id = ?"; + String sql = "select id, title, sequence, creator_id, created_at, updated_at from course where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Course( rs.getLong(1), rs.getString(2), - rs.getLong(3), - toLocalDateTime(rs.getTimestamp(4)), - toLocalDateTime(rs.getTimestamp(5))); + rs.getInt(3), + rs.getLong(4), + toLocalDateTime(rs.getTimestamp(5)), + toLocalDateTime(rs.getTimestamp(6))); return jdbcTemplate.queryForObject(sql, rowMapper, id); } diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 341f8228f4..af784e5a9f 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -37,7 +37,7 @@ public class CourseServiceTest { @BeforeEach void setUp() { - course = new Course("math", 1L); + course = new Course("math", 1, 1L); image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index 530375d3e1..e82f6d99c5 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -28,7 +28,7 @@ void setUp() { @Test void crud() { - Course course = new Course("TDD, 클린 코드 with Java", 1L); + Course course = new Course("TDD, 클린 코드 with Java", 1, 1L); int count = courseRepository.save(course); assertThat(count).isEqualTo(1); Course savedCourse = courseRepository.findById(1L); From c1527cd878a53291a323a20fb213d962bf9a9701 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sat, 9 Dec 2023 22:51:07 +0900 Subject: [PATCH 34/62] =?UTF-8?q?[refactor]=20=EA=B0=95=EC=9D=98=EC=9D=98?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20=EC=86=8D=EC=84=B1=EC=9D=84=20=EB=B3=84?= =?UTF-8?q?=EB=8F=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 생성자, 생성일시, 수정일시를 별도의 클래스를 활용하여 상속바도록 개선합니다. 모든 클래스의 공통 속성으로 개선합니다 --- .../domain/course/session/Session.java | 25 +++++++------------ .../course/service/CourseServiceTest.java | 2 +- .../course/service/SessionServiceTest.java | 2 +- .../domain/course/session/SessionTest.java | 22 ++++++++-------- .../domain/course/session/SessionsTest.java | 2 +- 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index b509a4bfe6..ecf32098d8 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.course.session; +import nextstep.courses.domain.BaseEntity; import nextstep.courses.domain.course.image.Image; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; @@ -8,7 +9,7 @@ import java.time.LocalDateTime; import java.util.Objects; -public class Session { +public class Session extends BaseEntity { private Long id; private Image image; @@ -23,10 +24,6 @@ public class Session { private Status status; - private LocalDateTime createdAt; - - private LocalDateTime updatedAt; - public enum Type { FREE("무료"), CHARGE("유료"); @@ -50,18 +47,16 @@ public enum Status { } } - public Session() { - } - - public Session(Image image, Duration duration, - Type type, Long amount, int quota) { + public Session(Image image, Duration duration, Type type, + Long amount, int quota, Long creatorId) { this(0L, image, duration, type, amount, new Applicants(quota), - Status.READY, LocalDateTime.now(), null); + Status.READY, creatorId, LocalDateTime.now(), null); } - public Session(Long id, Image image, Duration duration, - Type type, Long amount, Applicants applicants, - Status status, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Session(Long id, Image image, Duration duration, Type type, Long amount, + Applicants applicants, Status status, Long creatorId, + LocalDateTime createdAt, LocalDateTime updatedAt) { + super(creatorId, createdAt, updatedAt); if (image == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); } @@ -77,8 +72,6 @@ public Session(Long id, Image image, Duration duration, this.amount = amount; this.applicants = applicants; this.status = status; - this.createdAt = createdAt; - this.updatedAt = updatedAt; } public boolean sameAmount(Long amount) { diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index af784e5a9f..8725bc5cca 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -44,7 +44,7 @@ void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - new Applicants(10), Session.Status.RECRUIT, localDateTime, localDateTime); + new Applicants(10), Session.Status.RECRUIT, 1L, localDateTime, localDateTime); } @Mock diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 860b3b5116..ed713277c4 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -54,7 +54,7 @@ public void setUp() { applicants.addFreeApplicant(JAVAJIGI); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index c34190f366..3111b09236 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -45,14 +45,14 @@ void setUp() { this.applicants.addChargedApplicant(SANJIGI); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); } @Test @DisplayName("강의는 이미지가 없으면 이미지를 추가하라는 예외를 반환한다.") void newObject_imageNull_throwsException() { assertThatThrownBy( - () -> new Session(null, duration, Session.Type.FREE, 1000L, 10) + () -> new Session(null, duration, Session.Type.FREE, 1000L, 10, 1L) ).isInstanceOf(IllegalArgumentException.class); } @@ -60,7 +60,7 @@ void newObject_imageNull_throwsException() { @DisplayName("강의는 기간이 없으면 기간을 추가하라는 예외를 반환한다.") void newObject_durationNull_throwsException() { assertThatThrownBy( - () -> new Session(image, null, Session.Type.FREE, 1000L, 10) + () -> new Session(image, null, Session.Type.FREE, 1000L, 10, 1L) ).isInstanceOf(IllegalArgumentException.class); } @@ -68,7 +68,7 @@ void newObject_durationNull_throwsException() { @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { Session session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThat(session.applyCount()).isEqualTo(2); @@ -81,7 +81,7 @@ void apply_success() { @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { Session session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - applicants, Session.Status.READY, localDateTime, localDateTime); + applicants, Session.Status.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, payment) @@ -96,7 +96,7 @@ void apply_chargeSession_overQuota_throwsException() { applicants.addChargedApplicant(SANJIGI); Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, payment) @@ -107,7 +107,7 @@ void apply_chargeSession_overQuota_throwsException() { @DisplayName("수강 신청은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_notPaid_throwsException() { Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, null) @@ -118,7 +118,7 @@ void apply_chargeSession_notPaid_throwsException() { @DisplayName("수강 신청은 수강 금액과 지불 금액이 다르면 신청할 수 없다는 예외를 던진다.") void apply_chargeSession_differentAmount_throwsException() { Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, differentPayment) @@ -130,7 +130,7 @@ void apply_chargeSession_differentAmount_throwsException() { void changeOnReady_startDateIsBeforeOrSame_throwsException() { Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnReady(DATE_2023_12_5) @@ -146,7 +146,7 @@ void changeOnReady_startDateIsBeforeOrSame_throwsException() { void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.READY, localDateTime, localDateTime); + applicants, Session.Status.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnRecruit(DATE_2023_12_5) @@ -162,7 +162,7 @@ void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { void changeOnEnd_EndDateIsSameOrAfter_throwsException() { Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, localDateTime, localDateTime); + applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnEnd(DATE_2023_12_6) diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index a2e33dd5f4..4f68cf6e23 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -32,7 +32,7 @@ void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - new Applicants(10), Session.Status.RECRUIT, localDateTime, localDateTime); + new Applicants(10), Session.Status.RECRUIT, 1L, localDateTime, localDateTime); sessions.add(session); } From 3a1be5a7dfb8b986388c3cfbdf9619c8ea9b35d4 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sun, 10 Dec 2023 07:47:04 +0900 Subject: [PATCH 35/62] =?UTF-8?q?[fix]=20=EA=B0=95=EC=9D=98=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=9D=98=20=EA=B0=95=EC=9D=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A5=BC=20=EB=B6=84=EB=A6=AC=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 도메인이 많은 변수를 가지고 있어 공통속성을 묶어 분리합니다. 강의 상태는 강의 유형에 따라 구분되는 속성을 가지는데 무료/유료여부, 금액, 정원 수가 포함됩니다. --- .../courses/domain/course/image/Image.java | 4 +- .../image/{Type.java => ImageType.java} | 12 +-- .../domain/course/session/Applicants.java | 26 ++----- .../domain/course/session/Session.java | 44 ++++------- .../domain/course/session/SessionState.java | 38 +++++++++ .../domain/course/session/SessionType.java | 16 ++++ ...ypeTest.java => ImageSessionTypeTest.java} | 5 +- .../course/service/CourseServiceTest.java | 14 ++-- .../course/service/SessionServiceTest.java | 17 ++-- .../domain/course/session/ApplicantsTest.java | 36 +++------ .../domain/course/session/SessionTest.java | 78 +++++++++++-------- .../domain/course/session/SessionsTest.java | 6 +- 12 files changed, 160 insertions(+), 136 deletions(-) rename src/main/java/nextstep/courses/domain/course/image/{Type.java => ImageType.java} (71%) create mode 100644 src/main/java/nextstep/courses/domain/course/session/SessionState.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/SessionType.java rename src/test/java/nextstep/courses/domain/course/image/{TypeTest.java => ImageSessionTypeTest.java} (79%) diff --git a/src/main/java/nextstep/courses/domain/course/image/Image.java b/src/main/java/nextstep/courses/domain/course/image/Image.java index 93547a644d..e6a4f5a7f6 100644 --- a/src/main/java/nextstep/courses/domain/course/image/Image.java +++ b/src/main/java/nextstep/courses/domain/course/image/Image.java @@ -12,7 +12,7 @@ public class Image { private int imageSize; - private Type type; + private ImageType imageType; private int imageWidth; @@ -37,7 +37,7 @@ public Image(Long id, int imageSize, String type, int imageWidth, int imageHeigh this.id = id; this.imageSize = imageSize; - this.type = Type.find(type); + this.imageType = ImageType.find(type); this.imageWidth = imageWidth; this.imageHeight = imageHeight; this.createdAt = createdAt; diff --git a/src/main/java/nextstep/courses/domain/course/image/Type.java b/src/main/java/nextstep/courses/domain/course/image/ImageType.java similarity index 71% rename from src/main/java/nextstep/courses/domain/course/image/Type.java rename to src/main/java/nextstep/courses/domain/course/image/ImageType.java index 97cffe394f..61f08554d6 100644 --- a/src/main/java/nextstep/courses/domain/course/image/Type.java +++ b/src/main/java/nextstep/courses/domain/course/image/ImageType.java @@ -2,22 +2,22 @@ import java.util.Arrays; -public enum Type { +public enum ImageType { GIF("gif"), JPG("jpg"), JPEG("jpeg"), PNG("png"), SVG("svg"); - Type(String description) { + ImageType(String description) { this.description = description; } private final String description; - public static Type find(String name) { + public static ImageType find(String name) { return Arrays.stream(values()) - .filter(imageType -> imageType.description.equals(name)) + .filter(imageImageType -> imageImageType.description.equals(name)) .findAny() .orElseThrow( () -> new IllegalArgumentException( @@ -28,8 +28,8 @@ public static Type find(String name) { public static String descriptions() { StringBuilder sb = new StringBuilder(); - for (Type type : values()) { - sb.append(type.description).append(", "); + for (ImageType imageType : values()) { + sb.append(imageType.description).append(", "); } sb.setLength(sb.length() - 2); diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java index 457e232ea1..8835cddd09 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Applicants.java +++ b/src/main/java/nextstep/courses/domain/course/session/Applicants.java @@ -9,29 +9,21 @@ public class Applicants implements Iterable { private final List applicants; - private final int quota; - - public Applicants(int quota) { - this(new ArrayList<>(), quota); + public Applicants() { + this(new ArrayList<>()); } - public Applicants(List applicants, int quota) { + public Applicants(List applicants) { this.applicants = applicants; - this.quota = quota; } public int size() { return this.applicants.size(); } - public void addChargedApplicant(NsUser applicant) { - checkApplicantAlreadyExisted(applicant); - checkChargeAndApplySizeIsValid(); - this.applicants.add(applicant); - } - - public void addFreeApplicant(NsUser applicant) { + public void addApplicant(NsUser applicant, SessionState sessionState) { checkApplicantAlreadyExisted(applicant); + checkChargedAndApplySizeIsValid(sessionState); this.applicants.add(applicant); } @@ -41,16 +33,12 @@ private void checkApplicantAlreadyExisted(NsUser applicant) { } } - private void checkChargeAndApplySizeIsValid() { - if (this.isFull()) { + private void checkChargedAndApplySizeIsValid(SessionState sessionState) { + if (sessionState.chargedAndFull(applicants)) { throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); } } - public boolean isFull() { - return this. applicants.size() == this.quota; - } - @Override public Iterator iterator() { return this.applicants.iterator(); diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index ecf32098d8..fa6bfb7c80 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -16,25 +16,12 @@ public class Session extends BaseEntity { private Duration duration; - private Type type; - - private Long amount; + private SessionState sessionState; private Applicants applicants; private Status status; - public enum Type { - FREE("무료"), - CHARGE("유료"); - - private final String description; - - Type(String description) { - this.description = description; - } - } - public enum Status { READY("준비중"), RECRUIT("모집중"), @@ -47,13 +34,12 @@ public enum Status { } } - public Session(Image image, Duration duration, Type type, - Long amount, int quota, Long creatorId) { - this(0L, image, duration, type, amount, new Applicants(quota), + public Session(Image image, Duration duration, SessionState sessionState, Long creatorId) { + this(0L, image, duration, sessionState, new Applicants(), Status.READY, creatorId, LocalDateTime.now(), null); } - public Session(Long id, Image image, Duration duration, Type type, Long amount, + public Session(Long id, Image image, Duration duration, SessionState sessionState, Applicants applicants, Status status, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); @@ -65,17 +51,20 @@ public Session(Long id, Image image, Duration duration, Type type, Long amount, throw new IllegalArgumentException("기간을 추가해야 합니다."); } + if (sessionState == null) { + throw new IllegalArgumentException("강의 상태를 합니다."); + } + this.id = id; this.image = image; this.duration = duration; - this.type = type; - this.amount = amount; + this.sessionState = sessionState; this.applicants = applicants; this.status = status; } public boolean sameAmount(Long amount) { - return Objects.equals(this.amount, amount); + return this.sessionState.sameAmount(amount); } public boolean sameId(Long sessionId) { @@ -93,17 +82,11 @@ public int applyCount() { public void apply(NsUser loginUser, Payment payment) { checkStatusOnRecruit(); - if (typeCharged()) { + if (this.sessionState.charged()) { checkPaymentIsPaid(loginUser, payment); - this.applicants.addChargedApplicant(loginUser); - return; } - this.applicants.addFreeApplicant(loginUser); - } - - private boolean typeCharged() { - return this.type == Type.CHARGE; + this.applicants.addApplicant(loginUser, sessionState); } private void checkStatusOnRecruit() { @@ -145,4 +128,7 @@ private void checkEndDateIsSameOrAfter(LocalDate date) { } } + public SessionState sessionState() { + return this.sessionState; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionState.java b/src/main/java/nextstep/courses/domain/course/session/SessionState.java new file mode 100644 index 0000000000..2643d13049 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/SessionState.java @@ -0,0 +1,38 @@ +package nextstep.courses.domain.course.session; + +import nextstep.users.domain.NsUser; + +import java.util.List; +import java.util.Objects; + +public class SessionState { + private SessionType sessionType; + + private Long amount; + + private int quota; + + public SessionState() { + this.sessionType = SessionType.FREE; + this.amount = 0L; + this.quota = Integer.MAX_VALUE; + } + + public SessionState(SessionType sessionType, Long amount, int quota) { + this.sessionType = sessionType; + this.amount = amount; + this.quota = quota; + } + + public boolean sameAmount(Long amount) { + return Objects.equals(this.amount, amount); + } + + public boolean charged() { + return this.sessionType.charged(); + } + + public boolean chargedAndFull(List applicants) { + return this.sessionType == SessionType.CHARGE && this.quota == applicants.size(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionType.java b/src/main/java/nextstep/courses/domain/course/session/SessionType.java new file mode 100644 index 0000000000..10a7f78d0c --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/SessionType.java @@ -0,0 +1,16 @@ +package nextstep.courses.domain.course.session; + +public enum SessionType { + FREE("무료"), + CHARGE("유료"); + + private final String description; + + SessionType(String description) { + this.description = description; + } + + public boolean charged() { + return this == CHARGE; + } +} diff --git a/src/test/java/nextstep/courses/domain/course/image/TypeTest.java b/src/test/java/nextstep/courses/domain/course/image/ImageSessionTypeTest.java similarity index 79% rename from src/test/java/nextstep/courses/domain/course/image/TypeTest.java rename to src/test/java/nextstep/courses/domain/course/image/ImageSessionTypeTest.java index 0aff843b75..05d2db84c6 100644 --- a/src/test/java/nextstep/courses/domain/course/image/TypeTest.java +++ b/src/test/java/nextstep/courses/domain/course/image/ImageSessionTypeTest.java @@ -1,17 +1,16 @@ package nextstep.courses.domain.course.image; -import nextstep.courses.domain.course.session.Sessions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThatThrownBy; -public class TypeTest { +public class ImageSessionTypeTest { @Test @DisplayName("find는 존재하지 않는 값을 입력하면 찾을 수 없다는 예외를 던진다.") void find_notExistedName_throwsException() { assertThatThrownBy( - () -> Type.find("abcd") + () -> ImageType.find("abcd") ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 8725bc5cca..58f4b770b9 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -3,10 +3,7 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.session.Applicants; -import nextstep.courses.domain.course.session.Duration; -import nextstep.courses.domain.course.session.Session; -import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.domain.course.session.*; import nextstep.courses.service.CourseService; import nextstep.courses.service.SessionService; import nextstep.payments.domain.Payment; @@ -32,6 +29,7 @@ public class CourseServiceTest { private LocalDate localDate; private LocalDateTime localDateTime; private Duration duration; + private SessionState sessionState; private Course course; private Session session; @@ -43,13 +41,11 @@ void setUp() { localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); - session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - new Applicants(10), Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + sessionState = new SessionState(SessionType.FREE, 1000L, 10); + session = new Session(1L, image, duration, sessionState, new Applicants(), + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); } - @Mock - private SessionRepository sessionRepository; - @Mock private CourseRepository courseRepository; diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index ed713277c4..57fe5a9bc2 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -1,10 +1,7 @@ package nextstep.courses.domain.course.service; import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.session.Applicants; -import nextstep.courses.domain.course.session.Duration; -import nextstep.courses.domain.course.session.Session; -import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.domain.course.session.*; import nextstep.courses.service.SessionService; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; @@ -32,9 +29,9 @@ public class SessionServiceTest { private Payment payment; private LocalDate localDate; private LocalDateTime localDateTime; - private int quota; private Applicants applicants; private Duration duration; + private SessionState sessionState; private Session session; @Mock @@ -49,12 +46,12 @@ public void setUp() { payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - quota = 10; - applicants = new Applicants(quota); - applicants.addFreeApplicant(JAVAJIGI); duration = new Duration(localDate, localDate); - session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + sessionState = new SessionState(SessionType.FREE, 1000L, 10); + applicants = new Applicants(); + applicants.addApplicant(JAVAJIGI, sessionState); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java index dc1029f81a..083154e6d6 100644 --- a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ApplicantsTest { @@ -13,45 +12,34 @@ public class ApplicantsTest { private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); private static final NsUser APPLE = new NsUser(3L, "sanjigi", "password", "name", "apple@slipp.net"); - Applicants applicants; - private int qouta; + private Applicants applicants; + private SessionState sessionState; @BeforeEach void setUp() { - qouta = 2; - applicants = new Applicants(qouta); - applicants.addChargedApplicant(JAVAJIGI); + applicants = new Applicants(); + sessionState = new SessionState(SessionType.FREE, 1000L, 10); + applicants.addApplicant(JAVAJIGI, sessionState); } @Test - @DisplayName("addApplicant 는 이미 수강생이 강의를 신청 하였으면 예외를 던진다.") + @DisplayName("addApplicant 는 이미 수강생이 강의를 신청 했다면 예외를 던진다.") void addApplicant_alreadyExistedApplicant_throwsException() { assertThatThrownBy( - () -> applicants.addChargedApplicant(JAVAJIGI) + () -> applicants.addApplicant(JAVAJIGI, sessionState) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("addApplicant 는 유료강의 수강 정원이 찼으면 예외를 던진다.") void addApplicant_alreadyFull_throwsException() { - applicants = new Applicants(2); - applicants.addChargedApplicant(SANJIGI); - applicants.addChargedApplicant(APPLE); + applicants = new Applicants(); + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + applicants.addApplicant(SANJIGI, sessionState); + applicants.addApplicant(APPLE, sessionState); assertThatThrownBy( - () -> applicants.addChargedApplicant(SANJIGI) + () -> applicants.addApplicant(SANJIGI, sessionState) ).isInstanceOf(IllegalArgumentException.class); } - - @Test - @DisplayName("isFull 은 수강생 수와 정원이 같으면 true를 적으면 false를 반환한다.") - void isFull_sameOrOverQuota_throwsException() { - applicants = new Applicants(2); - applicants.addFreeApplicant(SANJIGI); - - assertThat(applicants.isFull()).isFalse(); - - applicants.addFreeApplicant(JAVAJIGI); - assertThat(applicants.isFull()).isTrue(); - } } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 3111b09236..de63b4e373 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -27,9 +27,9 @@ public class SessionTest { private Payment differentPayment; private LocalDate localDate; private LocalDateTime localDateTime; - private int quota; private Applicants applicants; private Duration duration; + private SessionState sessionState; private Session session; @BeforeEach @@ -39,20 +39,21 @@ void setUp() { differentPayment = new Payment("1", 1L, 3L, 500L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - quota = 10; - applicants = new Applicants(quota); - this.applicants.addChargedApplicant(JAVAJIGI); - this.applicants.addChargedApplicant(SANJIGI); duration = new Duration(localDate, localDate); - session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + sessionState = new SessionState(SessionType.FREE, 1000L, 10); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + applicants = new Applicants(); + this.applicants.addApplicant(JAVAJIGI, session.sessionState()); + this.applicants.addApplicant(SANJIGI, session.sessionState()); } @Test @DisplayName("강의는 이미지가 없으면 이미지를 추가하라는 예외를 반환한다.") void newObject_imageNull_throwsException() { + sessionState = new SessionState(SessionType.FREE, 1000L, 10); assertThatThrownBy( - () -> new Session(null, duration, Session.Type.FREE, 1000L, 10, 1L) + () -> new Session(null, duration, sessionState, 1L) ).isInstanceOf(IllegalArgumentException.class); } @@ -60,15 +61,23 @@ void newObject_imageNull_throwsException() { @DisplayName("강의는 기간이 없으면 기간을 추가하라는 예외를 반환한다.") void newObject_durationNull_throwsException() { assertThatThrownBy( - () -> new Session(image, null, Session.Type.FREE, 1000L, 10, 1L) + () -> new Session(image, null, sessionState, 1L) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("강의는 강의 상태가 없으면 상태를 추가하라는 예외를 반환한다.") + void newObject_sessionStateNull_throwsException() { + assertThatThrownBy( + () -> new Session(image, duration, null, 1L) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { - Session session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThat(session.applyCount()).isEqualTo(2); @@ -80,8 +89,8 @@ void apply_success() { @Test @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { - Session session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - applicants, Session.Status.READY, 1L, localDateTime, localDateTime); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, payment) @@ -91,12 +100,12 @@ void apply_notRecruitStatus_throwsException() { @Test @DisplayName("수강 신청은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_overQuota_throwsException() { - applicants = new Applicants(2); - applicants.addChargedApplicant(JAVAJIGI); - applicants.addChargedApplicant(SANJIGI); - - Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + applicants = new Applicants(); + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + applicants.addApplicant(JAVAJIGI, session.sessionState()); + applicants.addApplicant(SANJIGI, session.sessionState()); assertThatThrownBy( () -> session.apply(APPLE, payment) @@ -106,8 +115,9 @@ void apply_chargeSession_overQuota_throwsException() { @Test @DisplayName("수강 신청은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_notPaid_throwsException() { - Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, null) @@ -117,8 +127,9 @@ void apply_chargeSession_notPaid_throwsException() { @Test @DisplayName("수강 신청은 수강 금액과 지불 금액이 다르면 신청할 수 없다는 예외를 던진다.") void apply_chargeSession_differentAmount_throwsException() { - Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, differentPayment) @@ -128,9 +139,10 @@ void apply_chargeSession_differentAmount_throwsException() { @Test @DisplayName("changeOnReady는 강의 시작날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") void changeOnReady_startDateIsBeforeOrSame_throwsException() { - Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); - Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); + sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnReady(DATE_2023_12_5) @@ -144,9 +156,10 @@ void changeOnReady_startDateIsBeforeOrSame_throwsException() { @Test @DisplayName("changeOnRecruit는 강의 시작날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { - Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); - Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.READY, 1L, localDateTime, localDateTime); + duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); + sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnRecruit(DATE_2023_12_5) @@ -160,9 +173,10 @@ void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { @Test @DisplayName("changeOnRecruit는 강의 종료 날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") void changeOnEnd_EndDateIsSameOrAfter_throwsException() { - Duration duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); - Session session = new Session(1L, image, duration, Session.Type.CHARGE, 1000L, - applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); + sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); + session = new Session(1L, image, duration, sessionState, applicants, + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnEnd(DATE_2023_12_6) diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index 4f68cf6e23..33121db0a1 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -21,6 +21,7 @@ public class SessionsTest { private LocalDate localDate; private LocalDateTime localDateTime; private Duration duration; + private SessionState sessionState; private Session session; @BeforeEach @@ -31,8 +32,9 @@ void setUp() { localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); - session = new Session(1L, image, duration, Session.Type.FREE, 1000L, - new Applicants(10), Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + sessionState = new SessionState(SessionType.FREE, 1000L, 10); + session = new Session(1L, image, duration, sessionState, new Applicants(), + Session.Status.RECRUIT, 1L, localDateTime, localDateTime); sessions.add(session); } From 15f8086b71c43db466a42b97dbfeeff7ab11700f Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sun, 10 Dec 2023 08:01:40 +0900 Subject: [PATCH 36/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 무료 강의의 경우 정원수는 최대 인원이어야 하고 가격이 0원이 되어야 하는 유효성 검증을 추가합니다 --- .../domain/course/session/SessionState.java | 22 ++++++++++++++++- .../domain/course/session/SessionType.java | 4 ++++ .../course/service/CourseServiceTest.java | 2 +- .../course/service/SessionServiceTest.java | 2 +- .../domain/course/session/ApplicantsTest.java | 2 +- .../course/session/SessionStateTest.java | 24 +++++++++++++++++++ .../domain/course/session/SessionTest.java | 4 ++-- .../domain/course/session/SessionsTest.java | 2 +- 8 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionState.java b/src/main/java/nextstep/courses/domain/course/session/SessionState.java index 2643d13049..4a711e895d 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionState.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionState.java @@ -6,6 +6,8 @@ import java.util.Objects; public class SessionState { + private static final int MAX_APPLY = Integer.MAX_VALUE; + private SessionType sessionType; private Long amount; @@ -15,15 +17,33 @@ public class SessionState { public SessionState() { this.sessionType = SessionType.FREE; this.amount = 0L; - this.quota = Integer.MAX_VALUE; + this.quota = MAX_APPLY; } public SessionState(SessionType sessionType, Long amount, int quota) { + validate(sessionType, amount, quota); this.sessionType = sessionType; this.amount = amount; this.quota = quota; } + private void validate(SessionType sessionType, Long amount, int quota) { + checkFreeTypeAmountZero(sessionType, amount); + checkFreeTypeQuotaMax(sessionType, quota); + } + + private void checkFreeTypeAmountZero(SessionType sessionType, Long amount) { + if (sessionType.free() && amount != 0L) { + throw new IllegalArgumentException("무료 강의는 강의료가 0원이어야 합니다."); + } + } + + private void checkFreeTypeQuotaMax(SessionType sessionType, int quota) { + if (sessionType.free() && quota != MAX_APPLY) { + throw new IllegalArgumentException("무료 강의는 수강 가능 인원이 최대여야 합니다."); + } + } + public boolean sameAmount(Long amount) { return Objects.equals(this.amount, amount); } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionType.java b/src/main/java/nextstep/courses/domain/course/session/SessionType.java index 10a7f78d0c..e98f90bc41 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionType.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionType.java @@ -13,4 +13,8 @@ public enum SessionType { public boolean charged() { return this == CHARGE; } + + public boolean free() { + return this == FREE; + } } diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 58f4b770b9..f694d47974 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -41,7 +41,7 @@ void setUp() { localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 1000L, 10); + sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, new Applicants(), Session.Status.RECRUIT, 1L, localDateTime, localDateTime); } diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 57fe5a9bc2..48a77c5e97 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -47,7 +47,7 @@ public void setUp() { localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 1000L, 10); + sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); applicants = new Applicants(); applicants.addApplicant(JAVAJIGI, sessionState); session = new Session(1L, image, duration, sessionState, applicants, diff --git a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java index 083154e6d6..603eed9929 100644 --- a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java @@ -18,7 +18,7 @@ public class ApplicantsTest { @BeforeEach void setUp() { applicants = new Applicants(); - sessionState = new SessionState(SessionType.FREE, 1000L, 10); + sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); applicants.addApplicant(JAVAJIGI, sessionState); } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java new file mode 100644 index 0000000000..48aad9fdf1 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java @@ -0,0 +1,24 @@ +package nextstep.courses.domain.course.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class SessionStateTest { + @Test + @DisplayName("SessionState 는 무료 강의가 0원이 아니면 예외를 던진다") + void newObject_freeType_overZeroAmount_throwsException() { + assertThatThrownBy( + () -> new SessionState(SessionType.FREE, 1000L, Integer.MAX_VALUE) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("SessionState 는 무료 강의가 정원이 최대가 아니면 예외를 던진다.") + void newObject_freeType_lessThanMaxQuota_throwsException() { + assertThatThrownBy( + () -> new SessionState(SessionType.FREE, 0L, 100) + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index de63b4e373..7a8e34dbae 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -40,7 +40,7 @@ void setUp() { localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 1000L, 10); + sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, applicants, Session.Status.RECRUIT, 1L, localDateTime, localDateTime); applicants = new Applicants(); @@ -51,7 +51,7 @@ void setUp() { @Test @DisplayName("강의는 이미지가 없으면 이미지를 추가하라는 예외를 반환한다.") void newObject_imageNull_throwsException() { - sessionState = new SessionState(SessionType.FREE, 1000L, 10); + sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); assertThatThrownBy( () -> new Session(null, duration, sessionState, 1L) ).isInstanceOf(IllegalArgumentException.class); diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index 33121db0a1..38f5833ed8 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -32,7 +32,7 @@ void setUp() { localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 1000L, 10); + sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, new Applicants(), Session.Status.RECRUIT, 1L, localDateTime, localDateTime); sessions.add(session); From f37bcb7b8147735c054fc6e08aca2152517a0a56 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sun, 10 Dec 2023 08:42:01 +0900 Subject: [PATCH 37/62] =?UTF-8?q?[fix]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=97=90=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EC=83=81=EC=86=8D=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 생성자, 생성일시, 수정일시를 다루는 공통 속성 클래스 상속을 추가합니다 --- .../courses/domain/course/Course.java | 14 ++++++------- .../courses/domain/course/image/Image.java | 20 +++++++------------ .../infrastructure/JdbcCourseRepository.java | 2 +- .../domain/course/image/ImageTest.java | 10 +++++----- .../course/service/CourseServiceTest.java | 14 +++++-------- .../course/service/SessionServiceTest.java | 2 +- .../domain/course/session/SessionTest.java | 2 +- .../domain/course/session/SessionsTest.java | 2 +- 8 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 0aebb57ad4..25ebb5e5a2 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -11,20 +11,20 @@ public class Course extends BaseEntity { private String title; - private int sequence; + private int ordering; private Sessions sessions; - public Course(String title, int sequence, Long creatorId) { - this(0L, title, sequence, creatorId, LocalDateTime.now(), null); + public Course(String title, int ordering, Long creatorId) { + this(0L, title, ordering, creatorId, LocalDateTime.now(), null); } - public Course(Long id, String title, int sequence, Long creatorId, + public Course(Long id, String title, int ordering, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); this.id = id; this.title = title; - this.sequence = sequence; + this.ordering = ordering; this.sessions = new Sessions(); } @@ -44,8 +44,8 @@ public String getTitle() { return title; } - public int getSequence() { - return this.sequence; + public int getOrdering() { + return this.ordering; } public Long getCreatorId() { diff --git a/src/main/java/nextstep/courses/domain/course/image/Image.java b/src/main/java/nextstep/courses/domain/course/image/Image.java index e6a4f5a7f6..133fcc64d9 100644 --- a/src/main/java/nextstep/courses/domain/course/image/Image.java +++ b/src/main/java/nextstep/courses/domain/course/image/Image.java @@ -1,8 +1,10 @@ package nextstep.courses.domain.course.image; +import nextstep.courses.domain.BaseEntity; + import java.time.LocalDateTime; -public class Image { +public class Image extends BaseEntity { public static final int MB = 1024 * 1024; public static final int WIDTH_MIN = 300; public static final int HEIGHT_MIN = 200; @@ -18,19 +20,13 @@ public class Image { private int imageHeight; - private LocalDateTime createdAt; - - private LocalDateTime updatedAt; - - public Image() { - } - - public Image(int imageSize, String type, int imageWidth, int imageHeight) { - this(0L, imageSize, type, imageWidth, imageHeight, LocalDateTime.now(), null); + public Image(int imageSize, String type, int imageWidth, int imageHeight, Long creatorId) { + this(0L, imageSize, type, imageWidth, imageHeight, creatorId, LocalDateTime.now(), null); } public Image(Long id, int imageSize, String type, int imageWidth, int imageHeight, - LocalDateTime createdAt, LocalDateTime updatedAt) { + Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { + super(creatorId, createdAt, updatedAt); checkImageSizeIsValid(imageSize); checkWidthAndHeightSizeIsValid(imageWidth, imageHeight); checkWidthAndHeightRatioIsValid(imageWidth, imageHeight); @@ -40,8 +36,6 @@ public Image(Long id, int imageSize, String type, int imageWidth, int imageHeigh this.imageType = ImageType.find(type); this.imageWidth = imageWidth; this.imageHeight = imageHeight; - this.createdAt = createdAt; - this.updatedAt = updatedAt; } private static void checkImageSizeIsValid(int imageSize) { diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index 6dcd7c323e..7b09ec675b 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -20,7 +20,7 @@ public JdbcCourseRepository(JdbcOperations jdbcTemplate) { @Override public int save(Course course) { String sql = "insert into course (title, sequence, creator_id, created_at) values(?, ?, ?)"; - return jdbcTemplate.update(sql, course.getTitle(), course.getSequence(), course.getCreatorId(), course.getCreatedAt()); + return jdbcTemplate.update(sql, course.getTitle(), course.getOrdering(), course.getCreatorId(), course.getCreatedAt()); } @Override diff --git a/src/test/java/nextstep/courses/domain/course/image/ImageTest.java b/src/test/java/nextstep/courses/domain/course/image/ImageTest.java index 9826c7ed7b..225727e5ff 100644 --- a/src/test/java/nextstep/courses/domain/course/image/ImageTest.java +++ b/src/test/java/nextstep/courses/domain/course/image/ImageTest.java @@ -10,7 +10,7 @@ public class ImageTest { @DisplayName("이미지는 1 MB 를 초과하면 사이즈가 크다는 예외를 반환한다.") void newObject_over1MBSize_throwsException() { assertThatThrownBy( - () -> new Image(2 * Image.MB, "gif", 300, 200) + () -> new Image(2 * Image.MB, "gif", 300, 200, 1L) ).isInstanceOf(IllegalArgumentException.class); } @@ -18,7 +18,7 @@ void newObject_over1MBSize_throwsException() { @DisplayName("이미지는 가로 픽셀이 300 미만이면 길이가 작아는 예외를 반환한다.") void newObject_lessThanMinWidthSize_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", Image.WIDTH_MIN - 1, Image.HEIGHT_MIN) + () -> new Image(Image.MB, "gif", Image.WIDTH_MIN - 1, Image.HEIGHT_MIN, 1L) ).isInstanceOf(IllegalArgumentException.class); } @@ -26,7 +26,7 @@ void newObject_lessThanMinWidthSize_throwsException() { @DisplayName("이미지는 세로 픽셀이 200 미만이면 길이가 작아는 예외를 반환한다.") void newObject_lessThanMinHeightSize_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", Image.WIDTH_MIN, Image.HEIGHT_MIN - 1) + () -> new Image(Image.MB, "gif", Image.WIDTH_MIN, Image.HEIGHT_MIN - 1, 1L) ).isInstanceOf(IllegalArgumentException.class); } @@ -34,14 +34,14 @@ void newObject_lessThanMinHeightSize_throwsException() { @DisplayName("이미지는 가로 세로 비율이 3:2가 아니면 비율이 틀리다는 예외를 반환한다.") void newObject_inValidWidthHeightRatio_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", 600, 500) + () -> new Image(Image.MB, "gif", 600, 500, 1L) ).isInstanceOf(IllegalArgumentException.class); } @Test void newObject_inValidType_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "pdf", Image.WIDTH_MIN, Image.HEIGHT_MIN) + () -> new Image(Image.MB, "pdf", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index f694d47974..ec2f9a9941 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -5,7 +5,6 @@ import nextstep.courses.domain.course.image.Image; import nextstep.courses.domain.course.session.*; import nextstep.courses.service.CourseService; -import nextstep.courses.service.SessionService; import nextstep.payments.domain.Payment; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -17,7 +16,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -32,11 +30,15 @@ public class CourseServiceTest { private SessionState sessionState; private Course course; private Session session; + @Mock + private CourseRepository courseRepository; + @InjectMocks + private CourseService courseService; @BeforeEach void setUp() { course = new Course("math", 1, 1L); - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); @@ -46,12 +48,6 @@ void setUp() { Session.Status.RECRUIT, 1L, localDateTime, localDateTime); } - @Mock - private CourseRepository courseRepository; - - @InjectMocks - private CourseService courseService; - @Test @DisplayName("주어진 강의를 과정에 추가하면 과정에 강의가 추가된다.") void addSession_success() { diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 48a77c5e97..6121a0e78e 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -42,7 +42,7 @@ public class SessionServiceTest { @BeforeEach public void setUp() { - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 7a8e34dbae..a91f947cfa 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -34,7 +34,7 @@ public class SessionTest { @BeforeEach void setUp() { - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L); payment = new Payment("1", 1L, 3L, 1000L); differentPayment = new Payment("1", 1L, 3L, 500L); localDate = LocalDate.of(2023, 12, 5); diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index 38f5833ed8..1291a7e428 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -27,7 +27,7 @@ public class SessionsTest { @BeforeEach void setUp() { sessions = new Sessions(); - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); From 8104100fdd14f52cfbe7db106b6e0f2de2b61eab Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sun, 10 Dec 2023 08:53:08 +0900 Subject: [PATCH 38/62] =?UTF-8?q?[fix]=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=EC=97=90=20=EC=9D=BC=EC=8B=9C=20?= =?UTF-8?q?=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=EB=A5=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 일시 테스트를 용이하게 하고 일관성을 가지기 위해 생성일시를 외부에서 주입받을 수 있도록 수정합니다 --- .../java/nextstep/courses/domain/course/Course.java | 4 ++-- .../nextstep/courses/domain/course/image/Image.java | 4 ++-- .../courses/domain/course/session/Session.java | 4 ++-- .../courses/domain/course/image/ImageTest.java | 12 +++++++----- ...{ImageSessionTypeTest.java => ImageTypeTest.java} | 2 +- .../domain/course/service/CourseServiceTest.java | 6 +++--- .../domain/course/service/SessionServiceTest.java | 4 ++-- .../courses/domain/course/session/SessionTest.java | 8 ++++---- .../courses/domain/course/session/SessionsTest.java | 4 ++-- .../courses/infrastructure/CourseRepositoryTest.java | 4 +++- 10 files changed, 28 insertions(+), 24 deletions(-) rename src/test/java/nextstep/courses/domain/course/image/{ImageSessionTypeTest.java => ImageTypeTest.java} (93%) diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 25ebb5e5a2..9e46f162b4 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -15,8 +15,8 @@ public class Course extends BaseEntity { private Sessions sessions; - public Course(String title, int ordering, Long creatorId) { - this(0L, title, ordering, creatorId, LocalDateTime.now(), null); + public Course(String title, int ordering, Long creatorId, LocalDateTime date) { + this(0L, title, ordering, creatorId, date, null); } public Course(Long id, String title, int ordering, Long creatorId, diff --git a/src/main/java/nextstep/courses/domain/course/image/Image.java b/src/main/java/nextstep/courses/domain/course/image/Image.java index 133fcc64d9..1265e043f2 100644 --- a/src/main/java/nextstep/courses/domain/course/image/Image.java +++ b/src/main/java/nextstep/courses/domain/course/image/Image.java @@ -20,8 +20,8 @@ public class Image extends BaseEntity { private int imageHeight; - public Image(int imageSize, String type, int imageWidth, int imageHeight, Long creatorId) { - this(0L, imageSize, type, imageWidth, imageHeight, creatorId, LocalDateTime.now(), null); + public Image(int imageSize, String type, int imageWidth, int imageHeight, Long creatorId, LocalDateTime date) { + this(0L, imageSize, type, imageWidth, imageHeight, creatorId, date, null); } public Image(Long id, int imageSize, String type, int imageWidth, int imageHeight, diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index fa6bfb7c80..e00021cf85 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -34,9 +34,9 @@ public enum Status { } } - public Session(Image image, Duration duration, SessionState sessionState, Long creatorId) { + public Session(Image image, Duration duration, SessionState sessionState, Long creatorId, LocalDateTime date) { this(0L, image, duration, sessionState, new Applicants(), - Status.READY, creatorId, LocalDateTime.now(), null); + Status.READY, creatorId, date, null); } public Session(Long id, Image image, Duration duration, SessionState sessionState, diff --git a/src/test/java/nextstep/courses/domain/course/image/ImageTest.java b/src/test/java/nextstep/courses/domain/course/image/ImageTest.java index 225727e5ff..9c6b612836 100644 --- a/src/test/java/nextstep/courses/domain/course/image/ImageTest.java +++ b/src/test/java/nextstep/courses/domain/course/image/ImageTest.java @@ -3,6 +3,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.time.LocalDateTime; + import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ImageTest { @@ -10,7 +12,7 @@ public class ImageTest { @DisplayName("이미지는 1 MB 를 초과하면 사이즈가 크다는 예외를 반환한다.") void newObject_over1MBSize_throwsException() { assertThatThrownBy( - () -> new Image(2 * Image.MB, "gif", 300, 200, 1L) + () -> new Image(2 * Image.MB, "gif", 300, 200, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @@ -18,7 +20,7 @@ void newObject_over1MBSize_throwsException() { @DisplayName("이미지는 가로 픽셀이 300 미만이면 길이가 작아는 예외를 반환한다.") void newObject_lessThanMinWidthSize_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", Image.WIDTH_MIN - 1, Image.HEIGHT_MIN, 1L) + () -> new Image(Image.MB, "gif", Image.WIDTH_MIN - 1, Image.HEIGHT_MIN, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @@ -26,7 +28,7 @@ void newObject_lessThanMinWidthSize_throwsException() { @DisplayName("이미지는 세로 픽셀이 200 미만이면 길이가 작아는 예외를 반환한다.") void newObject_lessThanMinHeightSize_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", Image.WIDTH_MIN, Image.HEIGHT_MIN - 1, 1L) + () -> new Image(Image.MB, "gif", Image.WIDTH_MIN, Image.HEIGHT_MIN - 1, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @@ -34,14 +36,14 @@ void newObject_lessThanMinHeightSize_throwsException() { @DisplayName("이미지는 가로 세로 비율이 3:2가 아니면 비율이 틀리다는 예외를 반환한다.") void newObject_inValidWidthHeightRatio_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", 600, 500, 1L) + () -> new Image(Image.MB, "gif", 600, 500, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @Test void newObject_inValidType_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "pdf", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L) + () -> new Image(Image.MB, "pdf", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/course/image/ImageSessionTypeTest.java b/src/test/java/nextstep/courses/domain/course/image/ImageTypeTest.java similarity index 93% rename from src/test/java/nextstep/courses/domain/course/image/ImageSessionTypeTest.java rename to src/test/java/nextstep/courses/domain/course/image/ImageTypeTest.java index 05d2db84c6..3194248b88 100644 --- a/src/test/java/nextstep/courses/domain/course/image/ImageSessionTypeTest.java +++ b/src/test/java/nextstep/courses/domain/course/image/ImageTypeTest.java @@ -5,7 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -public class ImageSessionTypeTest { +public class ImageTypeTest { @Test @DisplayName("find는 존재하지 않는 값을 입력하면 찾을 수 없다는 예외를 던진다.") void find_notExistedName_throwsException() { diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index ec2f9a9941..7786088a5a 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -37,11 +37,11 @@ public class CourseServiceTest { @BeforeEach void setUp() { - course = new Course("math", 1, 1L); - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L); + localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + course = new Course("math", 1, 1L, localDateTime); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); - localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, new Applicants(), diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 6121a0e78e..47136c8944 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -42,10 +42,10 @@ public class SessionServiceTest { @BeforeEach public void setUp() { - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L); + localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); - localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); applicants = new Applicants(); diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index a91f947cfa..f30b744df8 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -34,11 +34,11 @@ public class SessionTest { @BeforeEach void setUp() { - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L); payment = new Payment("1", 1L, 3L, 1000L); differentPayment = new Payment("1", 1L, 3L, 500L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, applicants, @@ -53,7 +53,7 @@ void setUp() { void newObject_imageNull_throwsException() { sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); assertThatThrownBy( - () -> new Session(null, duration, sessionState, 1L) + () -> new Session(null, duration, sessionState, 1L, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } @@ -61,7 +61,7 @@ void newObject_imageNull_throwsException() { @DisplayName("강의는 기간이 없으면 기간을 추가하라는 예외를 반환한다.") void newObject_durationNull_throwsException() { assertThatThrownBy( - () -> new Session(image, null, sessionState, 1L) + () -> new Session(image, null, sessionState, 1L, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } @@ -69,7 +69,7 @@ void newObject_durationNull_throwsException() { @DisplayName("강의는 강의 상태가 없으면 상태를 추가하라는 예외를 반환한다.") void newObject_sessionStateNull_throwsException() { assertThatThrownBy( - () -> new Session(image, duration, null, 1L) + () -> new Session(image, duration, null, 1L, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index 1291a7e428..c7c2f3adb1 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -27,10 +27,10 @@ public class SessionsTest { @BeforeEach void setUp() { sessions = new Sessions(); - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L); + localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); - localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, new Applicants(), diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index e82f6d99c5..31ef737c22 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -10,6 +10,8 @@ import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.jdbc.core.JdbcTemplate; +import java.time.LocalDateTime; + import static org.assertj.core.api.Assertions.assertThat; @JdbcTest @@ -28,7 +30,7 @@ void setUp() { @Test void crud() { - Course course = new Course("TDD, 클린 코드 with Java", 1, 1L); + Course course = new Course("TDD, 클린 코드 with Java", 1, 1L, LocalDateTime.now()); int count = courseRepository.save(course); assertThat(count).isEqualTo(1); Course savedCourse = courseRepository.findById(1L); From db78578659430dc3fc78c3b346a18f5c2f5fce78 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sun, 10 Dec 2023 09:46:19 +0900 Subject: [PATCH 39/62] =?UTF-8?q?[feat]=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=8A=A4=ED=82=A4=EB=A7=88=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Image, Session, Apply 스키마를 추가하고 Course에 기수를 추가합니다 --- src/main/resources/schema.sql | 40 ++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 8d5a988c8b..59688d5800 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,7 +1,45 @@ +create table image ( + id bigint generated by default as identity, + image_size bigint not null, + image_type varchar(20) not null, + image_width bigint not null, + image_height bigint not null, + creator_id bigint not null, + created_at timestamp not null, + updated_at timestamp, + primary key (id) +); + +create table apply ( + session_id bigint not null, + ns_user_id bigint not null, + creator_id bigint not null, + created_at timestamp not null, + updated_at timestamp, + primary key(session_id, ns_user_id) +); + +create table session ( + id bigint generated by default as identity, + start_date timestamp not null, + end_date timestamp not null, + session_type varchar(20) not null, + session_status varchar(20) not null, + amount bigint not null, + quota bigint not null, + image_id bigint not null, + course_id bigint not null, + creator_id bigint not null, + created_at timestamp not null, + updated_at timestamp, + primary key (id) +); + create table course ( id bigint generated by default as identity, title varchar(255) not null, creator_id bigint not null, + ordering bigint not null, created_at timestamp not null, updated_at timestamp, primary key (id) @@ -47,4 +85,4 @@ create table delete_history ( created_date timestamp, deleted_by_id bigint, primary key (id) -); +); \ No newline at end of file From 6289f40e3c25a75e7522aeeb3346519484298c66 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sun, 10 Dec 2023 10:08:18 +0900 Subject: [PATCH 40/62] =?UTF-8?q?[fix]=20=EA=B3=BC=EC=A0=95=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A0=80=EC=9E=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 sequence 칼럼을 ordering으로 변경하고 모든 칼럼을 비교합니다 --- src/main/java/nextstep/courses/domain/course/Course.java | 2 +- .../courses/infrastructure/JdbcCourseRepository.java | 6 +++--- .../courses/infrastructure/CourseRepositoryTest.java | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 9e46f162b4..55110bd296 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -49,6 +49,6 @@ public int getOrdering() { } public Long getCreatorId() { - return this.getCreatorId(); + return super.getCreatorId(); } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index 7b09ec675b..293c96e774 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -11,7 +11,7 @@ @Repository("courseRepository") public class JdbcCourseRepository implements CourseRepository { - private JdbcOperations jdbcTemplate; + private final JdbcOperations jdbcTemplate; public JdbcCourseRepository(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -19,13 +19,13 @@ public JdbcCourseRepository(JdbcOperations jdbcTemplate) { @Override public int save(Course course) { - String sql = "insert into course (title, sequence, creator_id, created_at) values(?, ?, ?)"; + String sql = "insert into course (title, ordering, creator_id, created_at) values(?, ?, ?, ?)"; return jdbcTemplate.update(sql, course.getTitle(), course.getOrdering(), course.getCreatorId(), course.getCreatedAt()); } @Override public Course findById(Long id) { - String sql = "select id, title, sequence, creator_id, created_at, updated_at from course where id = ?"; + String sql = "select id, title, ordering, creator_id, created_at, updated_at from course where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Course( rs.getLong(1), rs.getString(2), diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index 31ef737c22..eff4697ddc 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -35,6 +35,8 @@ void crud() { assertThat(count).isEqualTo(1); Course savedCourse = courseRepository.findById(1L); assertThat(course.getTitle()).isEqualTo(savedCourse.getTitle()); + assertThat(course.getOrdering()).isEqualTo(savedCourse.getOrdering()); + assertThat(course.getCreatorId()).isEqualTo(savedCourse.getCreatorId()); LOGGER.debug("Course: {}", savedCourse); } } From 4e4dae15add6c0862842e4d50fbccaf7d4d929ec Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Sun, 10 Dec 2023 17:58:29 +0900 Subject: [PATCH 41/62] =?UTF-8?q?[feat]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20jd?= =?UTF-8?q?bc=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jdbc를 활용해 이미지의 저장과 조회를 추가합니다. --- .../courses/domain/course/image/Image.java | 37 +++++++++++-- .../domain/course/image/ImageRepository.java | 9 ++++ .../domain/course/image/ImageType.java | 2 +- .../infrastructure/JdbcImageRepository.java | 52 +++++++++++++++++++ src/main/resources/data.sql | 2 + .../domain/course/image/ImageTest.java | 15 ++---- .../course/service/CourseServiceTest.java | 3 +- .../course/service/SessionServiceTest.java | 3 +- .../domain/course/session/SessionTest.java | 3 +- .../domain/course/session/SessionsTest.java | 3 +- .../infrastructure/ImageRepositoryTest.java | 44 ++++++++++++++++ 11 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/image/ImageRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java create mode 100644 src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java diff --git a/src/main/java/nextstep/courses/domain/course/image/Image.java b/src/main/java/nextstep/courses/domain/course/image/Image.java index 1265e043f2..53219fcb9d 100644 --- a/src/main/java/nextstep/courses/domain/course/image/Image.java +++ b/src/main/java/nextstep/courses/domain/course/image/Image.java @@ -20,11 +20,11 @@ public class Image extends BaseEntity { private int imageHeight; - public Image(int imageSize, String type, int imageWidth, int imageHeight, Long creatorId, LocalDateTime date) { + public Image(int imageSize, ImageType type, int imageWidth, int imageHeight, Long creatorId, LocalDateTime date) { this(0L, imageSize, type, imageWidth, imageHeight, creatorId, date, null); } - public Image(Long id, int imageSize, String type, int imageWidth, int imageHeight, + public Image(Long id, int imageSize, ImageType imageType, int imageWidth, int imageHeight, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); checkImageSizeIsValid(imageSize); @@ -33,7 +33,7 @@ public Image(Long id, int imageSize, String type, int imageWidth, int imageHeigh this.id = id; this.imageSize = imageSize; - this.imageType = ImageType.find(type); + this.imageType = imageType; this.imageWidth = imageWidth; this.imageHeight = imageHeight; } @@ -57,4 +57,35 @@ private static void checkWidthAndHeightRatioIsValid(int imageWidth, int imageHei throw new IllegalArgumentException("가로 세로 비율은 3:2여야 합니다."); } } + + public Long getId() { + return id; + } + + public int getImageSize() { + return imageSize; + } + + public ImageType getImageType() { + return imageType; + } + + public int getImageWidth() { + return imageWidth; + } + + public int getImageHeight() { + return imageHeight; + } + + @Override + public String toString() { + return "Image{" + + "id=" + id + + ", imageSize=" + imageSize + + ", imageType=" + imageType + + ", imageWidth=" + imageWidth + + ", imageHeight=" + imageHeight + + '}'; + } } diff --git a/src/main/java/nextstep/courses/domain/course/image/ImageRepository.java b/src/main/java/nextstep/courses/domain/course/image/ImageRepository.java new file mode 100644 index 0000000000..4863ca37cb --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/image/ImageRepository.java @@ -0,0 +1,9 @@ +package nextstep.courses.domain.course.image; + +import java.util.Optional; + +public interface ImageRepository { + Optional findById(Long id); + + int save(Image image); +} diff --git a/src/main/java/nextstep/courses/domain/course/image/ImageType.java b/src/main/java/nextstep/courses/domain/course/image/ImageType.java index 61f08554d6..f2ab7b1a9b 100644 --- a/src/main/java/nextstep/courses/domain/course/image/ImageType.java +++ b/src/main/java/nextstep/courses/domain/course/image/ImageType.java @@ -17,7 +17,7 @@ public enum ImageType { public static ImageType find(String name) { return Arrays.stream(values()) - .filter(imageImageType -> imageImageType.description.equals(name)) + .filter(imageImageType -> imageImageType.name().equals(name)) .findAny() .orElseThrow( () -> new IllegalArgumentException( diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java new file mode 100644 index 0000000000..d2d0566cf0 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java @@ -0,0 +1,52 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.image.ImageRepository; +import nextstep.courses.domain.course.image.ImageType; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Optional; + +@Repository("imageRepository") +public class JdbcImageRepository implements ImageRepository { + private final JdbcOperations jdbcTemplate; + + public JdbcImageRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Optional findById(Long id) { + String sql = "select id, image_size, image_type, image_width, image_height, creator_id, created_at, updated_at from image where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Image( + rs.getLong(1), + rs.getInt(2), + ImageType.find(rs.getString(3)), + rs.getInt(4), + rs.getInt(5), + rs.getLong(6), + toLocalDateTime(rs.getTimestamp(7)), + toLocalDateTime(rs.getTimestamp(8))); + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } + + @Override + public int save(Image image) { + ImageType imageType = image.getImageType(); + + String sql = "insert into image (image_size, image_type, image_width, image_height, creator_id, created_at, updated_at) values(?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, image.getImageSize(), imageType.name(), image.getImageWidth(), image.getImageHeight(), image.getCreatorId(), image.getCreatedAt(), image.getUpdatedAt()); + } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 4a6bc5a665..1916b3ecdd 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -8,3 +8,5 @@ INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) VALUE INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) VALUES (2, '언더스코어 강력 추천드려요. 다만 최신 버전을 공부하는 것보다는 0.10.0 버전부터 보는게 더 좋더군요. 코드의 변천사도 알 수 있고, 최적화되지 않은 코드들이 기능은 그대로 두고 최적화되어 가는 걸 보면 재미가 있습니다 :)', CURRENT_TIMESTAMP(), 1, false); INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUES (2, 2, 'runtime 에 reflect 발동 주체 객체가 뭔지 알 방법이 있을까요?', '설계를 희한하게 하는 바람에 꼬인 문제같긴 합니다만. 여쭙습니다. 상황은 mybatis select 실행될 시에 return object 의 getter 가 호출되면서인데요. getter 안에 다른 property 에 의존중인 코드가 삽입되어 있어서, 만약 다른 mybatis select 구문에 해당 property 가 없다면 exception 이 발생하게 됩니다.', CURRENT_TIMESTAMP(), false); + +--INSERT INTO image (id, image_size, image_type, image_width, image_height, creator_id, created_at, update_at) VALUES (1, 1024, "jpg", 300, 200, 1L, CURRENT_TIMESTAMP(), null); \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/course/image/ImageTest.java b/src/test/java/nextstep/courses/domain/course/image/ImageTest.java index 9c6b612836..9fa73e053f 100644 --- a/src/test/java/nextstep/courses/domain/course/image/ImageTest.java +++ b/src/test/java/nextstep/courses/domain/course/image/ImageTest.java @@ -12,7 +12,7 @@ public class ImageTest { @DisplayName("이미지는 1 MB 를 초과하면 사이즈가 크다는 예외를 반환한다.") void newObject_over1MBSize_throwsException() { assertThatThrownBy( - () -> new Image(2 * Image.MB, "gif", 300, 200, 1L, LocalDateTime.now()) + () -> new Image(2 * Image.MB, ImageType.GIF, 300, 200, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @@ -20,7 +20,7 @@ void newObject_over1MBSize_throwsException() { @DisplayName("이미지는 가로 픽셀이 300 미만이면 길이가 작아는 예외를 반환한다.") void newObject_lessThanMinWidthSize_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", Image.WIDTH_MIN - 1, Image.HEIGHT_MIN, 1L, LocalDateTime.now()) + () -> new Image(Image.MB, ImageType.GIF, Image.WIDTH_MIN - 1, Image.HEIGHT_MIN, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @@ -28,7 +28,7 @@ void newObject_lessThanMinWidthSize_throwsException() { @DisplayName("이미지는 세로 픽셀이 200 미만이면 길이가 작아는 예외를 반환한다.") void newObject_lessThanMinHeightSize_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", Image.WIDTH_MIN, Image.HEIGHT_MIN - 1, 1L, LocalDateTime.now()) + () -> new Image(Image.MB, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN - 1, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } @@ -36,14 +36,7 @@ void newObject_lessThanMinHeightSize_throwsException() { @DisplayName("이미지는 가로 세로 비율이 3:2가 아니면 비율이 틀리다는 예외를 반환한다.") void newObject_inValidWidthHeightRatio_throwsException() { assertThatThrownBy( - () -> new Image(Image.MB, "gif", 600, 500, 1L, LocalDateTime.now()) - ).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void newObject_inValidType_throwsException() { - assertThatThrownBy( - () -> new Image(Image.MB, "pdf", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, LocalDateTime.now()) + () -> new Image(Image.MB, ImageType.GIF, 600, 500, 1L, LocalDateTime.now()) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 7786088a5a..22599a9dd3 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -3,6 +3,7 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.image.ImageType; import nextstep.courses.domain.course.session.*; import nextstep.courses.service.CourseService; import nextstep.payments.domain.Payment; @@ -39,7 +40,7 @@ public class CourseServiceTest { void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); course = new Course("math", 1, 1L, localDateTime); - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); + image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); duration = new Duration(localDate, localDate); diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 47136c8944..e8565f1ca0 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course.service; import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.image.ImageType; import nextstep.courses.domain.course.session.*; import nextstep.courses.service.SessionService; import nextstep.payments.domain.Payment; @@ -43,7 +44,7 @@ public class SessionServiceTest { @BeforeEach public void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); + image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); duration = new Duration(localDate, localDate); diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index f30b744df8..5fe4a2bf8c 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course.session; import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.image.ImageType; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; import org.junit.jupiter.api.BeforeEach; @@ -38,7 +39,7 @@ void setUp() { differentPayment = new Payment("1", 1L, 3L, 500L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); + image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, applicants, diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index c7c2f3adb1..9497d0c2ad 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course.session; import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.image.ImageType; import nextstep.payments.domain.Payment; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -28,7 +29,7 @@ public class SessionsTest { void setUp() { sessions = new Sessions(); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - image = new Image(1000, "jpeg", Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); + image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); duration = new Duration(localDate, localDate); diff --git a/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java new file mode 100644 index 0000000000..bc3d97cb08 --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java @@ -0,0 +1,44 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.image.ImageRepository; +import nextstep.courses.domain.course.image.ImageType; +import nextstep.qna.NotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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 static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +public class ImageRepositoryTest { + private static final Logger LOGGER = LoggerFactory.getLogger(CourseRepositoryTest.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + private ImageRepository imageRepository; + + @BeforeEach + void setUp() { + imageRepository = new JdbcImageRepository(jdbcTemplate); + } + + @Test + void save_success() { + Image image = new Image(1024, ImageType.JPG, 300, 200, 1L, LocalDateTime.now()); + int count = imageRepository.save(image); + Image savedImage = imageRepository.findById(1L).orElseThrow(NotFoundException::new); + assertThat(image.getImageSize()).isEqualTo(savedImage.getImageSize()); + assertThat(image.getImageType()).isEqualTo(savedImage.getImageType()); + assertThat(image.getImageWidth()).isEqualTo(savedImage.getImageWidth()); + assertThat(image.getImageHeight()).isEqualTo(savedImage.getImageHeight()); + LOGGER.debug("Image: {}", savedImage); + } +} From ef4e5419c1ac08424ba4f3f452beb76ed61be171 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 11 Dec 2023 18:17:26 +0900 Subject: [PATCH 42/62] =?UTF-8?q?[feat]=20=EC=88=98=EA=B0=95=EC=83=9D=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=86=8C=EC=99=80=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session id를 통해 수강생을 조회할 수 있도록 저장소와 조회를 추가합니다. --- .../domain/course/session/Applicants.java | 4 ++ .../course/session/ApplicantsRepository.java | 5 ++ .../JdbcApplicantsRepository.java | 64 +++++++++++++++++++ .../ApplicantsRepositoryTest.java | 40 ++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 src/main/java/nextstep/courses/domain/course/session/ApplicantsRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java create mode 100644 src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java index 8835cddd09..d08c00d8b5 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Applicants.java +++ b/src/main/java/nextstep/courses/domain/course/session/Applicants.java @@ -17,6 +17,10 @@ public Applicants(List applicants) { this.applicants = applicants; } + public NsUser find(int index) { + return this.applicants.get(index); + } + public int size() { return this.applicants.size(); } diff --git a/src/main/java/nextstep/courses/domain/course/session/ApplicantsRepository.java b/src/main/java/nextstep/courses/domain/course/session/ApplicantsRepository.java new file mode 100644 index 0000000000..00ed821d39 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/ApplicantsRepository.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain.course.session; + +public interface ApplicantsRepository { + Applicants findAllBySessionId(Long SessionId); +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java new file mode 100644 index 0000000000..d44f1d3ff5 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java @@ -0,0 +1,64 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.session.Applicants; +import nextstep.courses.domain.course.session.ApplicantsRepository; +import nextstep.qna.NotFoundException; +import nextstep.users.domain.NsUser; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Repository("applicantsRepository") +public class JdbcApplicantsRepository implements ApplicantsRepository { + private final JdbcOperations jdbcTemplate; + + public JdbcApplicantsRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Applicants findAllBySessionId(Long sessionId) { + List applicantIds = findAllApplicantIdsBySessionId(sessionId); + List nsUsers = new ArrayList<>(); + for (Long applicantId : applicantIds) { + NsUser nsUser = findAllNsUsersById(applicantId).orElseThrow(NotFoundException::new); + nsUsers.add(nsUser); + } + + return new Applicants(nsUsers); + } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } + + private List findAllApplicantIdsBySessionId(Long sessionId) { + String sql = "select ns_user_id from apply where session_id = ?"; + RowMapper rowMapper = (rs, rowNum) -> rs.getLong(1); + return jdbcTemplate.query(sql, rowMapper, sessionId); + } + + private Optional findAllNsUsersById(Long applicantId) { + String sql = "select id, user_id, password, name, email, created_at, updated_at from ns_user where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new NsUser( + rs.getLong(1), + rs.getString(2), + rs.getString(3), + rs.getString(4), + rs.getString(5), + toLocalDateTime(rs.getTimestamp(6)), + toLocalDateTime(rs.getTimestamp(7)) + ); + + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, applicantId)); + } +} diff --git a/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java new file mode 100644 index 0000000000..b2cfc90eab --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java @@ -0,0 +1,40 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.session.Applicants; +import nextstep.courses.domain.course.session.ApplicantsRepository; +import nextstep.users.domain.NsUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.jdbc.core.JdbcTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +public class ApplicantsRepositoryTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicantsRepositoryTest.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + private ApplicantsRepository applicantsRepository; + + @BeforeEach + void setUp() { + applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); + } + + @Test + void find_success() { + Applicants applicants = applicantsRepository.findAllBySessionId(1L); + NsUser nsUser_1 = applicants.find(0); + NsUser nsUser_2 = applicants.find(1); + + assertThat(applicants.size()).isEqualTo(2); + assertThat(nsUser_1.getId()).isEqualTo(1L); + assertThat(nsUser_2.getId()).isEqualTo(2L); + } +} From d11dc41952f7bc1af66de69853780feeaa91ace7 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 11 Dec 2023 20:24:47 +0900 Subject: [PATCH 43/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EC=9D=98=20=EC=A1=B0=ED=9A=8C=EC=99=80=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 저장소에서 강의 조회와 저장을 추가합니다 --- .../courses/domain/course/image/Image.java | 15 ++++ .../domain/course/session/Applicants.java | 7 ++ .../domain/course/session/Duration.java | 22 ++++++ .../domain/course/session/Session.java | 53 +++++++------- .../course/session/SessionRepository.java | 2 + .../domain/course/session/SessionState.java | 25 +++++++ .../domain/course/session/SessionStatus.java | 36 ++++++++++ .../domain/course/session/SessionType.java | 23 ++++++ .../infrastructure/JdbcSessionRepository.java | 67 ++++++++++++++++- src/main/resources/data.sql | 6 +- src/main/resources/schema.sql | 4 +- .../course/service/CourseServiceTest.java | 2 +- .../course/service/SessionServiceTest.java | 2 +- .../domain/course/session/SessionTest.java | 28 ++++---- .../domain/course/session/SessionsTest.java | 2 +- .../ApplicantsRepositoryTest.java | 1 + .../infrastructure/ImageRepositoryTest.java | 2 +- .../infrastructure/SessionRepositoryTest.java | 71 +++++++++++++++++++ 18 files changed, 320 insertions(+), 48 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/session/SessionStatus.java create mode 100644 src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java diff --git a/src/main/java/nextstep/courses/domain/course/image/Image.java b/src/main/java/nextstep/courses/domain/course/image/Image.java index 53219fcb9d..d4776e2c45 100644 --- a/src/main/java/nextstep/courses/domain/course/image/Image.java +++ b/src/main/java/nextstep/courses/domain/course/image/Image.java @@ -3,6 +3,7 @@ import nextstep.courses.domain.BaseEntity; import java.time.LocalDateTime; +import java.util.Objects; public class Image extends BaseEntity { public static final int MB = 1024 * 1024; @@ -88,4 +89,18 @@ public String toString() { ", imageHeight=" + imageHeight + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Image image = (Image) o; + return imageSize == image.imageSize && imageWidth == image.imageWidth && + imageHeight == image.imageHeight && Objects.equals(id, image.id) && imageType == image.imageType; + } + + @Override + public int hashCode() { + return Objects.hash(id, imageSize, imageType, imageWidth, imageHeight); + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java index d08c00d8b5..6beba95f1a 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Applicants.java +++ b/src/main/java/nextstep/courses/domain/course/session/Applicants.java @@ -47,4 +47,11 @@ private void checkChargedAndApplySizeIsValid(SessionState sessionState) { public Iterator iterator() { return this.applicants.iterator(); } + + @Override + public String toString() { + return "Applicants{" + + "applicants=" + applicants + + '}'; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/Duration.java b/src/main/java/nextstep/courses/domain/course/session/Duration.java index 590429ae98..c15ff2dc81 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Duration.java +++ b/src/main/java/nextstep/courses/domain/course/session/Duration.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course.session; import java.time.LocalDate; +import java.util.Objects; public class Duration { private LocalDate startDate; @@ -37,4 +38,25 @@ public boolean startDateIsSameOrBefore(LocalDate date) { public boolean endDateIsSameOrAfter(LocalDate date) { return this.endDate == date || this.endDate.isAfter(date); } + + public LocalDate getStartDate() { + return startDate; + } + + public LocalDate getEndDate() { + return endDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Duration duration = (Duration) o; + return Objects.equals(startDate, duration.startDate) && Objects.equals(endDate, duration.endDate); + } + + @Override + public int hashCode() { + return Objects.hash(startDate, endDate); + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index e00021cf85..a7452cee9a 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -9,7 +9,7 @@ import java.time.LocalDateTime; import java.util.Objects; -public class Session extends BaseEntity { +public class Session extends BaseEntity { private Long id; private Image image; @@ -20,27 +20,16 @@ public class Session extends BaseEntity { private Applicants applicants; - private Status status; + private SessionStatus session; - public enum Status { - READY("준비중"), - RECRUIT("모집중"), - END("종료"); - - private final String description; - - Status(String description) { - this.description = description; - } - } - - public Session(Image image, Duration duration, SessionState sessionState, Long creatorId, LocalDateTime date) { + public Session(Image image, Duration duration, SessionState sessionState, + Long creatorId, LocalDateTime date) { this(0L, image, duration, sessionState, new Applicants(), - Status.READY, creatorId, date, null); + SessionStatus.READY, creatorId, date, null); } public Session(Long id, Image image, Duration duration, SessionState sessionState, - Applicants applicants, Status status, Long creatorId, + Applicants applicants, SessionStatus session, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); if (image == null) { @@ -52,7 +41,7 @@ public Session(Long id, Image image, Duration duration, SessionState sessionStat } if (sessionState == null) { - throw new IllegalArgumentException("강의 상태를 합니다."); + throw new IllegalArgumentException("강의 상태를 추가해야 합니다."); } this.id = id; @@ -60,7 +49,7 @@ public Session(Long id, Image image, Duration duration, SessionState sessionStat this.duration = duration; this.sessionState = sessionState; this.applicants = applicants; - this.status = status; + this.session = session; } public boolean sameAmount(Long amount) { @@ -90,7 +79,7 @@ public void apply(NsUser loginUser, Payment payment) { } private void checkStatusOnRecruit() { - if (this.status != Status.RECRUIT) { + if (this.session != SessionStatus.RECRUIT) { throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); } } @@ -103,12 +92,12 @@ private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { public void changeOnReady(LocalDate date) { checkStartDateIsSameOrBefore(date); - this.status = Status.READY; + this.session = SessionStatus.READY; } public void changeOnRecruit(LocalDate date) { checkStartDateIsSameOrBefore(date); - this.status = Status.RECRUIT; + this.session = SessionStatus.RECRUIT; } private void checkStartDateIsSameOrBefore(LocalDate date) { @@ -119,7 +108,7 @@ private void checkStartDateIsSameOrBefore(LocalDate date) { public void changeOnEnd(LocalDate date) { checkEndDateIsSameOrAfter(date); - this.status = Status.END; + this.session = SessionStatus.END; } private void checkEndDateIsSameOrAfter(LocalDate date) { @@ -128,7 +117,23 @@ private void checkEndDateIsSameOrAfter(LocalDate date) { } } - public SessionState sessionState() { + public Image getImage() { + return image; + } + + public Duration getDuration() { + return duration; + } + + public SessionState getSessionState() { return this.sessionState; } + + public Applicants getApplicants() { + return applicants; + } + + public SessionStatus getSession() { + return session; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index 73aa8558db..a28ca2ba57 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -4,4 +4,6 @@ public interface SessionRepository { Optional findById(Long id); + + int save(Long courseId, Session session); } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionState.java b/src/main/java/nextstep/courses/domain/course/session/SessionState.java index 4a711e895d..dbcb730c50 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionState.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionState.java @@ -55,4 +55,29 @@ public boolean charged() { public boolean chargedAndFull(List applicants) { return this.sessionType == SessionType.CHARGE && this.quota == applicants.size(); } + + public SessionType getSessionType() { + return sessionType; + } + + public Long getAmount() { + return amount; + } + + public int getQuota() { + return quota; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SessionState that = (SessionState) o; + return quota == that.quota && sessionType == that.sessionType && Objects.equals(amount, that.amount); + } + + @Override + public int hashCode() { + return Objects.hash(sessionType, amount, quota); + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java b/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java new file mode 100644 index 0000000000..f9c5ad4cc3 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java @@ -0,0 +1,36 @@ +package nextstep.courses.domain.course.session; + +import java.util.Arrays; + +public enum SessionStatus { + READY("준비중"), + RECRUIT("모집중"), + END("종료"); + + private final String description; + + SessionStatus(String description) { + this.description = description; + } + + public static SessionStatus find(String name) { + return Arrays.stream(values()) + .filter(status -> status.name().equals(name)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException( + String.format("허용하는 값은 다음과 같습니다.\n %s", descriptions()) + ) + ); + } + + public static String descriptions() { + StringBuilder sb = new StringBuilder(); + for (SessionStatus status : values()) { + sb.append(status.description).append(", "); + } + sb.setLength(sb.length() - 2); + + return sb.toString(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionType.java b/src/main/java/nextstep/courses/domain/course/session/SessionType.java index e98f90bc41..26050db23e 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionType.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionType.java @@ -1,5 +1,7 @@ package nextstep.courses.domain.course.session; +import java.util.Arrays; + public enum SessionType { FREE("무료"), CHARGE("유료"); @@ -10,6 +12,27 @@ public enum SessionType { this.description = description; } + public static SessionType find(String name) { + return Arrays.stream(values()) + .filter(sessionType -> sessionType.name().equals(name)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException( + String.format("허용하는 값은 다음과 같습니다.\n %s", descriptions()) + ) + ); + } + + public static String descriptions() { + StringBuilder sb = new StringBuilder(); + for (SessionType sessionType : values()) { + sb.append(sessionType.description).append(", "); + } + sb.setLength(sb.length() - 2); + + return sb.toString(); + } + public boolean charged() { return this == CHARGE; } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 016b959e55..2294e33098 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,15 +1,76 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.course.session.Session; -import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.image.ImageRepository; +import nextstep.courses.domain.course.session.*; +import nextstep.qna.NotFoundException; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Optional; @Repository("sessionRepository") public class JdbcSessionRepository implements SessionRepository { + private final JdbcOperations jdbcTemplate; + private final ImageRepository imageRepository; + private final ApplicantsRepository applicantsRepository; + + public JdbcSessionRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.imageRepository = new JdbcImageRepository(jdbcTemplate); + this.applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); + } + @Override public Optional findById(Long id) { - return Optional.empty(); + String sql = "select id, image_id, start_date, end_date, session_type, amount, quota, session_status, course_id, creator_id, created_at, updated_at from session where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Session( + rs.getLong(1), + findImageById(rs.getLong(2)), + new Duration(toLocalDate(rs.getTimestamp(3)), toLocalDate(rs.getTimestamp(4))), + new SessionState(SessionType.find(rs.getString(5)), rs.getLong(6), rs.getInt(7)), + findAllBySessionId(id), + SessionStatus.find(rs.getString(8)), + rs.getLong(10), + toLocalDateTime(rs.getTimestamp(11)), + toLocalDateTime(rs.getTimestamp(12))); + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } + + @Override + public int save(Long courseId, Session session) { + Duration duration = session.getDuration(); + SessionState sessionState = session.getSessionState(); + SessionType sessionType = sessionState.getSessionType(); + SessionStatus status = session.getSession(); + Image image = session.getImage(); + String sql = "insert into session (start_date, end_date, session_type, session_status, amount, quota, image_id, course_id, creator_id, created_at, updated_at) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), status.name(), sessionState.getAmount(), sessionState.getQuota(), image.getId(), courseId, session.getCreatorId(), session.getCreatedAt(), session.getUpdatedAt()); + } + + private Image findImageById(Long id) { + return this.imageRepository.findById(id).orElseThrow(NotFoundException::new); + } + + private Applicants findAllBySessionId(Long id) { + return this.applicantsRepository.findAllBySessionId(id); + } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } + + private LocalDate toLocalDate(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime().toLocalDate(); } } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 1916b3ecdd..16662febe7 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -9,4 +9,8 @@ INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) VALUE INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUES (2, 2, 'runtime 에 reflect 발동 주체 객체가 뭔지 알 방법이 있을까요?', '설계를 희한하게 하는 바람에 꼬인 문제같긴 합니다만. 여쭙습니다. 상황은 mybatis select 실행될 시에 return object 의 getter 가 호출되면서인데요. getter 안에 다른 property 에 의존중인 코드가 삽입되어 있어서, 만약 다른 mybatis select 구문에 해당 property 가 없다면 exception 이 발생하게 됩니다.', CURRENT_TIMESTAMP(), false); ---INSERT INTO image (id, image_size, image_type, image_width, image_height, creator_id, created_at, update_at) VALUES (1, 1024, "jpg", 300, 200, 1L, CURRENT_TIMESTAMP(), null); \ No newline at end of file +INSERT INTO apply (session_id, ns_user_id, creator_id, created_at, updated_at) VALUES (1, 1, 1, CURRENT_TIMESTAMP(), null); + +INSERT INTO apply (session_id, ns_user_id, creator_id, created_at, updated_at) VALUES (1, 2, 2, CURRENT_TIMESTAMP(), null); + +--INSERT INTO image (id, image_size, image_type, image_width, image_height, creator_id, created_at, updated_at) VALUES (10, 1024, 'JPG', 300, 200, 1, CURRENT_TIMESTAMP(), null); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 59688d5800..ce6149e23a 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -21,8 +21,8 @@ create table apply ( create table session ( id bigint generated by default as identity, - start_date timestamp not null, - end_date timestamp not null, + start_date DATETIME not null, + end_date DATETIME not null, session_type varchar(20) not null, session_status varchar(20) not null, amount bigint not null, diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 22599a9dd3..275cce548e 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -46,7 +46,7 @@ void setUp() { duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, new Applicants(), - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index e8565f1ca0..6c948c27c1 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -52,7 +52,7 @@ public void setUp() { applicants = new Applicants(); applicants.addApplicant(JAVAJIGI, sessionState); session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 5fe4a2bf8c..6990ae2f04 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -42,11 +42,11 @@ void setUp() { image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); applicants = new Applicants(); - this.applicants.addApplicant(JAVAJIGI, session.sessionState()); - this.applicants.addApplicant(SANJIGI, session.sessionState()); + this.applicants.addApplicant(JAVAJIGI, sessionState); + this.applicants.addApplicant(SANJIGI, sessionState); + session = new Session(1L, image, duration, sessionState, applicants, + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); } @Test @@ -78,7 +78,7 @@ void newObject_sessionStateNull_throwsException() { @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThat(session.applyCount()).isEqualTo(2); @@ -91,7 +91,7 @@ void apply_success() { @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.READY, 1L, localDateTime, localDateTime); + SessionStatus.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, payment) @@ -104,9 +104,9 @@ void apply_chargeSession_overQuota_throwsException() { applicants = new Applicants(); sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); - applicants.addApplicant(JAVAJIGI, session.sessionState()); - applicants.addApplicant(SANJIGI, session.sessionState()); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + applicants.addApplicant(JAVAJIGI, session.getSessionState()); + applicants.addApplicant(SANJIGI, session.getSessionState()); assertThatThrownBy( () -> session.apply(APPLE, payment) @@ -118,7 +118,7 @@ void apply_chargeSession_overQuota_throwsException() { void apply_chargeSession_notPaid_throwsException() { sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, null) @@ -130,7 +130,7 @@ void apply_chargeSession_notPaid_throwsException() { void apply_chargeSession_differentAmount_throwsException() { sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, differentPayment) @@ -143,7 +143,7 @@ void changeOnReady_startDateIsBeforeOrSame_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnReady(DATE_2023_12_5) @@ -160,7 +160,7 @@ void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.READY, 1L, localDateTime, localDateTime); + SessionStatus.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnRecruit(DATE_2023_12_5) @@ -177,7 +177,7 @@ void changeOnEnd_EndDateIsSameOrAfter_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); session = new Session(1L, image, duration, sessionState, applicants, - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnEnd(DATE_2023_12_6) diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index 9497d0c2ad..ee428d65bb 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -35,7 +35,7 @@ void setUp() { duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, image, duration, sessionState, new Applicants(), - Session.Status.RECRUIT, 1L, localDateTime, localDateTime); + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); sessions.add(session); } diff --git a/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java index b2cfc90eab..d8184bd520 100644 --- a/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java @@ -36,5 +36,6 @@ void find_success() { assertThat(applicants.size()).isEqualTo(2); assertThat(nsUser_1.getId()).isEqualTo(1L); assertThat(nsUser_2.getId()).isEqualTo(2L); + LOGGER.debug("Applicants: {}", applicants); } } diff --git a/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java index bc3d97cb08..443ce1ab62 100644 --- a/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java @@ -18,7 +18,7 @@ @JdbcTest public class ImageRepositoryTest { - private static final Logger LOGGER = LoggerFactory.getLogger(CourseRepositoryTest.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ImageRepositoryTest.class); @Autowired private JdbcTemplate jdbcTemplate; diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java new file mode 100644 index 0000000000..e73bf15099 --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -0,0 +1,71 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.image.ImageRepository; +import nextstep.courses.domain.course.image.ImageType; +import nextstep.courses.domain.course.session.*; +import nextstep.payments.domain.Payment; +import nextstep.qna.NotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +public class SessionRepositoryTest { + private static final Logger LOGGER = LoggerFactory.getLogger(SessionRepositoryTest.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + private SessionRepository sessionRepository; + private ImageRepository imageRepository; + private ApplicantsRepository applicantsRepository; + + private Image image; + private Payment payment; + private LocalDate localDate; + private LocalDateTime localDateTime; + private Applicants applicants; + private Duration duration; + private SessionState sessionState; + private Session session; + + @BeforeEach + void setUp() { + sessionRepository = new JdbcSessionRepository(jdbcTemplate); + imageRepository = new JdbcImageRepository(jdbcTemplate); + applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); + + image = new Image(1L, 1024, ImageType.JPG, 300, 200, 1L, LocalDateTime.now(), null); + payment = new Payment("1", 1L, 3L, 1000L); + localDate = LocalDate.of(2023, 12, 5); + localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); + applicants = new Applicants(); + duration = new Duration(localDate, localDate); + sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); + } + + @Test + void save_success() { + imageRepository.save(image); + Image savedImage = imageRepository.findById(1L).orElseThrow(NotFoundException::new); + + session = new Session(savedImage, duration, sessionState, 1L, localDateTime); + int count = sessionRepository.save(1L, session); + Session savedSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); + assertThat(savedSession.getId()).isEqualTo(1L); + assertThat(savedSession.getImage()).isEqualTo(session.getImage()); + assertThat(savedSession.getDuration()).isEqualTo(session.getDuration()); + assertThat(savedSession.getSessionState()).isEqualTo(session.getSessionState()); + LOGGER.debug("Session: {}", savedSession); + } +} From 140fc408bed2b12a34beaea052e19fe2df2c6ce9 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 11 Dec 2023 21:47:07 +0900 Subject: [PATCH 44/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=A0=80=EC=9E=A5=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의를 신청하면 해당 강의에 수강생이 추가하도록 합니다. --- .../courses/domain/course/session/Apply.java | 34 +++++++++++++++++++ .../domain/course/session/Session.java | 12 +++++++ .../course/session/SessionRepository.java | 6 ++++ .../JdbcApplicantsRepository.java | 14 ++++---- .../infrastructure/JdbcSessionRepository.java | 19 +++++++++++ src/main/resources/data.sql | 4 +-- .../ApplicantsRepositoryTest.java | 3 +- .../infrastructure/SessionRepositoryTest.java | 20 +++++++++-- 8 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/session/Apply.java diff --git a/src/main/java/nextstep/courses/domain/course/session/Apply.java b/src/main/java/nextstep/courses/domain/course/session/Apply.java new file mode 100644 index 0000000000..a5906537c0 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/Apply.java @@ -0,0 +1,34 @@ +package nextstep.courses.domain.course.session; + +import nextstep.courses.domain.BaseEntity; + +import java.time.LocalDateTime; + +public class Apply extends BaseEntity { + private Long sessionId; + + private Long nsUserId; + + public Apply(Long sessionId, Long nsUserId, Long creatorId, + LocalDateTime createdAt, LocalDateTime updatedAt) { + super(creatorId, createdAt, updatedAt); + this.sessionId = sessionId; + this.nsUserId = nsUserId; + } + + public Long getSessionId() { + return sessionId; + } + + public Long getNsUserId() { + return nsUserId; + } + + @Override + public String toString() { + return "Apply{" + + "sessionId=" + sessionId + + ", nsUserId=" + nsUserId + + '}'; + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index a7452cee9a..1024ca66ab 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -136,4 +136,16 @@ public Applicants getApplicants() { public SessionStatus getSession() { return session; } + + @Override + public String toString() { + return "Session{" + + "id=" + id + + ", image=" + image + + ", duration=" + duration + + ", sessionState=" + sessionState + + ", applicants=" + applicants + + ", session=" + session + + '}'; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index a28ca2ba57..d4f13af2c0 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -1,9 +1,15 @@ package nextstep.courses.domain.course.session; +import nextstep.users.domain.NsUser; + import java.util.Optional; public interface SessionRepository { Optional findById(Long id); int save(Long courseId, Session session); + + int saveApply(NsUser user, Session session); + + Optional findApplyByIds(Long NsUserId, Long sessionId); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java index d44f1d3ff5..4ab50abed9 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java @@ -34,13 +34,6 @@ public Applicants findAllBySessionId(Long sessionId) { return new Applicants(nsUsers); } - private LocalDateTime toLocalDateTime(Timestamp timestamp) { - if (timestamp == null) { - return null; - } - return timestamp.toLocalDateTime(); - } - private List findAllApplicantIdsBySessionId(Long sessionId) { String sql = "select ns_user_id from apply where session_id = ?"; RowMapper rowMapper = (rs, rowNum) -> rs.getLong(1); @@ -61,4 +54,11 @@ private Optional findAllNsUsersById(Long applicantId) { return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, applicantId)); } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 2294e33098..5801867b81 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -4,6 +4,7 @@ import nextstep.courses.domain.course.image.ImageRepository; import nextstep.courses.domain.course.session.*; import nextstep.qna.NotFoundException; +import nextstep.users.domain.NsUser; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; @@ -52,6 +53,24 @@ public int save(Long courseId, Session session) { return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), status.name(), sessionState.getAmount(), sessionState.getQuota(), image.getId(), courseId, session.getCreatorId(), session.getCreatedAt(), session.getUpdatedAt()); } + @Override + public int saveApply(NsUser nsUser, Session session) { + String sql = "insert into apply (session_id, ns_user_id, creator_id, created_at, updated_at) values(?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, session.getId(), nsUser.getId(), nsUser.getId(), LocalDateTime.now(), null); + } + + @Override + public Optional findApplyByIds(Long nsUserId, Long sessionId) { + String sql = "select session_id, ns_user_id, creator_id, created_at, updated_at from apply where ns_user_id = ? and session_id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Apply( + rs.getLong(1), + rs.getLong(2), + rs.getLong(3), + toLocalDateTime(rs.getTimestamp(4)), + toLocalDateTime(rs.getTimestamp(5))); + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, nsUserId, sessionId)); + } + private Image findImageById(Long id) { return this.imageRepository.findById(id).orElseThrow(NotFoundException::new); } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 16662febe7..12d124f9e5 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -9,8 +9,8 @@ INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) VALUE INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUES (2, 2, 'runtime 에 reflect 발동 주체 객체가 뭔지 알 방법이 있을까요?', '설계를 희한하게 하는 바람에 꼬인 문제같긴 합니다만. 여쭙습니다. 상황은 mybatis select 실행될 시에 return object 의 getter 가 호출되면서인데요. getter 안에 다른 property 에 의존중인 코드가 삽입되어 있어서, 만약 다른 mybatis select 구문에 해당 property 가 없다면 exception 이 발생하게 됩니다.', CURRENT_TIMESTAMP(), false); -INSERT INTO apply (session_id, ns_user_id, creator_id, created_at, updated_at) VALUES (1, 1, 1, CURRENT_TIMESTAMP(), null); +INSERT INTO apply (session_id, ns_user_id, creator_id, created_at, updated_at) VALUES (10, 1, 1, CURRENT_TIMESTAMP(), null); -INSERT INTO apply (session_id, ns_user_id, creator_id, created_at, updated_at) VALUES (1, 2, 2, CURRENT_TIMESTAMP(), null); +INSERT INTO apply (session_id, ns_user_id, creator_id, created_at, updated_at) VALUES (10, 2, 2, CURRENT_TIMESTAMP(), null); --INSERT INTO image (id, image_size, image_type, image_width, image_height, creator_id, created_at, updated_at) VALUES (10, 1024, 'JPG', 300, 200, 1, CURRENT_TIMESTAMP(), null); \ No newline at end of file diff --git a/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java index d8184bd520..ea19c10adc 100644 --- a/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java @@ -27,9 +27,10 @@ void setUp() { applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); } + /* data.sql 파일에 이미 저장한 데이터로 테스트 합니다. */ @Test void find_success() { - Applicants applicants = applicantsRepository.findAllBySessionId(1L); + Applicants applicants = applicantsRepository.findAllBySessionId(10L); NsUser nsUser_1 = applicants.find(0); NsUser nsUser_2 = applicants.find(1); diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index e73bf15099..b1f2069224 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -6,6 +6,7 @@ import nextstep.courses.domain.course.session.*; import nextstep.payments.domain.Payment; import nextstep.qna.NotFoundException; +import nextstep.users.domain.NsUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -22,6 +23,7 @@ @JdbcTest public class SessionRepositoryTest { private static final Logger LOGGER = LoggerFactory.getLogger(SessionRepositoryTest.class); + private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); @Autowired private JdbcTemplate jdbcTemplate; @@ -45,13 +47,13 @@ void setUp() { imageRepository = new JdbcImageRepository(jdbcTemplate); applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); - image = new Image(1L, 1024, ImageType.JPG, 300, 200, 1L, LocalDateTime.now(), null); - payment = new Payment("1", 1L, 3L, 1000L); + image = new Image(1024, ImageType.JPG, 300, 200, 1L, LocalDateTime.now()); + payment = new Payment("1", 1L, 1L, 1000L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); applicants = new Applicants(); duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); + sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); } @Test @@ -68,4 +70,16 @@ void save_success() { assertThat(savedSession.getSessionState()).isEqualTo(session.getSessionState()); LOGGER.debug("Session: {}", savedSession); } + + @Test + void applySave_success() { + session = new Session(2L, image, duration, sessionState, new Applicants(), + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + int count = sessionRepository.saveApply(JAVAJIGI, session); + Apply savedApply = sessionRepository.findApplyByIds(JAVAJIGI.getId(), session.getId()) + .orElseThrow(NotFoundException::new); + + assertThat(savedApply.getNsUserId()).isEqualTo(JAVAJIGI.getId()); + assertThat(savedApply.getSessionId()).isEqualTo(session.getId()); + } } From 55be7251e45b4458132989c1292fcf14d73ec706 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 11 Dec 2023 23:09:51 +0900 Subject: [PATCH 45/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EC=97=90=20=EC=88=98=EC=A0=95=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 정보가 변경되는 경우를 고려해 수정을 추가합니다 --- .../domain/course/session/Session.java | 20 +++++++++---------- .../course/session/SessionRepository.java | 2 ++ .../infrastructure/JdbcSessionRepository.java | 13 +++++++++++- .../infrastructure/SessionRepositoryTest.java | 18 +++++++++++++++++ 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 1024ca66ab..92698f815b 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -20,7 +20,7 @@ public class Session extends BaseEntity { private Applicants applicants; - private SessionStatus session; + private SessionStatus sessionStatus; public Session(Image image, Duration duration, SessionState sessionState, Long creatorId, LocalDateTime date) { @@ -29,7 +29,7 @@ public Session(Image image, Duration duration, SessionState sessionState, } public Session(Long id, Image image, Duration duration, SessionState sessionState, - Applicants applicants, SessionStatus session, Long creatorId, + Applicants applicants, SessionStatus sessionStatus, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); if (image == null) { @@ -49,7 +49,7 @@ public Session(Long id, Image image, Duration duration, SessionState sessionStat this.duration = duration; this.sessionState = sessionState; this.applicants = applicants; - this.session = session; + this.sessionStatus = sessionStatus; } public boolean sameAmount(Long amount) { @@ -79,7 +79,7 @@ public void apply(NsUser loginUser, Payment payment) { } private void checkStatusOnRecruit() { - if (this.session != SessionStatus.RECRUIT) { + if (this.sessionStatus != SessionStatus.RECRUIT) { throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); } } @@ -92,12 +92,12 @@ private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { public void changeOnReady(LocalDate date) { checkStartDateIsSameOrBefore(date); - this.session = SessionStatus.READY; + this.sessionStatus = SessionStatus.READY; } public void changeOnRecruit(LocalDate date) { checkStartDateIsSameOrBefore(date); - this.session = SessionStatus.RECRUIT; + this.sessionStatus = SessionStatus.RECRUIT; } private void checkStartDateIsSameOrBefore(LocalDate date) { @@ -108,7 +108,7 @@ private void checkStartDateIsSameOrBefore(LocalDate date) { public void changeOnEnd(LocalDate date) { checkEndDateIsSameOrAfter(date); - this.session = SessionStatus.END; + this.sessionStatus = SessionStatus.END; } private void checkEndDateIsSameOrAfter(LocalDate date) { @@ -133,8 +133,8 @@ public Applicants getApplicants() { return applicants; } - public SessionStatus getSession() { - return session; + public SessionStatus getSessionStatus() { + return sessionStatus; } @Override @@ -145,7 +145,7 @@ public String toString() { ", duration=" + duration + ", sessionState=" + sessionState + ", applicants=" + applicants + - ", session=" + session + + ", session=" + sessionStatus + '}'; } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index d4f13af2c0..697ee0c8da 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -12,4 +12,6 @@ public interface SessionRepository { int saveApply(NsUser user, Session session); Optional findApplyByIds(Long NsUserId, Long sessionId); + + int update(Long sessionId, Session session); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 5801867b81..7d955e85d6 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -47,7 +47,7 @@ public int save(Long courseId, Session session) { Duration duration = session.getDuration(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); - SessionStatus status = session.getSession(); + SessionStatus status = session.getSessionStatus(); Image image = session.getImage(); String sql = "insert into session (start_date, end_date, session_type, session_status, amount, quota, image_id, course_id, creator_id, created_at, updated_at) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), status.name(), sessionState.getAmount(), sessionState.getQuota(), image.getId(), courseId, session.getCreatorId(), session.getCreatedAt(), session.getUpdatedAt()); @@ -71,6 +71,17 @@ public Optional findApplyByIds(Long nsUserId, Long sessionId) { return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, nsUserId, sessionId)); } + @Override + public int update(Long sessionId, Session session) { + Duration duration = session.getDuration(); + SessionState sessionState = session.getSessionState(); + SessionType sessionType = sessionState.getSessionType(); + SessionStatus sessionStatus = session.getSessionStatus(); + String sql = "update session set start_date = ?, end_date = ?, session_type = ?, amount = ?, quota = ?, session_status = ? where id = ?"; + return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), + sessionType.name(), sessionState.getAmount(), sessionState.getQuota(), sessionStatus.name(), sessionId); + } + private Image findImageById(Long id) { return this.imageRepository.findById(id).orElseThrow(NotFoundException::new); } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index b1f2069224..37ff15ac71 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -82,4 +82,22 @@ void applySave_success() { assertThat(savedApply.getNsUserId()).isEqualTo(JAVAJIGI.getId()); assertThat(savedApply.getSessionId()).isEqualTo(session.getId()); } + + @Test + void update_success() { + imageRepository.save(image); + Image savedImage = imageRepository.findById(1L).orElseThrow(NotFoundException::new); + + session = new Session(savedImage, duration, sessionState, 1L, localDateTime); + int count = sessionRepository.save(1L, session); + + SessionState updateSessionState = new SessionState(SessionType.CHARGE, 2000L, 30); + session = new Session(1L, savedImage, duration, updateSessionState, new Applicants(), session.getSessionStatus(), 1L, localDateTime, null); + sessionRepository.update(session.getId(), session); + Session updatedSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); + + assertThat(updatedSession.getId()).isEqualTo(1L); + assertThat(updatedSession.getSessionState()).isEqualTo(updateSessionState); + LOGGER.debug("Session: {}", updatedSession); + } } From 794f34655969bd697064db9a8a87543250fdab2d Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 11 Dec 2023 23:16:49 +0900 Subject: [PATCH 46/62] =?UTF-8?q?[feat]=20=EA=B3=BC=EC=A0=95=EA=B3=BC=20?= =?UTF-8?q?=EA=B0=95=EC=9D=98=EC=9D=98=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5=EC=86=8C=EB=A5=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 과정과 강의 서비스에 저장소 로직을 추가합니다 과정의 경우 조회와 저장 시 강의도 같이 할 수 있도록 개선해야 합니다 --- src/main/java/nextstep/courses/service/CourseService.java | 4 ++++ .../java/nextstep/courses/service/SessionService.java | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/main/java/nextstep/courses/service/CourseService.java b/src/main/java/nextstep/courses/service/CourseService.java index 07e590a346..93739060d6 100644 --- a/src/main/java/nextstep/courses/service/CourseService.java +++ b/src/main/java/nextstep/courses/service/CourseService.java @@ -12,6 +12,10 @@ public class CourseService { @Resource(name = "courseRepository") private CourseRepository courseRepository; + public void create(Course course) { + courseRepository.save(course); + } + public void addSession(long courseId, Session session) { Course course = getCourse(courseId); course.addSession(session); diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 14ab39aa15..7b297ebecb 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -15,24 +15,32 @@ public class SessionService { @Resource(name = "sessionRepository") private SessionRepository sessionRepository; + public void create(Long courseId, Session session) { + sessionRepository.save(courseId, session); + } + public void applySession(NsUser loginUser, long sessionId, Payment payment) { Session session = getSession(sessionId); session.apply(loginUser, payment); + sessionRepository.saveApply(loginUser, session); } public void changeOnReady(long sessionId, LocalDate date) { Session session = getSession(sessionId); session.changeOnReady(date); + sessionRepository.update(sessionId, session); } public void changeOnRecruit(long sessionId, LocalDate date) { Session session = getSession(sessionId); session.changeOnRecruit(date); + sessionRepository.update(sessionId, session); } public void changeOnEnd(long sessionId, LocalDate date) { Session session = getSession(sessionId); session.changeOnEnd(date); + sessionRepository.update(sessionId, session); } private Session getSession(long sessionId) { From 055544e7548fcd762225b741e1c20ee38f60b849 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 11 Dec 2023 23:31:42 +0900 Subject: [PATCH 47/62] =?UTF-8?q?[refactor]=20=EA=B3=BC=EC=A0=95=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=97=90=20=EA=B0=95=EC=9D=98=EA=B0=80=20?= =?UTF-8?q?=ED=8F=AC=ED=95=A8=EB=90=98=EB=8F=84=EB=A1=9D=20=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 과정은 강의를 포함하므로 조회 시 같이 조회하도록 개선합니다 --- .../courses/domain/course/Course.java | 8 ++++---- .../course/session/SessionRepository.java | 2 ++ .../infrastructure/JdbcCourseRepository.java | 9 +++++++++ .../infrastructure/JdbcSessionRepository.java | 19 +++++++++++++++++++ .../infrastructure/CourseRepositoryTest.java | 4 ++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 55110bd296..378b2e1e66 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -16,16 +16,16 @@ public class Course extends BaseEntity { private Sessions sessions; public Course(String title, int ordering, Long creatorId, LocalDateTime date) { - this(0L, title, ordering, creatorId, date, null); + this(0L, title, ordering, new Sessions(), creatorId, date, null); } - public Course(Long id, String title, int ordering, Long creatorId, - LocalDateTime createdAt, LocalDateTime updatedAt) { + public Course(Long id, String title, int ordering, Sessions sessions, + Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); this.id = id; this.title = title; this.ordering = ordering; - this.sessions = new Sessions(); + this.sessions = sessions; } public int sessionSize() { diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index 697ee0c8da..9cb424546b 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -14,4 +14,6 @@ public interface SessionRepository { Optional findApplyByIds(Long NsUserId, Long sessionId); int update(Long sessionId, Session session); + + Sessions findAllByCourseId(Long courseId); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index 293c96e774..4ec9a4128a 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -2,6 +2,8 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.domain.course.session.Sessions; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; @@ -12,9 +14,11 @@ @Repository("courseRepository") public class JdbcCourseRepository implements CourseRepository { private final JdbcOperations jdbcTemplate; + private final SessionRepository sessionRepository; public JdbcCourseRepository(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; + this.sessionRepository = new JdbcSessionRepository(jdbcTemplate); } @Override @@ -30,12 +34,17 @@ public Course findById(Long id) { rs.getLong(1), rs.getString(2), rs.getInt(3), + findAllByCourseId(rs.getLong(1)), rs.getLong(4), toLocalDateTime(rs.getTimestamp(5)), toLocalDateTime(rs.getTimestamp(6))); return jdbcTemplate.queryForObject(sql, rowMapper, id); } + private Sessions findAllByCourseId(Long id) { + return this.sessionRepository.findAllByCourseId(id); + } + private LocalDateTime toLocalDateTime(Timestamp timestamp) { if (timestamp == null) { return null; diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 7d955e85d6..ea62409c77 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -12,6 +12,7 @@ import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; @Repository("sessionRepository") @@ -82,6 +83,24 @@ public int update(Long sessionId, Session session) { sessionType.name(), sessionState.getAmount(), sessionState.getQuota(), sessionStatus.name(), sessionId); } + @Override + public Sessions findAllByCourseId(Long courseId) { + String sql = "select id, image_id, start_date, end_date, session_type, amount, quota, session_status, course_id, creator_id, created_at, updated_at from session where course_id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Session( + rs.getLong(1), + findImageById(rs.getLong(2)), + new Duration(toLocalDate(rs.getTimestamp(3)), toLocalDate(rs.getTimestamp(4))), + new SessionState(SessionType.find(rs.getString(5)), rs.getLong(6), rs.getInt(7)), + findAllBySessionId(rs.getLong(1)), + SessionStatus.find(rs.getString(8)), + rs.getLong(10), + toLocalDateTime(rs.getTimestamp(11)), + toLocalDateTime(rs.getTimestamp(12))); + + List sessions = jdbcTemplate.query(sql, rowMapper, courseId); + return new Sessions(sessions); + } + private Image findImageById(Long id) { return this.imageRepository.findById(id).orElseThrow(NotFoundException::new); } diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index eff4697ddc..67c0d588f3 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -2,6 +2,7 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.domain.course.session.SessionRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -23,9 +24,12 @@ public class CourseRepositoryTest { private CourseRepository courseRepository; + private SessionRepository sessionRepository; + @BeforeEach void setUp() { courseRepository = new JdbcCourseRepository(jdbcTemplate); + sessionRepository = new JdbcSessionRepository(jdbcTemplate); } @Test From 9e538ee51ec974d5a7d2fc8e7d512ceac7f967d6 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Mon, 11 Dec 2023 23:40:57 +0900 Subject: [PATCH 48/62] =?UTF-8?q?[feat]=20=EA=B3=BC=EC=A0=95=EC=97=90=20?= =?UTF-8?q?=EA=B0=95=EC=9D=98=20=EC=B6=94=EA=B0=80=EB=A5=BC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EC=97=90=EC=84=9C=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 과정에 강의를 추가하면 강의 테이블에 과정 정보를 입력하도록 합니다. --- .../courses/domain/course/session/SessionRepository.java | 2 ++ .../courses/infrastructure/JdbcSessionRepository.java | 6 ++++++ src/main/java/nextstep/courses/service/CourseService.java | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index 9cb424546b..e028536268 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -16,4 +16,6 @@ public interface SessionRepository { int update(Long sessionId, Session session); Sessions findAllByCourseId(Long courseId); + + int updateCourse(Long courseId, Session session); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index ea62409c77..e223327e68 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -101,6 +101,12 @@ public Sessions findAllByCourseId(Long courseId) { return new Sessions(sessions); } + @Override + public int updateCourse(Long courseId, Session session) { + String sql = "update session set course_id = ? where id = ?"; + return jdbcTemplate.update(sql, session.getId(), courseId); + } + private Image findImageById(Long id) { return this.imageRepository.findById(id).orElseThrow(NotFoundException::new); } diff --git a/src/main/java/nextstep/courses/service/CourseService.java b/src/main/java/nextstep/courses/service/CourseService.java index 93739060d6..74606b2784 100644 --- a/src/main/java/nextstep/courses/service/CourseService.java +++ b/src/main/java/nextstep/courses/service/CourseService.java @@ -3,6 +3,7 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; import nextstep.courses.domain.course.session.Session; +import nextstep.courses.domain.course.session.SessionRepository; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -12,6 +13,9 @@ public class CourseService { @Resource(name = "courseRepository") private CourseRepository courseRepository; + @Resource(name = "sessionRepository") + private SessionRepository sessionRepository; + public void create(Course course) { courseRepository.save(course); } @@ -19,6 +23,7 @@ public void create(Course course) { public void addSession(long courseId, Session session) { Course course = getCourse(courseId); course.addSession(session); + sessionRepository.updateCourse(courseId, session); } private Course getCourse(long courseId) { From a9e7b4f76d371b9fa1721b5506e9e54980ff47d0 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 12 Dec 2023 08:13:42 +0900 Subject: [PATCH 49/62] =?UTF-8?q?[feat]=20=EC=88=98=EA=B0=95=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EC=97=90=20apply=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit id만 사용했지만 수강 지원에 래핑된 클래스를 사용합니다 --- .../courses/domain/course/session/Session.java | 7 ++++++- .../domain/course/session/SessionRepository.java | 2 +- .../courses/infrastructure/JdbcSessionRepository.java | 4 ++-- .../java/nextstep/courses/service/SessionService.java | 10 ++++++---- .../domain/course/service/SessionServiceTest.java | 2 +- .../courses/domain/course/session/SessionTest.java | 10 +++++----- .../courses/infrastructure/SessionRepositoryTest.java | 6 ++++-- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 92698f815b..d325272716 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -68,7 +68,7 @@ public int applyCount() { return this.applicants.size(); } - public void apply(NsUser loginUser, Payment payment) { + public Apply apply(NsUser loginUser, Payment payment, LocalDateTime date) { checkStatusOnRecruit(); if (this.sessionState.charged()) { @@ -76,6 +76,11 @@ public void apply(NsUser loginUser, Payment payment) { } this.applicants.addApplicant(loginUser, sessionState); + return toApply(loginUser, date); + } + + private Apply toApply(NsUser loginUser, LocalDateTime date) { + return new Apply(this.id, loginUser.getId(), loginUser.getId(), date, null); } private void checkStatusOnRecruit() { diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index e028536268..f5a8f18935 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -9,7 +9,7 @@ public interface SessionRepository { int save(Long courseId, Session session); - int saveApply(NsUser user, Session session); + int saveApply(Apply apply); Optional findApplyByIds(Long NsUserId, Long sessionId); diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index e223327e68..abf5999399 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -55,9 +55,9 @@ public int save(Long courseId, Session session) { } @Override - public int saveApply(NsUser nsUser, Session session) { + public int saveApply(Apply apply) { String sql = "insert into apply (session_id, ns_user_id, creator_id, created_at, updated_at) values(?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, session.getId(), nsUser.getId(), nsUser.getId(), LocalDateTime.now(), null); + return jdbcTemplate.update(sql, apply.getSessionId(), apply.getNsUserId(), apply.getCreatorId(), apply.getCreatedAt(), apply.getUpdatedAt()); } @Override diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 7b297ebecb..69fd34c03f 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -1,5 +1,6 @@ package nextstep.courses.service; +import nextstep.courses.domain.course.session.Apply; import nextstep.courses.domain.course.session.Session; import nextstep.courses.domain.course.session.SessionRepository; import nextstep.payments.domain.Payment; @@ -9,20 +10,21 @@ import javax.annotation.Resource; import java.time.LocalDate; +import java.time.LocalDateTime; @Service("sessionService") public class SessionService { @Resource(name = "sessionRepository") private SessionRepository sessionRepository; - public void create(Long courseId, Session session) { + public void create(Long courseId, Session session, LocalDateTime date) { sessionRepository.save(courseId, session); } - public void applySession(NsUser loginUser, long sessionId, Payment payment) { + public void applySession(NsUser loginUser, long sessionId, Payment payment, LocalDateTime date) { Session session = getSession(sessionId); - session.apply(loginUser, payment); - sessionRepository.saveApply(loginUser, session); + Apply apply = session.apply(loginUser, payment, date); + sessionRepository.saveApply(apply); } public void changeOnReady(long sessionId, LocalDate date) { diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 6c948c27c1..da5b87cd6a 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -61,7 +61,7 @@ void apply_success() { when(sessionRepository.findById(session.getId())).thenReturn(Optional.of(session)); assertThat(session.applyCount()).isEqualTo(1); - sessionService.applySession(APPLE, session.getId(), payment); + sessionService.applySession(APPLE, session.getId(), payment, localDateTime); assertThat(session.applyCount()).isEqualTo(2); } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 6990ae2f04..f6abb56866 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -82,7 +82,7 @@ void apply_success() { assertThat(session.applyCount()).isEqualTo(2); - session.apply(APPLE, payment); + session.apply(APPLE, payment, localDateTime); assertThat(session.applyCount()).isEqualTo(3); } @@ -94,7 +94,7 @@ void apply_notRecruitStatus_throwsException() { SessionStatus.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( - () -> session.apply(APPLE, payment) + () -> session.apply(APPLE, payment, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } @@ -109,7 +109,7 @@ void apply_chargeSession_overQuota_throwsException() { applicants.addApplicant(SANJIGI, session.getSessionState()); assertThatThrownBy( - () -> session.apply(APPLE, payment) + () -> session.apply(APPLE, payment, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } @@ -121,7 +121,7 @@ void apply_chargeSession_notPaid_throwsException() { SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( - () -> session.apply(APPLE, null) + () -> session.apply(APPLE, null, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } @@ -133,7 +133,7 @@ void apply_chargeSession_differentAmount_throwsException() { SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( - () -> session.apply(APPLE, differentPayment) + () -> session.apply(APPLE, differentPayment, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 37ff15ac71..f2e9905629 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -40,6 +40,7 @@ public class SessionRepositoryTest { private Duration duration; private SessionState sessionState; private Session session; + private Apply apply; @BeforeEach void setUp() { @@ -54,6 +55,7 @@ void setUp() { applicants = new Applicants(); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); + apply = new Apply(1L, JAVAJIGI.getId(), JAVAJIGI.getId(), localDateTime, localDateTime); } @Test @@ -73,9 +75,9 @@ void save_success() { @Test void applySave_success() { - session = new Session(2L, image, duration, sessionState, new Applicants(), + session = new Session(1L, image, duration, sessionState, new Applicants(), SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); - int count = sessionRepository.saveApply(JAVAJIGI, session); + int count = sessionRepository.saveApply(apply); Apply savedApply = sessionRepository.findApplyByIds(JAVAJIGI.getId(), session.getId()) .orElseThrow(NotFoundException::new); From fa78818d3084c3631c8da5491b53da5ba79f4c2e Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 12 Dec 2023 17:52:02 +0900 Subject: [PATCH 50/62] =?UTF-8?q?[fix]=20=EA=B0=95=EC=9D=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit id는 자동으로 1씩 증가하여 생성되므로 통합 테스를 위해 id를 1 증가시켜서 만들도록 개선합니다 --- .../courses/infrastructure/SessionRepositoryTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index f2e9905629..465c0e28c8 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -88,17 +88,17 @@ void applySave_success() { @Test void update_success() { imageRepository.save(image); - Image savedImage = imageRepository.findById(1L).orElseThrow(NotFoundException::new); + Image savedImage = imageRepository.findById(2L).orElseThrow(NotFoundException::new); session = new Session(savedImage, duration, sessionState, 1L, localDateTime); int count = sessionRepository.save(1L, session); SessionState updateSessionState = new SessionState(SessionType.CHARGE, 2000L, 30); - session = new Session(1L, savedImage, duration, updateSessionState, new Applicants(), session.getSessionStatus(), 1L, localDateTime, null); + session = new Session(2L, savedImage, duration, updateSessionState, new Applicants(), session.getSessionStatus(), 1L, localDateTime, null); sessionRepository.update(session.getId(), session); - Session updatedSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); + Session updatedSession = sessionRepository.findById(2L).orElseThrow(NotFoundException::new); - assertThat(updatedSession.getId()).isEqualTo(1L); + assertThat(updatedSession.getId()).isEqualTo(2L); assertThat(updatedSession.getSessionState()).isEqualTo(updateSessionState); LOGGER.debug("Session: {}", updatedSession); } From f8fdd94c484c1a5d03ca65166b45a25b00c28f1b Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 12 Dec 2023 18:00:47 +0900 Subject: [PATCH 51/62] =?UTF-8?q?[style]=20=EC=BF=BC=EB=A6=AC=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=81=EC=A0=88=ED=95=9C=20=EA=B8=B8=EC=9D=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A4=84=EB=B0=94=EA=BF=88=20=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 쿼리가 기준 줄을 넘어 한줄로 작성되어 줄바꿈을 합니다 --- .../JdbcApplicantsRepository.java | 4 +- .../infrastructure/JdbcCourseRepository.java | 11 +++- .../infrastructure/JdbcImageRepository.java | 11 +++- .../infrastructure/JdbcSessionRepository.java | 60 ++++++++++++++----- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java index 4ab50abed9..d6698a273c 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java @@ -41,7 +41,9 @@ private List findAllApplicantIdsBySessionId(Long sessionId) { } private Optional findAllNsUsersById(Long applicantId) { - String sql = "select id, user_id, password, name, email, created_at, updated_at from ns_user where id = ?"; + String sql = "select " + + "id, user_id, password, name, email, created_at, updated_at " + + "from ns_user where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new NsUser( rs.getLong(1), rs.getString(2), diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index 4ec9a4128a..945e00bc2e 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -23,13 +23,18 @@ public JdbcCourseRepository(JdbcOperations jdbcTemplate) { @Override public int save(Course course) { - String sql = "insert into course (title, ordering, creator_id, created_at) values(?, ?, ?, ?)"; - return jdbcTemplate.update(sql, course.getTitle(), course.getOrdering(), course.getCreatorId(), course.getCreatedAt()); + String sql = "insert into course " + + "(title, ordering, creator_id, created_at) " + + "values(?, ?, ?, ?)"; + return jdbcTemplate.update(sql, course.getTitle(), course.getOrdering(), + course.getCreatorId(), course.getCreatedAt()); } @Override public Course findById(Long id) { - String sql = "select id, title, ordering, creator_id, created_at, updated_at from course where id = ?"; + String sql = "select " + + "id, title, ordering, creator_id, created_at, updated_at " + + "from course where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Course( rs.getLong(1), rs.getString(2), diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java index d2d0566cf0..2a50e6608d 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java @@ -22,7 +22,9 @@ public JdbcImageRepository(JdbcOperations jdbcTemplate) { @Override public Optional findById(Long id) { - String sql = "select id, image_size, image_type, image_width, image_height, creator_id, created_at, updated_at from image where id = ?"; + String sql = "select " + + "id, image_size, image_type, image_width, image_height, creator_id, created_at, updated_at " + + "from image where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Image( rs.getLong(1), rs.getInt(2), @@ -39,8 +41,11 @@ public Optional findById(Long id) { public int save(Image image) { ImageType imageType = image.getImageType(); - String sql = "insert into image (image_size, image_type, image_width, image_height, creator_id, created_at, updated_at) values(?, ?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, image.getImageSize(), imageType.name(), image.getImageWidth(), image.getImageHeight(), image.getCreatorId(), image.getCreatedAt(), image.getUpdatedAt()); + String sql = "insert into image " + + "(image_size, image_type, image_width, image_height, creator_id, created_at, updated_at) " + + "values(?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, image.getImageSize(), imageType.name(), image.getImageWidth(), + image.getImageHeight(), image.getCreatorId(), image.getCreatedAt(), image.getUpdatedAt()); } private LocalDateTime toLocalDateTime(Timestamp timestamp) { diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index abf5999399..6092b58205 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -29,12 +29,22 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public Optional findById(Long id) { - String sql = "select id, image_id, start_date, end_date, session_type, amount, quota, session_status, course_id, creator_id, created_at, updated_at from session where id = ?"; + String sql = "select " + + "id, image_id, start_date, end_date, session_type, amount, quota, " + + "session_status, course_id, creator_id, created_at, updated_at " + + "from session where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), findImageById(rs.getLong(2)), - new Duration(toLocalDate(rs.getTimestamp(3)), toLocalDate(rs.getTimestamp(4))), - new SessionState(SessionType.find(rs.getString(5)), rs.getLong(6), rs.getInt(7)), + new Duration( + toLocalDate(rs.getTimestamp(3)), + toLocalDate(rs.getTimestamp(4)) + ), + new SessionState( + SessionType.find(rs.getString(5)), + rs.getLong(6), + rs.getInt(7) + ), findAllBySessionId(id), SessionStatus.find(rs.getString(8)), rs.getLong(10), @@ -50,19 +60,29 @@ public int save(Long courseId, Session session) { SessionType sessionType = sessionState.getSessionType(); SessionStatus status = session.getSessionStatus(); Image image = session.getImage(); - String sql = "insert into session (start_date, end_date, session_type, session_status, amount, quota, image_id, course_id, creator_id, created_at, updated_at) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), status.name(), sessionState.getAmount(), sessionState.getQuota(), image.getId(), courseId, session.getCreatorId(), session.getCreatedAt(), session.getUpdatedAt()); + String sql = "insert into session " + + "(start_date, end_date, session_type, session_status, amount, " + + "quota, image_id, course_id, creator_id, created_at, updated_at) " + + "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), + status.name(), sessionState.getAmount(), sessionState.getQuota(), image.getId(), courseId, + session.getCreatorId(), session.getCreatedAt(), session.getUpdatedAt()); } @Override public int saveApply(Apply apply) { - String sql = "insert into apply (session_id, ns_user_id, creator_id, created_at, updated_at) values(?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, apply.getSessionId(), apply.getNsUserId(), apply.getCreatorId(), apply.getCreatedAt(), apply.getUpdatedAt()); + String sql = "insert into apply " + + "(session_id, ns_user_id, creator_id, created_at, updated_at) " + + "values(?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, apply.getSessionId(), apply.getNsUserId(), + apply.getCreatorId(), apply.getCreatedAt(), apply.getUpdatedAt()); } @Override public Optional findApplyByIds(Long nsUserId, Long sessionId) { - String sql = "select session_id, ns_user_id, creator_id, created_at, updated_at from apply where ns_user_id = ? and session_id = ?"; + String sql = "select " + + "session_id, ns_user_id, creator_id, created_at, updated_at " + + "from apply where ns_user_id = ? and session_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Apply( rs.getLong(1), rs.getLong(2), @@ -78,19 +98,31 @@ public int update(Long sessionId, Session session) { SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); SessionStatus sessionStatus = session.getSessionStatus(); - String sql = "update session set start_date = ?, end_date = ?, session_type = ?, amount = ?, quota = ?, session_status = ? where id = ?"; - return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), - sessionType.name(), sessionState.getAmount(), sessionState.getQuota(), sessionStatus.name(), sessionId); + String sql = "update session set " + + "start_date = ?, end_date = ?, session_type = ?, amount = ?, quota = ?, session_status = ? " + + "where id = ?"; + return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), + sessionState.getAmount(), sessionState.getQuota(), sessionStatus.name(), sessionId); } @Override public Sessions findAllByCourseId(Long courseId) { - String sql = "select id, image_id, start_date, end_date, session_type, amount, quota, session_status, course_id, creator_id, created_at, updated_at from session where course_id = ?"; + String sql = "select " + + "id, image_id, start_date, end_date, session_type, amount, quota, " + + "session_status, course_id, creator_id, created_at, updated_at " + + "from session where course_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), findImageById(rs.getLong(2)), - new Duration(toLocalDate(rs.getTimestamp(3)), toLocalDate(rs.getTimestamp(4))), - new SessionState(SessionType.find(rs.getString(5)), rs.getLong(6), rs.getInt(7)), + new Duration( + toLocalDate(rs.getTimestamp(3)), + toLocalDate(rs.getTimestamp(4)) + ), + new SessionState( + SessionType.find(rs.getString(5)), + rs.getLong(6), + rs.getInt(7) + ), findAllBySessionId(rs.getLong(1)), SessionStatus.find(rs.getString(8)), rs.getLong(10), From 4ff189553c19223b3769db6cfdaa3101a91e300c Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 12 Dec 2023 18:20:20 +0900 Subject: [PATCH 52/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=9D=98=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 생성, 강의 상태 변경 3가지 테스트를 추가합니다. 예외 테스트는 도메인 테스트에서 했기 때문에 정상 상황만 테스트합니다 --- .../courses/service/SessionService.java | 10 +-- .../course/service/SessionServiceTest.java | 69 ++++++++++++++++++- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 69fd34c03f..5d2cf81914 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -21,31 +21,31 @@ public void create(Long courseId, Session session, LocalDateTime date) { sessionRepository.save(courseId, session); } - public void applySession(NsUser loginUser, long sessionId, Payment payment, LocalDateTime date) { + public void applySession(NsUser loginUser, Long sessionId, Payment payment, LocalDateTime date) { Session session = getSession(sessionId); Apply apply = session.apply(loginUser, payment, date); sessionRepository.saveApply(apply); } - public void changeOnReady(long sessionId, LocalDate date) { + public void changeOnReady(Long sessionId, LocalDate date) { Session session = getSession(sessionId); session.changeOnReady(date); sessionRepository.update(sessionId, session); } - public void changeOnRecruit(long sessionId, LocalDate date) { + public void changeOnRecruit(Long sessionId, LocalDate date) { Session session = getSession(sessionId); session.changeOnRecruit(date); sessionRepository.update(sessionId, session); } - public void changeOnEnd(long sessionId, LocalDate date) { + public void changeOnEnd(Long sessionId, LocalDate date) { Session session = getSession(sessionId); session.changeOnEnd(date); sessionRepository.update(sessionId, session); } - private Session getSession(long sessionId) { + private Session getSession(Long sessionId) { return sessionRepository.findById(sessionId).orElseThrow(NotFoundException::new); } } diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index da5b87cd6a..e3d44b798a 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -5,6 +5,7 @@ import nextstep.courses.domain.course.session.*; import nextstep.courses.service.SessionService; import nextstep.payments.domain.Payment; +import nextstep.qna.NotFoundException; import nextstep.users.domain.NsUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -25,6 +26,10 @@ public class SessionServiceTest { private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net"); + private static final LocalDate DATE_2023_12_5 = LocalDate.of(2023, 12, 5); + private static final LocalDate DATE_2023_12_6 = LocalDate.of(2023, 12, 6); + private static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); + private static final LocalDate DATE_2023_12_12 = LocalDate.of(2023, 12, 12); private Image image; private Payment payment; @@ -33,7 +38,9 @@ public class SessionServiceTest { private Applicants applicants; private Duration duration; private SessionState sessionState; + private SessionStatus sessionStatus; private Session session; + private Session savedSession; @Mock private SessionRepository sessionRepository; @@ -49,10 +56,27 @@ public void setUp() { localDate = LocalDate.of(2023, 12, 5); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); + sessionStatus = SessionStatus.RECRUIT; applicants = new Applicants(); applicants.addApplicant(JAVAJIGI, sessionState); session = new Session(1L, image, duration, sessionState, applicants, - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + sessionStatus, 1L, localDateTime, localDateTime); + } + + @Test + @DisplayName("주어진 강의 정보로 강의를 생성한다.") + void create_success() { + Session newSession = new Session(image, duration, sessionState, 1L, localDateTime); + Session savedSession = new Session(1L, image, duration, sessionState, new Applicants(), + SessionStatus.READY, 1L, localDateTime, localDateTime); + when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); + + sessionService.create(1L, newSession, localDateTime); + Session findSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); + + assertThat(findSession.getId()).isEqualTo(1L); + assertThat(findSession.getSessionStatus()).isEqualTo(SessionStatus.READY); + assertThat(findSession.getApplicants()).hasSize(0); } @Test @@ -64,5 +88,48 @@ void apply_success() { sessionService.applySession(APPLE, session.getId(), payment, localDateTime); assertThat(session.applyCount()).isEqualTo(2); + assertThat(session.getApplicants()).contains(APPLE); + } + + @Test + @DisplayName("강의 시작 날짜 전이라면 주어진 식별자에 해당하는 강의를 준비 상태로 변경한다.") + void changeOnReady_success() { + duration = new Duration(DATE_2023_12_6, DATE_2023_12_12); + savedSession = new Session(1L, image, duration, sessionState, new Applicants(), + SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); + + sessionService.changeOnReady(1L, DATE_2023_12_5); + + assertThat(savedSession.getId()).isEqualTo(1L); + assertThat(savedSession.getSessionStatus()).isEqualTo(SessionStatus.READY); + } + + @Test + @DisplayName("강의 시작 날짜 전이라면 주어진 식별자에 해당하는 강의를 모집중 상태로 변경한다.") + void changeOnRecruit_success() { + duration = new Duration(DATE_2023_12_6, DATE_2023_12_12); + savedSession = new Session(1L, image, duration, sessionState, new Applicants(), + SessionStatus.READY, 1L, localDateTime, localDateTime); + when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); + + sessionService.changeOnRecruit(1L, DATE_2023_12_5); + + assertThat(savedSession.getId()).isEqualTo(1L); + assertThat(savedSession.getSessionStatus()).isEqualTo(SessionStatus.RECRUIT); + } + + @Test + @DisplayName("강의 종료날짜 이후라면 주어진 식별자에 해당하는 강의를 종료 상태로 변경한다.") + void changeOnEnd_success() { + duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); + savedSession = new Session(1L, image, duration, sessionState, new Applicants(), + SessionStatus.READY, 1L, localDateTime, localDateTime); + when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); + + sessionService.changeOnEnd(1L, DATE_2023_12_12); + + assertThat(savedSession.getId()).isEqualTo(1L); + assertThat(savedSession.getSessionStatus()).isEqualTo(SessionStatus.END); } } From e5984d36484ee580e83afc8412dd1084a017f829 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 12 Dec 2023 18:31:51 +0900 Subject: [PATCH 53/62] =?UTF-8?q?[fix]=20apply=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=EB=A5=BC=20=EA=B0=9D=EC=B2=B4=20=EC=A4=91=EC=8B=AC?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 객체의 상태를 꺼내지 않고 객체 자체를 매개변수로 전달하도록 개선합니다 --- .../java/nextstep/courses/domain/course/session/Apply.java | 5 +++++ .../java/nextstep/courses/domain/course/session/Session.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/nextstep/courses/domain/course/session/Apply.java b/src/main/java/nextstep/courses/domain/course/session/Apply.java index a5906537c0..5c452488fe 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Apply.java +++ b/src/main/java/nextstep/courses/domain/course/session/Apply.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course.session; import nextstep.courses.domain.BaseEntity; +import nextstep.users.domain.NsUser; import java.time.LocalDateTime; @@ -9,6 +10,10 @@ public class Apply extends BaseEntity { private Long nsUserId; + public Apply(Session session, NsUser nsUser, LocalDateTime createdAt) { + this(session.getId(), nsUser.getId(), nsUser.getId(), createdAt, null); + } + public Apply(Long sessionId, Long nsUserId, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index d325272716..fdd9bb19cc 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -80,7 +80,7 @@ public Apply apply(NsUser loginUser, Payment payment, LocalDateTime date) { } private Apply toApply(NsUser loginUser, LocalDateTime date) { - return new Apply(this.id, loginUser.getId(), loginUser.getId(), date, null); + return new Apply(this, loginUser, date); } private void checkStatusOnRecruit() { From 7520aeb864481d90398b59aa87d35250590744ad Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Wed, 13 Dec 2023 07:32:37 +0900 Subject: [PATCH 54/62] =?UTF-8?q?[fix]=20=EC=83=9D=EC=84=B1=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=9D=B4=20not=20null=EC=9D=B4=EB=9D=BC=EB=A9=B4=20nu?= =?UTF-8?q?ll=20=EA=B2=80=EC=82=AC=EB=A5=BC=20=EC=A0=9C=EC=99=B8=ED=95=98?= =?UTF-8?q?=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 테이블 스키마 정보에서 생성시간이 not null인 경우 null검사를 제외합니다. null 검사를 허용하면 nullable과 혼동 할 수 있습니다. --- .../infrastructure/JdbcApplicantsRepository.java | 2 +- .../infrastructure/JdbcCourseRepository.java | 2 +- .../infrastructure/JdbcImageRepository.java | 2 +- .../infrastructure/JdbcSessionRepository.java | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java index d6698a273c..920913ebd4 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java @@ -50,7 +50,7 @@ private Optional findAllNsUsersById(Long applicantId) { rs.getString(3), rs.getString(4), rs.getString(5), - toLocalDateTime(rs.getTimestamp(6)), + rs.getTimestamp(6).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(7)) ); diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index 945e00bc2e..5109a0fec1 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -41,7 +41,7 @@ public Course findById(Long id) { rs.getInt(3), findAllByCourseId(rs.getLong(1)), rs.getLong(4), - toLocalDateTime(rs.getTimestamp(5)), + rs.getTimestamp(5).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(6))); return jdbcTemplate.queryForObject(sql, rowMapper, id); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java index 2a50e6608d..4ce1a9b80a 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java @@ -32,7 +32,7 @@ public Optional findById(Long id) { rs.getInt(4), rs.getInt(5), rs.getLong(6), - toLocalDateTime(rs.getTimestamp(7)), + rs.getTimestamp(7).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(8))); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 6092b58205..8a445e3670 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -37,8 +37,8 @@ public Optional findById(Long id) { rs.getLong(1), findImageById(rs.getLong(2)), new Duration( - toLocalDate(rs.getTimestamp(3)), - toLocalDate(rs.getTimestamp(4)) + rs.getTimestamp(3).toLocalDateTime().toLocalDate(), + rs.getTimestamp(4).toLocalDateTime().toLocalDate() ), new SessionState( SessionType.find(rs.getString(5)), @@ -48,7 +48,7 @@ public Optional findById(Long id) { findAllBySessionId(id), SessionStatus.find(rs.getString(8)), rs.getLong(10), - toLocalDateTime(rs.getTimestamp(11)), + rs.getTimestamp(11).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(12))); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); } @@ -87,7 +87,7 @@ public Optional findApplyByIds(Long nsUserId, Long sessionId) { rs.getLong(1), rs.getLong(2), rs.getLong(3), - toLocalDateTime(rs.getTimestamp(4)), + rs.getTimestamp(4).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(5))); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, nsUserId, sessionId)); } @@ -115,8 +115,8 @@ public Sessions findAllByCourseId(Long courseId) { rs.getLong(1), findImageById(rs.getLong(2)), new Duration( - toLocalDate(rs.getTimestamp(3)), - toLocalDate(rs.getTimestamp(4)) + rs.getTimestamp(3).toLocalDateTime().toLocalDate(), + rs.getTimestamp(4).toLocalDateTime().toLocalDate() ), new SessionState( SessionType.find(rs.getString(5)), @@ -126,7 +126,7 @@ public Sessions findAllByCourseId(Long courseId) { findAllBySessionId(rs.getLong(1)), SessionStatus.find(rs.getString(8)), rs.getLong(10), - toLocalDateTime(rs.getTimestamp(11)), + rs.getTimestamp(11).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(12))); List sessions = jdbcTemplate.query(sql, rowMapper, courseId); From 3fcf81ea8234d8bb3a75cd4e684c6857b8458a5c Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Wed, 13 Dec 2023 07:49:26 +0900 Subject: [PATCH 55/62] =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경된 기능 요구 사항을 정리합니다 --- README.md | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 01d209f85d..4c316037b8 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,19 @@ # 학습 관리 시스템(Learning Management System) -## 수강 신청 기능 요구사항 -- 과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다. -- 강의는 시작일과 종료일을 가진다. -- 강의는 강의 커버 이미지 정보를 가진다. - - 이미지 크기는 1MB 이하여야 한다. - - 이미지 타입은 gif, jpg(jpeg 포함),, png, svg만 허용한다. - - 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. -- 강의는 무료 강의와 유료 강의로 나뉜다. - - 무료 강의는 최대 수강 인원 제한이 없다. - - 유료 강의는 강의 최대 수강 인원을 초과할 수 없다. - - 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다. -- 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다. +## 4단계 - 수강신청(요구사항 변경) + +### 변경된 기능 요구사항 - 강의 수강신청은 강의 상태가 모집중일 때만 가능하다. -- 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다. -- 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다. + - 강의가 진행 중인 상태에서도 수강신청이 가능해야 한다. + - 강의 진행 상태(준비중, 진행중, 종료)와 모집 상태(비모집중, 모집중)로 상태 값을 분리해야 한다. +- 강의는 강의 커버 이미지 정보를 가진다. + - 강의는 하나 이상의 커버 이미지를 가질 수 있다. +- 강사가 승인하지 않아도 수강 신청하는 모든 사람이 수강 가능하다. + - 우아한테크코스(무료), 우아한테크캠프 Pro(유료)와 같이 선발된 인원만 수강 가능해야 한다. + - 강사는 수강신청한 사람 중 선발된 인원에 대해서만 수강 승인이 가능해야 한다. + - 강사는 수강신청한 사람 중 선발되지 않은 사람은 수강을 취소할 수 있어야 한다. -## 프로그래밍 요구사항 -- DB 테이블 설계 없이 도메인 모델부터 구현한다. -- 도메인 모델은 TDD로 구현한다. - - 단, Service 클래스는 단위 테스트가 없어도 된다. -- 다음 동영상을 참고해 DB 테이블보다 도메인 모델을 먼저 설계하고 구현한다. \ No newline at end of file +### 프로그래밍 요구사항 +- 리팩터링할 때 컴파일 에러와 기존의 단위 테스트의 실패를 최소화하면서 점진적인 리팩터링이 가능하도록 한다. +- DB 테이블에 데이터가 존재한다는 가정하에 리팩터링해야 한다. + - 즉, 기존에 쌓인 데이터를 제거하지 않은 상태로 리팩터링 해야 한다. \ No newline at end of file From abaf3036d8c5663f2309699d6494e35d466e58d0 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Wed, 13 Dec 2023 20:56:54 +0900 Subject: [PATCH 56/62] =?UTF-8?q?[fix]=20=EA=B0=95=EC=9D=98=EC=97=90=201?= =?UTF-8?q?=EA=B0=9C=20=EC=9D=B4=EC=83=81=EC=9D=98=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=EB=A5=BC=20=EA=B0=80=EC=A7=80=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에는 강의가 1개의 이미지만 가질 수 있었지만 1개 이상의 이미지를 가질 수 있도록 개선합니다. --- .../courses/domain/course/Course.java | 10 ++ .../domain/course/image/ImageRepository.java | 9 -- .../domain/course/session/Session.java | 33 +++-- .../course/session/SessionRepository.java | 2 +- .../course/{ => session}/image/Image.java | 6 +- .../course/session/image/ImageRepository.java | 11 ++ .../course/{ => session}/image/ImageType.java | 2 +- .../domain/course/session/image/Images.java | 33 +++++ .../infrastructure/JdbcImageRepository.java | 76 ++++++++++-- .../infrastructure/JdbcSessionRepository.java | 115 ++++++++++++------ src/main/resources/schema.sql | 2 +- .../course/service/CourseServiceTest.java | 20 ++- .../course/service/SessionServiceTest.java | 20 +-- .../domain/course/session/SessionTest.java | 30 +++-- .../domain/course/session/SessionsTest.java | 9 +- .../course/{ => session}/image/ImageTest.java | 2 +- .../{ => session}/image/ImageTypeTest.java | 2 +- .../course/session/image/ImagesTest.java | 20 +++ .../infrastructure/ImageRepositoryTest.java | 10 +- .../infrastructure/SessionRepositoryTest.java | 43 ++++--- 20 files changed, 323 insertions(+), 132 deletions(-) delete mode 100644 src/main/java/nextstep/courses/domain/course/image/ImageRepository.java rename src/main/java/nextstep/courses/domain/course/{ => session}/image/Image.java (96%) create mode 100644 src/main/java/nextstep/courses/domain/course/session/image/ImageRepository.java rename src/main/java/nextstep/courses/domain/course/{ => session}/image/ImageType.java (94%) create mode 100644 src/main/java/nextstep/courses/domain/course/session/image/Images.java rename src/test/java/nextstep/courses/domain/course/{ => session}/image/ImageTest.java (96%) rename src/test/java/nextstep/courses/domain/course/{ => session}/image/ImageTypeTest.java (89%) create mode 100644 src/test/java/nextstep/courses/domain/course/session/image/ImagesTest.java diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 378b2e1e66..3451bb9f12 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -51,4 +51,14 @@ public int getOrdering() { public Long getCreatorId() { return super.getCreatorId(); } + + @Override + public String toString() { + return "Course{" + + "id=" + id + + ", title='" + title + '\'' + + ", ordering=" + ordering + + ", sessions=" + sessions + + '}'; + } } diff --git a/src/main/java/nextstep/courses/domain/course/image/ImageRepository.java b/src/main/java/nextstep/courses/domain/course/image/ImageRepository.java deleted file mode 100644 index 4863ca37cb..0000000000 --- a/src/main/java/nextstep/courses/domain/course/image/ImageRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package nextstep.courses.domain.course.image; - -import java.util.Optional; - -public interface ImageRepository { - Optional findById(Long id); - - int save(Image image); -} diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index fdd9bb19cc..dad5a1cbcf 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -1,7 +1,8 @@ package nextstep.courses.domain.course.session; import nextstep.courses.domain.BaseEntity; -import nextstep.courses.domain.course.image.Image; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.Images; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; @@ -12,7 +13,7 @@ public class Session extends BaseEntity { private Long id; - private Image image; + private Images images; private Duration duration; @@ -22,17 +23,17 @@ public class Session extends BaseEntity { private SessionStatus sessionStatus; - public Session(Image image, Duration duration, SessionState sessionState, + public Session(Images images, Duration duration, SessionState sessionState, Long creatorId, LocalDateTime date) { - this(0L, image, duration, sessionState, new Applicants(), + this(0L, images, duration, sessionState, new Applicants(), SessionStatus.READY, creatorId, date, null); } - public Session(Long id, Image image, Duration duration, SessionState sessionState, + public Session(Long id, Images images, Duration duration, SessionState sessionState, Applicants applicants, SessionStatus sessionStatus, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); - if (image == null) { + if (images == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); } @@ -45,13 +46,25 @@ public Session(Long id, Image image, Duration duration, SessionState sessionStat } this.id = id; - this.image = image; + this.images = images; this.duration = duration; this.sessionState = sessionState; this.applicants = applicants; this.sessionStatus = sessionStatus; } + public void setId(Long id) { + this.id = id; + } + + public void setImages(Images images) { + this.images = images; + } + + public void setSessionState(SessionState updateSessionState) { + this.sessionState = updateSessionState; + } + public boolean sameAmount(Long amount) { return this.sessionState.sameAmount(amount); } @@ -122,8 +135,8 @@ private void checkEndDateIsSameOrAfter(LocalDate date) { } } - public Image getImage() { - return image; + public Images getImages() { + return images; } public Duration getDuration() { @@ -146,7 +159,7 @@ public SessionStatus getSessionStatus() { public String toString() { return "Session{" + "id=" + id + - ", image=" + image + + ", images=" + images + ", duration=" + duration + ", sessionState=" + sessionState + ", applicants=" + applicants + diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index f5a8f18935..5266cf1746 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -7,7 +7,7 @@ public interface SessionRepository { Optional findById(Long id); - int save(Long courseId, Session session); + Session save(Long courseId, Session session); int saveApply(Apply apply); diff --git a/src/main/java/nextstep/courses/domain/course/image/Image.java b/src/main/java/nextstep/courses/domain/course/session/image/Image.java similarity index 96% rename from src/main/java/nextstep/courses/domain/course/image/Image.java rename to src/main/java/nextstep/courses/domain/course/session/image/Image.java index d4776e2c45..43dbe95d9c 100644 --- a/src/main/java/nextstep/courses/domain/course/image/Image.java +++ b/src/main/java/nextstep/courses/domain/course/session/image/Image.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain.course.image; +package nextstep.courses.domain.course.session.image; import nextstep.courses.domain.BaseEntity; @@ -59,6 +59,10 @@ private static void checkWidthAndHeightRatioIsValid(int imageWidth, int imageHei } } + public void setId(Long id) { + this.id = id; + } + public Long getId() { return id; } diff --git a/src/main/java/nextstep/courses/domain/course/session/image/ImageRepository.java b/src/main/java/nextstep/courses/domain/course/session/image/ImageRepository.java new file mode 100644 index 0000000000..2c296a3571 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/image/ImageRepository.java @@ -0,0 +1,11 @@ +package nextstep.courses.domain.course.session.image; + +import java.util.Optional; + +public interface ImageRepository { + Optional findById(Long id); + + Image save(Long sessionId, Image image); + + Images findAllBySessionId(Long sessionId); +} diff --git a/src/main/java/nextstep/courses/domain/course/image/ImageType.java b/src/main/java/nextstep/courses/domain/course/session/image/ImageType.java similarity index 94% rename from src/main/java/nextstep/courses/domain/course/image/ImageType.java rename to src/main/java/nextstep/courses/domain/course/session/image/ImageType.java index f2ab7b1a9b..727887c8f6 100644 --- a/src/main/java/nextstep/courses/domain/course/image/ImageType.java +++ b/src/main/java/nextstep/courses/domain/course/session/image/ImageType.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain.course.image; +package nextstep.courses.domain.course.session.image; import java.util.Arrays; diff --git a/src/main/java/nextstep/courses/domain/course/session/image/Images.java b/src/main/java/nextstep/courses/domain/course/session/image/Images.java new file mode 100644 index 0000000000..58ba6ef087 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/image/Images.java @@ -0,0 +1,33 @@ +package nextstep.courses.domain.course.session.image; + +import java.util.Iterator; +import java.util.List; + +public class Images implements Iterable { + private final List images; + + public Images(List images) { + validate(images); + + this.images = images; + } + + private void validate(List images) { + checkImagesIsNull(images); + } + + private void checkImagesIsNull(List images) { + if (images == null || images.isEmpty()) { + throw new IllegalArgumentException("이미지는 최소 1개 이상 존재해야 합니다."); + } + } + + public void add(Image image) { + this.images.add(image); + } + + @Override + public Iterator iterator() { + return this.images.iterator(); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java index 4ce1a9b80a..39358ee994 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java @@ -1,20 +1,28 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.course.Course; -import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.image.ImageRepository; -import nextstep.courses.domain.course.image.ImageType; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageRepository; +import nextstep.courses.domain.course.session.image.ImageType; +import nextstep.courses.domain.course.session.image.Images; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; import java.util.Optional; @Repository("imageRepository") public class JdbcImageRepository implements ImageRepository { private final JdbcOperations jdbcTemplate; + KeyHolder keyHolder = new GeneratedKeyHolder(); public JdbcImageRepository(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -23,7 +31,7 @@ public JdbcImageRepository(JdbcOperations jdbcTemplate) { @Override public Optional findById(Long id) { String sql = "select " + - "id, image_size, image_type, image_width, image_height, creator_id, created_at, updated_at " + + "id, image_size, image_type, image_width, image_height, session_id, creator_id, created_at, updated_at " + "from image where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Image( rs.getLong(1), @@ -31,21 +39,56 @@ public Optional findById(Long id) { ImageType.find(rs.getString(3)), rs.getInt(4), rs.getInt(5), - rs.getLong(6), - rs.getTimestamp(7).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(8))); + rs.getLong(7), + rs.getTimestamp(8).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(9))); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); } @Override - public int save(Image image) { + public Image save(Long sessionId, Image image) { ImageType imageType = image.getImageType(); String sql = "insert into image " + - "(image_size, image_type, image_width, image_height, creator_id, created_at, updated_at) " + - "values(?, ?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, image.getImageSize(), imageType.name(), image.getImageWidth(), - image.getImageHeight(), image.getCreatorId(), image.getCreatedAt(), image.getUpdatedAt()); + "(image_size, image_type, image_width, image_height, session_id, creator_id, created_at, updated_at) " + + "values(?, ?, ?, ?, ?, ?, ?, ?)"; + + jdbcTemplate.update((Connection connection) -> { + PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + ps.setInt(1, image.getImageSize()); + ps.setString(2, imageType.name()); + ps.setInt(3, image.getImageWidth()); + ps.setInt(4, image.getImageHeight()); + ps.setLong(5, sessionId); + ps.setLong(6, image.getCreatorId()); + ps.setTimestamp(7, Timestamp.valueOf(image.getCreatedAt())); + ps.setTimestamp(8, toTimeStamp(image.getCreatedAt())); + return ps; + }, keyHolder); + + Long imageId = Objects.requireNonNull(keyHolder.getKey()).longValue(); + image.setId(imageId); + + return image; + } + + @Override + public Images findAllBySessionId(Long sessionId) { + String sql = "select " + + "id, image_size, image_type, image_width, image_height, session_id, creator_id, created_at, updated_at " + + "from image where session_id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Image( + rs.getLong(1), + rs.getInt(2), + ImageType.find(rs.getString(3)), + rs.getInt(4), + rs.getInt(5), + rs.getLong(7), + rs.getTimestamp(8).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(9))); + + List images = jdbcTemplate.query(sql, rowMapper, sessionId); + return new Images(images); } private LocalDateTime toLocalDateTime(Timestamp timestamp) { @@ -54,4 +97,11 @@ private LocalDateTime toLocalDateTime(Timestamp timestamp) { } return timestamp.toLocalDateTime(); } + + private Timestamp toTimeStamp(LocalDateTime localDateTime) { + if (localDateTime == null) { + return null; + } + return Timestamp.valueOf(localDateTime); + } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 8a445e3670..f644217959 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,18 +1,23 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.image.ImageRepository; import nextstep.courses.domain.course.session.*; -import nextstep.qna.NotFoundException; -import nextstep.users.domain.NsUser; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageRepository; +import nextstep.courses.domain.course.session.image.Images; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; import java.sql.Timestamp; -import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; @Repository("sessionRepository") @@ -20,6 +25,7 @@ public class JdbcSessionRepository implements SessionRepository { private final JdbcOperations jdbcTemplate; private final ImageRepository imageRepository; private final ApplicantsRepository applicantsRepository; + KeyHolder keyHolder = new GeneratedKeyHolder(); public JdbcSessionRepository(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -30,43 +36,72 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public Optional findById(Long id) { String sql = "select " + - "id, image_id, start_date, end_date, session_type, amount, quota, " + + "id, start_date, end_date, session_type, amount, quota, " + "session_status, course_id, creator_id, created_at, updated_at " + "from session where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), - findImageById(rs.getLong(2)), + findAllImagesBySessionId(rs.getLong(1)), new Duration( - rs.getTimestamp(3).toLocalDateTime().toLocalDate(), - rs.getTimestamp(4).toLocalDateTime().toLocalDate() + rs.getTimestamp(2).toLocalDateTime().toLocalDate(), + rs.getTimestamp(3).toLocalDateTime().toLocalDate() ), new SessionState( - SessionType.find(rs.getString(5)), - rs.getLong(6), - rs.getInt(7) + SessionType.find(rs.getString(4)), + rs.getLong(5), + rs.getInt(6) ), findAllBySessionId(id), - SessionStatus.find(rs.getString(8)), - rs.getLong(10), - rs.getTimestamp(11).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(12))); + SessionStatus.find(rs.getString(7)), + rs.getLong(9), + rs.getTimestamp(10).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(11))); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); } @Override - public int save(Long courseId, Session session) { + public Session save(Long courseId, Session session) { + Session savedSession = saveSession(courseId, session); + + List images = new ArrayList<>(); + for (Image image : session.getImages()) { + Image savedImage = imageRepository.save(savedSession.getId(), image); + images.add(savedImage); + } + + savedSession.setImages(new Images(images)); + return savedSession; + } + + private Session saveSession(Long courseId, Session session) { Duration duration = session.getDuration(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); SessionStatus status = session.getSessionStatus(); - Image image = session.getImage(); String sql = "insert into session " + - "(start_date, end_date, session_type, session_status, amount, " + - "quota, image_id, course_id, creator_id, created_at, updated_at) " + - "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), - status.name(), sessionState.getAmount(), sessionState.getQuota(), image.getId(), courseId, - session.getCreatorId(), session.getCreatedAt(), session.getUpdatedAt()); + "(start_date, end_date, session_type, session_status, amount, " + + "quota, course_id, creator_id, created_at, updated_at) " + + "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + jdbcTemplate.update((Connection connection) -> { + PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + ps.setTimestamp(1, Timestamp.valueOf(duration.getStartDate().atStartOfDay())); + ps.setTimestamp(2, Timestamp.valueOf(duration.getEndDate().atStartOfDay())); + ps.setString(3, sessionType.name()); + ps.setString(4, status.name()); + ps.setLong(5, sessionState.getAmount()); + ps.setInt(6, sessionState.getQuota()); + ps.setLong(7, courseId); + ps.setLong(8, session.getCreatorId()); + ps.setTimestamp(9, Timestamp.valueOf(session.getCreatedAt())); + ps.setTimestamp(10, toTimeStamp(session.getUpdatedAt())); + return ps; + }, keyHolder); + + Long sessionId = Objects.requireNonNull(keyHolder.getKey()).longValue(); + session.setId(sessionId); + + return session; } @Override @@ -108,26 +143,26 @@ public int update(Long sessionId, Session session) { @Override public Sessions findAllByCourseId(Long courseId) { String sql = "select " + - "id, image_id, start_date, end_date, session_type, amount, quota, " + + "id, start_date, end_date, session_type, amount, quota, " + "session_status, course_id, creator_id, created_at, updated_at " + "from session where course_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), - findImageById(rs.getLong(2)), + findAllImagesBySessionId(rs.getLong(1)), new Duration( - rs.getTimestamp(3).toLocalDateTime().toLocalDate(), - rs.getTimestamp(4).toLocalDateTime().toLocalDate() + rs.getTimestamp(2).toLocalDateTime().toLocalDate(), + rs.getTimestamp(3).toLocalDateTime().toLocalDate() ), new SessionState( - SessionType.find(rs.getString(5)), - rs.getLong(6), - rs.getInt(7) + SessionType.find(rs.getString(4)), + rs.getLong(5), + rs.getInt(6) ), - findAllBySessionId(rs.getLong(1)), + findAllBySessionId(rs.getLong(7)), SessionStatus.find(rs.getString(8)), - rs.getLong(10), - rs.getTimestamp(11).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(12))); + rs.getLong(9), + rs.getTimestamp(10).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(11))); List sessions = jdbcTemplate.query(sql, rowMapper, courseId); return new Sessions(sessions); @@ -139,8 +174,8 @@ public int updateCourse(Long courseId, Session session) { return jdbcTemplate.update(sql, session.getId(), courseId); } - private Image findImageById(Long id) { - return this.imageRepository.findById(id).orElseThrow(NotFoundException::new); + private Images findAllImagesBySessionId(Long id) { + return this.imageRepository.findAllBySessionId(id); } private Applicants findAllBySessionId(Long id) { @@ -154,10 +189,10 @@ private LocalDateTime toLocalDateTime(Timestamp timestamp) { return timestamp.toLocalDateTime(); } - private LocalDate toLocalDate(Timestamp timestamp) { - if (timestamp == null) { + private Timestamp toTimeStamp(LocalDateTime localDateTime) { + if (localDateTime == null) { return null; } - return timestamp.toLocalDateTime().toLocalDate(); + return Timestamp.valueOf(localDateTime); } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index ce6149e23a..d521f48b28 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -4,6 +4,7 @@ create table image ( image_type varchar(20) not null, image_width bigint not null, image_height bigint not null, + session_id bigint not null, creator_id bigint not null, created_at timestamp not null, updated_at timestamp, @@ -27,7 +28,6 @@ create table session ( session_status varchar(20) not null, amount bigint not null, quota bigint not null, - image_id bigint not null, course_id bigint not null, creator_id bigint not null, created_at timestamp not null, diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 275cce548e..fba4d6005f 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -2,9 +2,10 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; -import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.image.ImageType; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageType; import nextstep.courses.domain.course.session.*; +import nextstep.courses.domain.course.session.image.Images; import nextstep.courses.service.CourseService; import nextstep.payments.domain.Payment; import org.junit.jupiter.api.BeforeEach; @@ -17,12 +18,14 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class CourseServiceTest { + private Images images; private Image image; private Payment payment; private LocalDate localDate; @@ -30,23 +33,32 @@ public class CourseServiceTest { private Duration duration; private SessionState sessionState; private Course course; + private Sessions sessions; private Session session; + @Mock private CourseRepository courseRepository; + + @Mock + private SessionRepository sessionRepository; + @InjectMocks private CourseService courseService; @BeforeEach void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - course = new Course("math", 1, 1L, localDateTime); + course = new Course(1L, "math", 1, new Sessions(), 1L, localDateTime, null); image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); + images = new Images(List.of(image)); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - session = new Session(1L, image, duration, sessionState, new Applicants(), + session = new Session(1L, images, duration, sessionState, new Applicants(), SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + sessions = new Sessions(); + sessions.add(session); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index e3d44b798a..e1402ba214 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -1,8 +1,9 @@ package nextstep.courses.domain.course.service; -import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.image.ImageType; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageType; import nextstep.courses.domain.course.session.*; +import nextstep.courses.domain.course.session.image.Images; import nextstep.courses.service.SessionService; import nextstep.payments.domain.Payment; import nextstep.qna.NotFoundException; @@ -17,6 +18,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -31,6 +33,7 @@ public class SessionServiceTest { private static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); private static final LocalDate DATE_2023_12_12 = LocalDate.of(2023, 12, 12); + private Images images; private Image image; private Payment payment; private LocalDate localDate; @@ -52,6 +55,7 @@ public class SessionServiceTest { public void setUp() { localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); + images = new Images(List.of(image)); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); duration = new Duration(localDate, localDate); @@ -59,15 +63,15 @@ public void setUp() { sessionStatus = SessionStatus.RECRUIT; applicants = new Applicants(); applicants.addApplicant(JAVAJIGI, sessionState); - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, sessionStatus, 1L, localDateTime, localDateTime); } @Test @DisplayName("주어진 강의 정보로 강의를 생성한다.") void create_success() { - Session newSession = new Session(image, duration, sessionState, 1L, localDateTime); - Session savedSession = new Session(1L, image, duration, sessionState, new Applicants(), + Session newSession = new Session(images, duration, sessionState, 1L, localDateTime); + Session savedSession = new Session(1L, images, duration, sessionState, new Applicants(), SessionStatus.READY, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); @@ -95,7 +99,7 @@ void apply_success() { @DisplayName("강의 시작 날짜 전이라면 주어진 식별자에 해당하는 강의를 준비 상태로 변경한다.") void changeOnReady_success() { duration = new Duration(DATE_2023_12_6, DATE_2023_12_12); - savedSession = new Session(1L, image, duration, sessionState, new Applicants(), + savedSession = new Session(1L, images, duration, sessionState, new Applicants(), SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); @@ -109,7 +113,7 @@ void changeOnReady_success() { @DisplayName("강의 시작 날짜 전이라면 주어진 식별자에 해당하는 강의를 모집중 상태로 변경한다.") void changeOnRecruit_success() { duration = new Duration(DATE_2023_12_6, DATE_2023_12_12); - savedSession = new Session(1L, image, duration, sessionState, new Applicants(), + savedSession = new Session(1L, images, duration, sessionState, new Applicants(), SessionStatus.READY, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); @@ -123,7 +127,7 @@ void changeOnRecruit_success() { @DisplayName("강의 종료날짜 이후라면 주어진 식별자에 해당하는 강의를 종료 상태로 변경한다.") void changeOnEnd_success() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); - savedSession = new Session(1L, image, duration, sessionState, new Applicants(), + savedSession = new Session(1L, images, duration, sessionState, new Applicants(), SessionStatus.READY, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index f6abb56866..4c079bcfb8 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -1,7 +1,8 @@ package nextstep.courses.domain.course.session; -import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.image.ImageType; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageType; +import nextstep.courses.domain.course.session.image.Images; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; import org.junit.jupiter.api.BeforeEach; @@ -10,6 +11,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -23,6 +25,7 @@ public class SessionTest { private static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); private static final LocalDate DATE_2023_12_12 = LocalDate.of(2023, 12, 12); + private Images images; private Image image; private Payment payment; private Payment differentPayment; @@ -40,12 +43,13 @@ void setUp() { localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); + images = new Images(List.of(image)); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); applicants = new Applicants(); this.applicants.addApplicant(JAVAJIGI, sessionState); this.applicants.addApplicant(SANJIGI, sessionState); - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); } @@ -62,7 +66,7 @@ void newObject_imageNull_throwsException() { @DisplayName("강의는 기간이 없으면 기간을 추가하라는 예외를 반환한다.") void newObject_durationNull_throwsException() { assertThatThrownBy( - () -> new Session(image, null, sessionState, 1L, localDateTime) + () -> new Session(images, null, sessionState, 1L, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } @@ -70,14 +74,14 @@ void newObject_durationNull_throwsException() { @DisplayName("강의는 강의 상태가 없으면 상태를 추가하라는 예외를 반환한다.") void newObject_sessionStateNull_throwsException() { assertThatThrownBy( - () -> new Session(image, duration, null, 1L, localDateTime) + () -> new Session(images, duration, null, 1L, localDateTime) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThat(session.applyCount()).isEqualTo(2); @@ -90,7 +94,7 @@ void apply_success() { @Test @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( @@ -103,7 +107,7 @@ void apply_notRecruitStatus_throwsException() { void apply_chargeSession_overQuota_throwsException() { applicants = new Applicants(); sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); applicants.addApplicant(JAVAJIGI, session.getSessionState()); applicants.addApplicant(SANJIGI, session.getSessionState()); @@ -117,7 +121,7 @@ void apply_chargeSession_overQuota_throwsException() { @DisplayName("수강 신청은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_notPaid_throwsException() { sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( @@ -129,7 +133,7 @@ void apply_chargeSession_notPaid_throwsException() { @DisplayName("수강 신청은 수강 금액과 지불 금액이 다르면 신청할 수 없다는 예외를 던진다.") void apply_chargeSession_differentAmount_throwsException() { sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( @@ -142,7 +146,7 @@ void apply_chargeSession_differentAmount_throwsException() { void changeOnReady_startDateIsBeforeOrSame_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( @@ -159,7 +163,7 @@ void changeOnReady_startDateIsBeforeOrSame_throwsException() { void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( @@ -176,7 +180,7 @@ void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { void changeOnEnd_EndDateIsSameOrAfter_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); - session = new Session(1L, image, duration, sessionState, applicants, + session = new Session(1L, images, duration, sessionState, applicants, SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); assertThatThrownBy( diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index ee428d65bb..7cff0de1eb 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -1,7 +1,8 @@ package nextstep.courses.domain.course.session; -import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.image.ImageType; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageType; +import nextstep.courses.domain.course.session.image.Images; import nextstep.payments.domain.Payment; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -17,6 +18,7 @@ public class SessionsTest { private Sessions sessions; + private Images images; private Image image; private Payment payment; private LocalDate localDate; @@ -30,11 +32,12 @@ void setUp() { sessions = new Sessions(); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); + images = new Images(List.of(image)); payment = new Payment("1", 1L, 3L, 1000L); localDate = LocalDate.of(2023, 12, 5); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - session = new Session(1L, image, duration, sessionState, new Applicants(), + session = new Session(1L, images, duration, sessionState, new Applicants(), SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); sessions.add(session); } diff --git a/src/test/java/nextstep/courses/domain/course/image/ImageTest.java b/src/test/java/nextstep/courses/domain/course/session/image/ImageTest.java similarity index 96% rename from src/test/java/nextstep/courses/domain/course/image/ImageTest.java rename to src/test/java/nextstep/courses/domain/course/session/image/ImageTest.java index 9fa73e053f..d2f1177cf5 100644 --- a/src/test/java/nextstep/courses/domain/course/image/ImageTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/image/ImageTest.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain.course.image; +package nextstep.courses.domain.course.session.image; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/nextstep/courses/domain/course/image/ImageTypeTest.java b/src/test/java/nextstep/courses/domain/course/session/image/ImageTypeTest.java similarity index 89% rename from src/test/java/nextstep/courses/domain/course/image/ImageTypeTest.java rename to src/test/java/nextstep/courses/domain/course/session/image/ImageTypeTest.java index 3194248b88..dc757815a4 100644 --- a/src/test/java/nextstep/courses/domain/course/image/ImageTypeTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/image/ImageTypeTest.java @@ -1,4 +1,4 @@ -package nextstep.courses.domain.course.image; +package nextstep.courses.domain.course.session.image; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/nextstep/courses/domain/course/session/image/ImagesTest.java b/src/test/java/nextstep/courses/domain/course/session/image/ImagesTest.java new file mode 100644 index 0000000000..e767cac0a8 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/image/ImagesTest.java @@ -0,0 +1,20 @@ +package nextstep.courses.domain.course.session.image; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ImagesTest { + @ParameterizedTest + @NullAndEmptySource + @DisplayName("Images 은 빈 값이 주어지면 예외를 던진다.") + void newObject_null_throwsException(List images) { + assertThatThrownBy( + () -> new Images(images) + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java index 443ce1ab62..2f4280eb7e 100644 --- a/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java @@ -1,9 +1,8 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.image.ImageRepository; -import nextstep.courses.domain.course.image.ImageType; -import nextstep.qna.NotFoundException; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageRepository; +import nextstep.courses.domain.course.session.image.ImageType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -33,8 +32,7 @@ void setUp() { @Test void save_success() { Image image = new Image(1024, ImageType.JPG, 300, 200, 1L, LocalDateTime.now()); - int count = imageRepository.save(image); - Image savedImage = imageRepository.findById(1L).orElseThrow(NotFoundException::new); + Image savedImage = imageRepository.save(1L, image); assertThat(image.getImageSize()).isEqualTo(savedImage.getImageSize()); assertThat(image.getImageType()).isEqualTo(savedImage.getImageType()); assertThat(image.getImageWidth()).isEqualTo(savedImage.getImageWidth()); diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 465c0e28c8..31f917a17c 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -1,9 +1,10 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.course.image.Image; -import nextstep.courses.domain.course.image.ImageRepository; -import nextstep.courses.domain.course.image.ImageType; +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageRepository; +import nextstep.courses.domain.course.session.image.ImageType; import nextstep.courses.domain.course.session.*; +import nextstep.courses.domain.course.session.image.Images; import nextstep.payments.domain.Payment; import nextstep.qna.NotFoundException; import nextstep.users.domain.NsUser; @@ -17,6 +18,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +35,7 @@ public class SessionRepositoryTest { private ImageRepository imageRepository; private ApplicantsRepository applicantsRepository; + private Images images; private Image image; private Payment payment; private LocalDate localDate; @@ -49,6 +53,9 @@ void setUp() { applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); image = new Image(1024, ImageType.JPG, 300, 200, 1L, LocalDateTime.now()); + List imageList = new ArrayList<>(); + imageList.add(image); + images = new Images(imageList); payment = new Payment("1", 1L, 1L, 1000L); localDate = LocalDate.of(2023, 12, 5); localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); @@ -60,22 +67,21 @@ void setUp() { @Test void save_success() { - imageRepository.save(image); - Image savedImage = imageRepository.findById(1L).orElseThrow(NotFoundException::new); - - session = new Session(savedImage, duration, sessionState, 1L, localDateTime); - int count = sessionRepository.save(1L, session); - Session savedSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); + session = new Session(images, duration, sessionState, 1L, localDateTime); + Session savedSession = sessionRepository.save(1L, session); + //Session savedSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getImage()).isEqualTo(session.getImage()); assertThat(savedSession.getDuration()).isEqualTo(session.getDuration()); assertThat(savedSession.getSessionState()).isEqualTo(session.getSessionState()); + + //Image savedImage = imageRepository.findById(1L).orElseThrow(NotFoundException::new); + LOGGER.debug("Session: {}", savedSession); } @Test void applySave_success() { - session = new Session(1L, image, duration, sessionState, new Applicants(), + session = new Session(1L, images, duration, sessionState, new Applicants(), SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); int count = sessionRepository.saveApply(apply); Apply savedApply = sessionRepository.findApplyByIds(JAVAJIGI.getId(), session.getId()) @@ -87,18 +93,15 @@ void applySave_success() { @Test void update_success() { - imageRepository.save(image); - Image savedImage = imageRepository.findById(2L).orElseThrow(NotFoundException::new); - - session = new Session(savedImage, duration, sessionState, 1L, localDateTime); - int count = sessionRepository.save(1L, session); + session = new Session(images, duration, sessionState, 1L, localDateTime); + Session savedSession = sessionRepository.save(1L, session); SessionState updateSessionState = new SessionState(SessionType.CHARGE, 2000L, 30); - session = new Session(2L, savedImage, duration, updateSessionState, new Applicants(), session.getSessionStatus(), 1L, localDateTime, null); - sessionRepository.update(session.getId(), session); - Session updatedSession = sessionRepository.findById(2L).orElseThrow(NotFoundException::new); + savedSession.setSessionState(updateSessionState); + sessionRepository.update(savedSession.getId(), savedSession); + Session updatedSession = sessionRepository.findById(savedSession.getId()).orElseThrow(NotFoundException::new); - assertThat(updatedSession.getId()).isEqualTo(2L); + assertThat(updatedSession.getId()).isEqualTo(savedSession.getId()); assertThat(updatedSession.getSessionState()).isEqualTo(updateSessionState); LOGGER.debug("Session: {}", updatedSession); } From 4a469cf3482752265ad5a356725b7aadd3263a21 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 14 Dec 2023 00:02:09 +0900 Subject: [PATCH 57/62] =?UTF-8?q?[feat]=20=EB=AA=A8=EC=A7=91=EC=A4=91/?= =?UTF-8?q?=EB=B9=84=EB=AA=A8=EC=A7=91=EC=A4=91=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EA=B3=A0=20Session=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EB=A5=BC=20=EA=B0=9C=EC=84=A0=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session에 칼럼이 많아 객체로 묶고 모집중/비모집중을 구분합니다 해당 경우에 따른 강의 상태 변경 로직을 추가해야 합니다 --- .../domain/course/session/Duration.java | 8 ++ .../domain/course/session/RecruitStatus.java | 35 +++++ .../domain/course/session/Session.java | 127 +++++++++--------- .../domain/course/session/SessionDetail.java | 113 ++++++++++++++++ .../domain/course/session/SessionState.java | 9 ++ .../domain/course/session/SessionStatus.java | 2 +- .../infrastructure/JdbcSessionRepository.java | 43 +++--- src/main/resources/schema.sql | 1 + .../course/service/CourseServiceTest.java | 2 +- .../course/service/SessionServiceTest.java | 19 +-- .../domain/course/session/SessionTest.java | 18 +-- .../domain/course/session/SessionsTest.java | 2 +- .../infrastructure/SessionRepositoryTest.java | 6 +- 13 files changed, 281 insertions(+), 104 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/session/RecruitStatus.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/SessionDetail.java diff --git a/src/main/java/nextstep/courses/domain/course/session/Duration.java b/src/main/java/nextstep/courses/domain/course/session/Duration.java index c15ff2dc81..015ce20eaa 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Duration.java +++ b/src/main/java/nextstep/courses/domain/course/session/Duration.java @@ -59,4 +59,12 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(startDate, endDate); } + + @Override + public String toString() { + return "Duration{" + + "startDate=" + startDate + + ", endDate=" + endDate + + '}'; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/RecruitStatus.java b/src/main/java/nextstep/courses/domain/course/session/RecruitStatus.java new file mode 100644 index 0000000000..e09ebb3915 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/RecruitStatus.java @@ -0,0 +1,35 @@ +package nextstep.courses.domain.course.session; + +import java.util.Arrays; + +public enum RecruitStatus { + RECRUIT("모집중"), + NOT_RECRUIT("비모집중"); + + private final String description; + + RecruitStatus(String description) { + this.description = description; + } + + public static RecruitStatus find(String name) { + return Arrays.stream(values()) + .filter(status -> status.name().equals(name)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException( + String.format("허용하는 값은 다음과 같습니다.\n %s", descriptions()) + ) + ); + } + + public static String descriptions() { + StringBuilder sb = new StringBuilder(); + for (RecruitStatus recruitStatus : values()) { + sb.append(recruitStatus.description).append(", "); + } + sb.setLength(sb.length() - 2); + + return sb.toString(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index dad5a1cbcf..518a39a725 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -1,7 +1,6 @@ package nextstep.courses.domain.course.session; import nextstep.courses.domain.BaseEntity; -import nextstep.courses.domain.course.session.image.Image; import nextstep.courses.domain.course.session.image.Images; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; @@ -15,80 +14,52 @@ public class Session extends BaseEntity { private Images images; - private Duration duration; - - private SessionState sessionState; - private Applicants applicants; - private SessionStatus sessionStatus; + private SessionDetail sessionDetail; public Session(Images images, Duration duration, SessionState sessionState, Long creatorId, LocalDateTime date) { this(0L, images, duration, sessionState, new Applicants(), - SessionStatus.READY, creatorId, date, null); + RecruitStatus.NOT_RECRUIT, SessionStatus.READY, creatorId, date, null); } public Session(Long id, Images images, Duration duration, SessionState sessionState, - Applicants applicants, SessionStatus sessionStatus, Long creatorId, - LocalDateTime createdAt, LocalDateTime updatedAt) { + Applicants applicants, RecruitStatus recruitStatus, SessionStatus sessionStatus, + Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { + this(id, images, applicants, new SessionDetail(duration, sessionState, sessionStatus, recruitStatus), + creatorId, createdAt, updatedAt); + } + + public Session(Long id, Images images, Applicants applicants, SessionDetail sessionDetail, + Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); if (images == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); } - if (duration == null) { - throw new IllegalArgumentException("기간을 추가해야 합니다."); + if (applicants == null) { + throw new IllegalArgumentException("수강생을 추가해야 합니다."); } - if (sessionState == null) { - throw new IllegalArgumentException("강의 상태를 추가해야 합니다."); + if (sessionDetail == null) { + throw new IllegalArgumentException("강의 정보를 추가해야 합니다."); } this.id = id; this.images = images; - this.duration = duration; - this.sessionState = sessionState; this.applicants = applicants; - this.sessionStatus = sessionStatus; - } - - public void setId(Long id) { - this.id = id; - } - - public void setImages(Images images) { - this.images = images; - } - - public void setSessionState(SessionState updateSessionState) { - this.sessionState = updateSessionState; - } - - public boolean sameAmount(Long amount) { - return this.sessionState.sameAmount(amount); - } - - public boolean sameId(Long sessionId) { - return Objects.equals(this.id, sessionId); - } - - public Long getId() { - return this.id; - } - - public int applyCount() { - return this.applicants.size(); + this.sessionDetail = sessionDetail; } public Apply apply(NsUser loginUser, Payment payment, LocalDateTime date) { checkStatusOnRecruit(); - if (this.sessionState.charged()) { + if (this.sessionDetail.charged()) { checkPaymentIsPaid(loginUser, payment); } - this.applicants.addApplicant(loginUser, sessionState); + this.applicants.addApplicant(loginUser, sessionDetail.getSessionState()); return toApply(loginUser, date); } @@ -97,7 +68,7 @@ private Apply toApply(NsUser loginUser, LocalDateTime date) { } private void checkStatusOnRecruit() { - if (this.sessionStatus != SessionStatus.RECRUIT) { + if (this.sessionDetail.notRecruiting()) { throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); } } @@ -110,49 +81,85 @@ private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { public void changeOnReady(LocalDate date) { checkStartDateIsSameOrBefore(date); - this.sessionStatus = SessionStatus.READY; + this.sessionDetail.changeOnReady(); } public void changeOnRecruit(LocalDate date) { checkStartDateIsSameOrBefore(date); - this.sessionStatus = SessionStatus.RECRUIT; + this.sessionDetail.changeOnRecruit(); } private void checkStartDateIsSameOrBefore(LocalDate date) { - if (duration.startDateIsSameOrBefore(date)) { + if (sessionDetail.startDateIsSameOrBefore(date)) { throw new IllegalArgumentException("강의 시작일 이전에 변경 가능합니다."); } } public void changeOnEnd(LocalDate date) { checkEndDateIsSameOrAfter(date); - this.sessionStatus = SessionStatus.END; + this.sessionDetail.changeOnEnd(); } private void checkEndDateIsSameOrAfter(LocalDate date) { - if (duration.endDateIsSameOrAfter(date)) { + if (sessionDetail.endDateIsSameOrAfter(date)) { throw new IllegalArgumentException("강의 종료일 이후 변경 가능합니다."); } } + public void setSessionState(SessionState updateSessionState) { + this.sessionDetail.setSessionState(updateSessionState); + } + + public boolean sameAmount(Long amount) { + return this.sessionDetail.sameAmount(amount); + } + + public boolean sameId(Long sessionId) { + return Objects.equals(this.id, sessionId); + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public int applyCount() { + return this.applicants.size(); + } + public Images getImages() { return images; } + public void setImages(Images images) { + this.images = images; + } + + public Applicants getApplicants() { + return applicants; + } + + public SessionDetail getSessionDetail() { + return sessionDetail; + } + public Duration getDuration() { - return duration; + return sessionDetail.getDuration(); } public SessionState getSessionState() { - return this.sessionState; + return sessionDetail.getSessionState(); } - public Applicants getApplicants() { - return applicants; + public SessionStatus getSessionStatus() { + return sessionDetail.getSessionStatus(); } - public SessionStatus getSessionStatus() { - return sessionStatus; + public RecruitStatus getRecruitStatus() { + return sessionDetail.getRecruitStatus(); } @Override @@ -160,10 +167,8 @@ public String toString() { return "Session{" + "id=" + id + ", images=" + images + - ", duration=" + duration + - ", sessionState=" + sessionState + ", applicants=" + applicants + - ", session=" + sessionStatus + + ", sessionDetail=" + sessionDetail + '}'; } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java new file mode 100644 index 0000000000..1a691bc9e3 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java @@ -0,0 +1,113 @@ +package nextstep.courses.domain.course.session; + +import java.time.LocalDate; +import java.util.Objects; + +public class SessionDetail { + private Duration duration; + + private SessionState sessionState; + + private SessionStatus sessionStatus; + + private RecruitStatus recruitStatus; + + public SessionDetail(Duration duration, SessionState sessionState, + SessionStatus sessionStatus, RecruitStatus recruitStatus) { + if (duration == null) { + throw new IllegalArgumentException("기간이 추가되어야 합니다."); + } + + if (sessionState == null) { + throw new IllegalArgumentException("강의 정보가 추가되어야 합니다."); + } + + if (sessionStatus == null) { + throw new IllegalArgumentException("강의 현황 상태가 추가되어야 합니다."); + } + + if (recruitStatus == null) { + throw new IllegalArgumentException("강의 모집 여부가 추가되어야 합니다."); + } + + this.duration = duration; + this.sessionState = sessionState; + this.sessionStatus = sessionStatus; + this.recruitStatus = recruitStatus; + } + + public Duration getDuration() { + return duration; + } + + public SessionState getSessionState() { + return sessionState; + } + + public SessionStatus getSessionStatus() { + return sessionStatus; + } + + public RecruitStatus getRecruitStatus() { + return recruitStatus; + } + + public boolean sameAmount(Long amount) { + return this.sessionState.sameAmount(amount); + } + + public boolean charged() { + return this.sessionState.charged(); + } + + public boolean notRecruiting() { + return this.sessionStatus != SessionStatus.ONGOING; + } + + public boolean endDateIsSameOrAfter(LocalDate date) { + return duration.endDateIsSameOrAfter(date); + } + + public void changeOnReady() { + this.sessionStatus = SessionStatus.READY; + } + + public void changeOnRecruit() { + this.sessionStatus = SessionStatus.ONGOING; + } + + public void changeOnEnd() { + this.sessionStatus = SessionStatus.END; + } + + public boolean startDateIsSameOrBefore(LocalDate date) { + return this.duration.startDateIsSameOrBefore(date); + } + + public void setSessionState(SessionState sessionState) { + this.sessionState = sessionState; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SessionDetail that = (SessionDetail) o; + return Objects.equals(duration, that.duration) && Objects.equals(sessionState, that.sessionState) && sessionStatus == that.sessionStatus && recruitStatus == that.recruitStatus; + } + + @Override + public int hashCode() { + return Objects.hash(duration, sessionState, sessionStatus, recruitStatus); + } + + @Override + public String toString() { + return "SessionDetail{" + + "duration=" + duration + + ", sessionState=" + sessionState + + ", sessionStatus=" + sessionStatus + + ", recruitStatus=" + recruitStatus + + '}'; + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionState.java b/src/main/java/nextstep/courses/domain/course/session/SessionState.java index dbcb730c50..184592ee04 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionState.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionState.java @@ -80,4 +80,13 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(sessionType, amount, quota); } + + @Override + public String toString() { + return "SessionState{" + + "sessionType=" + sessionType + + ", amount=" + amount + + ", quota=" + quota + + '}'; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java b/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java index f9c5ad4cc3..d5db7d8d75 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java @@ -4,7 +4,7 @@ public enum SessionStatus { READY("준비중"), - RECRUIT("모집중"), + ONGOING("진행중"), END("종료"); private final String description; diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index f644217959..a41b02aa5e 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -36,7 +36,7 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public Optional findById(Long id) { String sql = "select " + - "id, start_date, end_date, session_type, amount, quota, " + + "id, start_date, end_date, session_type, amount, quota, recruit_status " + "session_status, course_id, creator_id, created_at, updated_at " + "from session where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Session( @@ -52,10 +52,11 @@ public Optional findById(Long id) { rs.getInt(6) ), findAllBySessionId(id), - SessionStatus.find(rs.getString(7)), - rs.getLong(9), - rs.getTimestamp(10).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(11))); + RecruitStatus.find(rs.getString(7)), + SessionStatus.find(rs.getString(8)), + rs.getLong(10), + rs.getTimestamp(11).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(12))); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); } @@ -75,13 +76,14 @@ public Session save(Long courseId, Session session) { private Session saveSession(Long courseId, Session session) { Duration duration = session.getDuration(); + RecruitStatus recruitStatus = session.getRecruitStatus(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); SessionStatus status = session.getSessionStatus(); String sql = "insert into session " + "(start_date, end_date, session_type, session_status, amount, " + - "quota, course_id, creator_id, created_at, updated_at) " + - "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + "recruit_status, quota, course_id, creator_id, created_at, updated_at) " + + "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; jdbcTemplate.update((Connection connection) -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); @@ -90,11 +92,12 @@ private Session saveSession(Long courseId, Session session) { ps.setString(3, sessionType.name()); ps.setString(4, status.name()); ps.setLong(5, sessionState.getAmount()); - ps.setInt(6, sessionState.getQuota()); - ps.setLong(7, courseId); - ps.setLong(8, session.getCreatorId()); - ps.setTimestamp(9, Timestamp.valueOf(session.getCreatedAt())); - ps.setTimestamp(10, toTimeStamp(session.getUpdatedAt())); + ps.setString(6, recruitStatus.name()); + ps.setInt(7, sessionState.getQuota()); + ps.setLong(8, courseId); + ps.setLong(9, session.getCreatorId()); + ps.setTimestamp(10, Timestamp.valueOf(session.getCreatedAt())); + ps.setTimestamp(11, toTimeStamp(session.getUpdatedAt())); return ps; }, keyHolder); @@ -130,21 +133,22 @@ public Optional findApplyByIds(Long nsUserId, Long sessionId) { @Override public int update(Long sessionId, Session session) { Duration duration = session.getDuration(); + RecruitStatus recruitStatus = session.getRecruitStatus(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); SessionStatus sessionStatus = session.getSessionStatus(); String sql = "update session set " + - "start_date = ?, end_date = ?, session_type = ?, amount = ?, quota = ?, session_status = ? " + + "start_date = ?, end_date = ?, session_type = ?, recruit_status = ?, amount = ?, quota = ?, session_status = ? " + "where id = ?"; return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), - sessionState.getAmount(), sessionState.getQuota(), sessionStatus.name(), sessionId); + recruitStatus.name(), sessionState.getAmount(), sessionState.getQuota(), sessionStatus.name(), sessionId); } @Override public Sessions findAllByCourseId(Long courseId) { String sql = "select " + "id, start_date, end_date, session_type, amount, quota, " + - "session_status, course_id, creator_id, created_at, updated_at " + + "recruit_status, session_status, course_id, creator_id, created_at, updated_at " + "from session where course_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), @@ -159,10 +163,11 @@ public Sessions findAllByCourseId(Long courseId) { rs.getInt(6) ), findAllBySessionId(rs.getLong(7)), - SessionStatus.find(rs.getString(8)), - rs.getLong(9), - rs.getTimestamp(10).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(11))); + RecruitStatus.find(rs.getString(8)), + SessionStatus.find(rs.getString(9)), + rs.getLong(10), + rs.getTimestamp(11).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(12))); List sessions = jdbcTemplate.query(sql, rowMapper, courseId); return new Sessions(sessions); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index d521f48b28..38259ab967 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -26,6 +26,7 @@ create table session ( end_date DATETIME not null, session_type varchar(20) not null, session_status varchar(20) not null, + recruit_status varchar(20) not null, amount bigint not null, quota bigint not null, course_id bigint not null, diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index fba4d6005f..8dddadde51 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -56,7 +56,7 @@ void setUp() { duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, images, duration, sessionState, new Applicants(), - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.NOT_RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); sessions = new Sessions(); sessions.add(session); } diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index e1402ba214..5a53044dd6 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -40,6 +40,7 @@ public class SessionServiceTest { private LocalDateTime localDateTime; private Applicants applicants; private Duration duration; + private RecruitStatus recruitStatus; private SessionState sessionState; private SessionStatus sessionStatus; private Session session; @@ -60,11 +61,12 @@ public void setUp() { localDate = LocalDate.of(2023, 12, 5); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - sessionStatus = SessionStatus.RECRUIT; + sessionStatus = SessionStatus.ONGOING; + recruitStatus = RecruitStatus.NOT_RECRUIT; applicants = new Applicants(); applicants.addApplicant(JAVAJIGI, sessionState); session = new Session(1L, images, duration, sessionState, applicants, - sessionStatus, 1L, localDateTime, localDateTime); + recruitStatus, sessionStatus, 1L, localDateTime, localDateTime); } @Test @@ -72,14 +74,15 @@ public void setUp() { void create_success() { Session newSession = new Session(images, duration, sessionState, 1L, localDateTime); Session savedSession = new Session(1L, images, duration, sessionState, new Applicants(), - SessionStatus.READY, 1L, localDateTime, localDateTime); + RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); sessionService.create(1L, newSession, localDateTime); Session findSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); assertThat(findSession.getId()).isEqualTo(1L); - assertThat(findSession.getSessionStatus()).isEqualTo(SessionStatus.READY); + assertThat(findSession.getSessionDetail()).isEqualTo( + new SessionDetail(duration, sessionState, SessionStatus.READY, recruitStatus)); assertThat(findSession.getApplicants()).hasSize(0); } @@ -100,7 +103,7 @@ void apply_success() { void changeOnReady_success() { duration = new Duration(DATE_2023_12_6, DATE_2023_12_12); savedSession = new Session(1L, images, duration, sessionState, new Applicants(), - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.NOT_RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); sessionService.changeOnReady(1L, DATE_2023_12_5); @@ -114,13 +117,13 @@ void changeOnReady_success() { void changeOnRecruit_success() { duration = new Duration(DATE_2023_12_6, DATE_2023_12_12); savedSession = new Session(1L, images, duration, sessionState, new Applicants(), - SessionStatus.READY, 1L, localDateTime, localDateTime); + RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); sessionService.changeOnRecruit(1L, DATE_2023_12_5); assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionStatus()).isEqualTo(SessionStatus.RECRUIT); + assertThat(savedSession.getSessionStatus()).isEqualTo(SessionStatus.ONGOING); } @Test @@ -128,7 +131,7 @@ void changeOnRecruit_success() { void changeOnEnd_success() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); savedSession = new Session(1L, images, duration, sessionState, new Applicants(), - SessionStatus.READY, 1L, localDateTime, localDateTime); + RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); sessionService.changeOnEnd(1L, DATE_2023_12_12); diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 4c079bcfb8..3a1ea4cda2 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -50,7 +50,7 @@ void setUp() { this.applicants.addApplicant(JAVAJIGI, sessionState); this.applicants.addApplicant(SANJIGI, sessionState); session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); } @Test @@ -82,7 +82,7 @@ void newObject_sessionStateNull_throwsException() { @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); assertThat(session.applyCount()).isEqualTo(2); @@ -95,7 +95,7 @@ void apply_success() { @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.READY, 1L, localDateTime, localDateTime); + RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, payment, localDateTime) @@ -108,7 +108,7 @@ void apply_chargeSession_overQuota_throwsException() { applicants = new Applicants(); sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); applicants.addApplicant(JAVAJIGI, session.getSessionState()); applicants.addApplicant(SANJIGI, session.getSessionState()); @@ -122,7 +122,7 @@ void apply_chargeSession_overQuota_throwsException() { void apply_chargeSession_notPaid_throwsException() { sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, null, localDateTime) @@ -134,7 +134,7 @@ void apply_chargeSession_notPaid_throwsException() { void apply_chargeSession_differentAmount_throwsException() { sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.apply(APPLE, differentPayment, localDateTime) @@ -147,7 +147,7 @@ void changeOnReady_startDateIsBeforeOrSame_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnReady(DATE_2023_12_5) @@ -164,7 +164,7 @@ void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.READY, 1L, localDateTime, localDateTime); + RecruitStatus.RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnRecruit(DATE_2023_12_5) @@ -181,7 +181,7 @@ void changeOnEnd_EndDateIsSameOrAfter_throwsException() { duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); session = new Session(1L, images, duration, sessionState, applicants, - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.NOT_RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); assertThatThrownBy( () -> session.changeOnEnd(DATE_2023_12_6) diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index 7cff0de1eb..de4d39e9e0 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -38,7 +38,7 @@ void setUp() { duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); session = new Session(1L, images, duration, sessionState, new Applicants(), - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.NOT_RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); sessions.add(session); } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 31f917a17c..7c7228d2e5 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -69,20 +69,18 @@ void setUp() { void save_success() { session = new Session(images, duration, sessionState, 1L, localDateTime); Session savedSession = sessionRepository.save(1L, session); - //Session savedSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); + assertThat(savedSession.getId()).isEqualTo(1L); assertThat(savedSession.getDuration()).isEqualTo(session.getDuration()); assertThat(savedSession.getSessionState()).isEqualTo(session.getSessionState()); - //Image savedImage = imageRepository.findById(1L).orElseThrow(NotFoundException::new); - LOGGER.debug("Session: {}", savedSession); } @Test void applySave_success() { session = new Session(1L, images, duration, sessionState, new Applicants(), - SessionStatus.RECRUIT, 1L, localDateTime, localDateTime); + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); int count = sessionRepository.saveApply(apply); Apply savedApply = sessionRepository.findApplyByIds(JAVAJIGI.getId(), session.getId()) .orElseThrow(NotFoundException::new); From 427adf8d1c8544aa89968b16975e29a7177e79c2 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 14 Dec 2023 00:42:59 +0900 Subject: [PATCH 58/62] =?UTF-8?q?[feat]=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=9C=A0=ED=9A=A8=EC=84=B1=EC=9D=84=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수강신청 시, 모집중인 경우와 준비중이거나 진행중인 경우만 신청이 가능하도록 유효성 검증을 개선합니다 --- .../domain/course/session/Session.java | 46 +++++--------- .../domain/course/session/SessionDetail.java | 61 +++++++++++++++++-- .../domain/course/session/SessionTest.java | 30 ++++++++- 3 files changed, 96 insertions(+), 41 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 518a39a725..07dacaf949 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -53,57 +53,39 @@ public Session(Long id, Images images, Applicants applicants, SessionDetail sess } public Apply apply(NsUser loginUser, Payment payment, LocalDateTime date) { - checkStatusOnRecruit(); - - if (this.sessionDetail.charged()) { - checkPaymentIsPaid(loginUser, payment); - } + sessionDetail.checkApplyPossible(); + checkPaymentIsPaid(loginUser, payment); this.applicants.addApplicant(loginUser, sessionDetail.getSessionState()); return toApply(loginUser, date); } - private Apply toApply(NsUser loginUser, LocalDateTime date) { - return new Apply(this, loginUser, date); + private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { + if (sessionDetail.charged()) { + checkPaymentIsValid(loginUser, payment); + } } - private void checkStatusOnRecruit() { - if (this.sessionDetail.notRecruiting()) { - throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); + private void checkPaymentIsValid(NsUser loginUser, Payment payment) { + if (payment != null && payment.isPaid(loginUser, this)) { + throw new IllegalArgumentException("결제를 다시 확인하세요;"); } } - private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { - if (payment == null || !payment.isPaid(loginUser, this)) { - throw new IllegalArgumentException("결제를 진행해 주세요."); - } + private Apply toApply(NsUser loginUser, LocalDateTime date) { + return new Apply(this, loginUser, date); } public void changeOnReady(LocalDate date) { - checkStartDateIsSameOrBefore(date); - this.sessionDetail.changeOnReady(); + this.sessionDetail.changeOnReady(date); } public void changeOnRecruit(LocalDate date) { - checkStartDateIsSameOrBefore(date); - this.sessionDetail.changeOnRecruit(); - } - - private void checkStartDateIsSameOrBefore(LocalDate date) { - if (sessionDetail.startDateIsSameOrBefore(date)) { - throw new IllegalArgumentException("강의 시작일 이전에 변경 가능합니다."); - } + this.sessionDetail.changeOnRecruit(date); } public void changeOnEnd(LocalDate date) { - checkEndDateIsSameOrAfter(date); - this.sessionDetail.changeOnEnd(); - } - - private void checkEndDateIsSameOrAfter(LocalDate date) { - if (sessionDetail.endDateIsSameOrAfter(date)) { - throw new IllegalArgumentException("강의 종료일 이후 변경 가능합니다."); - } + this.sessionDetail.changeOnEnd(date); } public void setSessionState(SessionState updateSessionState) { diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java index 1a691bc9e3..4ecc24b62e 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java @@ -1,5 +1,8 @@ package nextstep.courses.domain.course.session; +import nextstep.payments.domain.Payment; +import nextstep.users.domain.NsUser; + import java.time.LocalDate; import java.util.Objects; @@ -56,30 +59,76 @@ public boolean sameAmount(Long amount) { return this.sessionState.sameAmount(amount); } - public boolean charged() { - return this.sessionState.charged(); + public void checkApplyPossible() { + checkStatusOnRecruit(); + checkStatusOnReadyOrOnGoing(); + } + + private void checkStatusOnRecruit() { + if (this.notRecruiting()) { + throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); + } + } + + private void checkStatusOnReadyOrOnGoing() { + if (this.notReadyOrOnGoing()) { + throw new IllegalArgumentException("강의 신청은 준비, 진행중일 때만 가능 합니다."); + } } public boolean notRecruiting() { - return this.sessionStatus != SessionStatus.ONGOING; + return this.recruitStatus == RecruitStatus.NOT_RECRUIT; + } + + public boolean notReadyOrOnGoing() { + return this.sessionStatus == SessionStatus.END; + } + + public void checkPaymentIsPaid(NsUser loginUser, Payment payment) { + if(isNotPaid(payment)) { + + } + } + + public boolean isNotPaid(Payment payment) { + return payment == null || !charged(); + } + + public boolean charged() { + return this.sessionState.charged(); } public boolean endDateIsSameOrAfter(LocalDate date) { return duration.endDateIsSameOrAfter(date); } - public void changeOnReady() { + public void changeOnReady(LocalDate date) { + checkStartDateIsSameOrBefore(date); this.sessionStatus = SessionStatus.READY; } - public void changeOnRecruit() { + public void changeOnRecruit(LocalDate date) { + checkStartDateIsSameOrBefore(date); this.sessionStatus = SessionStatus.ONGOING; } - public void changeOnEnd() { + private void checkStartDateIsSameOrBefore(LocalDate date) { + if (startDateIsSameOrBefore(date)) { + throw new IllegalArgumentException("강의 시작일 이전에 변경 가능합니다."); + } + } + + public void changeOnEnd(LocalDate date) { + checkEndDateIsSameOrAfter(date); this.sessionStatus = SessionStatus.END; } + private void checkEndDateIsSameOrAfter(LocalDate date) { + if (endDateIsSameOrAfter(date)) { + throw new IllegalArgumentException("강의 종료일 이후 변경 가능합니다."); + } + } + public boolean startDateIsSameOrBefore(LocalDate date) { return this.duration.startDateIsSameOrBefore(date); } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 3a1ea4cda2..0afea5f94a 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -79,8 +79,21 @@ void newObject_sessionStateNull_throwsException() { } @Test - @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") - void apply_success() { + @DisplayName("수강 신청은 모집 상태에서 준비중이면 해당 인원이 추가된다.") + void apply_recruit_ready_success() { + session = new Session(1L, images, duration, sessionState, applicants, + RecruitStatus.RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); + + assertThat(session.applyCount()).isEqualTo(2); + + session.apply(APPLE, payment, localDateTime); + + assertThat(session.applyCount()).isEqualTo(3); + } + + @Test + @DisplayName("수강 신청은 모집 상태에서 진행중이면 해당 인원이 추가된다.") + void apply_recruit_ongoing_success() { session = new Session(1L, images, duration, sessionState, applicants, RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); @@ -92,7 +105,7 @@ void apply_success() { } @Test - @DisplayName("수강 신청은 모집 중이 아니면 신청할 수 없다는 예외를 반환한다.") + @DisplayName("수강 신청은 비모집중이면 신청할 수 없다는 예외를 반환한다.") void apply_notRecruitStatus_throwsException() { session = new Session(1L, images, duration, sessionState, applicants, RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); @@ -102,6 +115,17 @@ void apply_notRecruitStatus_throwsException() { ).isInstanceOf(IllegalArgumentException.class); } + @Test + @DisplayName("수강 신청은 모집 중이어도 종료되었다면 신청할 수 없다는 예외를 반환한다.") + void apply_recruitStatus_endStatus_throwsException() { + session = new Session(1L, images, duration, sessionState, applicants, + RecruitStatus.RECRUIT, SessionStatus.END, 1L, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.apply(APPLE, payment, localDateTime) + ).isInstanceOf(IllegalArgumentException.class); + } + @Test @DisplayName("수강 신청은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") void apply_chargeSession_overQuota_throwsException() { From f85a1b07a3ef87c2d24542178d775089215ec5ac Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Thu, 14 Dec 2023 02:52:56 +0900 Subject: [PATCH 59/62] =?UTF-8?q?[feat]=20=EA=B0=95=EC=82=AC=EC=9D=98=20?= =?UTF-8?q?=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD=20=EC=8A=B9=EC=9D=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B7=A8=EC=86=8C=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강사는 수강신청을 승인하고 취소할 수 있습니다. 승인된 경우만 강의를 들을 수 있으며 수강취소를 할 수 있습니다 --- .../domain/course/session/Applicants.java | 15 +++ .../courses/domain/course/session/Apply.java | 30 +++++- .../domain/course/session/Session.java | 24 ++++- .../course/session/SessionRepository.java | 2 + .../domain/course/session/SessionState.java | 3 +- .../JdbcApplicantsRepository.java | 8 +- .../infrastructure/JdbcSessionRepository.java | 28 ++++-- .../courses/service/SessionService.java | 20 +++- .../java/nextstep/users/domain/NsUser.java | 16 +++- src/main/java/nextstep/users/domain/Type.java | 36 +++++++ .../infrastructure/JdbcUserRepository.java | 8 +- src/main/resources/data.sql | 10 +- src/main/resources/schema.sql | 2 + .../course/service/SessionServiceTest.java | 7 +- .../domain/course/session/ApplicantsTest.java | 7 +- .../course/session/SessionStatusTest.java | 16 ++++ .../domain/course/session/SessionTest.java | 95 +++++++++++++++++-- .../infrastructure/SessionRepositoryTest.java | 25 ++++- .../nextstep/users/domain/NsUserTest.java | 4 +- 19 files changed, 304 insertions(+), 52 deletions(-) create mode 100644 src/main/java/nextstep/users/domain/Type.java create mode 100644 src/test/java/nextstep/courses/domain/course/session/SessionStatusTest.java diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java index 6beba95f1a..c0378847a9 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Applicants.java +++ b/src/main/java/nextstep/courses/domain/course/session/Applicants.java @@ -43,6 +43,21 @@ private void checkChargedAndApplySizeIsValid(SessionState sessionState) { } } + public void checkApprovePossible(NsUser applicant, SessionState sessionState) { + checkApplicantExists(applicant); + checkChargedAndApplySizeIsValid(sessionState); + } + + private void checkApplicantExists(NsUser applicant) { + if (!this.applicants.contains(applicant)) { + throw new IllegalArgumentException("수강 신청 이력이 없습니다."); + } + } + + public void checkCancelPossible(NsUser applicant, SessionState sessionState) { + checkApplicantExists(applicant); + } + @Override public Iterator iterator() { return this.applicants.iterator(); diff --git a/src/main/java/nextstep/courses/domain/course/session/Apply.java b/src/main/java/nextstep/courses/domain/course/session/Apply.java index 5c452488fe..3b4928c0a8 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Apply.java +++ b/src/main/java/nextstep/courses/domain/course/session/Apply.java @@ -10,15 +10,18 @@ public class Apply extends BaseEntity { private Long nsUserId; - public Apply(Session session, NsUser nsUser, LocalDateTime createdAt) { - this(session.getId(), nsUser.getId(), nsUser.getId(), createdAt, null); + private boolean approved; + + public Apply(Session session, NsUser nsUser, boolean approved, LocalDateTime createdAt) { + this(session.getId(), nsUser.getId(), approved, nsUser.getId(), createdAt, null); } - public Apply(Long sessionId, Long nsUserId, Long creatorId, + public Apply(Long sessionId, Long nsUserId, boolean approved, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); this.sessionId = sessionId; this.nsUserId = nsUserId; + this.approved = approved; } public Long getSessionId() { @@ -29,6 +32,27 @@ public Long getNsUserId() { return nsUserId; } + public boolean isApproved() { + return approved; + } + + public Apply setApproved(boolean approved) { + this.approved = approved; + return this; + } + + public void checkApprovePossible() { + if (this.approved) { + throw new IllegalArgumentException("이미 수강 승인 상태입니다."); + } + } + + public void checkCancelPossible() { + if (!this.approved) { + throw new IllegalArgumentException("이미 수강 취소 상태입니다."); + } + } + @Override public String toString() { return "Apply{" + diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 07dacaf949..0729e42ba0 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -57,7 +57,23 @@ public Apply apply(NsUser loginUser, Payment payment, LocalDateTime date) { checkPaymentIsPaid(loginUser, payment); this.applicants.addApplicant(loginUser, sessionDetail.getSessionState()); - return toApply(loginUser, date); + return toApply(loginUser, false, date); + } + + public Apply approve(NsUser loginUser, NsUser applicant, Apply apply, LocalDateTime date) { + loginUser.checkUserHasAuthor(); + this.applicants.checkApprovePossible(applicant, sessionDetail.getSessionState()); + apply.checkApprovePossible(); + + return toApply(applicant, true, date); + } + + public Apply cancel(NsUser loginUser, NsUser applicant, Apply apply, LocalDateTime date) { + loginUser.checkUserHasAuthor(); + this.applicants.checkCancelPossible(applicant, sessionDetail.getSessionState()); + apply.checkCancelPossible(); + + return toApply(applicant, false, date); } private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { @@ -67,13 +83,13 @@ private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { } private void checkPaymentIsValid(NsUser loginUser, Payment payment) { - if (payment != null && payment.isPaid(loginUser, this)) { + if (payment == null || !payment.isPaid(loginUser, this)) { throw new IllegalArgumentException("결제를 다시 확인하세요;"); } } - private Apply toApply(NsUser loginUser, LocalDateTime date) { - return new Apply(this, loginUser, date); + private Apply toApply(NsUser loginUser, boolean approved, LocalDateTime date) { + return new Apply(this, loginUser, approved, date); } public void changeOnReady(LocalDate date) { diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index 5266cf1746..93d7f34d39 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -18,4 +18,6 @@ public interface SessionRepository { Sessions findAllByCourseId(Long courseId); int updateCourse(Long courseId, Session session); + + int updateApply(Apply apply); } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionState.java b/src/main/java/nextstep/courses/domain/course/session/SessionState.java index 184592ee04..b037a909d9 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionState.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionState.java @@ -53,7 +53,8 @@ public boolean charged() { } public boolean chargedAndFull(List applicants) { - return this.sessionType == SessionType.CHARGE && this.quota == applicants.size(); + return this.sessionType == SessionType.CHARGE + && this.quota == applicants.size(); } public SessionType getSessionType() { diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java index 920913ebd4..01485ced3a 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java @@ -4,6 +4,7 @@ import nextstep.courses.domain.course.session.ApplicantsRepository; import nextstep.qna.NotFoundException; import nextstep.users.domain.NsUser; +import nextstep.users.domain.Type; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; @@ -42,7 +43,7 @@ private List findAllApplicantIdsBySessionId(Long sessionId) { private Optional findAllNsUsersById(Long applicantId) { String sql = "select " + - "id, user_id, password, name, email, created_at, updated_at " + + "id, user_id, password, name, email, type, created_at, updated_at " + "from ns_user where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new NsUser( rs.getLong(1), @@ -50,8 +51,9 @@ private Optional findAllNsUsersById(Long applicantId) { rs.getString(3), rs.getString(4), rs.getString(5), - rs.getTimestamp(6).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(7)) + Type.find(rs.getString(6)), + rs.getTimestamp(7).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(8)) ); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, applicantId)); diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index a41b02aa5e..4c0102a3ff 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -36,9 +36,10 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public Optional findById(Long id) { String sql = "select " + - "id, start_date, end_date, session_type, amount, quota, recruit_status " + + "id, start_date, end_date, session_type, amount, quota, recruit_status, " + "session_status, course_id, creator_id, created_at, updated_at " + "from session where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), findAllImagesBySessionId(rs.getLong(1)), @@ -79,7 +80,7 @@ private Session saveSession(Long courseId, Session session) { RecruitStatus recruitStatus = session.getRecruitStatus(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); - SessionStatus status = session.getSessionStatus(); + SessionStatus sessionStatus = session.getSessionStatus(); String sql = "insert into session " + "(start_date, end_date, session_type, session_status, amount, " + "recruit_status, quota, course_id, creator_id, created_at, updated_at) " + @@ -90,7 +91,7 @@ private Session saveSession(Long courseId, Session session) { ps.setTimestamp(1, Timestamp.valueOf(duration.getStartDate().atStartOfDay())); ps.setTimestamp(2, Timestamp.valueOf(duration.getEndDate().atStartOfDay())); ps.setString(3, sessionType.name()); - ps.setString(4, status.name()); + ps.setString(4, sessionStatus.name()); ps.setLong(5, sessionState.getAmount()); ps.setString(6, recruitStatus.name()); ps.setInt(7, sessionState.getQuota()); @@ -110,23 +111,24 @@ private Session saveSession(Long courseId, Session session) { @Override public int saveApply(Apply apply) { String sql = "insert into apply " + - "(session_id, ns_user_id, creator_id, created_at, updated_at) " + - "values(?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, apply.getSessionId(), apply.getNsUserId(), + "(session_id, ns_user_id, approved, creator_id, created_at, updated_at) " + + "values(?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, apply.getSessionId(), apply.getNsUserId(), apply.isApproved(), apply.getCreatorId(), apply.getCreatedAt(), apply.getUpdatedAt()); } @Override public Optional findApplyByIds(Long nsUserId, Long sessionId) { String sql = "select " + - "session_id, ns_user_id, creator_id, created_at, updated_at " + + "session_id, ns_user_id, approved, creator_id, created_at, updated_at " + "from apply where ns_user_id = ? and session_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Apply( rs.getLong(1), rs.getLong(2), - rs.getLong(3), - rs.getTimestamp(4).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(5))); + rs.getBoolean(3), + rs.getLong(4), + rs.getTimestamp(5).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(6))); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, nsUserId, sessionId)); } @@ -179,6 +181,12 @@ public int updateCourse(Long courseId, Session session) { return jdbcTemplate.update(sql, session.getId(), courseId); } + @Override + public int updateApply(Apply apply) { + String sql = "update apply set approved = ? where session_id = ? and ns_user_id = ?"; + return jdbcTemplate.update(sql, apply.isApproved(), apply.getSessionId(), apply.getNsUserId()); + } + private Images findAllImagesBySessionId(Long id) { return this.imageRepository.findAllBySessionId(id); } diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 5d2cf81914..5d3cd6bc88 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -17,7 +17,7 @@ public class SessionService { @Resource(name = "sessionRepository") private SessionRepository sessionRepository; - public void create(Long courseId, Session session, LocalDateTime date) { + public void create(Long courseId, Session session) { sessionRepository.save(courseId, session); } @@ -27,6 +27,20 @@ public void applySession(NsUser loginUser, Long sessionId, Payment payment, Loca sessionRepository.saveApply(apply); } + public void approve(NsUser loginUser, NsUser applicant, Long sessionId, LocalDateTime date) { + Session session = getSession(sessionId); + Apply savedApply = getApply(sessionId, loginUser.getId()); + Apply apply = session.approve(loginUser, applicant, savedApply, date); + sessionRepository.updateApply(apply); + } + + public void cancel(NsUser loginUser, NsUser applicant, Long sessionId, LocalDateTime date) { + Session session = getSession(sessionId); + Apply savedApply = getApply(sessionId, loginUser.getId()); + Apply apply = session.cancel(loginUser, applicant, savedApply, date); + sessionRepository.updateApply(apply); + } + public void changeOnReady(Long sessionId, LocalDate date) { Session session = getSession(sessionId); session.changeOnReady(date); @@ -48,4 +62,8 @@ public void changeOnEnd(Long sessionId, LocalDate date) { private Session getSession(Long sessionId) { return sessionRepository.findById(sessionId).orElseThrow(NotFoundException::new); } + + private Apply getApply(Long sessionId, Long nsUserId) { + return sessionRepository.findApplyByIds(sessionId, nsUserId).orElseThrow(NotFoundException::new); + } } diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java index dfe07870fc..520ec850cc 100755 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -18,6 +18,8 @@ public class NsUser { private String email; + private Type type; + private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -25,16 +27,18 @@ public class NsUser { public NsUser() { } - public NsUser(Long id, String userId, String password, String name, String email) { - this(id, userId, password, name, email, LocalDateTime.now(), null); + public NsUser(Long id, String userId, String password, String name, String email, Type type) { + this(id, userId, password, name, email, type, LocalDateTime.now(), null); } - public NsUser(Long id, String userId, String password, String name, String email, LocalDateTime createdAt, LocalDateTime updatedAt) { + public NsUser(Long id, String userId, String password, String name, String email, + Type type, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.userId = userId; this.password = password; this.name = name; this.email = email; + this.type = type; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -121,6 +125,12 @@ public boolean isSame(Long nsUserId) { return Objects.equals(this.id, nsUserId); } + public void checkUserHasAuthor() { + if(this.type == Type.STUDENT) { + throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); + } + } + private static class GuestNsUser extends NsUser { @Override public boolean isGuestUser() { diff --git a/src/main/java/nextstep/users/domain/Type.java b/src/main/java/nextstep/users/domain/Type.java new file mode 100644 index 0000000000..d0cba06521 --- /dev/null +++ b/src/main/java/nextstep/users/domain/Type.java @@ -0,0 +1,36 @@ +package nextstep.users.domain; + +import nextstep.courses.domain.course.session.SessionStatus; + +import java.util.Arrays; + +public enum Type { + TEACHER("강사"), STUDENT("학생"); + + private final String description; + + Type(String description) { + this.description = description; + } + + public static Type find(String name) { + return Arrays.stream(values()) + .filter(status -> status.name().equals(name)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException( + String.format("허용하는 값은 다음과 같습니다.\n %s", descriptions()) + ) + ); + } + + public static String descriptions() { + StringBuilder sb = new StringBuilder(); + for (Type type : values()) { + sb.append(type.description).append(", "); + } + sb.setLength(sb.length() - 2); + + return sb.toString(); + } +} diff --git a/src/main/java/nextstep/users/infrastructure/JdbcUserRepository.java b/src/main/java/nextstep/users/infrastructure/JdbcUserRepository.java index 005f04fc5a..0be3c24494 100644 --- a/src/main/java/nextstep/users/infrastructure/JdbcUserRepository.java +++ b/src/main/java/nextstep/users/infrastructure/JdbcUserRepository.java @@ -1,6 +1,7 @@ package nextstep.users.infrastructure; import nextstep.users.domain.NsUser; +import nextstep.users.domain.Type; import nextstep.users.domain.UserRepository; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; @@ -20,15 +21,16 @@ public JdbcUserRepository(JdbcOperations jdbcTemplate) { @Override public Optional findByUserId(String userId) { - String sql = "select id, user_id, password, name, email, created_at, updated_at from ns_user where user_id = ?"; + String sql = "select id, user_id, password, name, email, type, created_at, updated_at from ns_user where user_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new NsUser( rs.getLong(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(5), - toLocalDateTime(rs.getTimestamp(6)), - toLocalDateTime(rs.getTimestamp(7))); + Type.find(rs.getString(6)), + toLocalDateTime(rs.getTimestamp(7)), + toLocalDateTime(rs.getTimestamp(8))); return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, userId)); } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 12d124f9e5..5895985264 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,5 +1,5 @@ -INSERT INTO ns_user (id, user_id, password, name, email, created_at) values (1, 'javajigi', 'test', '자바지기', 'javajigi@slipp.net', CURRENT_TIMESTAMP()); -INSERT INTO ns_user (id, user_id, password, name, email, created_at) values (2, 'sanjigi', 'test', '산지기', 'sanjigi@slipp.net', CURRENT_TIMESTAMP()); +INSERT INTO ns_user (id, user_id, password, name, email, type, created_at) values (1, 'javajigi', 'test', '자바지기', 'javajigi@slipp.net', 'TEACHER', CURRENT_TIMESTAMP()); +INSERT INTO ns_user (id, user_id, password, name, email, type, created_at) values (2, 'sanjigi', 'test', '산지기', 'sanjigi@slipp.net', 'TEACHER', CURRENT_TIMESTAMP()); INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUES (1, 1, '국내에서 Ruby on Rails와 Play가 활성화되기 힘든 이유는 뭘까?', 'Ruby on Rails(이하 RoR)는 2006년 즈음에 정말 뜨겁게 달아올랐다가 금방 가라 앉았다. Play 프레임워크는 정말 한 순간 잠시 눈에 뜨이다가 사라져 버렸다. RoR과 Play 기반으로 개발을 해보면 정말 생산성이 높으며, 웹 프로그래밍이 재미있기까지 하다. Spring MVC + JPA(Hibernate) 기반으로 진행하면 설정할 부분도 많고, 기본으로 지원하지 않는 기능도 많아 RoR과 Play에서 기본적으로 지원하는 기능을 서비스하려면 추가적인 개발이 필요하다.', CURRENT_TIMESTAMP(), false); @@ -9,8 +9,10 @@ INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) VALUE INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUES (2, 2, 'runtime 에 reflect 발동 주체 객체가 뭔지 알 방법이 있을까요?', '설계를 희한하게 하는 바람에 꼬인 문제같긴 합니다만. 여쭙습니다. 상황은 mybatis select 실행될 시에 return object 의 getter 가 호출되면서인데요. getter 안에 다른 property 에 의존중인 코드가 삽입되어 있어서, 만약 다른 mybatis select 구문에 해당 property 가 없다면 exception 이 발생하게 됩니다.', CURRENT_TIMESTAMP(), false); -INSERT INTO apply (session_id, ns_user_id, creator_id, created_at, updated_at) VALUES (10, 1, 1, CURRENT_TIMESTAMP(), null); +INSERT INTO apply (session_id, ns_user_id, approved, creator_id, created_at, updated_at) VALUES (10, 1, false, 1, CURRENT_TIMESTAMP(), null); -INSERT INTO apply (session_id, ns_user_id, creator_id, created_at, updated_at) VALUES (10, 2, 2, CURRENT_TIMESTAMP(), null); +INSERT INTO apply (session_id, ns_user_id, approved, creator_id, created_at, updated_at) VALUES (10, 2, false, 2, CURRENT_TIMESTAMP(), null); + +INSERT INTO apply (session_id, ns_user_id, approved, creator_id, created_at, updated_at) VALUES (4, 4, true, 2, CURRENT_TIMESTAMP(), null); --INSERT INTO image (id, image_size, image_type, image_width, image_height, creator_id, created_at, updated_at) VALUES (10, 1024, 'JPG', 300, 200, 1, CURRENT_TIMESTAMP(), null); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 38259ab967..ea8c491f87 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -14,6 +14,7 @@ create table image ( create table apply ( session_id bigint not null, ns_user_id bigint not null, + approved boolean not null, creator_id bigint not null, created_at timestamp not null, updated_at timestamp, @@ -52,6 +53,7 @@ create table ns_user ( password varchar(20) not null, name varchar(20) not null, email varchar(50), + type varchar(20) not null, created_at timestamp not null, updated_at timestamp, primary key (id) diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 5a53044dd6..f6506aacd4 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -8,6 +8,7 @@ import nextstep.payments.domain.Payment; import nextstep.qna.NotFoundException; import nextstep.users.domain.NsUser; +import nextstep.users.domain.Type; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -26,8 +27,8 @@ @ExtendWith(MockitoExtension.class) public class SessionServiceTest { - private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); - private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net"); + private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net", Type.TEACHER); + private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net", Type.TEACHER); private static final LocalDate DATE_2023_12_5 = LocalDate.of(2023, 12, 5); private static final LocalDate DATE_2023_12_6 = LocalDate.of(2023, 12, 6); private static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); @@ -77,7 +78,7 @@ void create_success() { RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); - sessionService.create(1L, newSession, localDateTime); + sessionService.create(1L, newSession); Session findSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); assertThat(findSession.getId()).isEqualTo(1L); diff --git a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java index 603eed9929..42396b6813 100644 --- a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course.session; import nextstep.users.domain.NsUser; +import nextstep.users.domain.Type; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -8,9 +9,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ApplicantsTest { - private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); - private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); - private static final NsUser APPLE = new NsUser(3L, "sanjigi", "password", "name", "apple@slipp.net"); + private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net", Type.TEACHER); + private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net", Type.TEACHER); + private static final NsUser APPLE = new NsUser(3L, "sanjigi", "password", "name", "apple@slipp.net", Type.TEACHER); private Applicants applicants; private SessionState sessionState; diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionStatusTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionStatusTest.java new file mode 100644 index 0000000000..8ceb8003f0 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/SessionStatusTest.java @@ -0,0 +1,16 @@ +package nextstep.courses.domain.course.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class SessionStatusTest { + @Test + @DisplayName("find는 존재하지 않는 값을 입력하면 찾을 수 없다는 예외를 던진다.") + void find_notExistedName_throwsException() { + assertThatThrownBy( + () -> SessionStatus.find("abcd") + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 0afea5f94a..5cb687368e 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -5,6 +5,7 @@ import nextstep.courses.domain.course.session.image.Images; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; +import nextstep.users.domain.Type; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,9 +18,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SessionTest { - private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); - private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); - private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net"); + private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net" ,Type.TEACHER); + private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net",Type.TEACHER); + private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net",Type.TEACHER); + private static final NsUser ERIC = new NsUser(4L, "apple", "password", "name", "apple@slipp.net",Type.STUDENT); private static final LocalDate DATE_2023_12_5 = LocalDate.of(2023, 12, 5); private static final LocalDate DATE_2023_12_6 = LocalDate.of(2023, 12, 6); private static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); @@ -49,6 +51,7 @@ void setUp() { applicants = new Applicants(); this.applicants.addApplicant(JAVAJIGI, sessionState); this.applicants.addApplicant(SANJIGI, sessionState); + this.applicants.addApplicant(ERIC, sessionState); session = new Session(1L, images, duration, sessionState, applicants, RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); } @@ -84,11 +87,11 @@ void apply_recruit_ready_success() { session = new Session(1L, images, duration, sessionState, applicants, RecruitStatus.RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); - assertThat(session.applyCount()).isEqualTo(2); + assertThat(session.applyCount()).isEqualTo(3); session.apply(APPLE, payment, localDateTime); - assertThat(session.applyCount()).isEqualTo(3); + assertThat(session.applyCount()).isEqualTo(4); } @Test @@ -97,11 +100,11 @@ void apply_recruit_ongoing_success() { session = new Session(1L, images, duration, sessionState, applicants, RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - assertThat(session.applyCount()).isEqualTo(2); + assertThat(session.applyCount()).isEqualTo(3); session.apply(APPLE, payment, localDateTime); - assertThat(session.applyCount()).isEqualTo(3); + assertThat(session.applyCount()).isEqualTo(4); } @Test @@ -215,4 +218,82 @@ void changeOnEnd_EndDateIsSameOrAfter_throwsException() { () -> session.changeOnEnd(DATE_2023_12_12) ).isInstanceOf(IllegalArgumentException.class); } + + @Test + @DisplayName("approve 는 선생님인 경우 수강생의 강의 신청을 승인한다.") + void approve_teacher_changeApproveTrue() { + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(1L, images, duration, sessionState, applicants, + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + Apply apply = new Apply(1L, 4L, false, 1L, localDateTime, localDateTime); + + Apply changedApply = session.approve(JAVAJIGI, ERIC, apply, localDateTime); + + assertThat(changedApply.isApproved()).isTrue(); + } + + @Test + @DisplayName("approve 는 학생인 경우 권한이 없다는 예외를 던진다.") + void approve_student_throwsException() { + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(1L, images, duration, sessionState, applicants, + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + Apply apply = new Apply(1L, 4L, false, 1L, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.approve(ERIC, JAVAJIGI, apply, localDateTime) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("approve 는 이미 수강 승인이 되었으면 예외를 던진다.") + void approve_alreadyApproved_throwsException() { + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(1L, images, duration, sessionState, applicants, + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + Apply apply = new Apply(1L, 4L, true, 1L, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.approve(JAVAJIGI, ERIC, apply, localDateTime) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("cancel 는 선생님인 경우 수강생의 강의 신청을 취소한다.") + void cancel_teacher_changeApproveTrue() { + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(4L, images, duration, sessionState, applicants, + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + Apply apply = new Apply(1L, 4L, true, 1L, localDateTime, localDateTime); + + Apply changedApply = session.cancel(JAVAJIGI, ERIC, apply, localDateTime); + + assertThat(changedApply.isApproved()).isFalse(); + } + + @Test + @DisplayName("cancel 는 학생인 경우 권한이 없다는 예외를 던진다.") + void cancel_student_throwsException() { + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(1L, images, duration, sessionState, applicants, + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + Apply apply = new Apply(1L, 4L, false, 1L, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.cancel(ERIC, JAVAJIGI, apply, localDateTime) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("cancel 는 이미 수강 취소가 되었으면 예외를 던진다.") + void cancel_alreadyCanceled_throwsException() { + sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); + session = new Session(1L, images, duration, sessionState, applicants, + RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + Apply apply = new Apply(1L, 4L, false, 1L, localDateTime, localDateTime); + + assertThatThrownBy( + () -> session.cancel(JAVAJIGI, ERIC, apply, localDateTime) + ).isInstanceOf(IllegalArgumentException.class); + } } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 7c7228d2e5..97e3fee66e 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -8,6 +8,7 @@ import nextstep.payments.domain.Payment; import nextstep.qna.NotFoundException; import nextstep.users.domain.NsUser; +import nextstep.users.domain.Type; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -26,7 +27,7 @@ @JdbcTest public class SessionRepositoryTest { private static final Logger LOGGER = LoggerFactory.getLogger(SessionRepositoryTest.class); - private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); + private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net", Type.TEACHER); @Autowired private JdbcTemplate jdbcTemplate; @@ -62,17 +63,18 @@ void setUp() { applicants = new Applicants(); duration = new Duration(localDate, localDate); sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); - apply = new Apply(1L, JAVAJIGI.getId(), JAVAJIGI.getId(), localDateTime, localDateTime); + apply = new Apply(1L, JAVAJIGI.getId(), false, JAVAJIGI.getId(), localDateTime, localDateTime); } @Test void save_success() { session = new Session(images, duration, sessionState, 1L, localDateTime); Session savedSession = sessionRepository.save(1L, session); + Session findSession = sessionRepository.findById(savedSession.getId()).orElseThrow(NotFoundException::new); - assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getDuration()).isEqualTo(session.getDuration()); - assertThat(savedSession.getSessionState()).isEqualTo(session.getSessionState()); + assertThat(findSession.getId()).isEqualTo(1L); + assertThat(findSession.getDuration()).isEqualTo(session.getDuration()); + assertThat(findSession.getSessionState()).isEqualTo(session.getSessionState()); LOGGER.debug("Session: {}", savedSession); } @@ -103,4 +105,17 @@ void update_success() { assertThat(updatedSession.getSessionState()).isEqualTo(updateSessionState); LOGGER.debug("Session: {}", updatedSession); } + + @Test + void updateApply_success() { + Apply apply = new Apply(10L, JAVAJIGI.getId(), false, JAVAJIGI.getId(), localDateTime, localDateTime); + + Apply updatedApply = apply.setApproved(true); + sessionRepository.updateApply(updatedApply); + + Apply savedApply = sessionRepository.findApplyByIds(JAVAJIGI.getId(), 10L).orElseThrow(NotFoundException::new); + assertThat(savedApply.isApproved()).isTrue(); + + LOGGER.debug("Apply: {}", savedApply); + } } diff --git a/src/test/java/nextstep/users/domain/NsUserTest.java b/src/test/java/nextstep/users/domain/NsUserTest.java index ffac9f9a7d..6973b784eb 100644 --- a/src/test/java/nextstep/users/domain/NsUserTest.java +++ b/src/test/java/nextstep/users/domain/NsUserTest.java @@ -1,6 +1,6 @@ package nextstep.users.domain; public class NsUserTest { - public static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net"); - public static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net"); + public static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net", Type.TEACHER); + public static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net", Type.TEACHER); } From b02d9fe7641c516c99836fbbf06a8de53821159f Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Fri, 22 Dec 2023 19:02:00 +0900 Subject: [PATCH 60/62] =?UTF-8?q?[refactor]=20=EA=B0=95=EC=9D=98=EB=A5=BC?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의의 비지니스 로직을 리팩토링 합니다. 지원자(Applicants)를 지원(Applies) 에 합치고 객체 대신 객체 식별자를 의존합니다. --- .../nextstep/courses/domain/BaseEntity.java | 4 + .../domain/course/session/Applicants.java | 72 ----- .../course/session/ApplicantsRepository.java | 5 - .../courses/domain/course/session/Apply.java | 63 ---- .../domain/course/session/Duration.java | 8 +- .../domain/course/session/Session.java | 90 +++--- .../domain/course/session/SessionDetail.java | 98 +++--- ...Status.java => SessionProgressStatus.java} | 12 +- ...tStatus.java => SessionRecruitStatus.java} | 14 +- .../course/session/SessionRepository.java | 10 +- .../domain/course/session/SessionState.java | 77 ++++- .../domain/course/session/Sessions.java | 2 +- .../domain/course/session/apply/Applies.java | 78 +++++ .../domain/course/session/apply/Apply.java | 93 ++++++ .../course/session/apply/ApplyRepository.java | 13 + .../JdbcApplicantsRepository.java | 68 ----- .../infrastructure/JdbcApplyRepository.java | 83 ++++++ .../infrastructure/JdbcSessionRepository.java | 72 ++--- .../courses/service/CourseService.java | 2 +- .../courses/service/SessionService.java | 30 +- .../nextstep/qna/domain/DeleteHistory.java | 2 - .../java/nextstep/qna/domain/Question.java | 2 - .../java/nextstep/qna/service/QnAService.java | 1 - .../java/nextstep/users/domain/NsUser.java | 6 +- src/main/java/nextstep/users/domain/Type.java | 6 +- .../course/service/CourseServiceTest.java | 45 +-- .../course/service/SessionServiceTest.java | 161 +++++----- .../domain/course/session/ApplicantsTest.java | 46 --- .../session/SessionProgressStatusTest.java | 16 + .../session/SessionRecruitStatusTest.java | 16 + .../course/session/SessionStateTest.java | 21 +- .../domain/course/session/SessionTest.java | 278 +++++++++--------- ...onStatusTest.java => SessionTypeTest.java} | 4 +- .../domain/course/session/SessionsTest.java | 42 +-- .../courses/fixture/ApplyFixtures.java | 32 ++ .../courses/fixture/CourseFixtures.java | 12 + .../courses/fixture/ImageFixtures.java | 18 ++ .../courses/fixture/SessionFixtures.java | 92 ++++++ .../ApplicantsRepositoryTest.java | 42 --- .../infrastructure/ApplyRepositoryTest.java | 72 +++++ .../infrastructure/SessionRepositoryTest.java | 80 +---- .../payments/fixture/PaymentFixtures.java | 17 ++ .../java/nextstep/qna/domain/AnswerTest.java | 12 +- .../nextstep/qna/domain/QuestionTest.java | 18 +- .../nextstep/qna/service/QnaServiceTest.java | 14 +- .../nextstep/users/domain/NsUserTest.java | 6 - .../users/fixtures/NsUserFixtures.java | 11 + 47 files changed, 1055 insertions(+), 911 deletions(-) delete mode 100644 src/main/java/nextstep/courses/domain/course/session/Applicants.java delete mode 100644 src/main/java/nextstep/courses/domain/course/session/ApplicantsRepository.java delete mode 100644 src/main/java/nextstep/courses/domain/course/session/Apply.java rename src/main/java/nextstep/courses/domain/course/session/{SessionStatus.java => SessionProgressStatus.java} (74%) rename src/main/java/nextstep/courses/domain/course/session/{RecruitStatus.java => SessionRecruitStatus.java} (67%) create mode 100644 src/main/java/nextstep/courses/domain/course/session/apply/Applies.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/apply/Apply.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/apply/ApplyRepository.java delete mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcApplyRepository.java mode change 100755 => 100644 src/main/java/nextstep/users/domain/NsUser.java delete mode 100644 src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java create mode 100644 src/test/java/nextstep/courses/domain/course/session/SessionProgressStatusTest.java create mode 100644 src/test/java/nextstep/courses/domain/course/session/SessionRecruitStatusTest.java rename src/test/java/nextstep/courses/domain/course/session/{SessionStatusTest.java => SessionTypeTest.java} (84%) create mode 100644 src/test/java/nextstep/courses/fixture/ApplyFixtures.java create mode 100644 src/test/java/nextstep/courses/fixture/CourseFixtures.java create mode 100644 src/test/java/nextstep/courses/fixture/ImageFixtures.java create mode 100644 src/test/java/nextstep/courses/fixture/SessionFixtures.java delete mode 100644 src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java create mode 100644 src/test/java/nextstep/courses/infrastructure/ApplyRepositoryTest.java create mode 100644 src/test/java/nextstep/payments/fixture/PaymentFixtures.java delete mode 100644 src/test/java/nextstep/users/domain/NsUserTest.java create mode 100644 src/test/java/nextstep/users/fixtures/NsUserFixtures.java diff --git a/src/main/java/nextstep/courses/domain/BaseEntity.java b/src/main/java/nextstep/courses/domain/BaseEntity.java index 9c3b681541..0f35235e00 100644 --- a/src/main/java/nextstep/courses/domain/BaseEntity.java +++ b/src/main/java/nextstep/courses/domain/BaseEntity.java @@ -26,4 +26,8 @@ public LocalDateTime getCreatedAt() { public LocalDateTime getUpdatedAt() { return updatedAt; } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/Applicants.java b/src/main/java/nextstep/courses/domain/course/session/Applicants.java deleted file mode 100644 index c0378847a9..0000000000 --- a/src/main/java/nextstep/courses/domain/course/session/Applicants.java +++ /dev/null @@ -1,72 +0,0 @@ -package nextstep.courses.domain.course.session; - -import nextstep.users.domain.NsUser; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -public class Applicants implements Iterable { - private final List applicants; - - public Applicants() { - this(new ArrayList<>()); - } - - public Applicants(List applicants) { - this.applicants = applicants; - } - - public NsUser find(int index) { - return this.applicants.get(index); - } - - public int size() { - return this.applicants.size(); - } - - public void addApplicant(NsUser applicant, SessionState sessionState) { - checkApplicantAlreadyExisted(applicant); - checkChargedAndApplySizeIsValid(sessionState); - this.applicants.add(applicant); - } - - private void checkApplicantAlreadyExisted(NsUser applicant) { - if (this.applicants.contains(applicant)) { - throw new IllegalArgumentException("이미 강의를 신청하였습니다."); - } - } - - private void checkChargedAndApplySizeIsValid(SessionState sessionState) { - if (sessionState.chargedAndFull(applicants)) { - throw new IllegalArgumentException("수강 인원은 정원을 초과할 수 없습니다."); - } - } - - public void checkApprovePossible(NsUser applicant, SessionState sessionState) { - checkApplicantExists(applicant); - checkChargedAndApplySizeIsValid(sessionState); - } - - private void checkApplicantExists(NsUser applicant) { - if (!this.applicants.contains(applicant)) { - throw new IllegalArgumentException("수강 신청 이력이 없습니다."); - } - } - - public void checkCancelPossible(NsUser applicant, SessionState sessionState) { - checkApplicantExists(applicant); - } - - @Override - public Iterator iterator() { - return this.applicants.iterator(); - } - - @Override - public String toString() { - return "Applicants{" + - "applicants=" + applicants + - '}'; - } -} diff --git a/src/main/java/nextstep/courses/domain/course/session/ApplicantsRepository.java b/src/main/java/nextstep/courses/domain/course/session/ApplicantsRepository.java deleted file mode 100644 index 00ed821d39..0000000000 --- a/src/main/java/nextstep/courses/domain/course/session/ApplicantsRepository.java +++ /dev/null @@ -1,5 +0,0 @@ -package nextstep.courses.domain.course.session; - -public interface ApplicantsRepository { - Applicants findAllBySessionId(Long SessionId); -} diff --git a/src/main/java/nextstep/courses/domain/course/session/Apply.java b/src/main/java/nextstep/courses/domain/course/session/Apply.java deleted file mode 100644 index 3b4928c0a8..0000000000 --- a/src/main/java/nextstep/courses/domain/course/session/Apply.java +++ /dev/null @@ -1,63 +0,0 @@ -package nextstep.courses.domain.course.session; - -import nextstep.courses.domain.BaseEntity; -import nextstep.users.domain.NsUser; - -import java.time.LocalDateTime; - -public class Apply extends BaseEntity { - private Long sessionId; - - private Long nsUserId; - - private boolean approved; - - public Apply(Session session, NsUser nsUser, boolean approved, LocalDateTime createdAt) { - this(session.getId(), nsUser.getId(), approved, nsUser.getId(), createdAt, null); - } - - public Apply(Long sessionId, Long nsUserId, boolean approved, Long creatorId, - LocalDateTime createdAt, LocalDateTime updatedAt) { - super(creatorId, createdAt, updatedAt); - this.sessionId = sessionId; - this.nsUserId = nsUserId; - this.approved = approved; - } - - public Long getSessionId() { - return sessionId; - } - - public Long getNsUserId() { - return nsUserId; - } - - public boolean isApproved() { - return approved; - } - - public Apply setApproved(boolean approved) { - this.approved = approved; - return this; - } - - public void checkApprovePossible() { - if (this.approved) { - throw new IllegalArgumentException("이미 수강 승인 상태입니다."); - } - } - - public void checkCancelPossible() { - if (!this.approved) { - throw new IllegalArgumentException("이미 수강 취소 상태입니다."); - } - } - - @Override - public String toString() { - return "Apply{" + - "sessionId=" + sessionId + - ", nsUserId=" + nsUserId + - '}'; - } -} diff --git a/src/main/java/nextstep/courses/domain/course/session/Duration.java b/src/main/java/nextstep/courses/domain/course/session/Duration.java index 015ce20eaa..4b6a8366d5 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Duration.java +++ b/src/main/java/nextstep/courses/domain/course/session/Duration.java @@ -31,12 +31,12 @@ private void checkDurationIsValid(LocalDate startDate, LocalDate endDate) { } } - public boolean startDateIsSameOrBefore(LocalDate date) { - return this.startDate == date || this.startDate.isBefore(date); + public boolean changeDateIsSameOrAfterWithEndDate(LocalDate date) { + return date == this.endDate || date.isAfter(this.endDate); } - public boolean endDateIsSameOrAfter(LocalDate date) { - return this.endDate == date || this.endDate.isAfter(date); + public boolean changeDateIsBeforeOrSameWithEndDate(LocalDate date) { + return date.isBefore(this.endDate) || date == this.endDate; } public LocalDate getStartDate() { diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 0729e42ba0..80ebeb6fe6 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -1,6 +1,7 @@ package nextstep.courses.domain.course.session; import nextstep.courses.domain.BaseEntity; +import nextstep.courses.domain.course.session.apply.Apply; import nextstep.courses.domain.course.session.image.Images; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; @@ -14,66 +15,41 @@ public class Session extends BaseEntity { private Images images; - private Applicants applicants; - private SessionDetail sessionDetail; public Session(Images images, Duration duration, SessionState sessionState, Long creatorId, LocalDateTime date) { - this(0L, images, duration, sessionState, new Applicants(), - RecruitStatus.NOT_RECRUIT, SessionStatus.READY, creatorId, date, null); + this(0L, images, duration, sessionState, SessionRecruitStatus.NOT_RECRUIT, + SessionProgressStatus.READY, creatorId, date, null); } public Session(Long id, Images images, Duration duration, SessionState sessionState, - Applicants applicants, RecruitStatus recruitStatus, SessionStatus sessionStatus, + SessionRecruitStatus sessionRecruitStatus, SessionProgressStatus sessionProgressStatus, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { - this(id, images, applicants, new SessionDetail(duration, sessionState, sessionStatus, recruitStatus), + this(id, images, new SessionDetail(duration, sessionState, sessionProgressStatus, sessionRecruitStatus), creatorId, createdAt, updatedAt); } - public Session(Long id, Images images, Applicants applicants, SessionDetail sessionDetail, + public Session(Long id, Images images, SessionDetail sessionDetail, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); if (images == null) { throw new IllegalArgumentException("이미지를 추가해야 합니다"); } - if (applicants == null) { - throw new IllegalArgumentException("수강생을 추가해야 합니다."); - } - if (sessionDetail == null) { throw new IllegalArgumentException("강의 정보를 추가해야 합니다."); } this.id = id; this.images = images; - this.applicants = applicants; this.sessionDetail = sessionDetail; } public Apply apply(NsUser loginUser, Payment payment, LocalDateTime date) { - sessionDetail.checkApplyPossible(); checkPaymentIsPaid(loginUser, payment); - this.applicants.addApplicant(loginUser, sessionDetail.getSessionState()); - return toApply(loginUser, false, date); - } - - public Apply approve(NsUser loginUser, NsUser applicant, Apply apply, LocalDateTime date) { - loginUser.checkUserHasAuthor(); - this.applicants.checkApprovePossible(applicant, sessionDetail.getSessionState()); - apply.checkApprovePossible(); - - return toApply(applicant, true, date); - } - - public Apply cancel(NsUser loginUser, NsUser applicant, Apply apply, LocalDateTime date) { - loginUser.checkUserHasAuthor(); - this.applicants.checkCancelPossible(applicant, sessionDetail.getSessionState()); - apply.checkCancelPossible(); - - return toApply(applicant, false, date); + return this.sessionDetail.addApply(this.id, loginUser.getId(), date); } private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { @@ -84,28 +60,42 @@ private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { private void checkPaymentIsValid(NsUser loginUser, Payment payment) { if (payment == null || !payment.isPaid(loginUser, this)) { - throw new IllegalArgumentException("결제를 다시 확인하세요;"); + throw new IllegalArgumentException("결제를 다시 확인하세요."); } } - private Apply toApply(NsUser loginUser, boolean approved, LocalDateTime date) { - return new Apply(this, loginUser, approved, date); + public Apply approve(NsUser loginUser, Apply apply, LocalDateTime date) { + checkUserHasAuthor(loginUser); + + return sessionDetail.approve(apply, date); + } + + public Apply cancel(NsUser loginUser, Apply apply, LocalDateTime date) { + checkUserHasAuthor(loginUser); + + return sessionDetail.cancel(apply, date); + } + + private void checkUserHasAuthor(NsUser loginUser) { + if(!loginUser.hasAuthor()) { + throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); + } } public void changeOnReady(LocalDate date) { this.sessionDetail.changeOnReady(date); } - public void changeOnRecruit(LocalDate date) { - this.sessionDetail.changeOnRecruit(date); + public void changeOnGoing(LocalDate date) { + this.sessionDetail.changeOnGoing(date); } public void changeOnEnd(LocalDate date) { this.sessionDetail.changeOnEnd(date); } - public void setSessionState(SessionState updateSessionState) { - this.sessionDetail.setSessionState(updateSessionState); + public void changeSessionState(SessionState updateSessionState) { + this.sessionDetail.changeSessionState(updateSessionState); } public boolean sameAmount(Long amount) { @@ -125,7 +115,7 @@ public void setId(Long id) { } public int applyCount() { - return this.applicants.size(); + return this.sessionDetail.size(); } public Images getImages() { @@ -136,10 +126,6 @@ public void setImages(Images images) { this.images = images; } - public Applicants getApplicants() { - return applicants; - } - public SessionDetail getSessionDetail() { return sessionDetail; } @@ -152,20 +138,32 @@ public SessionState getSessionState() { return sessionDetail.getSessionState(); } - public SessionStatus getSessionStatus() { + public SessionProgressStatus getSessionStatus() { return sessionDetail.getSessionStatus(); } - public RecruitStatus getRecruitStatus() { + public SessionRecruitStatus getRecruitStatus() { return sessionDetail.getRecruitStatus(); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Session session = (Session) o; + return Objects.equals(id, session.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + @Override public String toString() { return "Session{" + "id=" + id + ", images=" + images + - ", applicants=" + applicants + ", sessionDetail=" + sessionDetail + '}'; } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java index 4ecc24b62e..1e03f1223a 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java @@ -1,9 +1,9 @@ package nextstep.courses.domain.course.session; -import nextstep.payments.domain.Payment; -import nextstep.users.domain.NsUser; +import nextstep.courses.domain.course.session.apply.Apply; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Objects; public class SessionDetail { @@ -11,12 +11,12 @@ public class SessionDetail { private SessionState sessionState; - private SessionStatus sessionStatus; + private SessionProgressStatus sessionProgressStatus; - private RecruitStatus recruitStatus; + private SessionRecruitStatus sessionRecruitStatus; public SessionDetail(Duration duration, SessionState sessionState, - SessionStatus sessionStatus, RecruitStatus recruitStatus) { + SessionProgressStatus sessionProgressStatus, SessionRecruitStatus sessionRecruitStatus) { if (duration == null) { throw new IllegalArgumentException("기간이 추가되어야 합니다."); } @@ -25,18 +25,18 @@ public SessionDetail(Duration duration, SessionState sessionState, throw new IllegalArgumentException("강의 정보가 추가되어야 합니다."); } - if (sessionStatus == null) { + if (sessionProgressStatus == null) { throw new IllegalArgumentException("강의 현황 상태가 추가되어야 합니다."); } - if (recruitStatus == null) { + if (sessionRecruitStatus == null) { throw new IllegalArgumentException("강의 모집 여부가 추가되어야 합니다."); } this.duration = duration; this.sessionState = sessionState; - this.sessionStatus = sessionStatus; - this.recruitStatus = recruitStatus; + this.sessionProgressStatus = sessionProgressStatus; + this.sessionRecruitStatus = sessionRecruitStatus; } public Duration getDuration() { @@ -47,21 +47,27 @@ public SessionState getSessionState() { return sessionState; } - public SessionStatus getSessionStatus() { - return sessionStatus; + public SessionProgressStatus getSessionStatus() { + return sessionProgressStatus; } - public RecruitStatus getRecruitStatus() { - return recruitStatus; + public SessionRecruitStatus getRecruitStatus() { + return sessionRecruitStatus; + } + + public int size() { + return this.sessionState.size(); } public boolean sameAmount(Long amount) { return this.sessionState.sameAmount(amount); } - public void checkApplyPossible() { + public Apply addApply(Long sessionId, Long nsUserId, LocalDateTime date) { checkStatusOnRecruit(); checkStatusOnReadyOrOnGoing(); + + return this.sessionState.addApply(sessionId, nsUserId, date); } private void checkStatusOnRecruit() { @@ -77,64 +83,54 @@ private void checkStatusOnReadyOrOnGoing() { } public boolean notRecruiting() { - return this.recruitStatus == RecruitStatus.NOT_RECRUIT; + return this.sessionRecruitStatus.notRecruiting(); } public boolean notReadyOrOnGoing() { - return this.sessionStatus == SessionStatus.END; - } - - public void checkPaymentIsPaid(NsUser loginUser, Payment payment) { - if(isNotPaid(payment)) { - - } - } - - public boolean isNotPaid(Payment payment) { - return payment == null || !charged(); + return this.sessionProgressStatus.notReadyOrOnGoing(); } public boolean charged() { return this.sessionState.charged(); } - public boolean endDateIsSameOrAfter(LocalDate date) { - return duration.endDateIsSameOrAfter(date); - } - public void changeOnReady(LocalDate date) { - checkStartDateIsSameOrBefore(date); - this.sessionStatus = SessionStatus.READY; + checkChangeDateIsSameOrAfterWithEndDate(date); + this.sessionProgressStatus = SessionProgressStatus.READY; } - public void changeOnRecruit(LocalDate date) { - checkStartDateIsSameOrBefore(date); - this.sessionStatus = SessionStatus.ONGOING; + public void changeOnGoing(LocalDate date) { + checkChangeDateIsSameOrAfterWithEndDate(date); + this.sessionProgressStatus = SessionProgressStatus.ONGOING; } - private void checkStartDateIsSameOrBefore(LocalDate date) { - if (startDateIsSameOrBefore(date)) { - throw new IllegalArgumentException("강의 시작일 이전에 변경 가능합니다."); + private void checkChangeDateIsSameOrAfterWithEndDate(LocalDate date) { + if (this.duration.changeDateIsSameOrAfterWithEndDate(date)) { + throw new IllegalArgumentException("강의 종료일 이전에 변경 가능 합니다."); } } public void changeOnEnd(LocalDate date) { - checkEndDateIsSameOrAfter(date); - this.sessionStatus = SessionStatus.END; + checkChangeDateIsBeforeOrSameWithEndDate(date); + this.sessionProgressStatus = SessionProgressStatus.END; } - private void checkEndDateIsSameOrAfter(LocalDate date) { - if (endDateIsSameOrAfter(date)) { - throw new IllegalArgumentException("강의 종료일 이후 변경 가능합니다."); + private void checkChangeDateIsBeforeOrSameWithEndDate(LocalDate date) { + if (this.duration.changeDateIsBeforeOrSameWithEndDate(date)) { + throw new IllegalArgumentException("강의 종료일 이후에 변경 가능합니다."); } } - public boolean startDateIsSameOrBefore(LocalDate date) { - return this.duration.startDateIsSameOrBefore(date); + public void changeSessionState(SessionState sessionState) { + this.sessionState = sessionState; } - public void setSessionState(SessionState sessionState) { - this.sessionState = sessionState; + public Apply approve(Apply apply, LocalDateTime date) { + return this.sessionState.approve(apply, date); + } + + public Apply cancel(Apply apply, LocalDateTime date) { + return this.sessionState.cancel(apply, date); } @Override @@ -142,12 +138,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SessionDetail that = (SessionDetail) o; - return Objects.equals(duration, that.duration) && Objects.equals(sessionState, that.sessionState) && sessionStatus == that.sessionStatus && recruitStatus == that.recruitStatus; + return Objects.equals(duration, that.duration) && Objects.equals(sessionState, that.sessionState) && sessionProgressStatus == that.sessionProgressStatus && sessionRecruitStatus == that.sessionRecruitStatus; } @Override public int hashCode() { - return Objects.hash(duration, sessionState, sessionStatus, recruitStatus); + return Objects.hash(duration, sessionState, sessionProgressStatus, sessionRecruitStatus); } @Override @@ -155,8 +151,8 @@ public String toString() { return "SessionDetail{" + "duration=" + duration + ", sessionState=" + sessionState + - ", sessionStatus=" + sessionStatus + - ", recruitStatus=" + recruitStatus + + ", sessionStatus=" + sessionProgressStatus + + ", recruitStatus=" + sessionRecruitStatus + '}'; } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java b/src/main/java/nextstep/courses/domain/course/session/SessionProgressStatus.java similarity index 74% rename from src/main/java/nextstep/courses/domain/course/session/SessionStatus.java rename to src/main/java/nextstep/courses/domain/course/session/SessionProgressStatus.java index d5db7d8d75..6f3816d4d0 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionStatus.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionProgressStatus.java @@ -2,18 +2,18 @@ import java.util.Arrays; -public enum SessionStatus { +public enum SessionProgressStatus { READY("준비중"), ONGOING("진행중"), END("종료"); private final String description; - SessionStatus(String description) { + SessionProgressStatus(String description) { this.description = description; } - public static SessionStatus find(String name) { + public static SessionProgressStatus find(String name) { return Arrays.stream(values()) .filter(status -> status.name().equals(name)) .findAny() @@ -26,11 +26,15 @@ public static SessionStatus find(String name) { public static String descriptions() { StringBuilder sb = new StringBuilder(); - for (SessionStatus status : values()) { + for (SessionProgressStatus status : values()) { sb.append(status.description).append(", "); } sb.setLength(sb.length() - 2); return sb.toString(); } + + public boolean notReadyOrOnGoing() { + return this == SessionProgressStatus.END; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/RecruitStatus.java b/src/main/java/nextstep/courses/domain/course/session/SessionRecruitStatus.java similarity index 67% rename from src/main/java/nextstep/courses/domain/course/session/RecruitStatus.java rename to src/main/java/nextstep/courses/domain/course/session/SessionRecruitStatus.java index e09ebb3915..65c22de704 100644 --- a/src/main/java/nextstep/courses/domain/course/session/RecruitStatus.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRecruitStatus.java @@ -2,17 +2,17 @@ import java.util.Arrays; -public enum RecruitStatus { +public enum SessionRecruitStatus { RECRUIT("모집중"), NOT_RECRUIT("비모집중"); private final String description; - RecruitStatus(String description) { + SessionRecruitStatus(String description) { this.description = description; } - public static RecruitStatus find(String name) { + public static SessionRecruitStatus find(String name) { return Arrays.stream(values()) .filter(status -> status.name().equals(name)) .findAny() @@ -25,11 +25,15 @@ public static RecruitStatus find(String name) { public static String descriptions() { StringBuilder sb = new StringBuilder(); - for (RecruitStatus recruitStatus : values()) { - sb.append(recruitStatus.description).append(", "); + for (SessionRecruitStatus sessionRecruitStatus : values()) { + sb.append(sessionRecruitStatus.description).append(", "); } sb.setLength(sb.length() - 2); return sb.toString(); } + + public boolean notRecruiting() { + return this == SessionRecruitStatus.NOT_RECRUIT; + } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java index 93d7f34d39..dd780874a7 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionRepository.java @@ -1,7 +1,5 @@ package nextstep.courses.domain.course.session; -import nextstep.users.domain.NsUser; - import java.util.Optional; public interface SessionRepository { @@ -9,15 +7,9 @@ public interface SessionRepository { Session save(Long courseId, Session session); - int saveApply(Apply apply); - - Optional findApplyByIds(Long NsUserId, Long sessionId); - int update(Long sessionId, Session session); Sessions findAllByCourseId(Long courseId); - int updateCourse(Long courseId, Session session); - - int updateApply(Apply apply); + int updateCourseId(Long courseId, Session session); } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionState.java b/src/main/java/nextstep/courses/domain/course/session/SessionState.java index b037a909d9..3d45d61489 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionState.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionState.java @@ -1,8 +1,9 @@ package nextstep.courses.domain.course.session; -import nextstep.users.domain.NsUser; +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.Apply; -import java.util.List; +import java.time.LocalDateTime; import java.util.Objects; public class SessionState { @@ -14,47 +15,86 @@ public class SessionState { private int quota; + private Applies applies; + public SessionState() { this.sessionType = SessionType.FREE; this.amount = 0L; this.quota = MAX_APPLY; + this.applies = new Applies(); } - public SessionState(SessionType sessionType, Long amount, int quota) { + public SessionState(SessionType sessionType, Long amount, int quota, Applies applies) { + if (applies == null) { + throw new IllegalArgumentException("수강생은 빈 값이면 안됩니다."); + } validate(sessionType, amount, quota); this.sessionType = sessionType; this.amount = amount; this.quota = quota; + this.applies = applies; } private void validate(SessionType sessionType, Long amount, int quota) { - checkFreeTypeAmountZero(sessionType, amount); - checkFreeTypeQuotaMax(sessionType, quota); + if (sessionType.free()) { + checkTypeisFree(amount, quota); + } + + if (sessionType.charged()) { + checkTypeisCharged(amount, quota); + } } - private void checkFreeTypeAmountZero(SessionType sessionType, Long amount) { - if (sessionType.free() && amount != 0L) { - throw new IllegalArgumentException("무료 강의는 강의료가 0원이어야 합니다."); + private void checkTypeisFree(Long amount, int quota) { + if(amount != 0L || quota != MAX_APPLY) { + throw new IllegalArgumentException("무료 강의는 0원, 정원 수가 최대여야 합니다."); } } - private void checkFreeTypeQuotaMax(SessionType sessionType, int quota) { - if (sessionType.free() && quota != MAX_APPLY) { - throw new IllegalArgumentException("무료 강의는 수강 가능 인원이 최대여야 합니다."); + private void checkTypeisCharged(Long amount, int quota) { + if(amount == 0L || quota == 0) { + throw new IllegalArgumentException("유료강의는 0원보다 크고 정원 수가 0보다 커야 합니다."); } } - public boolean sameAmount(Long amount) { - return Objects.equals(this.amount, amount); + public Apply addApply(Long sessionId, Long nsUserId, LocalDateTime date) { + checkChargedAndApplySizeIsValid(); + + return this.applies.addApply(sessionId, nsUserId, date); + } + + private void checkChargedAndApplySizeIsValid() { + if(chargedAndFull()) { + throw new IllegalArgumentException("수강 신청 인원이 초과 되었습니다."); + } + } + + public Apply approve(Apply apply, LocalDateTime date) { + return this.applies.approve(apply, date); + } + + public Apply cancel(Apply apply, LocalDateTime date) { + return this.applies.cancel(apply, date); + } + + private boolean chargedAndFull() { + return this.charged() && applySizeFull(); } public boolean charged() { return this.sessionType.charged(); } - public boolean chargedAndFull(List applicants) { - return this.sessionType == SessionType.CHARGE - && this.quota == applicants.size(); + private boolean applySizeFull() { + return this.quota == applies.size(); + } + + public int size() { + return this.applies.size(); + } + + public boolean sameAmount(Long amount) { + return Objects.equals(this.amount, amount); } public SessionType getSessionType() { @@ -69,6 +109,10 @@ public int getQuota() { return quota; } + public Applies getApplies() { + return applies; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -88,6 +132,7 @@ public String toString() { "sessionType=" + sessionType + ", amount=" + amount + ", quota=" + quota + + ", applies=" + applies + '}'; } } diff --git a/src/main/java/nextstep/courses/domain/course/session/Sessions.java b/src/main/java/nextstep/courses/domain/course/session/Sessions.java index 3e5d36a0f0..6777975d3e 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Sessions.java +++ b/src/main/java/nextstep/courses/domain/course/session/Sessions.java @@ -38,7 +38,7 @@ public void add(Session session) { private void checkSessionAlreadyExisted(Session session) { if (this.sessions.contains(session)) { - throw new IllegalArgumentException("이미 강의를 추가하였습니다."); + throw new IllegalArgumentException("이미 강의를 추가 하였습니다."); } } diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java b/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java new file mode 100644 index 0000000000..ddd995e60f --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java @@ -0,0 +1,78 @@ +package nextstep.courses.domain.course.session.apply; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class Applies implements Iterable { + private final List applies; + + public Applies() { + this(new ArrayList<>()); + } + + public Applies(List applicants) { + this.applies = applicants; + } + + public int size() { + return this.applies.size(); + } + + public Apply addApply(Long sessionId, Long nsUserId, LocalDateTime date) { + checkApplicantAlreadyExisted(nsUserId); + + Apply addedApply = new Apply(sessionId, nsUserId, false, date); + this.applies.add(addedApply); + + return addedApply; + } + + private void checkApplicantAlreadyExisted(Long nsUserId) { + if (this.containsUserId(nsUserId)) { + throw new IllegalArgumentException("이미 수강 신청 이력이 있습니다."); + } + } + + public boolean containsUserId(Long nsUserId) { + return this.applies.stream() + .anyMatch(apply -> apply.isSameWithUserId(nsUserId)); + } + + public Apply approve(Apply apply, LocalDateTime date) { + return this.applies.stream() + .filter(savedApply -> approved(apply, savedApply)) + .findAny() + .map(savedApply -> savedApply.approve(date)) + .orElseThrow(() -> new IllegalArgumentException("지원자가 미승인 상태인지 확인하세요.")); + } + + private static boolean approved(Apply apply, Apply savedApply) { + return savedApply.isSame(apply) && savedApply.isCanceled(); + } + + public Apply cancel(Apply apply, LocalDateTime date) { + return this.applies.stream() + .filter(savedApply -> canceled(apply, savedApply)) + .findAny() + .map(savedApply -> savedApply.cancel(date)) + .orElseThrow(() -> new IllegalArgumentException("지원자가 승인 상태인지 확인하세요.")); + } + + private static boolean canceled(Apply apply, Apply savedApply) { + return savedApply.isSame(apply) && savedApply.isApproved(); + } + + @Override + public String toString() { + return "Applicants{" + + "applicants=" + applies + + '}'; + } + + @Override + public Iterator iterator() { + return this.applies.iterator(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java b/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java new file mode 100644 index 0000000000..ec0c4f4e60 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java @@ -0,0 +1,93 @@ +package nextstep.courses.domain.course.session.apply; + +import nextstep.courses.domain.BaseEntity; +import nextstep.courses.domain.course.session.Session; +import nextstep.users.domain.NsUser; + +import java.time.LocalDateTime; +import java.util.Objects; + +public class Apply extends BaseEntity { + private Long sessionId; + + private Long nsUserId; + + private boolean approved; + + public Apply(Long sessionId, Long nsUserId, boolean approved, LocalDateTime createdAt) { + this(sessionId, nsUserId, approved, nsUserId, createdAt, null); + } + + public Apply(Long sessionId, Long nsUserId, boolean approved, Long creatorId, + LocalDateTime createdAt, LocalDateTime updatedAt) { + super(creatorId, createdAt, updatedAt); + this.sessionId = sessionId; + this.nsUserId = nsUserId; + this.approved = approved; + } + + public Long getSessionId() { + return sessionId; + } + + public Long getNsUserId() { + return nsUserId; + } + + public boolean isSame(Apply apply) { + return Objects.equals(this.nsUserId, apply.nsUserId); + } + + public boolean isSameWithUserId(Long nsUserId) { + return Objects.equals(this.nsUserId, nsUserId); + } + + public boolean isApproved() { + return approved; + } + + public boolean isCanceled() { + return !approved; + } + + public Apply setApproved(boolean approved) { + this.approved = approved; + return this; + } + + public Apply approve(LocalDateTime date) { + this.approved = true; + this.setUpdatedAt(date); + + return this; + } + + public Apply cancel(LocalDateTime date) { + this.approved = false; + this.setUpdatedAt(date); + + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Apply apply = (Apply) o; + return approved == apply.approved && Objects.equals(sessionId, apply.sessionId) && Objects.equals(nsUserId, apply.nsUserId); + } + + @Override + public int hashCode() { + return Objects.hash(sessionId, nsUserId, approved); + } + + @Override + public String toString() { + return "Apply{" + + "sessionId=" + sessionId + + ", nsUserId=" + nsUserId + + ", approved=" + approved + + '}'; + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/ApplyRepository.java b/src/main/java/nextstep/courses/domain/course/session/apply/ApplyRepository.java new file mode 100644 index 0000000000..d9ac760722 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/apply/ApplyRepository.java @@ -0,0 +1,13 @@ +package nextstep.courses.domain.course.session.apply; + +import java.util.Optional; + +public interface ApplyRepository { + Applies findAllBySessionId(Long SessionId); + + Apply save(Apply apply); + + Apply update(Apply apply); + + Optional findApplyByNsUserIdAndSessionId(Long NsUserId, Long sessionId); +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java deleted file mode 100644 index 01485ced3a..0000000000 --- a/src/main/java/nextstep/courses/infrastructure/JdbcApplicantsRepository.java +++ /dev/null @@ -1,68 +0,0 @@ -package nextstep.courses.infrastructure; - -import nextstep.courses.domain.course.session.Applicants; -import nextstep.courses.domain.course.session.ApplicantsRepository; -import nextstep.qna.NotFoundException; -import nextstep.users.domain.NsUser; -import nextstep.users.domain.Type; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Repository; - -import java.sql.Timestamp; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -@Repository("applicantsRepository") -public class JdbcApplicantsRepository implements ApplicantsRepository { - private final JdbcOperations jdbcTemplate; - - public JdbcApplicantsRepository(JdbcOperations jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - @Override - public Applicants findAllBySessionId(Long sessionId) { - List applicantIds = findAllApplicantIdsBySessionId(sessionId); - List nsUsers = new ArrayList<>(); - for (Long applicantId : applicantIds) { - NsUser nsUser = findAllNsUsersById(applicantId).orElseThrow(NotFoundException::new); - nsUsers.add(nsUser); - } - - return new Applicants(nsUsers); - } - - private List findAllApplicantIdsBySessionId(Long sessionId) { - String sql = "select ns_user_id from apply where session_id = ?"; - RowMapper rowMapper = (rs, rowNum) -> rs.getLong(1); - return jdbcTemplate.query(sql, rowMapper, sessionId); - } - - private Optional findAllNsUsersById(Long applicantId) { - String sql = "select " + - "id, user_id, password, name, email, type, created_at, updated_at " + - "from ns_user where id = ?"; - RowMapper rowMapper = (rs, rowNum) -> new NsUser( - rs.getLong(1), - rs.getString(2), - rs.getString(3), - rs.getString(4), - rs.getString(5), - Type.find(rs.getString(6)), - rs.getTimestamp(7).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(8)) - ); - - return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, applicantId)); - } - - private LocalDateTime toLocalDateTime(Timestamp timestamp) { - if (timestamp == null) { - return null; - } - return timestamp.toLocalDateTime(); - } -} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcApplyRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcApplyRepository.java new file mode 100644 index 0000000000..d1b81a1e67 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcApplyRepository.java @@ -0,0 +1,83 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.courses.domain.course.session.apply.ApplyRepository; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository("applyRepository") +public class JdbcApplyRepository implements ApplyRepository { + private final JdbcOperations jdbcTemplate; + + public JdbcApplyRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Applies findAllBySessionId(Long sessionId) { + String sql = "select " + + "session_id, ns_user_id, approved, creator_id, created_at, updated_at " + + "from apply where session_id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Apply( + rs.getLong(1), + rs.getLong(2), + rs.getBoolean(3), + rs.getLong(4), + rs.getTimestamp(5).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(6)) + ); + + List applies = jdbcTemplate.query(sql, rowMapper, sessionId); + return new Applies(applies); + } + + @Override + public Apply save(Apply apply) { + String sql = "insert into apply " + + "(session_id, ns_user_id, approved, creator_id, created_at, updated_at) " + + "values(?, ?, ?, ?, ?, ?)"; + + jdbcTemplate.update(sql, apply.getSessionId(), apply.getNsUserId(), apply.isApproved(), + apply.getCreatorId(), apply.getCreatedAt(), apply.getUpdatedAt()); + + return apply; + } + + @Override + public Apply update(Apply apply) { + String sql = "update apply set approved = ? where session_id = ? and ns_user_id = ?"; + + jdbcTemplate.update(sql, apply.isApproved(), apply.getSessionId(), apply.getNsUserId()); + + return apply; + } + + @Override + public Optional findApplyByNsUserIdAndSessionId(Long nsUserId, Long sessionId) { + String sql = "select " + + "session_id, ns_user_id, approved, creator_id, created_at, updated_at " + + "from apply where ns_user_id = ? and session_id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Apply( + rs.getLong(1), + rs.getLong(2), + rs.getBoolean(3), + rs.getLong(4), + rs.getTimestamp(5).toLocalDateTime(), + toLocalDateTime(rs.getTimestamp(6))); + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, nsUserId, sessionId)); + } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 4c0102a3ff..1f8bb0a131 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,6 +1,8 @@ package nextstep.courses.infrastructure; import nextstep.courses.domain.course.session.*; +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.ApplyRepository; import nextstep.courses.domain.course.session.image.Image; import nextstep.courses.domain.course.session.image.ImageRepository; import nextstep.courses.domain.course.session.image.Images; @@ -24,13 +26,13 @@ public class JdbcSessionRepository implements SessionRepository { private final JdbcOperations jdbcTemplate; private final ImageRepository imageRepository; - private final ApplicantsRepository applicantsRepository; + private final ApplyRepository applyRepository; KeyHolder keyHolder = new GeneratedKeyHolder(); public JdbcSessionRepository(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; this.imageRepository = new JdbcImageRepository(jdbcTemplate); - this.applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); + this.applyRepository = new JdbcApplyRepository(jdbcTemplate); } @Override @@ -50,11 +52,11 @@ public Optional findById(Long id) { new SessionState( SessionType.find(rs.getString(4)), rs.getLong(5), - rs.getInt(6) + rs.getInt(6), + findAllAppliesBySessionId(id) ), - findAllBySessionId(id), - RecruitStatus.find(rs.getString(7)), - SessionStatus.find(rs.getString(8)), + SessionRecruitStatus.find(rs.getString(7)), + SessionProgressStatus.find(rs.getString(8)), rs.getLong(10), rs.getTimestamp(11).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(12))); @@ -77,10 +79,10 @@ public Session save(Long courseId, Session session) { private Session saveSession(Long courseId, Session session) { Duration duration = session.getDuration(); - RecruitStatus recruitStatus = session.getRecruitStatus(); + SessionRecruitStatus sessionRecruitStatus = session.getRecruitStatus(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); - SessionStatus sessionStatus = session.getSessionStatus(); + SessionProgressStatus sessionProgressStatus = session.getSessionStatus(); String sql = "insert into session " + "(start_date, end_date, session_type, session_status, amount, " + "recruit_status, quota, course_id, creator_id, created_at, updated_at) " + @@ -91,9 +93,9 @@ private Session saveSession(Long courseId, Session session) { ps.setTimestamp(1, Timestamp.valueOf(duration.getStartDate().atStartOfDay())); ps.setTimestamp(2, Timestamp.valueOf(duration.getEndDate().atStartOfDay())); ps.setString(3, sessionType.name()); - ps.setString(4, sessionStatus.name()); + ps.setString(4, sessionProgressStatus.name()); ps.setLong(5, sessionState.getAmount()); - ps.setString(6, recruitStatus.name()); + ps.setString(6, sessionRecruitStatus.name()); ps.setInt(7, sessionState.getQuota()); ps.setLong(8, courseId); ps.setLong(9, session.getCreatorId()); @@ -108,42 +110,18 @@ private Session saveSession(Long courseId, Session session) { return session; } - @Override - public int saveApply(Apply apply) { - String sql = "insert into apply " + - "(session_id, ns_user_id, approved, creator_id, created_at, updated_at) " + - "values(?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, apply.getSessionId(), apply.getNsUserId(), apply.isApproved(), - apply.getCreatorId(), apply.getCreatedAt(), apply.getUpdatedAt()); - } - - @Override - public Optional findApplyByIds(Long nsUserId, Long sessionId) { - String sql = "select " + - "session_id, ns_user_id, approved, creator_id, created_at, updated_at " + - "from apply where ns_user_id = ? and session_id = ?"; - RowMapper rowMapper = (rs, rowNum) -> new Apply( - rs.getLong(1), - rs.getLong(2), - rs.getBoolean(3), - rs.getLong(4), - rs.getTimestamp(5).toLocalDateTime(), - toLocalDateTime(rs.getTimestamp(6))); - return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, nsUserId, sessionId)); - } - @Override public int update(Long sessionId, Session session) { Duration duration = session.getDuration(); - RecruitStatus recruitStatus = session.getRecruitStatus(); + SessionRecruitStatus sessionRecruitStatus = session.getRecruitStatus(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); - SessionStatus sessionStatus = session.getSessionStatus(); + SessionProgressStatus sessionProgressStatus = session.getSessionStatus(); String sql = "update session set " + "start_date = ?, end_date = ?, session_type = ?, recruit_status = ?, amount = ?, quota = ?, session_status = ? " + "where id = ?"; return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), - recruitStatus.name(), sessionState.getAmount(), sessionState.getQuota(), sessionStatus.name(), sessionId); + sessionRecruitStatus.name(), sessionState.getAmount(), sessionState.getQuota(), sessionProgressStatus.name(), sessionId); } @Override @@ -162,11 +140,11 @@ public Sessions findAllByCourseId(Long courseId) { new SessionState( SessionType.find(rs.getString(4)), rs.getLong(5), - rs.getInt(6) + rs.getInt(6), + findAllAppliesBySessionId(rs.getLong(7)) ), - findAllBySessionId(rs.getLong(7)), - RecruitStatus.find(rs.getString(8)), - SessionStatus.find(rs.getString(9)), + SessionRecruitStatus.find(rs.getString(8)), + SessionProgressStatus.find(rs.getString(9)), rs.getLong(10), rs.getTimestamp(11).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(12))); @@ -176,23 +154,17 @@ public Sessions findAllByCourseId(Long courseId) { } @Override - public int updateCourse(Long courseId, Session session) { + public int updateCourseId(Long courseId, Session session) { String sql = "update session set course_id = ? where id = ?"; return jdbcTemplate.update(sql, session.getId(), courseId); } - @Override - public int updateApply(Apply apply) { - String sql = "update apply set approved = ? where session_id = ? and ns_user_id = ?"; - return jdbcTemplate.update(sql, apply.isApproved(), apply.getSessionId(), apply.getNsUserId()); - } - private Images findAllImagesBySessionId(Long id) { return this.imageRepository.findAllBySessionId(id); } - private Applicants findAllBySessionId(Long id) { - return this.applicantsRepository.findAllBySessionId(id); + private Applies findAllAppliesBySessionId(Long id) { + return this.applyRepository.findAllBySessionId(id); } private LocalDateTime toLocalDateTime(Timestamp timestamp) { diff --git a/src/main/java/nextstep/courses/service/CourseService.java b/src/main/java/nextstep/courses/service/CourseService.java index 74606b2784..dfb7b8c171 100644 --- a/src/main/java/nextstep/courses/service/CourseService.java +++ b/src/main/java/nextstep/courses/service/CourseService.java @@ -23,7 +23,7 @@ public void create(Course course) { public void addSession(long courseId, Session session) { Course course = getCourse(courseId); course.addSession(session); - sessionRepository.updateCourse(courseId, session); + sessionRepository.updateCourseId(courseId, session); } private Course getCourse(long courseId) { diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 5d3cd6bc88..56cd9ba386 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -1,8 +1,9 @@ package nextstep.courses.service; -import nextstep.courses.domain.course.session.Apply; import nextstep.courses.domain.course.session.Session; import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.courses.domain.course.session.apply.ApplyRepository; import nextstep.payments.domain.Payment; import nextstep.qna.NotFoundException; import nextstep.users.domain.NsUser; @@ -17,6 +18,9 @@ public class SessionService { @Resource(name = "sessionRepository") private SessionRepository sessionRepository; + @Resource(name = "applyRepository") + private ApplyRepository applyRepository; + public void create(Long courseId, Session session) { sessionRepository.save(courseId, session); } @@ -24,21 +28,21 @@ public void create(Long courseId, Session session) { public void applySession(NsUser loginUser, Long sessionId, Payment payment, LocalDateTime date) { Session session = getSession(sessionId); Apply apply = session.apply(loginUser, payment, date); - sessionRepository.saveApply(apply); + applyRepository.save(apply); } - public void approve(NsUser loginUser, NsUser applicant, Long sessionId, LocalDateTime date) { + public void approve(NsUser loginUser, Long applicantId, Long sessionId, LocalDateTime date) { Session session = getSession(sessionId); - Apply savedApply = getApply(sessionId, loginUser.getId()); - Apply apply = session.approve(loginUser, applicant, savedApply, date); - sessionRepository.updateApply(apply); + Apply savedApply = getApply(sessionId, applicantId); + Apply apply = session.approve(loginUser, savedApply, date); + applyRepository.update(apply); } - public void cancel(NsUser loginUser, NsUser applicant, Long sessionId, LocalDateTime date) { + public void cancel(NsUser loginUser, Long applicantId, Long sessionId, LocalDateTime date) { Session session = getSession(sessionId); - Apply savedApply = getApply(sessionId, loginUser.getId()); - Apply apply = session.cancel(loginUser, applicant, savedApply, date); - sessionRepository.updateApply(apply); + Apply savedApply = getApply(sessionId, applicantId); + Apply apply = session.cancel(loginUser, savedApply, date); + applyRepository.update(apply); } public void changeOnReady(Long sessionId, LocalDate date) { @@ -47,9 +51,9 @@ public void changeOnReady(Long sessionId, LocalDate date) { sessionRepository.update(sessionId, session); } - public void changeOnRecruit(Long sessionId, LocalDate date) { + public void changeOnGoing(Long sessionId, LocalDate date) { Session session = getSession(sessionId); - session.changeOnRecruit(date); + session.changeOnGoing(date); sessionRepository.update(sessionId, session); } @@ -64,6 +68,6 @@ private Session getSession(Long sessionId) { } private Apply getApply(Long sessionId, Long nsUserId) { - return sessionRepository.findApplyByIds(sessionId, nsUserId).orElseThrow(NotFoundException::new); + return applyRepository.findApplyByNsUserIdAndSessionId(sessionId, nsUserId).orElseThrow(NotFoundException::new); } } diff --git a/src/main/java/nextstep/qna/domain/DeleteHistory.java b/src/main/java/nextstep/qna/domain/DeleteHistory.java index 51d818b0c9..43c37e5e5c 100644 --- a/src/main/java/nextstep/qna/domain/DeleteHistory.java +++ b/src/main/java/nextstep/qna/domain/DeleteHistory.java @@ -3,8 +3,6 @@ import nextstep.users.domain.NsUser; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; public class DeleteHistory { diff --git a/src/main/java/nextstep/qna/domain/Question.java b/src/main/java/nextstep/qna/domain/Question.java index 2d0ed31ae1..54b39258de 100644 --- a/src/main/java/nextstep/qna/domain/Question.java +++ b/src/main/java/nextstep/qna/domain/Question.java @@ -4,8 +4,6 @@ import nextstep.users.domain.NsUser; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; public class Question { private Long id; diff --git a/src/main/java/nextstep/qna/service/QnAService.java b/src/main/java/nextstep/qna/service/QnAService.java index 6c2b646d12..51207d9666 100644 --- a/src/main/java/nextstep/qna/service/QnAService.java +++ b/src/main/java/nextstep/qna/service/QnAService.java @@ -9,7 +9,6 @@ import javax.annotation.Resource; import java.time.LocalDateTime; -import java.util.List; @Service("qnaService") public class QnAService { diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java old mode 100755 new mode 100644 index 520ec850cc..731b0252d5 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -125,10 +125,8 @@ public boolean isSame(Long nsUserId) { return Objects.equals(this.id, nsUserId); } - public void checkUserHasAuthor() { - if(this.type == Type.STUDENT) { - throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); - } + public boolean hasAuthor() { + return this.type.isTeacher(); } private static class GuestNsUser extends NsUser { diff --git a/src/main/java/nextstep/users/domain/Type.java b/src/main/java/nextstep/users/domain/Type.java index d0cba06521..a8cc1efa66 100644 --- a/src/main/java/nextstep/users/domain/Type.java +++ b/src/main/java/nextstep/users/domain/Type.java @@ -1,7 +1,5 @@ package nextstep.users.domain; -import nextstep.courses.domain.course.session.SessionStatus; - import java.util.Arrays; public enum Type { @@ -24,6 +22,10 @@ public static Type find(String name) { ); } + public boolean isTeacher() { + return this == TEACHER; + } + public static String descriptions() { StringBuilder sb = new StringBuilder(); for (Type type : values()) { diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 8dddadde51..81dc54b799 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -2,13 +2,10 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; -import nextstep.courses.domain.course.session.image.Image; -import nextstep.courses.domain.course.session.image.ImageType; -import nextstep.courses.domain.course.session.*; -import nextstep.courses.domain.course.session.image.Images; +import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.fixture.CourseFixtures; +import nextstep.courses.fixture.SessionFixtures; import nextstep.courses.service.CourseService; -import nextstep.payments.domain.Payment; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -16,26 +13,11 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class CourseServiceTest { - private Images images; - private Image image; - private Payment payment; - private LocalDate localDate; - private LocalDateTime localDateTime; - private Duration duration; - private SessionState sessionState; - private Course course; - private Sessions sessions; - private Session session; - @Mock private CourseRepository courseRepository; @@ -45,29 +27,16 @@ public class CourseServiceTest { @InjectMocks private CourseService courseService; - @BeforeEach - void setUp() { - localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - course = new Course(1L, "math", 1, new Sessions(), 1L, localDateTime, null); - image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); - images = new Images(List.of(image)); - payment = new Payment("1", 1L, 3L, 1000L); - localDate = LocalDate.of(2023, 12, 5); - duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - session = new Session(1L, images, duration, sessionState, new Applicants(), - RecruitStatus.NOT_RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - sessions = new Sessions(); - sessions.add(session); - } - @Test @DisplayName("주어진 강의를 과정에 추가하면 과정에 강의가 추가된다.") void addSession_success() { + Course course = CourseFixtures.course(); + when(courseRepository.findById(course.getId())).thenReturn(course); + assertThat(course.sessionSize()).isEqualTo(0); - courseService.addSession(course.getId(), session); + courseService.addSession(course.getId(), SessionFixtures.createdFreeSession()); assertThat(course.sessionSize()).isEqualTo(1); } } diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index f6506aacd4..01fa6c9f44 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -1,15 +1,16 @@ package nextstep.courses.domain.course.service; -import nextstep.courses.domain.course.session.image.Image; -import nextstep.courses.domain.course.session.image.ImageType; -import nextstep.courses.domain.course.session.*; -import nextstep.courses.domain.course.session.image.Images; +import nextstep.courses.domain.course.session.SessionRecruitStatus; +import nextstep.courses.domain.course.session.Session; +import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.domain.course.session.SessionProgressStatus; +import nextstep.courses.domain.course.session.apply.ApplyRepository; +import nextstep.courses.fixture.ApplyFixtures; +import nextstep.courses.fixture.SessionFixtures; import nextstep.courses.service.SessionService; -import nextstep.payments.domain.Payment; +import nextstep.payments.fixture.PaymentFixtures; import nextstep.qna.NotFoundException; -import nextstep.users.domain.NsUser; -import nextstep.users.domain.Type; -import org.junit.jupiter.api.BeforeEach; +import nextstep.users.fixtures.NsUserFixtures; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,9 +18,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -27,117 +25,114 @@ @ExtendWith(MockitoExtension.class) public class SessionServiceTest { - private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net", Type.TEACHER); - private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net", Type.TEACHER); - private static final LocalDate DATE_2023_12_5 = LocalDate.of(2023, 12, 5); - private static final LocalDate DATE_2023_12_6 = LocalDate.of(2023, 12, 6); - private static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); - private static final LocalDate DATE_2023_12_12 = LocalDate.of(2023, 12, 12); - - private Images images; - private Image image; - private Payment payment; - private LocalDate localDate; - private LocalDateTime localDateTime; - private Applicants applicants; - private Duration duration; - private RecruitStatus recruitStatus; - private SessionState sessionState; - private SessionStatus sessionStatus; - private Session session; - private Session savedSession; - @Mock private SessionRepository sessionRepository; + @Mock + private ApplyRepository applyRepository; + @InjectMocks private SessionService sessionService; - @BeforeEach - public void setUp() { - localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); - images = new Images(List.of(image)); - payment = new Payment("1", 1L, 3L, 1000L); - localDate = LocalDate.of(2023, 12, 5); - duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - sessionStatus = SessionStatus.ONGOING; - recruitStatus = RecruitStatus.NOT_RECRUIT; - applicants = new Applicants(); - applicants.addApplicant(JAVAJIGI, sessionState); - session = new Session(1L, images, duration, sessionState, applicants, - recruitStatus, sessionStatus, 1L, localDateTime, localDateTime); - } - @Test @DisplayName("주어진 강의 정보로 강의를 생성한다.") void create_success() { - Session newSession = new Session(images, duration, sessionState, 1L, localDateTime); - Session savedSession = new Session(1L, images, duration, sessionState, new Applicants(), - RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); - when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); + Session savedSession = SessionFixtures.createdFreeSession(); + when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); - sessionService.create(1L, newSession); + sessionService.create(1L, savedSession); Session findSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); assertThat(findSession.getId()).isEqualTo(1L); - assertThat(findSession.getSessionDetail()).isEqualTo( - new SessionDetail(duration, sessionState, SessionStatus.READY, recruitStatus)); - assertThat(findSession.getApplicants()).hasSize(0); + assertThat(findSession.getSessionDetail()).isEqualTo(savedSession.getSessionDetail()); } @Test @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { - when(sessionRepository.findById(session.getId())).thenReturn(Optional.of(session)); + Session savedSession = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING); + when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); - assertThat(session.applyCount()).isEqualTo(1); - sessionService.applySession(APPLE, session.getId(), payment, localDateTime); + sessionService.applySession(NsUserFixtures.TEACHER_JAVAJIGI_1L, + savedSession.getId(), + PaymentFixtures.payment(), + SessionFixtures.DATETIME_2023_12_5 + ); - assertThat(session.applyCount()).isEqualTo(2); - assertThat(session.getApplicants()).contains(APPLE); + assertThat(savedSession.applyCount()).isEqualTo(1); } @Test - @DisplayName("강의 시작 날짜 전이라면 주어진 식별자에 해당하는 강의를 준비 상태로 변경한다.") + @DisplayName("changeOnReady 는 강의 종료일보다 빠르면 강의를 준비중으로 변경 한다.") void changeOnReady_success() { - duration = new Duration(DATE_2023_12_6, DATE_2023_12_12); - savedSession = new Session(1L, images, duration, sessionState, new Applicants(), - RecruitStatus.NOT_RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); + Session savedSession = SessionFixtures.createdFreeSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING); + when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); - sessionService.changeOnReady(1L, DATE_2023_12_5); + sessionService.changeOnReady(savedSession.getId(), SessionFixtures.DATE_2023_12_5); assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionStatus()).isEqualTo(SessionStatus.READY); + assertThat(savedSession.getSessionStatus()).isEqualTo(SessionProgressStatus.READY); } @Test - @DisplayName("강의 시작 날짜 전이라면 주어진 식별자에 해당하는 강의를 모집중 상태로 변경한다.") - void changeOnRecruit_success() { - duration = new Duration(DATE_2023_12_6, DATE_2023_12_12); - savedSession = new Session(1L, images, duration, sessionState, new Applicants(), - RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); - when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); + @DisplayName("changeOnGoing 는 강의 종료일보다 빠르면 강의를 진행중으로 변경 한다.") + void changeOnGoing_success() { + Session savedSession = SessionFixtures.createdFreeSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY); + when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); - sessionService.changeOnRecruit(1L, DATE_2023_12_5); + sessionService.changeOnGoing(savedSession.getId(), SessionFixtures.DATE_2023_12_6); assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionStatus()).isEqualTo(SessionStatus.ONGOING); + assertThat(savedSession.getSessionStatus()).isEqualTo(SessionProgressStatus.ONGOING); } @Test - @DisplayName("강의 종료날짜 이후라면 주어진 식별자에 해당하는 강의를 종료 상태로 변경한다.") + @DisplayName("changeOnEnd 는 강의 종료일보다 늦으면 강의를 종료로 변경 한다.") void changeOnEnd_success() { - duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); - savedSession = new Session(1L, images, duration, sessionState, new Applicants(), - RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); - when(sessionRepository.findById(1L)).thenReturn(Optional.of(savedSession)); + Session savedSession = SessionFixtures.createdFreeSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY); + when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + + sessionService.changeOnEnd(savedSession.getId(), SessionFixtures.DATE_2023_12_12); - sessionService.changeOnEnd(1L, DATE_2023_12_12); + assertThat(savedSession.getId()).isEqualTo(1L); + assertThat(savedSession.getSessionStatus()).isEqualTo(SessionProgressStatus.END); + } + + @Test + @DisplayName("approve 는 수강 신청을 승인 상태로 변경한다.") + void approve_success() { + Session savedSession = SessionFixtures.chargedSessionFullCanceled(); + when(applyRepository.findApplyByNsUserIdAndSessionId(1L, 1L)) + .thenReturn(Optional.of(ApplyFixtures.apply_one_canceled())); + when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + + sessionService.approve(NsUserFixtures.TEACHER_JAVAJIGI_1L, + savedSession.getId(), + 1L, + SessionFixtures.DATETIME_2023_12_5 + ); + + assertThat(savedSession.getId()).isEqualTo(1L); + assertThat(savedSession.getSessionDetail().getSessionState().getApplies()) + .contains(ApplyFixtures.apply_one_approved()); + } + + @Test + @DisplayName("cancel 는 수강 신청을 취소 상태로 변경한다.") + void cancel_success() { + Session savedSession = SessionFixtures.chargedSessionFullApproved(); + when(applyRepository.findApplyByNsUserIdAndSessionId(1L, 1L)) + .thenReturn(Optional.of(ApplyFixtures.apply_one_approved())); + when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + + sessionService.cancel(NsUserFixtures.TEACHER_JAVAJIGI_1L, + savedSession.getId(), + 1L, + SessionFixtures.DATETIME_2023_12_5 + ); assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionStatus()).isEqualTo(SessionStatus.END); + assertThat(savedSession.getSessionDetail().getSessionState().getApplies()) + .contains(ApplyFixtures.apply_one_canceled()); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java b/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java deleted file mode 100644 index 42396b6813..0000000000 --- a/src/test/java/nextstep/courses/domain/course/session/ApplicantsTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package nextstep.courses.domain.course.session; - -import nextstep.users.domain.NsUser; -import nextstep.users.domain.Type; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class ApplicantsTest { - private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net", Type.TEACHER); - private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net", Type.TEACHER); - private static final NsUser APPLE = new NsUser(3L, "sanjigi", "password", "name", "apple@slipp.net", Type.TEACHER); - - private Applicants applicants; - private SessionState sessionState; - - @BeforeEach - void setUp() { - applicants = new Applicants(); - sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - applicants.addApplicant(JAVAJIGI, sessionState); - } - - @Test - @DisplayName("addApplicant 는 이미 수강생이 강의를 신청 했다면 예외를 던진다.") - void addApplicant_alreadyExistedApplicant_throwsException() { - assertThatThrownBy( - () -> applicants.addApplicant(JAVAJIGI, sessionState) - ).isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("addApplicant 는 유료강의 수강 정원이 찼으면 예외를 던진다.") - void addApplicant_alreadyFull_throwsException() { - applicants = new Applicants(); - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - applicants.addApplicant(SANJIGI, sessionState); - applicants.addApplicant(APPLE, sessionState); - - assertThatThrownBy( - () -> applicants.addApplicant(SANJIGI, sessionState) - ).isInstanceOf(IllegalArgumentException.class); - } -} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionProgressStatusTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionProgressStatusTest.java new file mode 100644 index 0000000000..823233a85b --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/SessionProgressStatusTest.java @@ -0,0 +1,16 @@ +package nextstep.courses.domain.course.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class SessionProgressStatusTest { + @Test + @DisplayName("find는 존재하지 않는 값을 입력하면 찾을 수 없다는 예외를 던진다.") + void find_notExistedName_throwsException() { + assertThatThrownBy( + () -> SessionProgressStatus.find("abcd") + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionRecruitStatusTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionRecruitStatusTest.java new file mode 100644 index 0000000000..62e35073bb --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/SessionRecruitStatusTest.java @@ -0,0 +1,16 @@ +package nextstep.courses.domain.course.session; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class SessionRecruitStatusTest { + @Test + @DisplayName("find는 존재하지 않는 값을 입력하면 찾을 수 없다는 예외를 던진다.") + void find_notExistedName_throwsException() { + assertThatThrownBy( + () -> SessionRecruitStatus.find("abcd") + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java index 48aad9fdf1..51f963ce7b 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.course.session; +import nextstep.courses.domain.course.session.apply.Applies; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -10,7 +11,7 @@ public class SessionStateTest { @DisplayName("SessionState 는 무료 강의가 0원이 아니면 예외를 던진다") void newObject_freeType_overZeroAmount_throwsException() { assertThatThrownBy( - () -> new SessionState(SessionType.FREE, 1000L, Integer.MAX_VALUE) + () -> new SessionState(SessionType.FREE, 1000L, Integer.MAX_VALUE, new Applies()) ).isInstanceOf(IllegalArgumentException.class); } @@ -18,7 +19,23 @@ void newObject_freeType_overZeroAmount_throwsException() { @DisplayName("SessionState 는 무료 강의가 정원이 최대가 아니면 예외를 던진다.") void newObject_freeType_lessThanMaxQuota_throwsException() { assertThatThrownBy( - () -> new SessionState(SessionType.FREE, 0L, 100) + () -> new SessionState(SessionType.FREE, 0L, 100, new Applies()) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("SessionState 는 유료 강의가 0원이면 예외를 던진다.") + void newObject_chargedType_zeroAmount_throwsException() { + assertThatThrownBy( + () -> new SessionState(SessionType.CHARGE, 0L, 100, new Applies()) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("SessionState 는 유료 강의가 정원 수가 0명이면 예외를 던진다.") + void newObject_chargedType_zeroQuota_throwsException() { + assertThatThrownBy( + () -> new SessionState(SessionType.CHARGE, 100L, 0, new Applies()) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 5cb687368e..2a422aa542 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -1,233 +1,216 @@ package nextstep.courses.domain.course.session; -import nextstep.courses.domain.course.session.image.Image; -import nextstep.courses.domain.course.session.image.ImageType; -import nextstep.courses.domain.course.session.image.Images; -import nextstep.payments.domain.Payment; -import nextstep.users.domain.NsUser; -import nextstep.users.domain.Type; -import org.junit.jupiter.api.BeforeEach; +import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.courses.fixture.ApplyFixtures; +import nextstep.courses.fixture.ImageFixtures; +import nextstep.courses.fixture.SessionFixtures; +import nextstep.payments.fixture.PaymentFixtures; +import nextstep.users.fixtures.NsUserFixtures; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SessionTest { - private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net" ,Type.TEACHER); - private static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net",Type.TEACHER); - private static final NsUser APPLE = new NsUser(3L, "apple", "password", "name", "apple@slipp.net",Type.TEACHER); - private static final NsUser ERIC = new NsUser(4L, "apple", "password", "name", "apple@slipp.net",Type.STUDENT); - private static final LocalDate DATE_2023_12_5 = LocalDate.of(2023, 12, 5); - private static final LocalDate DATE_2023_12_6 = LocalDate.of(2023, 12, 6); - private static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); - private static final LocalDate DATE_2023_12_12 = LocalDate.of(2023, 12, 12); - - private Images images; - private Image image; - private Payment payment; - private Payment differentPayment; - private LocalDate localDate; - private LocalDateTime localDateTime; - private Applicants applicants; - private Duration duration; - private SessionState sessionState; private Session session; - @BeforeEach - void setUp() { - payment = new Payment("1", 1L, 3L, 1000L); - differentPayment = new Payment("1", 1L, 3L, 500L); - localDate = LocalDate.of(2023, 12, 5); - localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); - images = new Images(List.of(image)); - duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - applicants = new Applicants(); - this.applicants.addApplicant(JAVAJIGI, sessionState); - this.applicants.addApplicant(SANJIGI, sessionState); - this.applicants.addApplicant(ERIC, sessionState); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - } - @Test - @DisplayName("강의는 이미지가 없으면 이미지를 추가하라는 예외를 반환한다.") + @DisplayName("강의는 이미지가 없으면 이미지를 추가하라는 예외를 반환 한다.") void newObject_imageNull_throwsException() { - sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); assertThatThrownBy( - () -> new Session(null, duration, sessionState, 1L, localDateTime) + () -> new Session( + null, + SessionFixtures.duration(), + SessionFixtures.freeSessionStateZero(), + 1L, + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("강의는 기간이 없으면 기간을 추가하라는 예외를 반환한다.") + @DisplayName("강의는 기간이 없으면 기간을 추가하라는 예외를 반환 한다.") void newObject_durationNull_throwsException() { assertThatThrownBy( - () -> new Session(images, null, sessionState, 1L, localDateTime) + () -> new Session( + ImageFixtures.images(), + null, + SessionFixtures.freeSessionStateZero(), + 1L, + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("강의는 강의 상태가 없으면 상태를 추가하라는 예외를 반환한다.") + @DisplayName("강의는 강의 상태가 없으면 상태를 추가하라는 예외를 반환 한다.") void newObject_sessionStateNull_throwsException() { assertThatThrownBy( - () -> new Session(images, duration, null, 1L, localDateTime) + () -> new Session( + ImageFixtures.images(), + SessionFixtures.duration(), + null, + 1L, + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("수강 신청은 모집 상태에서 준비중이면 해당 인원이 추가된다.") + @DisplayName("수강 신청은 모집 중, 준비 중이면 해당 인원이 추가 된다.") void apply_recruit_ready_success() { - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); + session = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY); - assertThat(session.applyCount()).isEqualTo(3); + int size = session.applyCount(); - session.apply(APPLE, payment, localDateTime); + session.apply( + NsUserFixtures.TEACHER_APPLE_3L, + PaymentFixtures.payment(1L, 3L), + SessionFixtures.DATETIME_2023_12_5 + ); - assertThat(session.applyCount()).isEqualTo(4); + assertThat(session.applyCount()).isEqualTo(size + 1); } @Test - @DisplayName("수강 신청은 모집 상태에서 진행중이면 해당 인원이 추가된다.") + @DisplayName("수강 신청은 모집 중, 진행 중이면 해당 인원이 추가 된다.") void apply_recruit_ongoing_success() { - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + session = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING); - assertThat(session.applyCount()).isEqualTo(3); + int size = session.applyCount(); - session.apply(APPLE, payment, localDateTime); + session.apply( + NsUserFixtures.TEACHER_APPLE_3L, + PaymentFixtures.payment(1L, 3L), + SessionFixtures.DATETIME_2023_12_5 + ); - assertThat(session.applyCount()).isEqualTo(4); + assertThat(session.applyCount()).isEqualTo(size + 1); } @Test - @DisplayName("수강 신청은 비모집중이면 신청할 수 없다는 예외를 반환한다.") + @DisplayName("수강 신청은 비 모집 중이면 신청할 수 없다는 예외를 반환 한다.") void apply_notRecruitStatus_throwsException() { - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.NOT_RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); + session = SessionFixtures.createdChargedSession(SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.ONGOING); assertThatThrownBy( - () -> session.apply(APPLE, payment, localDateTime) + () -> session.apply( + NsUserFixtures.TEACHER_APPLE_3L, + PaymentFixtures.payment(), + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("수강 신청은 모집 중이어도 종료되었다면 신청할 수 없다는 예외를 반환한다.") + @DisplayName("수강 신청은 모집 중, 종료 라면 신청할 수 없다는 예외를 반환 한다.") void apply_recruitStatus_endStatus_throwsException() { - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.END, 1L, localDateTime, localDateTime); + session = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.END); assertThatThrownBy( - () -> session.apply(APPLE, payment, localDateTime) + () -> session.apply( + NsUserFixtures.TEACHER_APPLE_3L, + PaymentFixtures.payment(), + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("수강 신청은 유료 강의 수강 인원 정원을 초과하면 신청할 수 없다는 예외를 반환한다.") + @DisplayName("수강 신청은 유료 강의의 경우, 수강 인원 정원을 초과 하면 신청할 수 없다는 예외를 반환 한다.") void apply_chargeSession_overQuota_throwsException() { - applicants = new Applicants(); - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - applicants.addApplicant(JAVAJIGI, session.getSessionState()); - applicants.addApplicant(SANJIGI, session.getSessionState()); + session = SessionFixtures.chargedSessionFullCanceled(); assertThatThrownBy( - () -> session.apply(APPLE, payment, localDateTime) + () -> session.apply( + NsUserFixtures.TEACHER_APPLE_3L, + PaymentFixtures.payment(), + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("수강 신청은 유료 강의 결제가 안되었다면 신청할 수 없다는 예외를 반환한다.") + @DisplayName("수강 신청은 유료 강의 결제가 안 되었다면 신청할 수 없다는 예외를 반환 한다.") void apply_chargeSession_notPaid_throwsException() { - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + session = SessionFixtures.createdChargedSession(); assertThatThrownBy( - () -> session.apply(APPLE, null, localDateTime) + () -> session.apply( + NsUserFixtures.TEACHER_APPLE_3L, + null, + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("수강 신청은 수강 금액과 지불 금액이 다르면 신청할 수 없다는 예외를 던진다.") void apply_chargeSession_differentAmount_throwsException() { - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + session = SessionFixtures.createdChargedSession(); assertThatThrownBy( - () -> session.apply(APPLE, differentPayment, localDateTime) + () -> session.apply( + NsUserFixtures.TEACHER_APPLE_3L, + PaymentFixtures.differentPayment(), + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("changeOnReady는 강의 시작날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") - void changeOnReady_startDateIsBeforeOrSame_throwsException() { - duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); - sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + @DisplayName("changeOnReady 는 변경할 날짜가 강의 종료일과 같거나 늦으면 예외를 던진다.") + void changeOnReady_changeDateIsSameOrAfterWithEndDate_throwsException() { + session = SessionFixtures.createdFreeSession(); assertThatThrownBy( - () -> session.changeOnReady(DATE_2023_12_5) + () -> session.changeOnReady(SessionFixtures.DATE_2023_12_10) ).isInstanceOf(IllegalArgumentException.class); assertThatThrownBy( - () -> session.changeOnReady(DATE_2023_12_6) + () -> session.changeOnReady(SessionFixtures.DATE_2023_12_12) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("changeOnRecruit는 강의 시작날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") - void changeOnRecruit_startDateIsBeforeOrSame_throwsException() { - duration = new Duration(DATE_2023_12_5, DATE_2023_12_10); - sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.READY, 1L, localDateTime, localDateTime); + @DisplayName("changeOnGoing 는 변경할 날짜가 강의 종료일과 같거나 늦으면 예외를 던진다.") + void changeOnGoing_changeDateIsSameOrAfterWithEndDate_throwsException() { + session = SessionFixtures.createdFreeSession(); assertThatThrownBy( - () -> session.changeOnRecruit(DATE_2023_12_5) + () -> session.changeOnGoing(SessionFixtures.DATE_2023_12_10) ).isInstanceOf(IllegalArgumentException.class); assertThatThrownBy( - () -> session.changeOnRecruit(DATE_2023_12_6) + () -> session.changeOnGoing(SessionFixtures.DATE_2023_12_12) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("changeOnRecruit는 강의 종료 날짜가 변경하려는 날짜와 같거나 늦으면 변경 할 수 없다는 예외를 던진다.") - void changeOnEnd_EndDateIsSameOrAfter_throwsException() { - duration = new Duration(DATE_2023_12_5, DATE_2023_12_12); - sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.NOT_RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); + @DisplayName("changeOnEnd 는 변경할 날짜가 강의 종료일보다 빠르거나 같다면 예외를 던진다.") + void changeOnEnd_changeDateIsBeforeOrSameWithEndDate_throwsException() { + session = SessionFixtures.createdFreeSession(); assertThatThrownBy( - () -> session.changeOnEnd(DATE_2023_12_6) + () -> session.changeOnEnd(SessionFixtures.DATE_2023_12_6) ).isInstanceOf(IllegalArgumentException.class); assertThatThrownBy( - () -> session.changeOnEnd(DATE_2023_12_12) + () -> session.changeOnEnd(SessionFixtures.DATE_2023_12_10) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("approve 는 선생님인 경우 수강생의 강의 신청을 승인한다.") void approve_teacher_changeApproveTrue() { - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - Apply apply = new Apply(1L, 4L, false, 1L, localDateTime, localDateTime); + session = SessionFixtures.chargedSessionFullCanceled(); - Apply changedApply = session.approve(JAVAJIGI, ERIC, apply, localDateTime); + Apply changedApply = session.approve + (NsUserFixtures.TEACHER_JAVAJIGI_1L, + ApplyFixtures.apply_one_canceled(), + SessionFixtures.DATETIME_2023_12_5 + ); assertThat(changedApply.isApproved()).isTrue(); } @@ -235,38 +218,41 @@ void approve_teacher_changeApproveTrue() { @Test @DisplayName("approve 는 학생인 경우 권한이 없다는 예외를 던진다.") void approve_student_throwsException() { - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - Apply apply = new Apply(1L, 4L, false, 1L, localDateTime, localDateTime); + session = SessionFixtures.chargedSessionFullCanceled(); assertThatThrownBy( - () -> session.approve(ERIC, JAVAJIGI, apply, localDateTime) + () -> session.approve( + NsUserFixtures.STUDENT_ERIC_4L, + ApplyFixtures.apply_one_canceled(), + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("approve 는 이미 수강 승인이 되었으면 예외를 던진다.") void approve_alreadyApproved_throwsException() { - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - Apply apply = new Apply(1L, 4L, true, 1L, localDateTime, localDateTime); + session = SessionFixtures.chargedSessionFullApproved(); assertThatThrownBy( - () -> session.approve(JAVAJIGI, ERIC, apply, localDateTime) + () -> session.approve( + NsUserFixtures.TEACHER_JAVAJIGI_1L, + ApplyFixtures.apply_one_approved(), + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("cancel 는 선생님인 경우 수강생의 강의 신청을 취소한다.") void cancel_teacher_changeApproveTrue() { - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(4L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - Apply apply = new Apply(1L, 4L, true, 1L, localDateTime, localDateTime); + session = SessionFixtures.chargedSessionFullApproved(); - Apply changedApply = session.cancel(JAVAJIGI, ERIC, apply, localDateTime); + Apply changedApply = session.cancel( + NsUserFixtures.TEACHER_JAVAJIGI_1L, + ApplyFixtures.apply_one_canceled(), + SessionFixtures.DATETIME_2023_12_5 + ); assertThat(changedApply.isApproved()).isFalse(); } @@ -274,26 +260,28 @@ void cancel_teacher_changeApproveTrue() { @Test @DisplayName("cancel 는 학생인 경우 권한이 없다는 예외를 던진다.") void cancel_student_throwsException() { - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - Apply apply = new Apply(1L, 4L, false, 1L, localDateTime, localDateTime); + session = SessionFixtures.chargedSessionFullApproved(); assertThatThrownBy( - () -> session.cancel(ERIC, JAVAJIGI, apply, localDateTime) + () -> session.cancel( + NsUserFixtures.STUDENT_ERIC_4L, + ApplyFixtures.apply_one_approved(), + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("cancel 는 이미 수강 취소가 되었으면 예외를 던진다.") void cancel_alreadyCanceled_throwsException() { - sessionState = new SessionState(SessionType.CHARGE, 1000L, 2); - session = new Session(1L, images, duration, sessionState, applicants, - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - Apply apply = new Apply(1L, 4L, false, 1L, localDateTime, localDateTime); + session = SessionFixtures.chargedSessionFullCanceled(); assertThatThrownBy( - () -> session.cancel(JAVAJIGI, ERIC, apply, localDateTime) + () -> session.cancel( + NsUserFixtures.TEACHER_JAVAJIGI_1L, + ApplyFixtures.apply_one_canceled(), + SessionFixtures.DATETIME_2023_12_5 + ) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionStatusTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTypeTest.java similarity index 84% rename from src/test/java/nextstep/courses/domain/course/session/SessionStatusTest.java rename to src/test/java/nextstep/courses/domain/course/session/SessionTypeTest.java index 8ceb8003f0..5a452587f4 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionStatusTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTypeTest.java @@ -5,12 +5,12 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -public class SessionStatusTest { +public class SessionTypeTest { @Test @DisplayName("find는 존재하지 않는 값을 입력하면 찾을 수 없다는 예외를 던진다.") void find_notExistedName_throwsException() { assertThatThrownBy( - () -> SessionStatus.find("abcd") + () -> SessionType.find("abcd") ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java index de4d39e9e0..b2cd9a5249 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionsTest.java @@ -1,47 +1,17 @@ package nextstep.courses.domain.course.session; -import nextstep.courses.domain.course.session.image.Image; -import nextstep.courses.domain.course.session.image.ImageType; -import nextstep.courses.domain.course.session.image.Images; -import nextstep.payments.domain.Payment; -import org.junit.jupiter.api.BeforeEach; +import nextstep.courses.fixture.SessionFixtures; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; -import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SessionsTest { - private Sessions sessions; - private Images images; - private Image image; - private Payment payment; - private LocalDate localDate; - private LocalDateTime localDateTime; - private Duration duration; - private SessionState sessionState; - private Session session; - - @BeforeEach - void setUp() { - sessions = new Sessions(); - localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - image = new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, localDateTime); - images = new Images(List.of(image)); - payment = new Payment("1", 1L, 3L, 1000L); - localDate = LocalDate.of(2023, 12, 5); - duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.FREE, 0L, Integer.MAX_VALUE); - session = new Session(1L, images, duration, sessionState, new Applicants(), - RecruitStatus.NOT_RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - sessions.add(session); - } - @ParameterizedTest @NullSource @DisplayName("Sessions 은 빈 값이 주어지면 예외를 던진다.") @@ -52,10 +22,14 @@ void newObject_null_throwsException(List sessions) { } @Test - @DisplayName("add 는 이미 강의가 추가되었으면 예외를 던진다.") + @DisplayName("add 는 이미 강의가 추가 되었으면 예외를 던진다.") void add_alreadyExistedSession_throwsException() { + List sessionList = new ArrayList<>(); + sessionList.add(SessionFixtures.createdChargedSession()); + Sessions sessions = new Sessions(sessionList); + assertThatThrownBy( - () -> sessions.add(session) + () -> sessions.add(SessionFixtures.createdChargedSession()) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/fixture/ApplyFixtures.java b/src/test/java/nextstep/courses/fixture/ApplyFixtures.java new file mode 100644 index 0000000000..706e8e136e --- /dev/null +++ b/src/test/java/nextstep/courses/fixture/ApplyFixtures.java @@ -0,0 +1,32 @@ +package nextstep.courses.fixture; + +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.Apply; + +import java.util.List; + +public class ApplyFixtures { + public static Applies applies_two_canceled() { + return new Applies(List.of(apply_one_canceled(), apply_two_canceled())); + } + + public static Apply apply_one_canceled() { + return new Apply(1L, 1L, false, SessionFixtures.DATETIME_2023_12_5); + } + + public static Apply apply_two_canceled() { + return new Apply(1L, 2L, false, SessionFixtures.DATETIME_2023_12_5); + } + + public static Applies applies_two_approved() { + return new Applies(List.of(apply_one_approved(), apply_two_approved())); + } + + public static Apply apply_one_approved() { + return new Apply(1L, 1L, true, SessionFixtures.DATETIME_2023_12_5); + } + + public static Apply apply_two_approved() { + return new Apply(1L, 2L, true, SessionFixtures.DATETIME_2023_12_5); + } +} diff --git a/src/test/java/nextstep/courses/fixture/CourseFixtures.java b/src/test/java/nextstep/courses/fixture/CourseFixtures.java new file mode 100644 index 0000000000..645b8cfe88 --- /dev/null +++ b/src/test/java/nextstep/courses/fixture/CourseFixtures.java @@ -0,0 +1,12 @@ +package nextstep.courses.fixture; + +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.session.Sessions; + +import java.time.LocalDateTime; + +public class CourseFixtures { + public static Course course() { + return new Course(1L, "math", 1, new Sessions(), 1L, LocalDateTime.now(), null); + } +} diff --git a/src/test/java/nextstep/courses/fixture/ImageFixtures.java b/src/test/java/nextstep/courses/fixture/ImageFixtures.java new file mode 100644 index 0000000000..cc57cf72f1 --- /dev/null +++ b/src/test/java/nextstep/courses/fixture/ImageFixtures.java @@ -0,0 +1,18 @@ +package nextstep.courses.fixture; + +import nextstep.courses.domain.course.session.image.Image; +import nextstep.courses.domain.course.session.image.ImageType; +import nextstep.courses.domain.course.session.image.Images; + +import java.time.LocalDateTime; +import java.util.List; + +public class ImageFixtures { + public static Image image() { + return new Image(1000, ImageType.GIF, Image.WIDTH_MIN, Image.HEIGHT_MIN, 1L, LocalDateTime.now()); + } + + public static Images images() { + return new Images(List.of(image())); + } +} diff --git a/src/test/java/nextstep/courses/fixture/SessionFixtures.java b/src/test/java/nextstep/courses/fixture/SessionFixtures.java new file mode 100644 index 0000000000..8c019a755d --- /dev/null +++ b/src/test/java/nextstep/courses/fixture/SessionFixtures.java @@ -0,0 +1,92 @@ +package nextstep.courses.fixture; + +import nextstep.courses.domain.course.session.*; +import nextstep.courses.domain.course.session.apply.Applies; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class SessionFixtures { + public static final LocalDate DATE_2023_12_5 = LocalDate.of(2023, 12, 5); + public static final LocalDate DATE_2023_12_6 = LocalDate.of(2023, 12, 6); + public static final LocalDate DATE_2023_12_10 = LocalDate.of(2023, 12, 10); + public static final LocalDate DATE_2023_12_12 = LocalDate.of(2023, 12, 12); + public static final LocalDateTime DATETIME_2023_12_5 = LocalDateTime.of(2023, 12, 5, 0, 0); + + public static Duration duration() { + return new Duration(DATE_2023_12_5, DATE_2023_12_10); + } + + public static Session createdFreeSession() { + return createdFreeSession(SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.READY); + } + + public static Session createdFreeSession(SessionRecruitStatus sessionRecruitStatus, SessionProgressStatus sessionProgressStatus) { + return new Session( + 1L, + ImageFixtures.images(), + new Duration(DATE_2023_12_5, DATE_2023_12_10), + freeSessionStateZero(), + sessionRecruitStatus, + sessionProgressStatus, + 1L, + DATETIME_2023_12_5, + null); + } + + public static Session createdChargedSession() { + return createdChargedSession(SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.READY); + } + + public static Session createdChargedSession( + SessionRecruitStatus sessionRecruitStatus, SessionProgressStatus sessionProgressStatus + ) { + return new Session( + 1L, + ImageFixtures.images(), + new Duration(DATE_2023_12_5, DATE_2023_12_10), + chargedSessionStateZero(1000L, 2, new Applies()), + sessionRecruitStatus, + sessionProgressStatus, + 1L, + DATETIME_2023_12_5, + null + ); + } + + public static Session chargedSessionFullCanceled() { + return new Session( + 1L, + ImageFixtures.images(), + new Duration(DATE_2023_12_5, DATE_2023_12_10), + chargedSessionStateZero(1000L, 2, ApplyFixtures.applies_two_canceled()), + SessionRecruitStatus.RECRUIT, + SessionProgressStatus.READY, + 1L, + DATETIME_2023_12_5, + null + ); + } + + public static Session chargedSessionFullApproved() { + return new Session( + 1L, + ImageFixtures.images(), + new Duration(DATE_2023_12_5, DATE_2023_12_10), + chargedSessionStateZero(1000L, 2, ApplyFixtures.applies_two_approved()), + SessionRecruitStatus.RECRUIT, + SessionProgressStatus.READY, + 1L, + DATETIME_2023_12_5, + null + ); + } + + public static SessionState freeSessionStateZero() { + return new SessionState(); + } + + public static SessionState chargedSessionStateZero(Long amount, int quota, Applies applies) { + return new SessionState(SessionType.CHARGE, amount, quota, applies); + } +} diff --git a/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java deleted file mode 100644 index ea19c10adc..0000000000 --- a/src/test/java/nextstep/courses/infrastructure/ApplicantsRepositoryTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package nextstep.courses.infrastructure; - -import nextstep.courses.domain.course.session.Applicants; -import nextstep.courses.domain.course.session.ApplicantsRepository; -import nextstep.users.domain.NsUser; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; -import org.springframework.jdbc.core.JdbcTemplate; - -import static org.assertj.core.api.Assertions.assertThat; - -@JdbcTest -public class ApplicantsRepositoryTest { - private static final Logger LOGGER = LoggerFactory.getLogger(ApplicantsRepositoryTest.class); - - @Autowired - private JdbcTemplate jdbcTemplate; - - private ApplicantsRepository applicantsRepository; - - @BeforeEach - void setUp() { - applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); - } - - /* data.sql 파일에 이미 저장한 데이터로 테스트 합니다. */ - @Test - void find_success() { - Applicants applicants = applicantsRepository.findAllBySessionId(10L); - NsUser nsUser_1 = applicants.find(0); - NsUser nsUser_2 = applicants.find(1); - - assertThat(applicants.size()).isEqualTo(2); - assertThat(nsUser_1.getId()).isEqualTo(1L); - assertThat(nsUser_2.getId()).isEqualTo(2L); - LOGGER.debug("Applicants: {}", applicants); - } -} diff --git a/src/test/java/nextstep/courses/infrastructure/ApplyRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ApplyRepositoryTest.java new file mode 100644 index 0000000000..56420ee55d --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/ApplyRepositoryTest.java @@ -0,0 +1,72 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.courses.domain.course.session.apply.ApplyRepository; +import nextstep.courses.fixture.ApplyFixtures; +import nextstep.courses.fixture.SessionFixtures; +import nextstep.qna.NotFoundException; +import nextstep.users.fixtures.NsUserFixtures; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.jdbc.core.JdbcTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +public class ApplyRepositoryTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ApplyRepositoryTest.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + private ApplyRepository applyRepository; + + @BeforeEach + void setUp() { + applyRepository = new JdbcApplyRepository(jdbcTemplate); + } + + /* data.sql 파일에 이미 저장한 데이터로 테스트 합니다. */ + @Test + void find_success() { + Applies applies = applyRepository.findAllBySessionId(10L); + + assertThat(applies.size()).isEqualTo(2); + LOGGER.debug("Applies: {}", applies); + } + + @Test + void saveApply_success() { + Apply savedApply = applyRepository.save(ApplyFixtures.apply_one_canceled()); + + Apply findApply = applyRepository + .findApplyByNsUserIdAndSessionId( + NsUserFixtures.TEACHER_JAVAJIGI_1L.getId(), + SessionFixtures.createdFreeSession().getId() + ) + .orElseThrow(NotFoundException::new); + + assertThat(findApply.getNsUserId()).isEqualTo(savedApply.getNsUserId()); + assertThat(findApply.getSessionId()).isEqualTo(savedApply.getSessionId()); + } + + @Test + void updateApply_success() { + Apply savedApply = applyRepository.save(ApplyFixtures.apply_one_canceled()); + Apply updatedApply = savedApply.setApproved(true); + applyRepository.update(updatedApply); + + Apply findApply = applyRepository + .findApplyByNsUserIdAndSessionId( + NsUserFixtures.TEACHER_JAVAJIGI_1L.getId(), + SessionFixtures.createdFreeSession().getId() + ) + .orElseThrow(NotFoundException::new); + assertThat(findApply.isApproved()).isTrue(); + } +} diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 97e3fee66e..e3464d9ac3 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -1,14 +1,12 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.course.session.image.Image; -import nextstep.courses.domain.course.session.image.ImageRepository; -import nextstep.courses.domain.course.session.image.ImageType; -import nextstep.courses.domain.course.session.*; -import nextstep.courses.domain.course.session.image.Images; -import nextstep.payments.domain.Payment; +import nextstep.courses.domain.course.session.Session; +import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.domain.course.session.SessionState; +import nextstep.courses.domain.course.session.SessionType; +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.fixture.SessionFixtures; import nextstep.qna.NotFoundException; -import nextstep.users.domain.NsUser; -import nextstep.users.domain.Type; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -17,58 +15,25 @@ import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.jdbc.core.JdbcTemplate; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; @JdbcTest public class SessionRepositoryTest { private static final Logger LOGGER = LoggerFactory.getLogger(SessionRepositoryTest.class); - private static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net", Type.TEACHER); @Autowired private JdbcTemplate jdbcTemplate; private SessionRepository sessionRepository; - private ImageRepository imageRepository; - private ApplicantsRepository applicantsRepository; - - private Images images; - private Image image; - private Payment payment; - private LocalDate localDate; - private LocalDateTime localDateTime; - private Applicants applicants; - private Duration duration; - private SessionState sessionState; - private Session session; - private Apply apply; @BeforeEach void setUp() { sessionRepository = new JdbcSessionRepository(jdbcTemplate); - imageRepository = new JdbcImageRepository(jdbcTemplate); - applicantsRepository = new JdbcApplicantsRepository(jdbcTemplate); - - image = new Image(1024, ImageType.JPG, 300, 200, 1L, LocalDateTime.now()); - List imageList = new ArrayList<>(); - imageList.add(image); - images = new Images(imageList); - payment = new Payment("1", 1L, 1L, 1000L); - localDate = LocalDate.of(2023, 12, 5); - localDateTime = LocalDateTime.of(2023, 12, 5, 12, 0); - applicants = new Applicants(); - duration = new Duration(localDate, localDate); - sessionState = new SessionState(SessionType.CHARGE, 1000L, 10); - apply = new Apply(1L, JAVAJIGI.getId(), false, JAVAJIGI.getId(), localDateTime, localDateTime); } @Test void save_success() { - session = new Session(images, duration, sessionState, 1L, localDateTime); + Session session = SessionFixtures.createdFreeSession(); Session savedSession = sessionRepository.save(1L, session); Session findSession = sessionRepository.findById(savedSession.getId()).orElseThrow(NotFoundException::new); @@ -79,25 +44,13 @@ void save_success() { LOGGER.debug("Session: {}", savedSession); } - @Test - void applySave_success() { - session = new Session(1L, images, duration, sessionState, new Applicants(), - RecruitStatus.RECRUIT, SessionStatus.ONGOING, 1L, localDateTime, localDateTime); - int count = sessionRepository.saveApply(apply); - Apply savedApply = sessionRepository.findApplyByIds(JAVAJIGI.getId(), session.getId()) - .orElseThrow(NotFoundException::new); - - assertThat(savedApply.getNsUserId()).isEqualTo(JAVAJIGI.getId()); - assertThat(savedApply.getSessionId()).isEqualTo(session.getId()); - } - @Test void update_success() { - session = new Session(images, duration, sessionState, 1L, localDateTime); + Session session = SessionFixtures.createdChargedSession(); Session savedSession = sessionRepository.save(1L, session); - SessionState updateSessionState = new SessionState(SessionType.CHARGE, 2000L, 30); - savedSession.setSessionState(updateSessionState); + SessionState updateSessionState = new SessionState(SessionType.CHARGE, 2000L, 30, new Applies()); + savedSession.changeSessionState(updateSessionState); sessionRepository.update(savedSession.getId(), savedSession); Session updatedSession = sessionRepository.findById(savedSession.getId()).orElseThrow(NotFoundException::new); @@ -105,17 +58,4 @@ void update_success() { assertThat(updatedSession.getSessionState()).isEqualTo(updateSessionState); LOGGER.debug("Session: {}", updatedSession); } - - @Test - void updateApply_success() { - Apply apply = new Apply(10L, JAVAJIGI.getId(), false, JAVAJIGI.getId(), localDateTime, localDateTime); - - Apply updatedApply = apply.setApproved(true); - sessionRepository.updateApply(updatedApply); - - Apply savedApply = sessionRepository.findApplyByIds(JAVAJIGI.getId(), 10L).orElseThrow(NotFoundException::new); - assertThat(savedApply.isApproved()).isTrue(); - - LOGGER.debug("Apply: {}", savedApply); - } } diff --git a/src/test/java/nextstep/payments/fixture/PaymentFixtures.java b/src/test/java/nextstep/payments/fixture/PaymentFixtures.java new file mode 100644 index 0000000000..7699ed54fa --- /dev/null +++ b/src/test/java/nextstep/payments/fixture/PaymentFixtures.java @@ -0,0 +1,17 @@ +package nextstep.payments.fixture; + +import nextstep.payments.domain.Payment; + +public class PaymentFixtures { + public static Payment payment() { + return payment(1L, 1L); + } + + public static Payment differentPayment() { + return payment(999L, 999L); + } + + public static Payment payment(Long sessionId, Long nsUserId) { + return new Payment("1", sessionId, nsUserId, 1000L); + } +} diff --git a/src/test/java/nextstep/qna/domain/AnswerTest.java b/src/test/java/nextstep/qna/domain/AnswerTest.java index 37869d647f..bebdc5c86f 100644 --- a/src/test/java/nextstep/qna/domain/AnswerTest.java +++ b/src/test/java/nextstep/qna/domain/AnswerTest.java @@ -1,7 +1,7 @@ package nextstep.qna.domain; import nextstep.qna.CannotDeleteException; -import nextstep.users.domain.NsUserTest; +import nextstep.users.fixtures.NsUserFixtures; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -9,14 +9,14 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AnswerTest { - private static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); - private static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, Q1, "Answers Contents1"); - private static final Answer A2 = new Answer(NsUserTest.SANJIGI, Q1, "Answers Contents2"); + private static final Question Q1 = new Question(NsUserFixtures.TEACHER_JAVAJIGI_1L, "title1", "contents1"); + private static final Answer A1 = new Answer(NsUserFixtures.TEACHER_JAVAJIGI_1L, Q1, "Answers Contents1"); + private static final Answer A2 = new Answer(NsUserFixtures.TEACHER_SANJIGI_2L, Q1, "Answers Contents2"); @Test @DisplayName("답변 작성자가 로그인한 사용자면 답변을 삭제 상태로 변경하고 해당 답변을 반환한다") void setDeleted_success() throws CannotDeleteException { - Answer deletedAnswer = A1.delete(NsUserTest.JAVAJIGI); + Answer deletedAnswer = A1.delete(NsUserFixtures.TEACHER_JAVAJIGI_1L); assertThat(deletedAnswer.isDeleted()).isTrue(); } @@ -25,7 +25,7 @@ void setDeleted_success() throws CannotDeleteException { @DisplayName("질문 작성자가 로그인한 사용자가 아니면 삭제할 수 없다는 예외를 던진다") void setDeleted_different_answerWriter_loginUser_throwsException() { assertThatThrownBy( - () -> A2.delete(NsUserTest.JAVAJIGI) + () -> A2.delete(NsUserFixtures.TEACHER_JAVAJIGI_1L) ).isInstanceOf(CannotDeleteException.class); } } diff --git a/src/test/java/nextstep/qna/domain/QuestionTest.java b/src/test/java/nextstep/qna/domain/QuestionTest.java index 2481e62cb3..009080bd95 100644 --- a/src/test/java/nextstep/qna/domain/QuestionTest.java +++ b/src/test/java/nextstep/qna/domain/QuestionTest.java @@ -1,28 +1,24 @@ package nextstep.qna.domain; import nextstep.qna.CannotDeleteException; -import nextstep.users.domain.NsUserTest; -import org.junit.jupiter.api.BeforeEach; +import nextstep.users.fixtures.NsUserFixtures; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.List; - -import static nextstep.qna.domain.AnswerTest.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class QuestionTest { - private static final Question Q1 = new Question(NsUserTest.JAVAJIGI, "title1", "contents1"); - private static final Answer A1 = new Answer(NsUserTest.JAVAJIGI, Q1, "Answers Contents1"); - private static final Answer A2 = new Answer(NsUserTest.SANJIGI, Q1, "Answers Contents2"); + private static final Question Q1 = new Question(NsUserFixtures.TEACHER_JAVAJIGI_1L, "title1", "contents1"); + private static final Answer A1 = new Answer(NsUserFixtures.TEACHER_JAVAJIGI_1L, Q1, "Answers Contents1"); + private static final Answer A2 = new Answer(NsUserFixtures.TEACHER_SANJIGI_2L, Q1, "Answers Contents2"); - private static final Question Q2 = new Question(NsUserTest.SANJIGI, "title2", "contents2"); + private static final Question Q2 = new Question(NsUserFixtures.TEACHER_SANJIGI_2L, "title2", "contents2"); @Test @DisplayName("질문 작성자가 로그인한 사용자면 질문을 삭제 상태로 변경하고 해당 질문을 반환한다") void delete_success() throws CannotDeleteException { - Question deletedQuestion = Q1.delete(NsUserTest.JAVAJIGI); + Question deletedQuestion = Q1.delete(NsUserFixtures.TEACHER_JAVAJIGI_1L); assertThat(deletedQuestion.isDeleted()).isTrue(); Answers answers = deletedQuestion.getAnswers(); @@ -35,7 +31,7 @@ void delete_success() throws CannotDeleteException { @DisplayName("질문 작성자가 로그인한 사용자가 아니면 삭제할 수 없다는 예외를 던진다") void delete_different_questionWriter_loginUser_throwsException() { assertThatThrownBy( - () -> Q2.delete(NsUserTest.JAVAJIGI) + () -> Q2.delete(NsUserFixtures.TEACHER_JAVAJIGI_1L) ).isInstanceOf(CannotDeleteException.class); } } diff --git a/src/test/java/nextstep/qna/service/QnaServiceTest.java b/src/test/java/nextstep/qna/service/QnaServiceTest.java index 07f4e075d0..761654f357 100644 --- a/src/test/java/nextstep/qna/service/QnaServiceTest.java +++ b/src/test/java/nextstep/qna/service/QnaServiceTest.java @@ -2,7 +2,7 @@ import nextstep.qna.CannotDeleteException; import nextstep.qna.domain.*; -import nextstep.users.domain.NsUserTest; +import nextstep.users.fixtures.NsUserFixtures; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -42,8 +42,8 @@ public class QnaServiceTest { @BeforeEach public void setUp() throws Exception { - question = new Question(1L, NsUserTest.JAVAJIGI, "title1", "contents1"); - answer = new Answer(11L, NsUserTest.JAVAJIGI, question, "Answers Contents1"); + question = new Question(1L, NsUserFixtures.TEACHER_JAVAJIGI_1L, "title1", "contents1"); + answer = new Answer(11L, NsUserFixtures.TEACHER_JAVAJIGI_1L, question, "Answers Contents1"); answers.add(answer); question.addAnswer(answer); } @@ -53,7 +53,7 @@ public void setUp() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); assertThat(question.isDeleted()).isFalse(); - qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId(), date); + qnAService.deleteQuestion(NsUserFixtures.TEACHER_JAVAJIGI_1L, question.getId(), date); assertThat(question.isDeleted()).isTrue(); verifyDeleteHistories(); @@ -64,7 +64,7 @@ public void setUp() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); assertThatThrownBy(() -> { - qnAService.deleteQuestion(NsUserTest.SANJIGI, question.getId(), date); + qnAService.deleteQuestion(NsUserFixtures.TEACHER_SANJIGI_2L, question.getId(), date); }).isInstanceOf(CannotDeleteException.class); } @@ -72,7 +72,7 @@ public void setUp() throws Exception { public void delete_성공_질문자_답변자_같음() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); - qnAService.deleteQuestion(NsUserTest.JAVAJIGI, question.getId(), date); + qnAService.deleteQuestion(NsUserFixtures.TEACHER_SANJIGI_2L, question.getId(), date); assertThat(question.isDeleted()).isTrue(); assertThat(answer.isDeleted()).isTrue(); @@ -84,7 +84,7 @@ public void setUp() throws Exception { when(questionRepository.findById(question.getId())).thenReturn(Optional.of(question)); assertThatThrownBy(() -> { - qnAService.deleteQuestion(NsUserTest.SANJIGI, question.getId(), date); + qnAService.deleteQuestion(NsUserFixtures.TEACHER_SANJIGI_2L, question.getId(), date); }).isInstanceOf(CannotDeleteException.class); } diff --git a/src/test/java/nextstep/users/domain/NsUserTest.java b/src/test/java/nextstep/users/domain/NsUserTest.java deleted file mode 100644 index 6973b784eb..0000000000 --- a/src/test/java/nextstep/users/domain/NsUserTest.java +++ /dev/null @@ -1,6 +0,0 @@ -package nextstep.users.domain; - -public class NsUserTest { - public static final NsUser JAVAJIGI = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net", Type.TEACHER); - public static final NsUser SANJIGI = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net", Type.TEACHER); -} diff --git a/src/test/java/nextstep/users/fixtures/NsUserFixtures.java b/src/test/java/nextstep/users/fixtures/NsUserFixtures.java new file mode 100644 index 0000000000..358e389c60 --- /dev/null +++ b/src/test/java/nextstep/users/fixtures/NsUserFixtures.java @@ -0,0 +1,11 @@ +package nextstep.users.fixtures; + +import nextstep.users.domain.NsUser; +import nextstep.users.domain.Type; + +public class NsUserFixtures { + public static final NsUser TEACHER_JAVAJIGI_1L = new NsUser(1L, "javajigi", "password", "name", "javajigi@slipp.net" , Type.TEACHER); + public static final NsUser TEACHER_SANJIGI_2L = new NsUser(2L, "sanjigi", "password", "name", "sanjigi@slipp.net",Type.TEACHER); + public static final NsUser TEACHER_APPLE_3L = new NsUser(3L, "apple", "password", "name", "apple@slipp.net",Type.TEACHER); + public static final NsUser STUDENT_ERIC_4L = new NsUser(4L, "apple", "password", "name", "apple@slipp.net",Type.STUDENT); +} From f8386a4d02e515909f1ee96016dccc36c590ae00 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Tue, 26 Dec 2023 22:26:44 +0900 Subject: [PATCH 61/62] =?UTF-8?q?[feat]=20=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=EB=A5=BC=20=EC=B5=9C=EB=8C=80=ED=95=9C=20=EC=82=B4=EB=A6=AC?= =?UTF-8?q?=EA=B3=A0=20=EB=B9=84=EC=A7=80=EB=8B=88=EC=8A=A4=EB=8A=94=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 엔티티에 연관관게 리스트를 변수로 가집니다 강의 신청, 취소, 등록 비지니스를 별도의 클래스에서 검증합니다. 강의 서비스 레이어에서 강의 엔티티를 이용해 강의, 신청, 취소의 3가지 비지니스 클래스를 반환합니다. 이는 단순히 필요한 변수를 반환하기 위함입니다. 주요 비지니스 로직은 강의, 신청, 취소 클래스에 있습니다 --- .../domain/course/session/Approve.java | 38 ++++++ .../courses/domain/course/session/Cancel.java | 38 ++++++ .../domain/course/session/Enrollment.java | 109 ++++++++++++++++++ .../domain/course/session/Session.java | 70 ++++------- .../domain/course/session/SessionDetail.java | 72 ++---------- .../{Duration.java => SessionDuration.java} | 8 +- .../domain/course/session/SessionState.java | 60 +--------- .../domain/course/session/apply/Applies.java | 45 +------- .../domain/course/session/apply/Apply.java | 14 +-- .../infrastructure/JdbcSessionRepository.java | 30 ++--- .../courses/service/SessionService.java | 14 ++- .../nextstep/payments/domain/Payment.java | 12 +- .../course/service/SessionServiceTest.java | 8 +- ...tionTest.java => SessionDurationTest.java} | 10 +- .../courses/fixture/SessionFixtures.java | 12 +- 15 files changed, 271 insertions(+), 269 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/course/session/Approve.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/Cancel.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/Enrollment.java rename src/main/java/nextstep/courses/domain/course/session/{Duration.java => SessionDuration.java} (86%) rename src/test/java/nextstep/courses/domain/course/session/{DurationTest.java => SessionDurationTest.java} (79%) diff --git a/src/main/java/nextstep/courses/domain/course/session/Approve.java b/src/main/java/nextstep/courses/domain/course/session/Approve.java new file mode 100644 index 0000000000..c3831ec427 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/Approve.java @@ -0,0 +1,38 @@ +package nextstep.courses.domain.course.session; + +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.users.domain.NsUser; + +import java.time.LocalDateTime; + +public class Approve { + private Applies applies; + + private SessionState sessionState; + + public Approve(Applies applies, SessionState sessionState) { + this.applies = applies; + this.sessionState = sessionState; + } + + public Apply approve(NsUser loginUser, Apply apply, LocalDateTime date) { + checkUserHasAuthor(loginUser); + + return this.applies.getApplies().stream() + .filter(savedApply -> approved(apply, savedApply)) + .findAny() + .map(savedApply -> savedApply.approve(date)) + .orElseThrow(() -> new IllegalArgumentException("지원자가 미승인 상태인지 확인하세요.")); + } + + private void checkUserHasAuthor(NsUser loginUser) { + if(!loginUser.hasAuthor()) { + throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); + } + } + + private static boolean approved(Apply apply, Apply savedApply) { + return savedApply.isSame(apply) && savedApply.isCanceled(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/Cancel.java b/src/main/java/nextstep/courses/domain/course/session/Cancel.java new file mode 100644 index 0000000000..1a4b45cd19 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/Cancel.java @@ -0,0 +1,38 @@ +package nextstep.courses.domain.course.session; + +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.users.domain.NsUser; + +import java.time.LocalDateTime; + +public class Cancel { + private Applies applies; + + private SessionState sessionState; + + public Cancel(Applies applies, SessionState sessionState) { + this.applies = applies; + this.sessionState = sessionState; + } + + public Apply cancel(NsUser loginUser, Apply apply, LocalDateTime date) { + checkUserHasAuthor(loginUser); + + return this.applies.getApplies().stream() + .filter(savedApply -> cancel(apply, savedApply)) + .findAny() + .map(savedApply -> savedApply.cancel(date)) + .orElseThrow(() -> new IllegalArgumentException("지원자가 미승인 상태인지 확인하세요.")); + } + + private void checkUserHasAuthor(NsUser loginUser) { + if(!loginUser.hasAuthor()) { + throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); + } + } + + private static boolean cancel(Apply apply, Apply savedApply) { + return savedApply.isSame(apply) && savedApply.isCanceled(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/Enrollment.java b/src/main/java/nextstep/courses/domain/course/session/Enrollment.java new file mode 100644 index 0000000000..1bbcb76efa --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/Enrollment.java @@ -0,0 +1,109 @@ +package nextstep.courses.domain.course.session; + +import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.payments.domain.Payment; + +import java.time.LocalDateTime; + +public class Enrollment { + private Long sessionId; + + private Applies applies; + + private SessionState sessionState; + + private SessionProgressStatus sessionProgressStatus; + + private SessionRecruitStatus sessionRecruitStatus; + + public Enrollment(Long sessionId, Applies applies, SessionState sessionState, + SessionProgressStatus sessionProgressStatus, SessionRecruitStatus sessionRecruitStatus) { + this.sessionId = sessionId; + this.applies = applies; + this.sessionState = sessionState; + this.sessionProgressStatus = sessionProgressStatus; + this.sessionRecruitStatus = sessionRecruitStatus; + } + + public Apply apply(Long nsUserId, Payment payment, LocalDateTime date) { + checkPaymentIsPaid(nsUserId, payment); + checkStatusOnRecruit(); + checkStatusOnReadyOrOnGoing(); + checkChargedAndApplySizeIsValid(); + checkApplicantAlreadyExisted(nsUserId); + + return new Apply(sessionId, nsUserId, false, date); + } + + private void checkPaymentIsPaid(Long nsUserId, Payment payment) { + if (this.charged()) { + checkPaymentIsValid(nsUserId, payment); + } + } + + public boolean charged() { + return this.sessionState.getSessionType().charged(); + } + + private void checkPaymentIsValid(Long nsUserId, Payment payment) { + if (payment == null || + !payment.isPaid( + nsUserId, + sessionId, + sessionState.getAmount() + ) + ) { + throw new IllegalArgumentException("결제를 다시 확인하세요."); + } + } + + private void checkStatusOnRecruit() { + if (this.notRecruiting()) { + throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); + } + } + + private void checkStatusOnReadyOrOnGoing() { + if (this.notReadyOrOnGoing()) { + throw new IllegalArgumentException("강의 신청은 준비, 진행중일 때만 가능 합니다."); + } + } + + private void checkChargedAndApplySizeIsValid() { + if(chargedAndFull()) { + throw new IllegalArgumentException("수강 신청 인원이 초과 되었습니다."); + } + } + + private boolean chargedAndFull() { + return this.charged() && applySizeFull(); + } + + private boolean applySizeFull() { + return this.sessionState.getQuota() == applies.size(); + } + + private void checkApplicantAlreadyExisted(Long nsUserId) { + if (this.containsUserId(nsUserId)) { + throw new IllegalArgumentException("이미 수강 신청 이력이 있습니다."); + } + } + + public boolean containsUserId(Long nsUserId) { + return this.applies.getApplies().stream() + .anyMatch(apply -> apply.isSameWithUserId(nsUserId)); + } + + public int size() { + return this.applies.size(); + } + + public boolean notRecruiting() { + return this.sessionRecruitStatus.notRecruiting(); + } + + public boolean notReadyOrOnGoing() { + return this.sessionProgressStatus.notReadyOrOnGoing(); + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index 80ebeb6fe6..fe394de4ae 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -1,10 +1,8 @@ package nextstep.courses.domain.course.session; import nextstep.courses.domain.BaseEntity; -import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.courses.domain.course.session.apply.Applies; import nextstep.courses.domain.course.session.image.Images; -import nextstep.payments.domain.Payment; -import nextstep.users.domain.NsUser; import java.time.LocalDate; import java.time.LocalDateTime; @@ -15,22 +13,24 @@ public class Session extends BaseEntity { private Images images; + private Applies applies; + private SessionDetail sessionDetail; - public Session(Images images, Duration duration, SessionState sessionState, + public Session(Images images, SessionDuration sessionDuration, SessionState sessionState, Long creatorId, LocalDateTime date) { - this(0L, images, duration, sessionState, SessionRecruitStatus.NOT_RECRUIT, + this(0L, images, new Applies(), sessionDuration, sessionState, SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.READY, creatorId, date, null); } - public Session(Long id, Images images, Duration duration, SessionState sessionState, + public Session(Long id, Images images, Applies applies, SessionDuration sessionDuration, SessionState sessionState, SessionRecruitStatus sessionRecruitStatus, SessionProgressStatus sessionProgressStatus, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { - this(id, images, new SessionDetail(duration, sessionState, sessionProgressStatus, sessionRecruitStatus), + this(id, images, applies, new SessionDetail(sessionDuration, sessionState, sessionProgressStatus, sessionRecruitStatus), creatorId, createdAt, updatedAt); } - public Session(Long id, Images images, SessionDetail sessionDetail, + public Session(Long id, Images images, Applies applies, SessionDetail sessionDetail, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); if (images == null) { @@ -43,43 +43,21 @@ public Session(Long id, Images images, SessionDetail sessionDetail, this.id = id; this.images = images; + this.applies = applies; this.sessionDetail = sessionDetail; } - public Apply apply(NsUser loginUser, Payment payment, LocalDateTime date) { - checkPaymentIsPaid(loginUser, payment); - - return this.sessionDetail.addApply(this.id, loginUser.getId(), date); + public Enrollment enrollment() { + return new Enrollment(this.id, this.applies, this.getSessionState(), + this.getSessionProgressStatus(), this.getSessionRecruitStatus()); } - private void checkPaymentIsPaid(NsUser loginUser, Payment payment) { - if (sessionDetail.charged()) { - checkPaymentIsValid(loginUser, payment); - } + public Approve approve() { + return new Approve(this.applies, this.sessionDetail.getSessionState()); } - private void checkPaymentIsValid(NsUser loginUser, Payment payment) { - if (payment == null || !payment.isPaid(loginUser, this)) { - throw new IllegalArgumentException("결제를 다시 확인하세요."); - } - } - - public Apply approve(NsUser loginUser, Apply apply, LocalDateTime date) { - checkUserHasAuthor(loginUser); - - return sessionDetail.approve(apply, date); - } - - public Apply cancel(NsUser loginUser, Apply apply, LocalDateTime date) { - checkUserHasAuthor(loginUser); - - return sessionDetail.cancel(apply, date); - } - - private void checkUserHasAuthor(NsUser loginUser) { - if(!loginUser.hasAuthor()) { - throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); - } + public Cancel cancel() { + return new Cancel(this.applies, this.sessionDetail.getSessionState()); } public void changeOnReady(LocalDate date) { @@ -98,14 +76,6 @@ public void changeSessionState(SessionState updateSessionState) { this.sessionDetail.changeSessionState(updateSessionState); } - public boolean sameAmount(Long amount) { - return this.sessionDetail.sameAmount(amount); - } - - public boolean sameId(Long sessionId) { - return Objects.equals(this.id, sessionId); - } - public Long getId() { return this.id; } @@ -115,7 +85,7 @@ public void setId(Long id) { } public int applyCount() { - return this.sessionDetail.size(); + return this.applies.size(); } public Images getImages() { @@ -130,7 +100,7 @@ public SessionDetail getSessionDetail() { return sessionDetail; } - public Duration getDuration() { + public SessionDuration getDuration() { return sessionDetail.getDuration(); } @@ -138,11 +108,11 @@ public SessionState getSessionState() { return sessionDetail.getSessionState(); } - public SessionProgressStatus getSessionStatus() { + public SessionProgressStatus getSessionProgressStatus() { return sessionDetail.getSessionStatus(); } - public SessionRecruitStatus getRecruitStatus() { + public SessionRecruitStatus getSessionRecruitStatus() { return sessionDetail.getRecruitStatus(); } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java index 1e03f1223a..84fd89a4b5 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java @@ -1,13 +1,10 @@ package nextstep.courses.domain.course.session; -import nextstep.courses.domain.course.session.apply.Apply; - import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.Objects; public class SessionDetail { - private Duration duration; + private SessionDuration sessionDuration; private SessionState sessionState; @@ -15,9 +12,9 @@ public class SessionDetail { private SessionRecruitStatus sessionRecruitStatus; - public SessionDetail(Duration duration, SessionState sessionState, + public SessionDetail(SessionDuration sessionDuration, SessionState sessionState, SessionProgressStatus sessionProgressStatus, SessionRecruitStatus sessionRecruitStatus) { - if (duration == null) { + if (sessionDuration == null) { throw new IllegalArgumentException("기간이 추가되어야 합니다."); } @@ -33,14 +30,14 @@ public SessionDetail(Duration duration, SessionState sessionState, throw new IllegalArgumentException("강의 모집 여부가 추가되어야 합니다."); } - this.duration = duration; + this.sessionDuration = sessionDuration; this.sessionState = sessionState; this.sessionProgressStatus = sessionProgressStatus; this.sessionRecruitStatus = sessionRecruitStatus; } - public Duration getDuration() { - return duration; + public SessionDuration getDuration() { + return sessionDuration; } public SessionState getSessionState() { @@ -55,45 +52,6 @@ public SessionRecruitStatus getRecruitStatus() { return sessionRecruitStatus; } - public int size() { - return this.sessionState.size(); - } - - public boolean sameAmount(Long amount) { - return this.sessionState.sameAmount(amount); - } - - public Apply addApply(Long sessionId, Long nsUserId, LocalDateTime date) { - checkStatusOnRecruit(); - checkStatusOnReadyOrOnGoing(); - - return this.sessionState.addApply(sessionId, nsUserId, date); - } - - private void checkStatusOnRecruit() { - if (this.notRecruiting()) { - throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); - } - } - - private void checkStatusOnReadyOrOnGoing() { - if (this.notReadyOrOnGoing()) { - throw new IllegalArgumentException("강의 신청은 준비, 진행중일 때만 가능 합니다."); - } - } - - public boolean notRecruiting() { - return this.sessionRecruitStatus.notRecruiting(); - } - - public boolean notReadyOrOnGoing() { - return this.sessionProgressStatus.notReadyOrOnGoing(); - } - - public boolean charged() { - return this.sessionState.charged(); - } - public void changeOnReady(LocalDate date) { checkChangeDateIsSameOrAfterWithEndDate(date); this.sessionProgressStatus = SessionProgressStatus.READY; @@ -105,7 +63,7 @@ public void changeOnGoing(LocalDate date) { } private void checkChangeDateIsSameOrAfterWithEndDate(LocalDate date) { - if (this.duration.changeDateIsSameOrAfterWithEndDate(date)) { + if (this.sessionDuration.changeDateIsSameOrAfterWithEndDate(date)) { throw new IllegalArgumentException("강의 종료일 이전에 변경 가능 합니다."); } } @@ -116,7 +74,7 @@ public void changeOnEnd(LocalDate date) { } private void checkChangeDateIsBeforeOrSameWithEndDate(LocalDate date) { - if (this.duration.changeDateIsBeforeOrSameWithEndDate(date)) { + if (this.sessionDuration.changeDateIsBeforeOrSameWithEndDate(date)) { throw new IllegalArgumentException("강의 종료일 이후에 변경 가능합니다."); } } @@ -125,31 +83,23 @@ public void changeSessionState(SessionState sessionState) { this.sessionState = sessionState; } - public Apply approve(Apply apply, LocalDateTime date) { - return this.sessionState.approve(apply, date); - } - - public Apply cancel(Apply apply, LocalDateTime date) { - return this.sessionState.cancel(apply, date); - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SessionDetail that = (SessionDetail) o; - return Objects.equals(duration, that.duration) && Objects.equals(sessionState, that.sessionState) && sessionProgressStatus == that.sessionProgressStatus && sessionRecruitStatus == that.sessionRecruitStatus; + return Objects.equals(sessionDuration, that.sessionDuration) && Objects.equals(sessionState, that.sessionState) && sessionProgressStatus == that.sessionProgressStatus && sessionRecruitStatus == that.sessionRecruitStatus; } @Override public int hashCode() { - return Objects.hash(duration, sessionState, sessionProgressStatus, sessionRecruitStatus); + return Objects.hash(sessionDuration, sessionState, sessionProgressStatus, sessionRecruitStatus); } @Override public String toString() { return "SessionDetail{" + - "duration=" + duration + + "duration=" + sessionDuration + ", sessionState=" + sessionState + ", sessionStatus=" + sessionProgressStatus + ", recruitStatus=" + sessionRecruitStatus + diff --git a/src/main/java/nextstep/courses/domain/course/session/Duration.java b/src/main/java/nextstep/courses/domain/course/session/SessionDuration.java similarity index 86% rename from src/main/java/nextstep/courses/domain/course/session/Duration.java rename to src/main/java/nextstep/courses/domain/course/session/SessionDuration.java index 4b6a8366d5..67d0ca3f5a 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Duration.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionDuration.java @@ -3,12 +3,12 @@ import java.time.LocalDate; import java.util.Objects; -public class Duration { +public class SessionDuration { private LocalDate startDate; private LocalDate endDate; - public Duration(LocalDate startDate, LocalDate endDate) { + public SessionDuration(LocalDate startDate, LocalDate endDate) { validate(startDate, endDate); this.startDate = startDate; this.endDate = endDate; @@ -51,8 +51,8 @@ public LocalDate getEndDate() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - Duration duration = (Duration) o; - return Objects.equals(startDate, duration.startDate) && Objects.equals(endDate, duration.endDate); + SessionDuration sessionDuration = (SessionDuration) o; + return Objects.equals(startDate, sessionDuration.startDate) && Objects.equals(endDate, sessionDuration.endDate); } @Override diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionState.java b/src/main/java/nextstep/courses/domain/course/session/SessionState.java index 3d45d61489..9a39b096f8 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionState.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionState.java @@ -1,9 +1,5 @@ package nextstep.courses.domain.course.session; -import nextstep.courses.domain.course.session.apply.Applies; -import nextstep.courses.domain.course.session.apply.Apply; - -import java.time.LocalDateTime; import java.util.Objects; public class SessionState { @@ -15,24 +11,17 @@ public class SessionState { private int quota; - private Applies applies; - public SessionState() { this.sessionType = SessionType.FREE; this.amount = 0L; this.quota = MAX_APPLY; - this.applies = new Applies(); } - public SessionState(SessionType sessionType, Long amount, int quota, Applies applies) { - if (applies == null) { - throw new IllegalArgumentException("수강생은 빈 값이면 안됩니다."); - } + public SessionState(SessionType sessionType, Long amount, int quota) { validate(sessionType, amount, quota); this.sessionType = sessionType; this.amount = amount; this.quota = quota; - this.applies = applies; } private void validate(SessionType sessionType, Long amount, int quota) { @@ -53,50 +42,10 @@ private void checkTypeisFree(Long amount, int quota) { private void checkTypeisCharged(Long amount, int quota) { if(amount == 0L || quota == 0) { - throw new IllegalArgumentException("유료강의는 0원보다 크고 정원 수가 0보다 커야 합니다."); + throw new IllegalArgumentException("유료 강의는 0원보다 크고 정원 수가 0보다 커야 합니다."); } } - public Apply addApply(Long sessionId, Long nsUserId, LocalDateTime date) { - checkChargedAndApplySizeIsValid(); - - return this.applies.addApply(sessionId, nsUserId, date); - } - - private void checkChargedAndApplySizeIsValid() { - if(chargedAndFull()) { - throw new IllegalArgumentException("수강 신청 인원이 초과 되었습니다."); - } - } - - public Apply approve(Apply apply, LocalDateTime date) { - return this.applies.approve(apply, date); - } - - public Apply cancel(Apply apply, LocalDateTime date) { - return this.applies.cancel(apply, date); - } - - private boolean chargedAndFull() { - return this.charged() && applySizeFull(); - } - - public boolean charged() { - return this.sessionType.charged(); - } - - private boolean applySizeFull() { - return this.quota == applies.size(); - } - - public int size() { - return this.applies.size(); - } - - public boolean sameAmount(Long amount) { - return Objects.equals(this.amount, amount); - } - public SessionType getSessionType() { return sessionType; } @@ -109,10 +58,6 @@ public int getQuota() { return quota; } - public Applies getApplies() { - return applies; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -132,7 +77,6 @@ public String toString() { "sessionType=" + sessionType + ", amount=" + amount + ", quota=" + quota + - ", applies=" + applies + '}'; } } diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java b/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java index ddd995e60f..df8ad5b198 100644 --- a/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java +++ b/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java @@ -1,6 +1,5 @@ package nextstep.courses.domain.course.session.apply; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -20,48 +19,8 @@ public int size() { return this.applies.size(); } - public Apply addApply(Long sessionId, Long nsUserId, LocalDateTime date) { - checkApplicantAlreadyExisted(nsUserId); - - Apply addedApply = new Apply(sessionId, nsUserId, false, date); - this.applies.add(addedApply); - - return addedApply; - } - - private void checkApplicantAlreadyExisted(Long nsUserId) { - if (this.containsUserId(nsUserId)) { - throw new IllegalArgumentException("이미 수강 신청 이력이 있습니다."); - } - } - - public boolean containsUserId(Long nsUserId) { - return this.applies.stream() - .anyMatch(apply -> apply.isSameWithUserId(nsUserId)); - } - - public Apply approve(Apply apply, LocalDateTime date) { - return this.applies.stream() - .filter(savedApply -> approved(apply, savedApply)) - .findAny() - .map(savedApply -> savedApply.approve(date)) - .orElseThrow(() -> new IllegalArgumentException("지원자가 미승인 상태인지 확인하세요.")); - } - - private static boolean approved(Apply apply, Apply savedApply) { - return savedApply.isSame(apply) && savedApply.isCanceled(); - } - - public Apply cancel(Apply apply, LocalDateTime date) { - return this.applies.stream() - .filter(savedApply -> canceled(apply, savedApply)) - .findAny() - .map(savedApply -> savedApply.cancel(date)) - .orElseThrow(() -> new IllegalArgumentException("지원자가 승인 상태인지 확인하세요.")); - } - - private static boolean canceled(Apply apply, Apply savedApply) { - return savedApply.isSame(apply) && savedApply.isApproved(); + public List getApplies() { + return applies; } @Override diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java b/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java index ec0c4f4e60..02721e9633 100644 --- a/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java +++ b/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java @@ -1,8 +1,6 @@ package nextstep.courses.domain.course.session.apply; import nextstep.courses.domain.BaseEntity; -import nextstep.courses.domain.course.session.Session; -import nextstep.users.domain.NsUser; import java.time.LocalDateTime; import java.util.Objects; @@ -56,17 +54,13 @@ public Apply setApproved(boolean approved) { } public Apply approve(LocalDateTime date) { - this.approved = true; - this.setUpdatedAt(date); - - return this; + return new Apply(this.sessionId, this.nsUserId, true, + this.getCreatorId(), this.getCreatedAt(), date); } public Apply cancel(LocalDateTime date) { - this.approved = false; - this.setUpdatedAt(date); - - return this; + return new Apply(this.sessionId, this.nsUserId, false, + this.getCreatorId(), this.getCreatedAt(), date); } @Override diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 1f8bb0a131..73cf6e695f 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -45,15 +45,15 @@ public Optional findById(Long id) { RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), findAllImagesBySessionId(rs.getLong(1)), - new Duration( + findAllAppliesBySessionId(rs.getLong(1)), + new SessionDuration( rs.getTimestamp(2).toLocalDateTime().toLocalDate(), rs.getTimestamp(3).toLocalDateTime().toLocalDate() ), new SessionState( SessionType.find(rs.getString(4)), rs.getLong(5), - rs.getInt(6), - findAllAppliesBySessionId(id) + rs.getInt(6) ), SessionRecruitStatus.find(rs.getString(7)), SessionProgressStatus.find(rs.getString(8)), @@ -78,11 +78,11 @@ public Session save(Long courseId, Session session) { } private Session saveSession(Long courseId, Session session) { - Duration duration = session.getDuration(); - SessionRecruitStatus sessionRecruitStatus = session.getRecruitStatus(); + SessionDuration sessionDuration = session.getDuration(); + SessionRecruitStatus sessionRecruitStatus = session.getSessionRecruitStatus(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); - SessionProgressStatus sessionProgressStatus = session.getSessionStatus(); + SessionProgressStatus sessionProgressStatus = session.getSessionProgressStatus(); String sql = "insert into session " + "(start_date, end_date, session_type, session_status, amount, " + "recruit_status, quota, course_id, creator_id, created_at, updated_at) " + @@ -90,8 +90,8 @@ private Session saveSession(Long courseId, Session session) { jdbcTemplate.update((Connection connection) -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); - ps.setTimestamp(1, Timestamp.valueOf(duration.getStartDate().atStartOfDay())); - ps.setTimestamp(2, Timestamp.valueOf(duration.getEndDate().atStartOfDay())); + ps.setTimestamp(1, Timestamp.valueOf(sessionDuration.getStartDate().atStartOfDay())); + ps.setTimestamp(2, Timestamp.valueOf(sessionDuration.getEndDate().atStartOfDay())); ps.setString(3, sessionType.name()); ps.setString(4, sessionProgressStatus.name()); ps.setLong(5, sessionState.getAmount()); @@ -112,15 +112,15 @@ private Session saveSession(Long courseId, Session session) { @Override public int update(Long sessionId, Session session) { - Duration duration = session.getDuration(); - SessionRecruitStatus sessionRecruitStatus = session.getRecruitStatus(); + SessionDuration sessionDuration = session.getDuration(); + SessionRecruitStatus sessionRecruitStatus = session.getSessionRecruitStatus(); SessionState sessionState = session.getSessionState(); SessionType sessionType = sessionState.getSessionType(); - SessionProgressStatus sessionProgressStatus = session.getSessionStatus(); + SessionProgressStatus sessionProgressStatus = session.getSessionProgressStatus(); String sql = "update session set " + "start_date = ?, end_date = ?, session_type = ?, recruit_status = ?, amount = ?, quota = ?, session_status = ? " + "where id = ?"; - return jdbcTemplate.update(sql, duration.getStartDate(), duration.getEndDate(), sessionType.name(), + return jdbcTemplate.update(sql, sessionDuration.getStartDate(), sessionDuration.getEndDate(), sessionType.name(), sessionRecruitStatus.name(), sessionState.getAmount(), sessionState.getQuota(), sessionProgressStatus.name(), sessionId); } @@ -133,15 +133,15 @@ public Sessions findAllByCourseId(Long courseId) { RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), findAllImagesBySessionId(rs.getLong(1)), - new Duration( + findAllAppliesBySessionId(rs.getLong(1)), + new SessionDuration( rs.getTimestamp(2).toLocalDateTime().toLocalDate(), rs.getTimestamp(3).toLocalDateTime().toLocalDate() ), new SessionState( SessionType.find(rs.getString(4)), rs.getLong(5), - rs.getInt(6), - findAllAppliesBySessionId(rs.getLong(7)) + rs.getInt(6) ), SessionRecruitStatus.find(rs.getString(8)), SessionProgressStatus.find(rs.getString(9)), diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index 56cd9ba386..b919ecd942 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -1,7 +1,6 @@ package nextstep.courses.service; -import nextstep.courses.domain.course.session.Session; -import nextstep.courses.domain.course.session.SessionRepository; +import nextstep.courses.domain.course.session.*; import nextstep.courses.domain.course.session.apply.Apply; import nextstep.courses.domain.course.session.apply.ApplyRepository; import nextstep.payments.domain.Payment; @@ -25,23 +24,26 @@ public void create(Long courseId, Session session) { sessionRepository.save(courseId, session); } - public void applySession(NsUser loginUser, Long sessionId, Payment payment, LocalDateTime date) { + public void apply(NsUser loginUser, Long sessionId, Payment payment, LocalDateTime date) { Session session = getSession(sessionId); - Apply apply = session.apply(loginUser, payment, date); + Enrollment enrollment = session.enrollment(); + Apply apply = enrollment.apply(loginUser.getId(), payment, date); applyRepository.save(apply); } public void approve(NsUser loginUser, Long applicantId, Long sessionId, LocalDateTime date) { Session session = getSession(sessionId); Apply savedApply = getApply(sessionId, applicantId); - Apply apply = session.approve(loginUser, savedApply, date); + Approve approve = session.approve(); + Apply apply = approve.approve(loginUser, savedApply, date); applyRepository.update(apply); } public void cancel(NsUser loginUser, Long applicantId, Long sessionId, LocalDateTime date) { Session session = getSession(sessionId); Apply savedApply = getApply(sessionId, applicantId); - Apply apply = session.cancel(loginUser, savedApply, date); + Cancel cancel = session.cancel(); + Apply apply = cancel.cancel(loginUser, savedApply, date); applyRepository.update(apply); } diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index a1a15689cf..f637724c63 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -1,9 +1,7 @@ package nextstep.payments.domain; -import nextstep.courses.domain.course.session.Session; -import nextstep.users.domain.NsUser; - import java.time.LocalDateTime; +import java.util.Objects; public class Payment { private String id; @@ -30,9 +28,9 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) { this.createdAt = LocalDateTime.now(); } - public boolean isPaid(NsUser nsUser, Session session) { - return nsUser.isSame(this.nsUserId) - && session.sameId(this.sessionId) - && session.sameAmount(this.amount); + public boolean isPaid(Long userId, Long sessionId, Long amount) { + return (Objects.equals(this.nsUserId, userId)) + && (Objects.equals(this.sessionId, sessionId)) + && (Objects.equals(this.amount, amount)); } } diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index 01fa6c9f44..eeb55fc6e2 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -53,7 +53,7 @@ void apply_success() { Session savedSession = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING); when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); - sessionService.applySession(NsUserFixtures.TEACHER_JAVAJIGI_1L, + sessionService.apply(NsUserFixtures.TEACHER_JAVAJIGI_1L, savedSession.getId(), PaymentFixtures.payment(), SessionFixtures.DATETIME_2023_12_5 @@ -71,7 +71,7 @@ void changeOnReady_success() { sessionService.changeOnReady(savedSession.getId(), SessionFixtures.DATE_2023_12_5); assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionStatus()).isEqualTo(SessionProgressStatus.READY); + assertThat(savedSession.getSessionProgressStatus()).isEqualTo(SessionProgressStatus.READY); } @Test @@ -83,7 +83,7 @@ void changeOnGoing_success() { sessionService.changeOnGoing(savedSession.getId(), SessionFixtures.DATE_2023_12_6); assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionStatus()).isEqualTo(SessionProgressStatus.ONGOING); + assertThat(savedSession.getSessionProgressStatus()).isEqualTo(SessionProgressStatus.ONGOING); } @Test @@ -95,7 +95,7 @@ void changeOnEnd_success() { sessionService.changeOnEnd(savedSession.getId(), SessionFixtures.DATE_2023_12_12); assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionStatus()).isEqualTo(SessionProgressStatus.END); + assertThat(savedSession.getSessionProgressStatus()).isEqualTo(SessionProgressStatus.END); } @Test diff --git a/src/test/java/nextstep/courses/domain/course/session/DurationTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionDurationTest.java similarity index 79% rename from src/test/java/nextstep/courses/domain/course/session/DurationTest.java rename to src/test/java/nextstep/courses/domain/course/session/SessionDurationTest.java index 9b0e19ff18..039c2e9b59 100644 --- a/src/test/java/nextstep/courses/domain/course/session/DurationTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionDurationTest.java @@ -8,7 +8,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -public class DurationTest { +public class SessionDurationTest { private LocalDate localDate; @BeforeEach @@ -20,15 +20,15 @@ void setUp() { @DisplayName("Duration 은 시작 혹은 종료 날짜에 빈 값이 주어지면 예외를 던진다.") void newObject_nullAndEmpty_throwsException() { assertThatThrownBy( - () -> new Duration(null, null) + () -> new SessionDuration(null, null) ).isInstanceOf(IllegalArgumentException.class); assertThatThrownBy( - () -> new Duration(localDate, null) + () -> new SessionDuration(localDate, null) ).isInstanceOf(IllegalArgumentException.class); assertThatThrownBy( - () -> new Duration(null, localDate) + () -> new SessionDuration(null, localDate) ).isInstanceOf(IllegalArgumentException.class); } @@ -36,7 +36,7 @@ void newObject_nullAndEmpty_throwsException() { @DisplayName("Duration 은 시작 날짜가 종료날짜보다 늦으면 예외를 던진다.") void newObject_startDateIsAfterBeforeDate_throwsException() { assertThatThrownBy( - () -> new Duration(localDate.plusDays(1), localDate) + () -> new SessionDuration(localDate.plusDays(1), localDate) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/fixture/SessionFixtures.java b/src/test/java/nextstep/courses/fixture/SessionFixtures.java index 8c019a755d..baae92a39d 100644 --- a/src/test/java/nextstep/courses/fixture/SessionFixtures.java +++ b/src/test/java/nextstep/courses/fixture/SessionFixtures.java @@ -13,8 +13,8 @@ public class SessionFixtures { public static final LocalDate DATE_2023_12_12 = LocalDate.of(2023, 12, 12); public static final LocalDateTime DATETIME_2023_12_5 = LocalDateTime.of(2023, 12, 5, 0, 0); - public static Duration duration() { - return new Duration(DATE_2023_12_5, DATE_2023_12_10); + public static SessionDuration duration() { + return new SessionDuration(DATE_2023_12_5, DATE_2023_12_10); } public static Session createdFreeSession() { @@ -25,7 +25,7 @@ public static Session createdFreeSession(SessionRecruitStatus sessionRecruitStat return new Session( 1L, ImageFixtures.images(), - new Duration(DATE_2023_12_5, DATE_2023_12_10), + new SessionDuration(DATE_2023_12_5, DATE_2023_12_10), freeSessionStateZero(), sessionRecruitStatus, sessionProgressStatus, @@ -44,7 +44,7 @@ public static Session createdChargedSession( return new Session( 1L, ImageFixtures.images(), - new Duration(DATE_2023_12_5, DATE_2023_12_10), + new SessionDuration(DATE_2023_12_5, DATE_2023_12_10), chargedSessionStateZero(1000L, 2, new Applies()), sessionRecruitStatus, sessionProgressStatus, @@ -58,7 +58,7 @@ public static Session chargedSessionFullCanceled() { return new Session( 1L, ImageFixtures.images(), - new Duration(DATE_2023_12_5, DATE_2023_12_10), + new SessionDuration(DATE_2023_12_5, DATE_2023_12_10), chargedSessionStateZero(1000L, 2, ApplyFixtures.applies_two_canceled()), SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY, @@ -72,7 +72,7 @@ public static Session chargedSessionFullApproved() { return new Session( 1L, ImageFixtures.images(), - new Duration(DATE_2023_12_5, DATE_2023_12_10), + new SessionDuration(DATE_2023_12_5, DATE_2023_12_10), chargedSessionStateZero(1000L, 2, ApplyFixtures.applies_two_approved()), SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY, From 05458266a4527f6e3a9e4938731e814a0e84ebb0 Mon Sep 17 00:00:00 2001 From: sunggyupaik Date: Wed, 27 Dec 2023 20:06:16 +0900 Subject: [PATCH 62/62] =?UTF-8?q?[refactor]=20=EB=B9=84=EC=A7=80=EB=8B=88?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=EA=B3=BC=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EB=A5=BC=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 강의 상태 변경 같은 간단한 경우에는 엔티티에서 해결하고 수강신청, 승인 및 취소는 별도 클래스에서 비지니스 로직을 수행합니다. getter와 setter를 제거하고 모든 변수에 final을 추가합니다. --- .../nextstep/courses/domain/BaseEntity.java | 16 +- .../courses/domain/course/Course.java | 22 +- .../domain/course/CourseRepository.java | 2 +- .../domain/course/session/Approve.java | 38 ---- .../courses/domain/course/session/Cancel.java | 38 ---- .../domain/course/session/Enrollment.java | 61 ++--- .../domain/course/session/Session.java | 79 +++---- .../domain/course/session/SessionDetail.java | 58 +++-- .../course/session/SessionDuration.java | 8 +- .../domain/course/session/SessionState.java | 12 +- .../domain/course/session/apply/Applies.java | 34 ++- .../domain/course/session/apply/Apply.java | 57 +++-- .../course/session/apply/ApprovalStatus.java | 44 ++++ .../course/session/apply/ApproveCancel.java | 31 +++ .../domain/course/session/image/Image.java | 26 +-- .../domain/course/session/image/Images.java | 9 +- .../infrastructure/JdbcApplyRepository.java | 19 +- .../infrastructure/JdbcCourseRepository.java | 25 ++- .../infrastructure/JdbcImageRepository.java | 18 +- .../infrastructure/JdbcSessionRepository.java | 52 ++--- .../courses/service/SessionService.java | 21 +- src/main/resources/schema.sql | 2 +- .../course/service/CourseServiceTest.java | 11 +- .../course/service/SessionServiceTest.java | 63 +++--- .../domain/course/session/EnrollmentTest.java | 131 +++++++++++ .../course/session/SessionStateTest.java | 9 +- .../domain/course/session/SessionTest.java | 208 ++---------------- .../session/apply/ApprovalCancelTest.java | 106 +++++++++ .../session/apply/ApprovalStatusTest.java | 16 ++ .../courses/fixture/ApplyFixtures.java | 13 +- .../courses/fixture/SessionFixtures.java | 14 +- .../infrastructure/ApplyRepositoryTest.java | 16 +- .../infrastructure/CourseRepositoryTest.java | 9 +- .../infrastructure/ImageRepositoryTest.java | 8 +- .../infrastructure/SessionRepositoryTest.java | 29 ++- 35 files changed, 706 insertions(+), 599 deletions(-) delete mode 100644 src/main/java/nextstep/courses/domain/course/session/Approve.java delete mode 100644 src/main/java/nextstep/courses/domain/course/session/Cancel.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/apply/ApprovalStatus.java create mode 100644 src/main/java/nextstep/courses/domain/course/session/apply/ApproveCancel.java create mode 100644 src/test/java/nextstep/courses/domain/course/session/EnrollmentTest.java create mode 100644 src/test/java/nextstep/courses/domain/course/session/apply/ApprovalCancelTest.java create mode 100644 src/test/java/nextstep/courses/domain/course/session/apply/ApprovalStatusTest.java diff --git a/src/main/java/nextstep/courses/domain/BaseEntity.java b/src/main/java/nextstep/courses/domain/BaseEntity.java index 0f35235e00..7548ea4651 100644 --- a/src/main/java/nextstep/courses/domain/BaseEntity.java +++ b/src/main/java/nextstep/courses/domain/BaseEntity.java @@ -3,11 +3,11 @@ import java.time.LocalDateTime; public class BaseEntity { - private Long creatorId; + private final Long creatorId; - private LocalDateTime createdAt; + private final LocalDateTime createdAt; - private LocalDateTime updatedAt; + private final LocalDateTime updatedAt; public BaseEntity(Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { this.creatorId = creatorId; @@ -15,19 +15,15 @@ public BaseEntity(Long creatorId, LocalDateTime createdAt, LocalDateTime updated this.updatedAt = updatedAt; } - public Long getCreatorId() { + public Long creatorId() { return creatorId; } - public LocalDateTime getCreatedAt() { + public LocalDateTime createdAt() { return createdAt; } - public LocalDateTime getUpdatedAt() { + public LocalDateTime updatedAt() { return updatedAt; } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } } diff --git a/src/main/java/nextstep/courses/domain/course/Course.java b/src/main/java/nextstep/courses/domain/course/Course.java index 3451bb9f12..42c42a19bb 100644 --- a/src/main/java/nextstep/courses/domain/course/Course.java +++ b/src/main/java/nextstep/courses/domain/course/Course.java @@ -7,13 +7,13 @@ import java.time.LocalDateTime; public class Course extends BaseEntity { - private Long id; + private final Long id; - private String title; + private final String title; - private int ordering; + private final int ordering; - private Sessions sessions; + private final Sessions sessions; public Course(String title, int ordering, Long creatorId, LocalDateTime date) { this(0L, title, ordering, new Sessions(), creatorId, date, null); @@ -28,28 +28,24 @@ public Course(Long id, String title, int ordering, Sessions sessions, this.sessions = sessions; } - public int sessionSize() { - return this.sessions.size(); - } - public void addSession(Session session) { this.sessions.add(session); } - public Long getId() { + public Long id() { return id; } - public String getTitle() { + public String title() { return title; } - public int getOrdering() { + public int ordering() { return this.ordering; } - public Long getCreatorId() { - return super.getCreatorId(); + public Sessions sessions() { + return this.sessions; } @Override diff --git a/src/main/java/nextstep/courses/domain/course/CourseRepository.java b/src/main/java/nextstep/courses/domain/course/CourseRepository.java index 28180d25e0..b11dd8d46b 100644 --- a/src/main/java/nextstep/courses/domain/course/CourseRepository.java +++ b/src/main/java/nextstep/courses/domain/course/CourseRepository.java @@ -1,7 +1,7 @@ package nextstep.courses.domain.course; public interface CourseRepository { - int save(Course course); + Course save(Course course); Course findById(Long id); } diff --git a/src/main/java/nextstep/courses/domain/course/session/Approve.java b/src/main/java/nextstep/courses/domain/course/session/Approve.java deleted file mode 100644 index c3831ec427..0000000000 --- a/src/main/java/nextstep/courses/domain/course/session/Approve.java +++ /dev/null @@ -1,38 +0,0 @@ -package nextstep.courses.domain.course.session; - -import nextstep.courses.domain.course.session.apply.Applies; -import nextstep.courses.domain.course.session.apply.Apply; -import nextstep.users.domain.NsUser; - -import java.time.LocalDateTime; - -public class Approve { - private Applies applies; - - private SessionState sessionState; - - public Approve(Applies applies, SessionState sessionState) { - this.applies = applies; - this.sessionState = sessionState; - } - - public Apply approve(NsUser loginUser, Apply apply, LocalDateTime date) { - checkUserHasAuthor(loginUser); - - return this.applies.getApplies().stream() - .filter(savedApply -> approved(apply, savedApply)) - .findAny() - .map(savedApply -> savedApply.approve(date)) - .orElseThrow(() -> new IllegalArgumentException("지원자가 미승인 상태인지 확인하세요.")); - } - - private void checkUserHasAuthor(NsUser loginUser) { - if(!loginUser.hasAuthor()) { - throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); - } - } - - private static boolean approved(Apply apply, Apply savedApply) { - return savedApply.isSame(apply) && savedApply.isCanceled(); - } -} diff --git a/src/main/java/nextstep/courses/domain/course/session/Cancel.java b/src/main/java/nextstep/courses/domain/course/session/Cancel.java deleted file mode 100644 index 1a4b45cd19..0000000000 --- a/src/main/java/nextstep/courses/domain/course/session/Cancel.java +++ /dev/null @@ -1,38 +0,0 @@ -package nextstep.courses.domain.course.session; - -import nextstep.courses.domain.course.session.apply.Applies; -import nextstep.courses.domain.course.session.apply.Apply; -import nextstep.users.domain.NsUser; - -import java.time.LocalDateTime; - -public class Cancel { - private Applies applies; - - private SessionState sessionState; - - public Cancel(Applies applies, SessionState sessionState) { - this.applies = applies; - this.sessionState = sessionState; - } - - public Apply cancel(NsUser loginUser, Apply apply, LocalDateTime date) { - checkUserHasAuthor(loginUser); - - return this.applies.getApplies().stream() - .filter(savedApply -> cancel(apply, savedApply)) - .findAny() - .map(savedApply -> savedApply.cancel(date)) - .orElseThrow(() -> new IllegalArgumentException("지원자가 미승인 상태인지 확인하세요.")); - } - - private void checkUserHasAuthor(NsUser loginUser) { - if(!loginUser.hasAuthor()) { - throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); - } - } - - private static boolean cancel(Apply apply, Apply savedApply) { - return savedApply.isSame(apply) && savedApply.isCanceled(); - } -} diff --git a/src/main/java/nextstep/courses/domain/course/session/Enrollment.java b/src/main/java/nextstep/courses/domain/course/session/Enrollment.java index 1bbcb76efa..4c1b64f621 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/course/session/Enrollment.java @@ -2,28 +2,22 @@ import nextstep.courses.domain.course.session.apply.Applies; import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.courses.domain.course.session.apply.ApprovalStatus; import nextstep.payments.domain.Payment; import java.time.LocalDateTime; public class Enrollment { - private Long sessionId; + private final Long sessionId; - private Applies applies; + private final Applies applies; - private SessionState sessionState; + private final SessionDetail sessionDetail; - private SessionProgressStatus sessionProgressStatus; - - private SessionRecruitStatus sessionRecruitStatus; - - public Enrollment(Long sessionId, Applies applies, SessionState sessionState, - SessionProgressStatus sessionProgressStatus, SessionRecruitStatus sessionRecruitStatus) { + public Enrollment(Long sessionId, Applies applies, SessionDetail sessionDetail) { this.sessionId = sessionId; this.applies = applies; - this.sessionState = sessionState; - this.sessionProgressStatus = sessionProgressStatus; - this.sessionRecruitStatus = sessionRecruitStatus; + this.sessionDetail = sessionDetail; } public Apply apply(Long nsUserId, Payment payment, LocalDateTime date) { @@ -33,25 +27,21 @@ public Apply apply(Long nsUserId, Payment payment, LocalDateTime date) { checkChargedAndApplySizeIsValid(); checkApplicantAlreadyExisted(nsUserId); - return new Apply(sessionId, nsUserId, false, date); + return new Apply(sessionId, nsUserId, ApprovalStatus.WAIT, date); } private void checkPaymentIsPaid(Long nsUserId, Payment payment) { - if (this.charged()) { + if (this.sessionDetail.charged()) { checkPaymentIsValid(nsUserId, payment); } } - public boolean charged() { - return this.sessionState.getSessionType().charged(); - } - private void checkPaymentIsValid(Long nsUserId, Payment payment) { if (payment == null || !payment.isPaid( nsUserId, sessionId, - sessionState.getAmount() + this.sessionDetail.getAmount() ) ) { throw new IllegalArgumentException("결제를 다시 확인하세요."); @@ -59,51 +49,26 @@ private void checkPaymentIsValid(Long nsUserId, Payment payment) { } private void checkStatusOnRecruit() { - if (this.notRecruiting()) { + if (this.sessionDetail.notRecruiting()) { throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다."); } } private void checkStatusOnReadyOrOnGoing() { - if (this.notReadyOrOnGoing()) { + if (this.sessionDetail.notReadyOrOnGoing()) { throw new IllegalArgumentException("강의 신청은 준비, 진행중일 때만 가능 합니다."); } } private void checkChargedAndApplySizeIsValid() { - if(chargedAndFull()) { + if(this.sessionDetail.chargedAndFull(applies.size())) { throw new IllegalArgumentException("수강 신청 인원이 초과 되었습니다."); } } - private boolean chargedAndFull() { - return this.charged() && applySizeFull(); - } - - private boolean applySizeFull() { - return this.sessionState.getQuota() == applies.size(); - } - private void checkApplicantAlreadyExisted(Long nsUserId) { - if (this.containsUserId(nsUserId)) { + if (this.applies.containsUserId(nsUserId)) { throw new IllegalArgumentException("이미 수강 신청 이력이 있습니다."); } } - - public boolean containsUserId(Long nsUserId) { - return this.applies.getApplies().stream() - .anyMatch(apply -> apply.isSameWithUserId(nsUserId)); - } - - public int size() { - return this.applies.size(); - } - - public boolean notRecruiting() { - return this.sessionRecruitStatus.notRecruiting(); - } - - public boolean notReadyOrOnGoing() { - return this.sessionProgressStatus.notReadyOrOnGoing(); - } } diff --git a/src/main/java/nextstep/courses/domain/course/session/Session.java b/src/main/java/nextstep/courses/domain/course/session/Session.java index fe394de4ae..e6f7e31a17 100644 --- a/src/main/java/nextstep/courses/domain/course/session/Session.java +++ b/src/main/java/nextstep/courses/domain/course/session/Session.java @@ -2,6 +2,7 @@ import nextstep.courses.domain.BaseEntity; import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.apply.ApproveCancel; import nextstep.courses.domain.course.session.image.Images; import java.time.LocalDate; @@ -9,13 +10,13 @@ import java.util.Objects; public class Session extends BaseEntity { - private Long id; + private final Long id; - private Images images; + private final Images images; - private Applies applies; + private final Applies applies; - private SessionDetail sessionDetail; + private final SessionDetail sessionDetail; public Session(Images images, SessionDuration sessionDuration, SessionState sessionState, Long creatorId, LocalDateTime date) { @@ -34,7 +35,11 @@ public Session(Long id, Images images, Applies applies, SessionDetail sessionDet Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); if (images == null) { - throw new IllegalArgumentException("이미지를 추가해야 합니다"); + throw new IllegalArgumentException("이미지를 추가해야 합니다."); + } + + if (applies == null) { + throw new IllegalArgumentException("지원자를 추가해야 합니다."); } if (sessionDetail == null) { @@ -48,72 +53,58 @@ public Session(Long id, Images images, Applies applies, SessionDetail sessionDet } public Enrollment enrollment() { - return new Enrollment(this.id, this.applies, this.getSessionState(), - this.getSessionProgressStatus(), this.getSessionRecruitStatus()); - } - - public Approve approve() { - return new Approve(this.applies, this.sessionDetail.getSessionState()); + return new Enrollment(this.id, this.applies, this.sessionDetail); } - public Cancel cancel() { - return new Cancel(this.applies, this.sessionDetail.getSessionState()); + public ApproveCancel approve() { + return new ApproveCancel(this.applies); } - public void changeOnReady(LocalDate date) { - this.sessionDetail.changeOnReady(date); + public ApproveCancel cancel() { + return new ApproveCancel(this.applies); } - public void changeOnGoing(LocalDate date) { - this.sessionDetail.changeOnGoing(date); + public Session changeOnReady(LocalDate date) { + SessionDetail changedSessionDetail = this.sessionDetail.changeOnReady(date); + return new Session(id, images, applies, changedSessionDetail, creatorId(), createdAt(), updatedAt()); } - public void changeOnEnd(LocalDate date) { - this.sessionDetail.changeOnEnd(date); + public Session changeOnGoing(LocalDate date) { + SessionDetail changedSessionDetail = this.sessionDetail.changeOnGoing(date); + return new Session(id, images, applies, changedSessionDetail, creatorId(), createdAt(), updatedAt()); } - public void changeSessionState(SessionState updateSessionState) { - this.sessionDetail.changeSessionState(updateSessionState); + public Session changeOnEnd(LocalDate date) { + SessionDetail changedSessionDetail = this.sessionDetail.changeOnEnd(date); + return new Session(id, images, applies, changedSessionDetail, creatorId(), createdAt(), updatedAt()); } - public Long getId() { + public Long id() { return this.id; } - public void setId(Long id) { - this.id = id; - } - - public int applyCount() { - return this.applies.size(); - } - - public Images getImages() { + public Images images() { return images; } - public void setImages(Images images) { - this.images = images; - } - - public SessionDetail getSessionDetail() { + public SessionDetail sessionDetail() { return sessionDetail; } - public SessionDuration getDuration() { - return sessionDetail.getDuration(); + public SessionDuration sessionDuration() { + return sessionDetail.sessionDuration(); } - public SessionState getSessionState() { - return sessionDetail.getSessionState(); + public SessionState sessionState() { + return sessionDetail.sessionState(); } - public SessionProgressStatus getSessionProgressStatus() { - return sessionDetail.getSessionStatus(); + public SessionProgressStatus sessionProgressStatus() { + return sessionDetail.sessionProgressStatus(); } - public SessionRecruitStatus getSessionRecruitStatus() { - return sessionDetail.getRecruitStatus(); + public SessionRecruitStatus sessionRecruitStatus() { + return sessionDetail.sessionRecruitStatus(); } @Override diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java index 84fd89a4b5..8a95c546a4 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionDetail.java @@ -4,13 +4,13 @@ import java.util.Objects; public class SessionDetail { - private SessionDuration sessionDuration; + private final SessionDuration sessionDuration; - private SessionState sessionState; + private final SessionState sessionState; - private SessionProgressStatus sessionProgressStatus; + private final SessionProgressStatus sessionProgressStatus; - private SessionRecruitStatus sessionRecruitStatus; + private final SessionRecruitStatus sessionRecruitStatus; public SessionDetail(SessionDuration sessionDuration, SessionState sessionState, SessionProgressStatus sessionProgressStatus, SessionRecruitStatus sessionRecruitStatus) { @@ -36,30 +36,30 @@ public SessionDetail(SessionDuration sessionDuration, SessionState sessionState, this.sessionRecruitStatus = sessionRecruitStatus; } - public SessionDuration getDuration() { + public SessionDuration sessionDuration() { return sessionDuration; } - public SessionState getSessionState() { + public SessionState sessionState() { return sessionState; } - public SessionProgressStatus getSessionStatus() { + public SessionProgressStatus sessionProgressStatus() { return sessionProgressStatus; } - public SessionRecruitStatus getRecruitStatus() { + public SessionRecruitStatus sessionRecruitStatus() { return sessionRecruitStatus; } - public void changeOnReady(LocalDate date) { + public SessionDetail changeOnReady(LocalDate date) { checkChangeDateIsSameOrAfterWithEndDate(date); - this.sessionProgressStatus = SessionProgressStatus.READY; + return new SessionDetail(sessionDuration, sessionState, SessionProgressStatus.READY, sessionRecruitStatus); } - public void changeOnGoing(LocalDate date) { + public SessionDetail changeOnGoing(LocalDate date) { checkChangeDateIsSameOrAfterWithEndDate(date); - this.sessionProgressStatus = SessionProgressStatus.ONGOING; + return new SessionDetail(sessionDuration, sessionState, SessionProgressStatus.ONGOING, sessionRecruitStatus); } private void checkChangeDateIsSameOrAfterWithEndDate(LocalDate date) { @@ -68,9 +68,9 @@ private void checkChangeDateIsSameOrAfterWithEndDate(LocalDate date) { } } - public void changeOnEnd(LocalDate date) { + public SessionDetail changeOnEnd(LocalDate date) { checkChangeDateIsBeforeOrSameWithEndDate(date); - this.sessionProgressStatus = SessionProgressStatus.END; + return new SessionDetail(sessionDuration, sessionState, SessionProgressStatus.END, sessionRecruitStatus); } private void checkChangeDateIsBeforeOrSameWithEndDate(LocalDate date) { @@ -79,8 +79,28 @@ private void checkChangeDateIsBeforeOrSameWithEndDate(LocalDate date) { } } - public void changeSessionState(SessionState sessionState) { - this.sessionState = sessionState; + public boolean charged() { + return this.sessionState.sessionType().charged(); + } + + public Long getAmount() { + return this.sessionState.amount(); + } + + public boolean notRecruiting() { + return this.sessionRecruitStatus.notRecruiting(); + } + + public boolean notReadyOrOnGoing() { + return this.sessionProgressStatus.notReadyOrOnGoing(); + } + + public boolean chargedAndFull(int applySize) { + return this.charged() && applySizeFull(applySize); + } + + private boolean applySizeFull(int applySize) { + return this.sessionState.quota() == applySize; } @Override @@ -99,10 +119,10 @@ public int hashCode() { @Override public String toString() { return "SessionDetail{" + - "duration=" + sessionDuration + + "sessionDuration=" + sessionDuration + ", sessionState=" + sessionState + - ", sessionStatus=" + sessionProgressStatus + - ", recruitStatus=" + sessionRecruitStatus + + ", sessionProgressStatus=" + sessionProgressStatus + + ", sessionRecruitStatus=" + sessionRecruitStatus + '}'; } } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionDuration.java b/src/main/java/nextstep/courses/domain/course/session/SessionDuration.java index 67d0ca3f5a..cd8231a739 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionDuration.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionDuration.java @@ -4,9 +4,9 @@ import java.util.Objects; public class SessionDuration { - private LocalDate startDate; + private final LocalDate startDate; - private LocalDate endDate; + private final LocalDate endDate; public SessionDuration(LocalDate startDate, LocalDate endDate) { validate(startDate, endDate); @@ -39,11 +39,11 @@ public boolean changeDateIsBeforeOrSameWithEndDate(LocalDate date) { return date.isBefore(this.endDate) || date == this.endDate; } - public LocalDate getStartDate() { + public LocalDate startDate() { return startDate; } - public LocalDate getEndDate() { + public LocalDate endDate() { return endDate; } diff --git a/src/main/java/nextstep/courses/domain/course/session/SessionState.java b/src/main/java/nextstep/courses/domain/course/session/SessionState.java index 9a39b096f8..8db571d903 100644 --- a/src/main/java/nextstep/courses/domain/course/session/SessionState.java +++ b/src/main/java/nextstep/courses/domain/course/session/SessionState.java @@ -5,11 +5,11 @@ public class SessionState { private static final int MAX_APPLY = Integer.MAX_VALUE; - private SessionType sessionType; + private final SessionType sessionType; - private Long amount; + private final Long amount; - private int quota; + private final int quota; public SessionState() { this.sessionType = SessionType.FREE; @@ -46,15 +46,15 @@ private void checkTypeisCharged(Long amount, int quota) { } } - public SessionType getSessionType() { + public SessionType sessionType() { return sessionType; } - public Long getAmount() { + public Long amount() { return amount; } - public int getQuota() { + public int quota() { return quota; } diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java b/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java index df8ad5b198..6153ea1b2a 100644 --- a/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java +++ b/src/main/java/nextstep/courses/domain/course/session/apply/Applies.java @@ -1,5 +1,6 @@ package nextstep.courses.domain.course.session.apply; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -19,14 +20,39 @@ public int size() { return this.applies.size(); } - public List getApplies() { - return applies; + public boolean containsUserId(Long nsUserId) { + return this.applies.stream() + .anyMatch(apply -> apply.isSameWithUserId(nsUserId)); + } + + public Apply approve(Apply apply, LocalDateTime date) { + return this.applies.stream() + .filter(savedApply -> matchesNotApproved(apply, savedApply)) + .findAny() + .map(savedApply -> savedApply.approve(date)) + .orElseThrow(() -> new IllegalArgumentException("지원자가 대기, 취소 상태인지 확인하세요.")); + } + + private static boolean matchesNotApproved(Apply apply, Apply savedApply) { + return savedApply.isSame(apply) && savedApply.notApproved(); + } + + public Apply cancel(Apply apply, LocalDateTime date) { + return this.applies.stream() + .filter(savedApply -> matchesNotCanceled(apply, savedApply)) + .findAny() + .map(savedApply -> savedApply.cancel(date)) + .orElseThrow(() -> new IllegalArgumentException("지원자가 대기, 승인 상태인지 확인하세요.")); + } + + private boolean matchesNotCanceled(Apply apply, Apply savedApply) { + return savedApply.isSame(apply) && savedApply.notCanceled(); } @Override public String toString() { - return "Applicants{" + - "applicants=" + applies + + return "Applies{" + + "applies=" + applies + '}'; } diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java b/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java index 02721e9633..71d963a6c8 100644 --- a/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java +++ b/src/main/java/nextstep/courses/domain/course/session/apply/Apply.java @@ -6,30 +6,22 @@ import java.util.Objects; public class Apply extends BaseEntity { - private Long sessionId; + private final Long sessionId; - private Long nsUserId; + private final Long nsUserId; - private boolean approved; + private final ApprovalStatus approvalStatus; - public Apply(Long sessionId, Long nsUserId, boolean approved, LocalDateTime createdAt) { - this(sessionId, nsUserId, approved, nsUserId, createdAt, null); + public Apply(Long sessionId, Long nsUserId, ApprovalStatus approvalStatus, LocalDateTime createdAt) { + this(sessionId, nsUserId, approvalStatus, nsUserId, createdAt, null); } - public Apply(Long sessionId, Long nsUserId, boolean approved, Long creatorId, + public Apply(Long sessionId, Long nsUserId, ApprovalStatus approvalStatus, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { super(creatorId, createdAt, updatedAt); this.sessionId = sessionId; this.nsUserId = nsUserId; - this.approved = approved; - } - - public Long getSessionId() { - return sessionId; - } - - public Long getNsUserId() { - return nsUserId; + this.approvalStatus = approvalStatus; } public boolean isSame(Apply apply) { @@ -40,27 +32,34 @@ public boolean isSameWithUserId(Long nsUserId) { return Objects.equals(this.nsUserId, nsUserId); } - public boolean isApproved() { - return approved; + public Long sessionId() { + return sessionId; } - public boolean isCanceled() { - return !approved; + public Long nsUserId() { + return nsUserId; } - public Apply setApproved(boolean approved) { - this.approved = approved; - return this; + public ApprovalStatus approval() { + return approvalStatus; } public Apply approve(LocalDateTime date) { - return new Apply(this.sessionId, this.nsUserId, true, - this.getCreatorId(), this.getCreatedAt(), date); + return new Apply(this.sessionId, this.nsUserId, ApprovalStatus.APPROVED, + this.creatorId(), this.createdAt(), date); } public Apply cancel(LocalDateTime date) { - return new Apply(this.sessionId, this.nsUserId, false, - this.getCreatorId(), this.getCreatedAt(), date); + return new Apply(this.sessionId, this.nsUserId, ApprovalStatus.CANCELED, + this.creatorId(), this.createdAt(), date); + } + + public boolean notApproved() { + return !this.approvalStatus.approved(); + } + + public boolean notCanceled() { + return !this.approvalStatus.canceled(); } @Override @@ -68,12 +67,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Apply apply = (Apply) o; - return approved == apply.approved && Objects.equals(sessionId, apply.sessionId) && Objects.equals(nsUserId, apply.nsUserId); + return approvalStatus == apply.approvalStatus && Objects.equals(sessionId, apply.sessionId) && Objects.equals(nsUserId, apply.nsUserId); } @Override public int hashCode() { - return Objects.hash(sessionId, nsUserId, approved); + return Objects.hash(sessionId, nsUserId, approvalStatus); } @Override @@ -81,7 +80,7 @@ public String toString() { return "Apply{" + "sessionId=" + sessionId + ", nsUserId=" + nsUserId + - ", approved=" + approved + + ", approved=" + approvalStatus + '}'; } } diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/ApprovalStatus.java b/src/main/java/nextstep/courses/domain/course/session/apply/ApprovalStatus.java new file mode 100644 index 0000000000..b0286bc993 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/apply/ApprovalStatus.java @@ -0,0 +1,44 @@ +package nextstep.courses.domain.course.session.apply; + +import java.util.Arrays; + +public enum ApprovalStatus { + WAIT("대기중"), + APPROVED("승인"), + CANCELED("취소"); + + private final String description; + + ApprovalStatus(String description) { + this.description = description; + } + + public static ApprovalStatus find(String name) { + return Arrays.stream(values()) + .filter(status -> status.name().equals(name)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException( + String.format("허용하는 값은 다음과 같습니다.\n %s", descriptions()) + ) + ); + } + + public static String descriptions() { + StringBuilder sb = new StringBuilder(); + for (ApprovalStatus approvalStatus : values()) { + sb.append(approvalStatus.description).append(", "); + } + sb.setLength(sb.length() - 2); + + return sb.toString(); + } + + public boolean approved() { + return this == APPROVED; + } + + public boolean canceled() { + return this == CANCELED; + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/apply/ApproveCancel.java b/src/main/java/nextstep/courses/domain/course/session/apply/ApproveCancel.java new file mode 100644 index 0000000000..41b42b7d25 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/course/session/apply/ApproveCancel.java @@ -0,0 +1,31 @@ +package nextstep.courses.domain.course.session.apply; + +import nextstep.users.domain.NsUser; + +import java.time.LocalDateTime; + +public class ApproveCancel { + private final Applies applies; + + public ApproveCancel(Applies applies) { + this.applies = applies; + } + + public Apply approve(NsUser loginUser, Apply apply, LocalDateTime date) { + checkUserHasAuthor(loginUser); + + return this.applies.approve(apply, date); + } + + public Apply cancel(NsUser loginUser, Apply apply, LocalDateTime date) { + checkUserHasAuthor(loginUser); + + return this.applies.cancel(apply, date); + } + + private void checkUserHasAuthor(NsUser loginUser) { + if(!loginUser.hasAuthor()) { + throw new IllegalArgumentException("신청을 승인 할 권한이 없습니다."); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/course/session/image/Image.java b/src/main/java/nextstep/courses/domain/course/session/image/Image.java index 43dbe95d9c..b9f7082faf 100644 --- a/src/main/java/nextstep/courses/domain/course/session/image/Image.java +++ b/src/main/java/nextstep/courses/domain/course/session/image/Image.java @@ -11,15 +11,15 @@ public class Image extends BaseEntity { public static final int HEIGHT_MIN = 200; public static final double WIDTH_HEIGHT_RATIO = 1.5; - private Long id; + private final Long id; - private int imageSize; + private final int imageSize; - private ImageType imageType; + private final ImageType imageType; - private int imageWidth; + private final int imageWidth; - private int imageHeight; + private final int imageHeight; public Image(int imageSize, ImageType type, int imageWidth, int imageHeight, Long creatorId, LocalDateTime date) { this(0L, imageSize, type, imageWidth, imageHeight, creatorId, date, null); @@ -40,7 +40,7 @@ public Image(Long id, int imageSize, ImageType imageType, int imageWidth, int im } private static void checkImageSizeIsValid(int imageSize) { - if (imageSize > 1 * MB) { + if (imageSize > MB) { throw new IllegalArgumentException("사진 크기는 1MB를 넘을 수 없습니다."); } } @@ -59,27 +59,23 @@ private static void checkWidthAndHeightRatioIsValid(int imageWidth, int imageHei } } - public void setId(Long id) { - this.id = id; - } - - public Long getId() { + public Long id() { return id; } - public int getImageSize() { + public int imageSize() { return imageSize; } - public ImageType getImageType() { + public ImageType imageType() { return imageType; } - public int getImageWidth() { + public int imageWidth() { return imageWidth; } - public int getImageHeight() { + public int imageHeight() { return imageHeight; } diff --git a/src/main/java/nextstep/courses/domain/course/session/image/Images.java b/src/main/java/nextstep/courses/domain/course/session/image/Images.java index 58ba6ef087..8f108cb72d 100644 --- a/src/main/java/nextstep/courses/domain/course/session/image/Images.java +++ b/src/main/java/nextstep/courses/domain/course/session/image/Images.java @@ -1,11 +1,16 @@ package nextstep.courses.domain.course.session.image; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Images implements Iterable { private final List images; + public Images() { + this(new ArrayList<>()); + } + public Images(List images) { validate(images); @@ -22,10 +27,6 @@ private void checkImagesIsNull(List images) { } } - public void add(Image image) { - this.images.add(image); - } - @Override public Iterator iterator() { return this.images.iterator(); diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcApplyRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcApplyRepository.java index d1b81a1e67..1fb04c6c54 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcApplyRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcApplyRepository.java @@ -3,6 +3,7 @@ import nextstep.courses.domain.course.session.apply.Applies; import nextstep.courses.domain.course.session.apply.Apply; import nextstep.courses.domain.course.session.apply.ApplyRepository; +import nextstep.courses.domain.course.session.apply.ApprovalStatus; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; @@ -23,12 +24,12 @@ public JdbcApplyRepository(JdbcOperations jdbcTemplate) { @Override public Applies findAllBySessionId(Long sessionId) { String sql = "select " + - "session_id, ns_user_id, approved, creator_id, created_at, updated_at " + + "session_id, ns_user_id, approval_status, creator_id, created_at, updated_at " + "from apply where session_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Apply( rs.getLong(1), rs.getLong(2), - rs.getBoolean(3), + ApprovalStatus.find(rs.getString(3)), rs.getLong(4), rs.getTimestamp(5).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(6)) @@ -41,20 +42,20 @@ public Applies findAllBySessionId(Long sessionId) { @Override public Apply save(Apply apply) { String sql = "insert into apply " + - "(session_id, ns_user_id, approved, creator_id, created_at, updated_at) " + + "(session_id, ns_user_id, approval_status, creator_id, created_at, updated_at) " + "values(?, ?, ?, ?, ?, ?)"; - jdbcTemplate.update(sql, apply.getSessionId(), apply.getNsUserId(), apply.isApproved(), - apply.getCreatorId(), apply.getCreatedAt(), apply.getUpdatedAt()); + jdbcTemplate.update(sql, apply.sessionId(), apply.nsUserId(), apply.approval(), + apply.creatorId(), apply.createdAt(), apply.updatedAt()); return apply; } @Override public Apply update(Apply apply) { - String sql = "update apply set approved = ? where session_id = ? and ns_user_id = ?"; + String sql = "update apply set approval_status = ? where session_id = ? and ns_user_id = ?"; - jdbcTemplate.update(sql, apply.isApproved(), apply.getSessionId(), apply.getNsUserId()); + jdbcTemplate.update(sql, apply.approval(), apply.sessionId(), apply.nsUserId()); return apply; } @@ -62,12 +63,12 @@ public Apply update(Apply apply) { @Override public Optional findApplyByNsUserIdAndSessionId(Long nsUserId, Long sessionId) { String sql = "select " + - "session_id, ns_user_id, approved, creator_id, created_at, updated_at " + + "session_id, ns_user_id, approval_status, creator_id, created_at, updated_at " + "from apply where ns_user_id = ? and session_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Apply( rs.getLong(1), rs.getLong(2), - rs.getBoolean(3), + ApprovalStatus.find(rs.getString(3)), rs.getLong(4), rs.getTimestamp(5).toLocalDateTime(), toLocalDateTime(rs.getTimestamp(6))); diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index 5109a0fec1..96d325db82 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -6,15 +6,22 @@ import nextstep.courses.domain.course.session.Sessions; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.Objects; @Repository("courseRepository") public class JdbcCourseRepository implements CourseRepository { private final JdbcOperations jdbcTemplate; private final SessionRepository sessionRepository; + KeyHolder keyHolder = new GeneratedKeyHolder(); public JdbcCourseRepository(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -22,12 +29,24 @@ public JdbcCourseRepository(JdbcOperations jdbcTemplate) { } @Override - public int save(Course course) { + public Course save(Course course) { String sql = "insert into course " + "(title, ordering, creator_id, created_at) " + "values(?, ?, ?, ?)"; - return jdbcTemplate.update(sql, course.getTitle(), course.getOrdering(), - course.getCreatorId(), course.getCreatedAt()); + + jdbcTemplate.update((Connection connection) -> { + PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, course.title()); + ps.setInt(2, course.ordering()); + ps.setLong(3, course.creatorId()); + ps.setTimestamp(4, Timestamp.valueOf(course.createdAt())); + return ps; + }, keyHolder); + + Long courseId = Objects.requireNonNull(keyHolder.getKey()).longValue(); + + return new Course(courseId, course.title(), course.ordering(), course.sessions(), + course.creatorId(), course.createdAt(), course.updatedAt()); } @Override diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java index 39358ee994..9ecb4f9324 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcImageRepository.java @@ -47,7 +47,7 @@ public Optional findById(Long id) { @Override public Image save(Long sessionId, Image image) { - ImageType imageType = image.getImageType(); + ImageType imageType = image.imageType(); String sql = "insert into image " + "(image_size, image_type, image_width, image_height, session_id, creator_id, created_at, updated_at) " + @@ -55,21 +55,21 @@ public Image save(Long sessionId, Image image) { jdbcTemplate.update((Connection connection) -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); - ps.setInt(1, image.getImageSize()); + ps.setInt(1, image.imageSize()); ps.setString(2, imageType.name()); - ps.setInt(3, image.getImageWidth()); - ps.setInt(4, image.getImageHeight()); + ps.setInt(3, image.imageWidth()); + ps.setInt(4, image.imageHeight()); ps.setLong(5, sessionId); - ps.setLong(6, image.getCreatorId()); - ps.setTimestamp(7, Timestamp.valueOf(image.getCreatedAt())); - ps.setTimestamp(8, toTimeStamp(image.getCreatedAt())); + ps.setLong(6, image.creatorId()); + ps.setTimestamp(7, Timestamp.valueOf(image.createdAt())); + ps.setTimestamp(8, toTimeStamp(image.createdAt())); return ps; }, keyHolder); Long imageId = Objects.requireNonNull(keyHolder.getKey()).longValue(); - image.setId(imageId); - return image; + return new Image(imageId, image.imageSize(), image.imageType(), image.imageWidth(), + image.imageHeight(), image.creatorId(), image.createdAt(), image.updatedAt()); } @Override diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 73cf6e695f..a72431bf83 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -68,21 +68,21 @@ public Session save(Long courseId, Session session) { Session savedSession = saveSession(courseId, session); List images = new ArrayList<>(); - for (Image image : session.getImages()) { - Image savedImage = imageRepository.save(savedSession.getId(), image); + for (Image image : session.images()) { + Image savedImage = imageRepository.save(savedSession.id(), image); images.add(savedImage); } - savedSession.setImages(new Images(images)); - return savedSession; + return new Session(savedSession.id(), new Images(images), new Applies(), session.sessionDetail(), + session.creatorId(), session.createdAt(), session.updatedAt()); } private Session saveSession(Long courseId, Session session) { - SessionDuration sessionDuration = session.getDuration(); - SessionRecruitStatus sessionRecruitStatus = session.getSessionRecruitStatus(); - SessionState sessionState = session.getSessionState(); - SessionType sessionType = sessionState.getSessionType(); - SessionProgressStatus sessionProgressStatus = session.getSessionProgressStatus(); + SessionDuration sessionDuration = session.sessionDuration(); + SessionRecruitStatus sessionRecruitStatus = session.sessionRecruitStatus(); + SessionState sessionState = session.sessionState(); + SessionType sessionType = sessionState.sessionType(); + SessionProgressStatus sessionProgressStatus = session.sessionProgressStatus(); String sql = "insert into session " + "(start_date, end_date, session_type, session_status, amount, " + "recruit_status, quota, course_id, creator_id, created_at, updated_at) " + @@ -90,38 +90,38 @@ private Session saveSession(Long courseId, Session session) { jdbcTemplate.update((Connection connection) -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); - ps.setTimestamp(1, Timestamp.valueOf(sessionDuration.getStartDate().atStartOfDay())); - ps.setTimestamp(2, Timestamp.valueOf(sessionDuration.getEndDate().atStartOfDay())); + ps.setTimestamp(1, Timestamp.valueOf(sessionDuration.startDate().atStartOfDay())); + ps.setTimestamp(2, Timestamp.valueOf(sessionDuration.endDate().atStartOfDay())); ps.setString(3, sessionType.name()); ps.setString(4, sessionProgressStatus.name()); - ps.setLong(5, sessionState.getAmount()); + ps.setLong(5, sessionState.amount()); ps.setString(6, sessionRecruitStatus.name()); - ps.setInt(7, sessionState.getQuota()); + ps.setInt(7, sessionState.quota()); ps.setLong(8, courseId); - ps.setLong(9, session.getCreatorId()); - ps.setTimestamp(10, Timestamp.valueOf(session.getCreatedAt())); - ps.setTimestamp(11, toTimeStamp(session.getUpdatedAt())); + ps.setLong(9, session.creatorId()); + ps.setTimestamp(10, Timestamp.valueOf(session.createdAt())); + ps.setTimestamp(11, toTimeStamp(session.updatedAt())); return ps; }, keyHolder); Long sessionId = Objects.requireNonNull(keyHolder.getKey()).longValue(); - session.setId(sessionId); - return session; + return new Session(sessionId, new Images(), new Applies(), session.sessionDetail(), + session.creatorId(), session.createdAt(), session.updatedAt()); } @Override public int update(Long sessionId, Session session) { - SessionDuration sessionDuration = session.getDuration(); - SessionRecruitStatus sessionRecruitStatus = session.getSessionRecruitStatus(); - SessionState sessionState = session.getSessionState(); - SessionType sessionType = sessionState.getSessionType(); - SessionProgressStatus sessionProgressStatus = session.getSessionProgressStatus(); + SessionDuration sessionDuration = session.sessionDuration(); + SessionRecruitStatus sessionRecruitStatus = session.sessionRecruitStatus(); + SessionState sessionState = session.sessionState(); + SessionType sessionType = sessionState.sessionType(); + SessionProgressStatus sessionProgressStatus = session.sessionProgressStatus(); String sql = "update session set " + "start_date = ?, end_date = ?, session_type = ?, recruit_status = ?, amount = ?, quota = ?, session_status = ? " + "where id = ?"; - return jdbcTemplate.update(sql, sessionDuration.getStartDate(), sessionDuration.getEndDate(), sessionType.name(), - sessionRecruitStatus.name(), sessionState.getAmount(), sessionState.getQuota(), sessionProgressStatus.name(), sessionId); + return jdbcTemplate.update(sql, sessionDuration.startDate(), sessionDuration.endDate(), sessionType.name(), + sessionRecruitStatus.name(), sessionState.amount(), sessionState.quota(), sessionProgressStatus.name(), sessionId); } @Override @@ -156,7 +156,7 @@ public Sessions findAllByCourseId(Long courseId) { @Override public int updateCourseId(Long courseId, Session session) { String sql = "update session set course_id = ? where id = ?"; - return jdbcTemplate.update(sql, session.getId(), courseId); + return jdbcTemplate.update(sql, session.id(), courseId); } private Images findAllImagesBySessionId(Long id) { diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java index b919ecd942..b5d25a4061 100644 --- a/src/main/java/nextstep/courses/service/SessionService.java +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -3,6 +3,7 @@ import nextstep.courses.domain.course.session.*; import nextstep.courses.domain.course.session.apply.Apply; import nextstep.courses.domain.course.session.apply.ApplyRepository; +import nextstep.courses.domain.course.session.apply.ApproveCancel; import nextstep.payments.domain.Payment; import nextstep.qna.NotFoundException; import nextstep.users.domain.NsUser; @@ -34,35 +35,35 @@ public void apply(NsUser loginUser, Long sessionId, Payment payment, LocalDateTi public void approve(NsUser loginUser, Long applicantId, Long sessionId, LocalDateTime date) { Session session = getSession(sessionId); Apply savedApply = getApply(sessionId, applicantId); - Approve approve = session.approve(); - Apply apply = approve.approve(loginUser, savedApply, date); + ApproveCancel approveCancel = session.approve(); + Apply apply = approveCancel.approve(loginUser, savedApply, date); applyRepository.update(apply); } public void cancel(NsUser loginUser, Long applicantId, Long sessionId, LocalDateTime date) { Session session = getSession(sessionId); Apply savedApply = getApply(sessionId, applicantId); - Cancel cancel = session.cancel(); - Apply apply = cancel.cancel(loginUser, savedApply, date); + ApproveCancel approveCancel = session.cancel(); + Apply apply = approveCancel.cancel(loginUser, savedApply, date); applyRepository.update(apply); } public void changeOnReady(Long sessionId, LocalDate date) { Session session = getSession(sessionId); - session.changeOnReady(date); - sessionRepository.update(sessionId, session); + Session updatedSession = session.changeOnReady(date); + sessionRepository.update(sessionId, updatedSession); } public void changeOnGoing(Long sessionId, LocalDate date) { Session session = getSession(sessionId); - session.changeOnGoing(date); - sessionRepository.update(sessionId, session); + Session updatedSession = session.changeOnGoing(date); + sessionRepository.update(sessionId, updatedSession); } public void changeOnEnd(Long sessionId, LocalDate date) { Session session = getSession(sessionId); - session.changeOnEnd(date); - sessionRepository.update(sessionId, session); + Session updatedSession = session.changeOnEnd(date); + sessionRepository.update(sessionId, updatedSession); } private Session getSession(Long sessionId) { diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index ea8c491f87..f257a5cde2 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -14,7 +14,7 @@ create table image ( create table apply ( session_id bigint not null, ns_user_id bigint not null, - approved boolean not null, + approval_status varchar(20) not null, creator_id bigint not null, created_at timestamp not null, updated_at timestamp, diff --git a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java index 81dc54b799..fbb2b3da3a 100644 --- a/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/CourseServiceTest.java @@ -2,6 +2,7 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.domain.course.session.Session; import nextstep.courses.domain.course.session.SessionRepository; import nextstep.courses.fixture.CourseFixtures; import nextstep.courses.fixture.SessionFixtures; @@ -13,7 +14,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -32,11 +33,11 @@ public class CourseServiceTest { void addSession_success() { Course course = CourseFixtures.course(); - when(courseRepository.findById(course.getId())).thenReturn(course); + when(courseRepository.findById(course.id())).thenReturn(course); - assertThat(course.sessionSize()).isEqualTo(0); + Session session = SessionFixtures.createdFreeSession(); + courseService.addSession(course.id(), session); - courseService.addSession(course.getId(), SessionFixtures.createdFreeSession()); - assertThat(course.sessionSize()).isEqualTo(1); + verify(sessionRepository).updateCourseId(course.id(), session); } } diff --git a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java index eeb55fc6e2..9a6069e74b 100644 --- a/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java +++ b/src/test/java/nextstep/courses/domain/course/service/SessionServiceTest.java @@ -1,9 +1,9 @@ package nextstep.courses.domain.course.service; -import nextstep.courses.domain.course.session.SessionRecruitStatus; import nextstep.courses.domain.course.session.Session; -import nextstep.courses.domain.course.session.SessionRepository; import nextstep.courses.domain.course.session.SessionProgressStatus; +import nextstep.courses.domain.course.session.SessionRecruitStatus; +import nextstep.courses.domain.course.session.SessionRepository; import nextstep.courses.domain.course.session.apply.ApplyRepository; import nextstep.courses.fixture.ApplyFixtures; import nextstep.courses.fixture.SessionFixtures; @@ -21,6 +21,7 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -38,64 +39,66 @@ public class SessionServiceTest { @DisplayName("주어진 강의 정보로 강의를 생성한다.") void create_success() { Session savedSession = SessionFixtures.createdFreeSession(); - when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + when(sessionRepository.findById(savedSession.id())).thenReturn(Optional.of(savedSession)); sessionService.create(1L, savedSession); Session findSession = sessionRepository.findById(1L).orElseThrow(NotFoundException::new); - assertThat(findSession.getId()).isEqualTo(1L); - assertThat(findSession.getSessionDetail()).isEqualTo(savedSession.getSessionDetail()); + assertThat(findSession.id()).isEqualTo(1L); + assertThat(findSession.sessionDetail()).isEqualTo(savedSession.sessionDetail()); } @Test @DisplayName("수강 신청은 수강 신청 인원에 해당 인원이 추가된다.") void apply_success() { - Session savedSession = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING); - when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + Session savedSession = SessionFixtures.createdChargedSession( + SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING + ); + when(sessionRepository.findById(savedSession.id())).thenReturn(Optional.of(savedSession)); sessionService.apply(NsUserFixtures.TEACHER_JAVAJIGI_1L, - savedSession.getId(), + savedSession.id(), PaymentFixtures.payment(), SessionFixtures.DATETIME_2023_12_5 ); - assertThat(savedSession.applyCount()).isEqualTo(1); + verify(applyRepository).save(ApplyFixtures.apply_one_wait()); } @Test @DisplayName("changeOnReady 는 강의 종료일보다 빠르면 강의를 준비중으로 변경 한다.") void changeOnReady_success() { Session savedSession = SessionFixtures.createdFreeSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING); - when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + when(sessionRepository.findById(savedSession.id())).thenReturn(Optional.of(savedSession)); - sessionService.changeOnReady(savedSession.getId(), SessionFixtures.DATE_2023_12_5); + sessionService.changeOnReady(savedSession.id(), SessionFixtures.DATE_2023_12_5); - assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionProgressStatus()).isEqualTo(SessionProgressStatus.READY); + Session updatedSession = savedSession.changeOnReady(SessionFixtures.DATE_2023_12_5); + verify(sessionRepository).update(savedSession.id(), updatedSession); } @Test @DisplayName("changeOnGoing 는 강의 종료일보다 빠르면 강의를 진행중으로 변경 한다.") void changeOnGoing_success() { Session savedSession = SessionFixtures.createdFreeSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY); - when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + when(sessionRepository.findById(savedSession.id())).thenReturn(Optional.of(savedSession)); - sessionService.changeOnGoing(savedSession.getId(), SessionFixtures.DATE_2023_12_6); + sessionService.changeOnGoing(savedSession.id(), SessionFixtures.DATE_2023_12_6); - assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionProgressStatus()).isEqualTo(SessionProgressStatus.ONGOING); + Session updatedSession = savedSession.changeOnGoing(SessionFixtures.DATE_2023_12_6); + verify(sessionRepository).update(savedSession.id(), updatedSession); } @Test @DisplayName("changeOnEnd 는 강의 종료일보다 늦으면 강의를 종료로 변경 한다.") void changeOnEnd_success() { Session savedSession = SessionFixtures.createdFreeSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY); - when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + when(sessionRepository.findById(savedSession.id())).thenReturn(Optional.of(savedSession)); - sessionService.changeOnEnd(savedSession.getId(), SessionFixtures.DATE_2023_12_12); + sessionService.changeOnEnd(savedSession.id(), SessionFixtures.DATE_2023_12_12); - assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionProgressStatus()).isEqualTo(SessionProgressStatus.END); + Session updatedSession = savedSession.changeOnEnd(SessionFixtures.DATE_2023_12_12); + verify(sessionRepository).update(savedSession.id(), updatedSession); } @Test @@ -104,17 +107,16 @@ void approve_success() { Session savedSession = SessionFixtures.chargedSessionFullCanceled(); when(applyRepository.findApplyByNsUserIdAndSessionId(1L, 1L)) .thenReturn(Optional.of(ApplyFixtures.apply_one_canceled())); - when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + when(sessionRepository.findById(savedSession.id())).thenReturn(Optional.of(savedSession)); sessionService.approve(NsUserFixtures.TEACHER_JAVAJIGI_1L, - savedSession.getId(), + savedSession.id(), 1L, SessionFixtures.DATETIME_2023_12_5 ); - assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionDetail().getSessionState().getApplies()) - .contains(ApplyFixtures.apply_one_approved()); + assertThat(savedSession.id()).isEqualTo(1L); + verify(applyRepository).update(ApplyFixtures.apply_one_approved()); } @Test @@ -123,16 +125,15 @@ void cancel_success() { Session savedSession = SessionFixtures.chargedSessionFullApproved(); when(applyRepository.findApplyByNsUserIdAndSessionId(1L, 1L)) .thenReturn(Optional.of(ApplyFixtures.apply_one_approved())); - when(sessionRepository.findById(savedSession.getId())).thenReturn(Optional.of(savedSession)); + when(sessionRepository.findById(savedSession.id())).thenReturn(Optional.of(savedSession)); sessionService.cancel(NsUserFixtures.TEACHER_JAVAJIGI_1L, - savedSession.getId(), + savedSession.id(), 1L, SessionFixtures.DATETIME_2023_12_5 ); - assertThat(savedSession.getId()).isEqualTo(1L); - assertThat(savedSession.getSessionDetail().getSessionState().getApplies()) - .contains(ApplyFixtures.apply_one_canceled()); + assertThat(savedSession.id()).isEqualTo(1L); + verify(applyRepository).update(ApplyFixtures.apply_one_canceled()); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/EnrollmentTest.java b/src/test/java/nextstep/courses/domain/course/session/EnrollmentTest.java new file mode 100644 index 0000000000..12f8abd7d8 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/EnrollmentTest.java @@ -0,0 +1,131 @@ +package nextstep.courses.domain.course.session; + +import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.courses.fixture.SessionFixtures; +import nextstep.payments.fixture.PaymentFixtures; +import nextstep.users.fixtures.NsUserFixtures; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class EnrollmentTest { + private Session session; + private Enrollment enrollment; + + @Test + @DisplayName("수강 신청은 모집 중, 준비 중이면 새로운 신청을 반환 한다.") + void apply_recruit_ready_success() { + session = SessionFixtures.createdChargedSession( + SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY + ); + + enrollment = session.enrollment(); + Apply newApply = enrollment.apply( + NsUserFixtures.TEACHER_APPLE_3L.getId(), + PaymentFixtures.payment(1L, 3L), + SessionFixtures.DATETIME_2023_12_5 + ); + + assertThat(newApply.sessionId()).isEqualTo(session.id()); + assertThat(newApply.nsUserId()).isEqualTo(NsUserFixtures.TEACHER_APPLE_3L.getId()); + } + + @Test + @DisplayName("수강 신청은 모집 중, 진행 중이면 새로운 신청을 반환 한다.") + void apply_recruit_ongoing_success() { + session = SessionFixtures.createdChargedSession( + SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING + ); + + enrollment = session.enrollment(); + Apply newApply = enrollment.apply( + NsUserFixtures.TEACHER_APPLE_3L.getId(), + PaymentFixtures.payment(1L, 3L), + SessionFixtures.DATETIME_2023_12_5 + ); + + assertThat(newApply.sessionId()).isEqualTo(session.id()); + assertThat(newApply.nsUserId()).isEqualTo(NsUserFixtures.TEACHER_APPLE_3L.getId()); + } + + @Test + @DisplayName("수강 신청은 비 모집 중이면 신청할 수 없다는 예외를 반환 한다.") + void apply_notRecruitStatus_throwsException() { + session = SessionFixtures.createdChargedSession( + SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.ONGOING + ); + enrollment = session.enrollment(); + + assertThatThrownBy( + () -> enrollment.apply( + NsUserFixtures.TEACHER_APPLE_3L.getId(), + PaymentFixtures.payment(), + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("수강 신청은 모집 중, 종료 라면 신청할 수 없다는 예외를 반환 한다.") + void apply_recruitStatus_endStatus_throwsException() { + session = SessionFixtures.createdChargedSession( + SessionRecruitStatus.RECRUIT, SessionProgressStatus.END + ); + enrollment = session.enrollment(); + + assertThatThrownBy( + () -> enrollment.apply( + NsUserFixtures.TEACHER_APPLE_3L.getId(), + PaymentFixtures.payment(), + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("수강 신청은 유료 강의의 경우, 수강 인원 정원을 초과 하면 신청할 수 없다는 예외를 반환 한다.") + void apply_chargeSession_overQuota_throwsException() { + session = SessionFixtures.chargedSessionFullCanceled(); + enrollment = session.enrollment(); + + assertThatThrownBy( + () -> enrollment.apply( + NsUserFixtures.TEACHER_APPLE_3L.getId(), + PaymentFixtures.payment(), + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("수강 신청은 유료 강의 결제가 안 되었다면 신청할 수 없다는 예외를 반환 한다.") + void apply_chargeSession_notPaid_throwsException() { + session = SessionFixtures.createdChargedSession(); + enrollment = session.enrollment(); + + assertThatThrownBy( + () -> enrollment.apply( + NsUserFixtures.TEACHER_APPLE_3L.getId(), + null, + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("수강 신청은 수강 금액과 지불 금액이 다르면 신청할 수 없다는 예외를 던진다.") + void apply_chargeSession_differentAmount_throwsException() { + session = SessionFixtures.createdChargedSession(); + enrollment = session.enrollment(); + + assertThatThrownBy( + () -> enrollment.apply( + NsUserFixtures.TEACHER_APPLE_3L.getId(), + PaymentFixtures.differentPayment(), + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java index 51f963ce7b..3d9aa43a63 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionStateTest.java @@ -1,6 +1,5 @@ package nextstep.courses.domain.course.session; -import nextstep.courses.domain.course.session.apply.Applies; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,7 +10,7 @@ public class SessionStateTest { @DisplayName("SessionState 는 무료 강의가 0원이 아니면 예외를 던진다") void newObject_freeType_overZeroAmount_throwsException() { assertThatThrownBy( - () -> new SessionState(SessionType.FREE, 1000L, Integer.MAX_VALUE, new Applies()) + () -> new SessionState(SessionType.FREE, 1000L, Integer.MAX_VALUE) ).isInstanceOf(IllegalArgumentException.class); } @@ -19,7 +18,7 @@ void newObject_freeType_overZeroAmount_throwsException() { @DisplayName("SessionState 는 무료 강의가 정원이 최대가 아니면 예외를 던진다.") void newObject_freeType_lessThanMaxQuota_throwsException() { assertThatThrownBy( - () -> new SessionState(SessionType.FREE, 0L, 100, new Applies()) + () -> new SessionState(SessionType.FREE, 0L, 100) ).isInstanceOf(IllegalArgumentException.class); } @@ -27,7 +26,7 @@ void newObject_freeType_lessThanMaxQuota_throwsException() { @DisplayName("SessionState 는 유료 강의가 0원이면 예외를 던진다.") void newObject_chargedType_zeroAmount_throwsException() { assertThatThrownBy( - () -> new SessionState(SessionType.CHARGE, 0L, 100, new Applies()) + () -> new SessionState(SessionType.CHARGE, 0L, 100) ).isInstanceOf(IllegalArgumentException.class); } @@ -35,7 +34,7 @@ void newObject_chargedType_zeroAmount_throwsException() { @DisplayName("SessionState 는 유료 강의가 정원 수가 0명이면 예외를 던진다.") void newObject_chargedType_zeroQuota_throwsException() { assertThatThrownBy( - () -> new SessionState(SessionType.CHARGE, 100L, 0, new Applies()) + () -> new SessionState(SessionType.CHARGE, 100L, 0) ).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java index 2a422aa542..ec7507ad86 100644 --- a/src/test/java/nextstep/courses/domain/course/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/course/session/SessionTest.java @@ -1,11 +1,7 @@ package nextstep.courses.domain.course.session; -import nextstep.courses.domain.course.session.apply.Apply; -import nextstep.courses.fixture.ApplyFixtures; import nextstep.courses.fixture.ImageFixtures; import nextstep.courses.fixture.SessionFixtures; -import nextstep.payments.fixture.PaymentFixtures; -import nextstep.users.fixtures.NsUserFixtures; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -57,108 +53,6 @@ void newObject_sessionStateNull_throwsException() { ).isInstanceOf(IllegalArgumentException.class); } - @Test - @DisplayName("수강 신청은 모집 중, 준비 중이면 해당 인원이 추가 된다.") - void apply_recruit_ready_success() { - session = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY); - - int size = session.applyCount(); - - session.apply( - NsUserFixtures.TEACHER_APPLE_3L, - PaymentFixtures.payment(1L, 3L), - SessionFixtures.DATETIME_2023_12_5 - ); - - assertThat(session.applyCount()).isEqualTo(size + 1); - } - - @Test - @DisplayName("수강 신청은 모집 중, 진행 중이면 해당 인원이 추가 된다.") - void apply_recruit_ongoing_success() { - session = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.ONGOING); - - int size = session.applyCount(); - - session.apply( - NsUserFixtures.TEACHER_APPLE_3L, - PaymentFixtures.payment(1L, 3L), - SessionFixtures.DATETIME_2023_12_5 - ); - - assertThat(session.applyCount()).isEqualTo(size + 1); - } - - @Test - @DisplayName("수강 신청은 비 모집 중이면 신청할 수 없다는 예외를 반환 한다.") - void apply_notRecruitStatus_throwsException() { - session = SessionFixtures.createdChargedSession(SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.ONGOING); - - assertThatThrownBy( - () -> session.apply( - NsUserFixtures.TEACHER_APPLE_3L, - PaymentFixtures.payment(), - SessionFixtures.DATETIME_2023_12_5 - ) - ).isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("수강 신청은 모집 중, 종료 라면 신청할 수 없다는 예외를 반환 한다.") - void apply_recruitStatus_endStatus_throwsException() { - session = SessionFixtures.createdChargedSession(SessionRecruitStatus.RECRUIT, SessionProgressStatus.END); - - assertThatThrownBy( - () -> session.apply( - NsUserFixtures.TEACHER_APPLE_3L, - PaymentFixtures.payment(), - SessionFixtures.DATETIME_2023_12_5 - ) - ).isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("수강 신청은 유료 강의의 경우, 수강 인원 정원을 초과 하면 신청할 수 없다는 예외를 반환 한다.") - void apply_chargeSession_overQuota_throwsException() { - session = SessionFixtures.chargedSessionFullCanceled(); - - assertThatThrownBy( - () -> session.apply( - NsUserFixtures.TEACHER_APPLE_3L, - PaymentFixtures.payment(), - SessionFixtures.DATETIME_2023_12_5 - ) - ).isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("수강 신청은 유료 강의 결제가 안 되었다면 신청할 수 없다는 예외를 반환 한다.") - void apply_chargeSession_notPaid_throwsException() { - session = SessionFixtures.createdChargedSession(); - - assertThatThrownBy( - () -> session.apply( - NsUserFixtures.TEACHER_APPLE_3L, - null, - SessionFixtures.DATETIME_2023_12_5 - ) - ).isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("수강 신청은 수강 금액과 지불 금액이 다르면 신청할 수 없다는 예외를 던진다.") - void apply_chargeSession_differentAmount_throwsException() { - session = SessionFixtures.createdChargedSession(); - - assertThatThrownBy( - () -> session.apply( - NsUserFixtures.TEACHER_APPLE_3L, - PaymentFixtures.differentPayment(), - SessionFixtures.DATETIME_2023_12_5 - ) - ).isInstanceOf(IllegalArgumentException.class); - } - @Test @DisplayName("changeOnReady 는 변경할 날짜가 강의 종료일과 같거나 늦으면 예외를 던진다.") void changeOnReady_changeDateIsSameOrAfterWithEndDate_throwsException() { @@ -174,114 +68,60 @@ void changeOnReady_changeDateIsSameOrAfterWithEndDate_throwsException() { } @Test - @DisplayName("changeOnGoing 는 변경할 날짜가 강의 종료일과 같거나 늦으면 예외를 던진다.") - void changeOnGoing_changeDateIsSameOrAfterWithEndDate_throwsException() { - session = SessionFixtures.createdFreeSession(); + @DisplayName("changeOnReady 는 강의를 준비중으로 변경한다.") + void changeOnReady_success() { + session = SessionFixtures.createdFreeSession(SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.ONGOING); - assertThatThrownBy( - () -> session.changeOnGoing(SessionFixtures.DATE_2023_12_10) - ).isInstanceOf(IllegalArgumentException.class); + Session updatedSession = session.changeOnReady(SessionFixtures.DATE_2023_12_5); - assertThatThrownBy( - () -> session.changeOnGoing(SessionFixtures.DATE_2023_12_12) - ).isInstanceOf(IllegalArgumentException.class); + assertThat(updatedSession.sessionProgressStatus()).isEqualTo(SessionProgressStatus.READY); } @Test - @DisplayName("changeOnEnd 는 변경할 날짜가 강의 종료일보다 빠르거나 같다면 예외를 던진다.") - void changeOnEnd_changeDateIsBeforeOrSameWithEndDate_throwsException() { + @DisplayName("changeOnGoing 는 변경할 날짜가 강의 종료일과 같거나 늦으면 예외를 던진다.") + void changeOnGoing_changeDateIsSameOrAfterWithEndDate_throwsException() { session = SessionFixtures.createdFreeSession(); assertThatThrownBy( - () -> session.changeOnEnd(SessionFixtures.DATE_2023_12_6) + () -> session.changeOnGoing(SessionFixtures.DATE_2023_12_10) ).isInstanceOf(IllegalArgumentException.class); assertThatThrownBy( - () -> session.changeOnEnd(SessionFixtures.DATE_2023_12_10) + () -> session.changeOnGoing(SessionFixtures.DATE_2023_12_12) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("approve 는 선생님인 경우 수강생의 강의 신청을 승인한다.") - void approve_teacher_changeApproveTrue() { - session = SessionFixtures.chargedSessionFullCanceled(); + @DisplayName("changeOnGoing 는 강의를 진행중으로 변경한다.") + void changeOnGoing_success() { + session = SessionFixtures.createdFreeSession(SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.READY); - Apply changedApply = session.approve - (NsUserFixtures.TEACHER_JAVAJIGI_1L, - ApplyFixtures.apply_one_canceled(), - SessionFixtures.DATETIME_2023_12_5 - ); + Session updatedSession = session.changeOnGoing(SessionFixtures.DATE_2023_12_5); - assertThat(changedApply.isApproved()).isTrue(); + assertThat(updatedSession.sessionProgressStatus()).isEqualTo(SessionProgressStatus.ONGOING); } @Test - @DisplayName("approve 는 학생인 경우 권한이 없다는 예외를 던진다.") - void approve_student_throwsException() { - session = SessionFixtures.chargedSessionFullCanceled(); + @DisplayName("changeOnEnd 는 변경할 날짜가 강의 종료일보다 빠르거나 같다면 예외를 던진다.") + void changeOnEnd_changeDateIsBeforeOrSameWithEndDate_throwsException() { + session = SessionFixtures.createdFreeSession(); assertThatThrownBy( - () -> session.approve( - NsUserFixtures.STUDENT_ERIC_4L, - ApplyFixtures.apply_one_canceled(), - SessionFixtures.DATETIME_2023_12_5 - ) + () -> session.changeOnEnd(SessionFixtures.DATE_2023_12_6) ).isInstanceOf(IllegalArgumentException.class); - } - - @Test - @DisplayName("approve 는 이미 수강 승인이 되었으면 예외를 던진다.") - void approve_alreadyApproved_throwsException() { - session = SessionFixtures.chargedSessionFullApproved(); assertThatThrownBy( - () -> session.approve( - NsUserFixtures.TEACHER_JAVAJIGI_1L, - ApplyFixtures.apply_one_approved(), - SessionFixtures.DATETIME_2023_12_5 - ) + () -> session.changeOnEnd(SessionFixtures.DATE_2023_12_10) ).isInstanceOf(IllegalArgumentException.class); } @Test - @DisplayName("cancel 는 선생님인 경우 수강생의 강의 신청을 취소한다.") - void cancel_teacher_changeApproveTrue() { - session = SessionFixtures.chargedSessionFullApproved(); - - Apply changedApply = session.cancel( - NsUserFixtures.TEACHER_JAVAJIGI_1L, - ApplyFixtures.apply_one_canceled(), - SessionFixtures.DATETIME_2023_12_5 - ); - - assertThat(changedApply.isApproved()).isFalse(); - } - - @Test - @DisplayName("cancel 는 학생인 경우 권한이 없다는 예외를 던진다.") - void cancel_student_throwsException() { - session = SessionFixtures.chargedSessionFullApproved(); - - assertThatThrownBy( - () -> session.cancel( - NsUserFixtures.STUDENT_ERIC_4L, - ApplyFixtures.apply_one_approved(), - SessionFixtures.DATETIME_2023_12_5 - ) - ).isInstanceOf(IllegalArgumentException.class); - } + @DisplayName("changeOnEnd 는 강의를 종료로 변경한다.") + void changeOnEnd_success() { + session = SessionFixtures.createdFreeSession(SessionRecruitStatus.NOT_RECRUIT, SessionProgressStatus.READY); - @Test - @DisplayName("cancel 는 이미 수강 취소가 되었으면 예외를 던진다.") - void cancel_alreadyCanceled_throwsException() { - session = SessionFixtures.chargedSessionFullCanceled(); + Session updatedSession = session.changeOnEnd(SessionFixtures.DATE_2023_12_12); - assertThatThrownBy( - () -> session.cancel( - NsUserFixtures.TEACHER_JAVAJIGI_1L, - ApplyFixtures.apply_one_canceled(), - SessionFixtures.DATETIME_2023_12_5 - ) - ).isInstanceOf(IllegalArgumentException.class); + assertThat(updatedSession.sessionProgressStatus()).isEqualTo(SessionProgressStatus.END); } } diff --git a/src/test/java/nextstep/courses/domain/course/session/apply/ApprovalCancelTest.java b/src/test/java/nextstep/courses/domain/course/session/apply/ApprovalCancelTest.java new file mode 100644 index 0000000000..4f965b663c --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/apply/ApprovalCancelTest.java @@ -0,0 +1,106 @@ +package nextstep.courses.domain.course.session.apply; + +import nextstep.courses.domain.course.session.Session; +import nextstep.courses.fixture.ApplyFixtures; +import nextstep.courses.fixture.SessionFixtures; +import nextstep.users.fixtures.NsUserFixtures; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ApprovalCancelTest { + private Session session; + private ApproveCancel approveCancel; + + @Test + @DisplayName("approve 는 선생님인 경우 수강생의 강의 신청을 승인한다.") + void approve_teacher_changeApproveTrue() { + session = SessionFixtures.chargedSessionFullCanceled(); + approveCancel = session.approve(); + + Apply changedApply = approveCancel.approve + (NsUserFixtures.TEACHER_JAVAJIGI_1L, + ApplyFixtures.apply_one_canceled(), + SessionFixtures.DATETIME_2023_12_5 + ); + + assertThat(changedApply.approval()).isEqualTo(ApprovalStatus.APPROVED); + } + + @Test + @DisplayName("approve 는 학생인 경우 권한이 없다는 예외를 던진다.") + void approve_student_throwsException() { + session = SessionFixtures.chargedSessionFullCanceled(); + approveCancel = session.approve(); + + assertThatThrownBy( + () -> approveCancel.approve( + NsUserFixtures.STUDENT_ERIC_4L, + ApplyFixtures.apply_one_canceled(), + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("approve 는 이미 수강 승인이 되었으면 예외를 던진다.") + void approve_alreadyApproved_throwsException() { + session = SessionFixtures.chargedSessionFullApproved(); + approveCancel = session.approve(); + + assertThatThrownBy( + () -> approveCancel.approve( + NsUserFixtures.TEACHER_JAVAJIGI_1L, + ApplyFixtures.apply_one_approved(), + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("cancel 는 선생님인 경우 수강생의 강의 신청을 취소한다.") + void cancel_teacher_changeApproveTrue() { + session = SessionFixtures.chargedSessionFullApproved(); + approveCancel = session.approve(); + + Apply changedApply = approveCancel.cancel( + NsUserFixtures.TEACHER_JAVAJIGI_1L, + ApplyFixtures.apply_one_canceled(), + SessionFixtures.DATETIME_2023_12_5 + ); + + assertThat(changedApply.approval()).isEqualTo(ApprovalStatus.CANCELED); + } + + @Test + @DisplayName("cancel 는 학생인 경우 권한이 없다는 예외를 던진다.") + void cancel_student_throwsException() { + session = SessionFixtures.chargedSessionFullApproved(); + approveCancel = session.approve(); + + assertThatThrownBy( + () -> approveCancel.cancel( + NsUserFixtures.STUDENT_ERIC_4L, + ApplyFixtures.apply_one_approved(), + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("cancel 는 이미 수강 취소가 되었으면 예외를 던진다.") + void cancel_alreadyCanceled_throwsException() { + session = SessionFixtures.chargedSessionFullCanceled(); + approveCancel = session.approve(); + + assertThatThrownBy( + () -> approveCancel.cancel( + NsUserFixtures.TEACHER_JAVAJIGI_1L, + ApplyFixtures.apply_one_canceled(), + SessionFixtures.DATETIME_2023_12_5 + ) + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/domain/course/session/apply/ApprovalStatusTest.java b/src/test/java/nextstep/courses/domain/course/session/apply/ApprovalStatusTest.java new file mode 100644 index 0000000000..8c45aa4012 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/course/session/apply/ApprovalStatusTest.java @@ -0,0 +1,16 @@ +package nextstep.courses.domain.course.session.apply; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ApprovalStatusTest { + @Test + @DisplayName("find는 존재하지 않는 값을 입력하면 찾을 수 없다는 예외를 던진다.") + void find_notExistedName_throwsException() { + assertThatThrownBy( + () -> ApprovalStatus.find("abcd") + ).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/nextstep/courses/fixture/ApplyFixtures.java b/src/test/java/nextstep/courses/fixture/ApplyFixtures.java index 706e8e136e..19017a1327 100644 --- a/src/test/java/nextstep/courses/fixture/ApplyFixtures.java +++ b/src/test/java/nextstep/courses/fixture/ApplyFixtures.java @@ -2,20 +2,25 @@ import nextstep.courses.domain.course.session.apply.Applies; import nextstep.courses.domain.course.session.apply.Apply; +import nextstep.courses.domain.course.session.apply.ApprovalStatus; import java.util.List; public class ApplyFixtures { + public static Apply apply_one_wait() { + return new Apply(1L, 1L, ApprovalStatus.WAIT, SessionFixtures.DATETIME_2023_12_5); + } + public static Applies applies_two_canceled() { return new Applies(List.of(apply_one_canceled(), apply_two_canceled())); } public static Apply apply_one_canceled() { - return new Apply(1L, 1L, false, SessionFixtures.DATETIME_2023_12_5); + return new Apply(1L, 1L, ApprovalStatus.CANCELED, SessionFixtures.DATETIME_2023_12_5); } public static Apply apply_two_canceled() { - return new Apply(1L, 2L, false, SessionFixtures.DATETIME_2023_12_5); + return new Apply(1L, 2L, ApprovalStatus.CANCELED, SessionFixtures.DATETIME_2023_12_5); } public static Applies applies_two_approved() { @@ -23,10 +28,10 @@ public static Applies applies_two_approved() { } public static Apply apply_one_approved() { - return new Apply(1L, 1L, true, SessionFixtures.DATETIME_2023_12_5); + return new Apply(1L, 1L, ApprovalStatus.APPROVED, SessionFixtures.DATETIME_2023_12_5); } public static Apply apply_two_approved() { - return new Apply(1L, 2L, true, SessionFixtures.DATETIME_2023_12_5); + return new Apply(1L, 2L, ApprovalStatus.APPROVED, SessionFixtures.DATETIME_2023_12_5); } } diff --git a/src/test/java/nextstep/courses/fixture/SessionFixtures.java b/src/test/java/nextstep/courses/fixture/SessionFixtures.java index baae92a39d..5b96642e67 100644 --- a/src/test/java/nextstep/courses/fixture/SessionFixtures.java +++ b/src/test/java/nextstep/courses/fixture/SessionFixtures.java @@ -25,6 +25,7 @@ public static Session createdFreeSession(SessionRecruitStatus sessionRecruitStat return new Session( 1L, ImageFixtures.images(), + new Applies(), new SessionDuration(DATE_2023_12_5, DATE_2023_12_10), freeSessionStateZero(), sessionRecruitStatus, @@ -44,8 +45,9 @@ public static Session createdChargedSession( return new Session( 1L, ImageFixtures.images(), + new Applies(), new SessionDuration(DATE_2023_12_5, DATE_2023_12_10), - chargedSessionStateZero(1000L, 2, new Applies()), + chargedSessionStateZero(1000L, 2), sessionRecruitStatus, sessionProgressStatus, 1L, @@ -58,8 +60,9 @@ public static Session chargedSessionFullCanceled() { return new Session( 1L, ImageFixtures.images(), + ApplyFixtures.applies_two_canceled(), new SessionDuration(DATE_2023_12_5, DATE_2023_12_10), - chargedSessionStateZero(1000L, 2, ApplyFixtures.applies_two_canceled()), + chargedSessionStateZero(1000L, 2), SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY, 1L, @@ -72,8 +75,9 @@ public static Session chargedSessionFullApproved() { return new Session( 1L, ImageFixtures.images(), + ApplyFixtures.applies_two_approved(), new SessionDuration(DATE_2023_12_5, DATE_2023_12_10), - chargedSessionStateZero(1000L, 2, ApplyFixtures.applies_two_approved()), + chargedSessionStateZero(1000L, 2), SessionRecruitStatus.RECRUIT, SessionProgressStatus.READY, 1L, @@ -86,7 +90,7 @@ public static SessionState freeSessionStateZero() { return new SessionState(); } - public static SessionState chargedSessionStateZero(Long amount, int quota, Applies applies) { - return new SessionState(SessionType.CHARGE, amount, quota, applies); + public static SessionState chargedSessionStateZero(Long amount, int quota) { + return new SessionState(SessionType.CHARGE, amount, quota); } } diff --git a/src/test/java/nextstep/courses/infrastructure/ApplyRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ApplyRepositoryTest.java index 56420ee55d..70c23b29ad 100644 --- a/src/test/java/nextstep/courses/infrastructure/ApplyRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/ApplyRepositoryTest.java @@ -1,8 +1,6 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.course.session.apply.Applies; -import nextstep.courses.domain.course.session.apply.Apply; -import nextstep.courses.domain.course.session.apply.ApplyRepository; +import nextstep.courses.domain.course.session.apply.*; import nextstep.courses.fixture.ApplyFixtures; import nextstep.courses.fixture.SessionFixtures; import nextstep.qna.NotFoundException; @@ -47,26 +45,26 @@ void saveApply_success() { Apply findApply = applyRepository .findApplyByNsUserIdAndSessionId( NsUserFixtures.TEACHER_JAVAJIGI_1L.getId(), - SessionFixtures.createdFreeSession().getId() + SessionFixtures.createdFreeSession().id() ) .orElseThrow(NotFoundException::new); - assertThat(findApply.getNsUserId()).isEqualTo(savedApply.getNsUserId()); - assertThat(findApply.getSessionId()).isEqualTo(savedApply.getSessionId()); + assertThat(findApply.nsUserId()).isEqualTo(savedApply.nsUserId()); + assertThat(findApply.sessionId()).isEqualTo(savedApply.sessionId()); } @Test void updateApply_success() { Apply savedApply = applyRepository.save(ApplyFixtures.apply_one_canceled()); - Apply updatedApply = savedApply.setApproved(true); + Apply updatedApply = savedApply.approve(SessionFixtures.DATETIME_2023_12_5); applyRepository.update(updatedApply); Apply findApply = applyRepository .findApplyByNsUserIdAndSessionId( NsUserFixtures.TEACHER_JAVAJIGI_1L.getId(), - SessionFixtures.createdFreeSession().getId() + SessionFixtures.createdFreeSession().id() ) .orElseThrow(NotFoundException::new); - assertThat(findApply.isApproved()).isTrue(); + assertThat(findApply.approval()).isEqualTo(ApprovalStatus.APPROVED); } } diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index 67c0d588f3..4b79a43c29 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -35,12 +35,11 @@ void setUp() { @Test void crud() { Course course = new Course("TDD, 클린 코드 with Java", 1, 1L, LocalDateTime.now()); - int count = courseRepository.save(course); - assertThat(count).isEqualTo(1); + courseRepository.save(course); Course savedCourse = courseRepository.findById(1L); - assertThat(course.getTitle()).isEqualTo(savedCourse.getTitle()); - assertThat(course.getOrdering()).isEqualTo(savedCourse.getOrdering()); - assertThat(course.getCreatorId()).isEqualTo(savedCourse.getCreatorId()); + assertThat(course.title()).isEqualTo(savedCourse.title()); + assertThat(course.ordering()).isEqualTo(savedCourse.ordering()); + assertThat(course.creatorId()).isEqualTo(savedCourse.creatorId()); LOGGER.debug("Course: {}", savedCourse); } } diff --git a/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java index 2f4280eb7e..21d92ab7f6 100644 --- a/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/ImageRepositoryTest.java @@ -33,10 +33,10 @@ void setUp() { void save_success() { Image image = new Image(1024, ImageType.JPG, 300, 200, 1L, LocalDateTime.now()); Image savedImage = imageRepository.save(1L, image); - assertThat(image.getImageSize()).isEqualTo(savedImage.getImageSize()); - assertThat(image.getImageType()).isEqualTo(savedImage.getImageType()); - assertThat(image.getImageWidth()).isEqualTo(savedImage.getImageWidth()); - assertThat(image.getImageHeight()).isEqualTo(savedImage.getImageHeight()); + assertThat(image.imageSize()).isEqualTo(savedImage.imageSize()); + assertThat(image.imageType()).isEqualTo(savedImage.imageType()); + assertThat(image.imageWidth()).isEqualTo(savedImage.imageWidth()); + assertThat(image.imageHeight()).isEqualTo(savedImage.imageHeight()); LOGGER.debug("Image: {}", savedImage); } } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index e3464d9ac3..46254bc799 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -1,10 +1,6 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.course.session.Session; -import nextstep.courses.domain.course.session.SessionRepository; -import nextstep.courses.domain.course.session.SessionState; -import nextstep.courses.domain.course.session.SessionType; -import nextstep.courses.domain.course.session.apply.Applies; +import nextstep.courses.domain.course.session.*; import nextstep.courses.fixture.SessionFixtures; import nextstep.qna.NotFoundException; import org.junit.jupiter.api.BeforeEach; @@ -35,11 +31,11 @@ void setUp() { void save_success() { Session session = SessionFixtures.createdFreeSession(); Session savedSession = sessionRepository.save(1L, session); - Session findSession = sessionRepository.findById(savedSession.getId()).orElseThrow(NotFoundException::new); + Session findSession = sessionRepository.findById(savedSession.id()).orElseThrow(NotFoundException::new); - assertThat(findSession.getId()).isEqualTo(1L); - assertThat(findSession.getDuration()).isEqualTo(session.getDuration()); - assertThat(findSession.getSessionState()).isEqualTo(session.getSessionState()); + assertThat(findSession.id()).isEqualTo(1L); + assertThat(findSession.sessionDuration()).isEqualTo(session.sessionDuration()); + assertThat(findSession.sessionState()).isEqualTo(session.sessionState()); LOGGER.debug("Session: {}", savedSession); } @@ -49,13 +45,14 @@ void update_success() { Session session = SessionFixtures.createdChargedSession(); Session savedSession = sessionRepository.save(1L, session); - SessionState updateSessionState = new SessionState(SessionType.CHARGE, 2000L, 30, new Applies()); - savedSession.changeSessionState(updateSessionState); - sessionRepository.update(savedSession.getId(), savedSession); - Session updatedSession = sessionRepository.findById(savedSession.getId()).orElseThrow(NotFoundException::new); + SessionState updateSessionState = new SessionState(SessionType.CHARGE, 2000L, 30); + Session updatedSession = new Session(savedSession.images(), savedSession.sessionDuration(), + updateSessionState, savedSession.creatorId(), savedSession.createdAt()); + sessionRepository.update(savedSession.id(), updatedSession); + Session findUpdatedSession = sessionRepository.findById(savedSession.id()).orElseThrow(NotFoundException::new); - assertThat(updatedSession.getId()).isEqualTo(savedSession.getId()); - assertThat(updatedSession.getSessionState()).isEqualTo(updateSessionState); - LOGGER.debug("Session: {}", updatedSession); + assertThat(findUpdatedSession.id()).isEqualTo(savedSession.id()); + assertThat(findUpdatedSession.sessionState()).isEqualTo(updateSessionState); + LOGGER.debug("Session: {}", findUpdatedSession); } }