diff --git a/README.md b/README.md index 9b4867cc0f..2d53204580 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,27 @@ * 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다. ## 온라인 코드 리뷰 과정 -* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview) \ No newline at end of file +* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview) + +## 질문 삭제하기 요구사항 +* [x] 질문 데이터를 완전히 삭제하는 것이 아니라 데이터의 상태를 삭제 상태(deleted - boolean type)로 변경한다. +* [x] 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능하다. +* [x] 답변이 없는 경우 삭제가 가능하다. +* [x] 질문자와 답변 글의 모든 답변자 같은 경우 삭제가 가능하다. +* [x] 질문을 삭제할 때 답변 또한 삭제해야 하며, 답변의 삭제 또한 삭제 상태(deleted)를 변경한다. +* [x] 질문자와 답변자가 다른 경우 답변을 삭제할 수 없다. +* [x] 질문과 답변 삭제 이력에 대한 정보를 DeleteHistory를 활용해 남긴다. + +## 프로그래밍 요구사항 +* [x] qna.service.QnaService의 deleteQuestion()는 앞의 질문 삭제 기능을 구현한 코드이다. 이 메소드는 단위 테스트하기 어려운 코드와 단위 테스트 가능한 코드가 섞여 있다. +* [x] 단위 테스트하기 어려운 코드와 단위 테스트 가능한 코드를 분리해 단위 테스트 가능한 코드 에 대해 단위 테스트를 구현한다. + +### 힌트1 +* 객체의 상태 데이터를 꺼내지(get)말고 메시지를 보낸다. +* 규칙 8: 일급 콜렉션을 쓴다. + * Question의 List를 일급 콜렉션으로 구현해 본다. +* 규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. + 인스턴스 변수의 수를 줄이기 위해 도전한다. + +### 힌트2 +* 테스트하기 쉬운 부분과 테스트하기 어려운 부분을 분리해 테스트 가능한 부분만 단위테스트한다. diff --git a/src/main/java/qna/domain/Answer.java b/src/main/java/qna/domain/Answer.java index 548b71ed71..8ad4d75ede 100644 --- a/src/main/java/qna/domain/Answer.java +++ b/src/main/java/qna/domain/Answer.java @@ -1,5 +1,6 @@ package qna.domain; +import qna.CannotDeleteException; import qna.NotFoundException; import qna.UnAuthorizedException; @@ -52,7 +53,7 @@ public boolean isDeleted() { return deleted; } - public boolean isOwner(User writer) { + private boolean isOwner(User writer) { return this.writer.equals(writer); } @@ -72,4 +73,10 @@ public void toQuestion(Question question) { public String toString() { return "Answer [id=" + getId() + ", writer=" + writer + ", contents=" + contents + "]"; } + + public void validateAnswerExists(User loginUser) throws CannotDeleteException { + if (!isOwner(loginUser)) { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } + } } diff --git a/src/main/java/qna/domain/DeleteHistories.java b/src/main/java/qna/domain/DeleteHistories.java new file mode 100644 index 0000000000..b974763157 --- /dev/null +++ b/src/main/java/qna/domain/DeleteHistories.java @@ -0,0 +1,31 @@ +package qna.domain; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public class DeleteHistories { + private List deleteHistories = new ArrayList<>(); + + public DeleteHistories() { + } + + public DeleteHistories(List deleteHistories) { + this.deleteHistories = deleteHistories; + } + + public void add(long questionId, Question question) { + question.setDeleted(true); + LocalDateTime now = LocalDateTime.now(); + deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), now)); + + for (Answer answer : question.getAnswers()) { + answer.setDeleted(true); + deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), now)); + } + } + + public List getHistories() { + return deleteHistories; + } +} diff --git a/src/main/java/qna/domain/Question.java b/src/main/java/qna/domain/Question.java index 1e8bb11251..e3ceb511b3 100644 --- a/src/main/java/qna/domain/Question.java +++ b/src/main/java/qna/domain/Question.java @@ -1,6 +1,7 @@ package qna.domain; import org.hibernate.annotations.Where; +import qna.CannotDeleteException; import javax.persistence.*; import java.util.ArrayList; @@ -33,28 +34,16 @@ public Question(String title, String contents) { this.contents = contents; } - public Question(long id, String title, String contents) { - super(id); + public Question(String title, String contents, List answers) { this.title = title; this.contents = contents; + this.answers = answers; } - public String getTitle() { - return title; - } - - public Question setTitle(String title) { + public Question(long id, String title, String contents) { + super(id); this.title = title; - return this; - } - - public String getContents() { - return contents; - } - - public Question setContents(String contents) { this.contents = contents; - return this; } public User getWriter() { @@ -71,7 +60,7 @@ public void addAnswer(Answer answer) { answers.add(answer); } - public boolean isOwner(User loginUser) { + private boolean isOwner(User loginUser) { return writer.equals(loginUser); } @@ -92,4 +81,21 @@ public List getAnswers() { public String toString() { return "Question [id=" + getId() + ", title=" + title + ", contents=" + contents + ", writer=" + writer + "]"; } + + public void validate(User loginUser) throws CannotDeleteException { + validateQuestionAuthority(loginUser); + validateAnswerExists(loginUser); + } + + private void validateQuestionAuthority(User loginUser) throws CannotDeleteException { + if (!isOwner(loginUser)) { + throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); + } + } + + private void validateAnswerExists(User loginUser) throws CannotDeleteException { + for (Answer answer : answers) { + answer.validateAnswerExists(loginUser); + } + } } diff --git a/src/main/java/qna/service/QnAService.java b/src/main/java/qna/service/QnAService.java index 66821cd9c2..60c16ab637 100644 --- a/src/main/java/qna/service/QnAService.java +++ b/src/main/java/qna/service/QnAService.java @@ -35,24 +35,10 @@ public Question findQuestionById(Long id) { @Transactional public void deleteQuestion(User loginUser, long questionId) throws CannotDeleteException { Question question = findQuestionById(questionId); - if (!question.isOwner(loginUser)) { - throw new CannotDeleteException("질문을 삭제할 권한이 없습니다."); - } - - List answers = question.getAnswers(); - for (Answer answer : answers) { - if (!answer.isOwner(loginUser)) { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - } - } - - 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); - deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); - } - deleteHistoryService.saveAll(deleteHistories); + question.validate(loginUser); + + DeleteHistories deleteHistories = new DeleteHistories(); + deleteHistories.add(questionId, question); + deleteHistoryService.saveAll(deleteHistories.getHistories()); } } diff --git a/src/test/java/qna/domain/AnswerTest.java b/src/test/java/qna/domain/AnswerTest.java index d858181e31..9befbd016c 100644 --- a/src/test/java/qna/domain/AnswerTest.java +++ b/src/test/java/qna/domain/AnswerTest.java @@ -1,6 +1,21 @@ package qna.domain; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import qna.CannotDeleteException; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + public class AnswerTest { public static final Answer A1 = new Answer(UserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); public static final Answer A2 = new Answer(UserTest.SANJIGI, QuestionTest.Q1, "Answers Contents2"); + + @Test + @DisplayName("다른 사람이 쓴 답변이 있는 경우 CannotDeleteException을 throw한다.") + public void validate_다른_사람의_답변_있는_글() { + assertThatThrownBy(() -> { + A1.validateAnswerExists(UserTest.SANJIGI); + }).isInstanceOf(CannotDeleteException.class) + .hasMessageContaining("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } } diff --git a/src/test/java/qna/domain/DeleteHistoriesTest.java b/src/test/java/qna/domain/DeleteHistoriesTest.java new file mode 100644 index 0000000000..8ea6381f86 --- /dev/null +++ b/src/test/java/qna/domain/DeleteHistoriesTest.java @@ -0,0 +1,29 @@ +package qna.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static qna.domain.QuestionTest.Q1; + +class DeleteHistoriesTest { + public static final DeleteHistory DELETE_HISTORY1 = new DeleteHistory(ContentType.QUESTION, 1L, Q1.getWriter(), + LocalDateTime.now()); + + public static final DeleteHistories DELETE_HISTORIES1 = new DeleteHistories(List.of(DELETE_HISTORY1)); + + public static final DeleteHistories DELETE_HISTORIES2 = new DeleteHistories(); + + @Test + @DisplayName("Delete 히스토리를 추가한다.") + public void add_히스토리를_추가한다() { + int questionId = 1; + + DELETE_HISTORIES2.add(questionId, Q1); + + assertThat(DELETE_HISTORIES2.getHistories()).isEqualTo(DELETE_HISTORIES1.getHistories()); + } +} diff --git a/src/test/java/qna/domain/QuestionTest.java b/src/test/java/qna/domain/QuestionTest.java index b48c9a2209..3028ddf862 100644 --- a/src/test/java/qna/domain/QuestionTest.java +++ b/src/test/java/qna/domain/QuestionTest.java @@ -1,6 +1,34 @@ package qna.domain; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import qna.CannotDeleteException; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static qna.domain.AnswerTest.A2; + public class QuestionTest { public static final Question Q1 = new Question("title1", "contents1").writeBy(UserTest.JAVAJIGI); public static final Question Q2 = new Question("title2", "contents2").writeBy(UserTest.SANJIGI); + public static final Question Q3 = new Question("title1", "contents1", List.of(A2)).writeBy(UserTest.JAVAJIGI); + + @Test + @DisplayName("다른 사람이 쓴 글의 경우 CannotDeleteException을 throw한다.") + public void validate_다른_사람이_쓴_글() { + assertThatThrownBy(() -> { + Q1.validate(UserTest.SANJIGI); + }).isInstanceOf(CannotDeleteException.class) + .hasMessageContaining("질문을 삭제할 권한이 없습니다."); + } + + @Test + @DisplayName("다른 사람이 쓴 답변이 있는 경우 CannotDeleteException을 throw한다.") + public void validate_다른_사람의_답변_있는_글() { + assertThatThrownBy(() -> { + Q3.validate(UserTest.JAVAJIGI); + }).isInstanceOf(CannotDeleteException.class) + .hasMessageContaining("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } }