From 0f8192de50eb3f872760f993738b725b20f7e74a Mon Sep 17 00:00:00 2001 From: blossun Date: Sat, 28 May 2022 15:41:58 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20Question=EC=97=90=EC=84=9C=20delete?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/qna/CannotDeleteException.java | 4 +- src/main/java/qna/domain/Question.java | 37 ++++++++++++++-- src/main/java/qna/service/QnAService.java | 13 +----- src/test/java/qna/domain/QuestionTest.java | 44 +++++++++++++++++++ src/test/java/qna/service/QnaServiceTest.java | 20 +++++---- 5 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/main/java/qna/CannotDeleteException.java b/src/main/java/qna/CannotDeleteException.java index 12ea9bc148..6e4897d025 100644 --- a/src/main/java/qna/CannotDeleteException.java +++ b/src/main/java/qna/CannotDeleteException.java @@ -1,9 +1,9 @@ package qna; -public class CannotDeleteException extends Exception { +public class CannotDeleteException extends RuntimeException { private static final long serialVersionUID = 1L; public CannotDeleteException(String message) { super(message); } -} \ No newline at end of file +} diff --git a/src/main/java/qna/domain/Question.java b/src/main/java/qna/domain/Question.java index 1e8bb11251..9d965c126e 100644 --- a/src/main/java/qna/domain/Question.java +++ b/src/main/java/qna/domain/Question.java @@ -1,11 +1,22 @@ package qna.domain; -import org.hibernate.annotations.Where; - -import javax.persistence.*; import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +import org.hibernate.annotations.Where; + +import qna.CannotDeleteException; + @Entity public class Question extends AbstractEntity { @Column(length = 100, nullable = false) @@ -80,6 +91,26 @@ public Question setDeleted(boolean deleted) { return this; } + public void delete(final User loginUser) throws CannotDeleteException { + if (!this.writer.equals(loginUser)) { + throw new CannotDeleteException("질문자 본인만 삭제할 수 있습니다."); + } + + if (answers.isEmpty()) { + this.deleted = true; + return; + } + + answers.stream() + .filter(answer -> !answer.isOwner(loginUser)) + .findAny() + .ifPresent(answer -> { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + }); + + this.deleted = true; + } + public boolean isDeleted() { return deleted; } diff --git a/src/main/java/qna/service/QnAService.java b/src/main/java/qna/service/QnAService.java index 66821cd9c2..fd8f8ae948 100644 --- a/src/main/java/qna/service/QnAService.java +++ b/src/main/java/qna/service/QnAService.java @@ -35,19 +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("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - } - } + question.delete(loginUser); List deleteHistories = new ArrayList<>(); - question.setDeleted(true); + List answers = question.getAnswers(); deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); for (Answer answer : answers) { answer.setDeleted(true); diff --git a/src/test/java/qna/domain/QuestionTest.java b/src/test/java/qna/domain/QuestionTest.java index b48c9a2209..6347e8398b 100644 --- a/src/test/java/qna/domain/QuestionTest.java +++ b/src/test/java/qna/domain/QuestionTest.java @@ -1,6 +1,50 @@ package qna.domain; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import qna.CannotDeleteException; + 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); + + @DisplayName("질문자가 본인이 아니면 질문을 삭제할 수 없다.") + @Test + public void delete_fail_not_owner() { + assertThatThrownBy(() -> Q1.delete(UserTest.SANJIGI)) + .isInstanceOf(CannotDeleteException.class); + } + + @DisplayName("질문자 본인이고, 답변이 없으면 삭제가 가능하다.") + @Test + public void delete_success_owner_and_no_answer() { + Q1.delete(UserTest.JAVAJIGI); + + assertThat(Q1.isDeleted()).isTrue(); + } + + @DisplayName("질문자가 본인이고, 답변이 있는 경우, 모든 답변자가 본인이면 삭제가 가능하다.") + @Test + public void delete_success_question_and_answer_owner() { + Q1.addAnswer(AnswerTest.A1); + + Q1.delete(UserTest.JAVAJIGI); + assertThat(Q1.isDeleted()).isTrue(); + } + + @DisplayName("질문자가 본인이고, 답변이 있는 경우, 모든 답변자가 본인이 아니면 삭제가 불가능하다.") + @Test + public void delete_fail_answer_other() { + Q1.addAnswer(AnswerTest.A1); + Q1.addAnswer(AnswerTest.A2); + + assertThatThrownBy(() -> Q1.delete(UserTest.JAVAJIGI)) + .isInstanceOf(CannotDeleteException.class) + .hasMessage("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } + } diff --git a/src/test/java/qna/service/QnaServiceTest.java b/src/test/java/qna/service/QnaServiceTest.java index 3504343caa..3c5bf16dfd 100644 --- a/src/test/java/qna/service/QnaServiceTest.java +++ b/src/test/java/qna/service/QnaServiceTest.java @@ -6,6 +6,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + import qna.CannotDeleteException; import qna.domain.*; @@ -34,14 +35,14 @@ public class QnaServiceTest { private Answer answer; @BeforeEach - public void setUp() throws Exception { + public void setUp() { question = new Question(1L, "title1", "contents1").writeBy(UserTest.JAVAJIGI); answer = new Answer(11L, UserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); question.addAnswer(answer); } @Test - public void delete_성공() throws Exception { + public void delete_성공() { when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); assertThat(question.isDeleted()).isFalse(); @@ -52,16 +53,17 @@ public void setUp() throws Exception { } @Test - public void delete_다른_사람이_쓴_글() throws Exception { + public void delete_실패_다른_사람이_쓴_글() { when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); assertThatThrownBy(() -> { qnAService.deleteQuestion(UserTest.SANJIGI, question.getId()); - }).isInstanceOf(CannotDeleteException.class); + }).isInstanceOf(CannotDeleteException.class) + .hasMessage("질문자 본인만 삭제할 수 있습니다."); } @Test - public void delete_성공_질문자_답변자_같음() throws Exception { + public void delete_성공_질문자_답변자_같음() { when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); qnAService.deleteQuestion(UserTest.JAVAJIGI, question.getId()); @@ -72,12 +74,14 @@ public void setUp() throws Exception { } @Test - public void delete_답변_중_다른_사람이_쓴_글() throws Exception { + public void delete_실패_답변_중_다른_사람이_쓴_글() { + question.addAnswer(AnswerTest.A2); when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); assertThatThrownBy(() -> { - qnAService.deleteQuestion(UserTest.SANJIGI, question.getId()); - }).isInstanceOf(CannotDeleteException.class); + qnAService.deleteQuestion(UserTest.JAVAJIGI, question.getId()); + }).isInstanceOf(CannotDeleteException.class) + .hasMessage("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); } private void verifyDeleteHistories() { From a5bebe2defb6bede893c21ab7b06b44c4058ac65 Mon Sep 17 00:00:00 2001 From: blossun Date: Sat, 28 May 2022 17:29:47 +0900 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20=EB=B3=B8=EC=9D=B8=EC=9D=B4=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=20=EB=8B=B5=EB=B3=80=EB=A7=8C=20?= =?UTF-8?q?=EC=9E=88=EC=9C=BC=EB=A9=B4=20=EC=A7=88=EB=AC=B8=EC=9D=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 답변목록을 AnswerList 일급컬렉션으로 구현 - AnswerList에서 모든 답변을 삭제하는 기능 구현 --- src/main/java/qna/domain/Answer.java | 5 +- src/main/java/qna/domain/AnswerList.java | 50 +++++++++++++++++++ src/main/java/qna/domain/Question.java | 24 ++------- src/main/java/qna/service/QnAService.java | 5 +- src/test/java/qna/domain/QuestionTest.java | 18 +++++++ src/test/java/qna/service/QnaServiceTest.java | 19 +++++++ 6 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 src/main/java/qna/domain/AnswerList.java diff --git a/src/main/java/qna/domain/Answer.java b/src/main/java/qna/domain/Answer.java index 548b71ed71..de30315460 100644 --- a/src/main/java/qna/domain/Answer.java +++ b/src/main/java/qna/domain/Answer.java @@ -43,9 +43,8 @@ public Answer(Long id, User writer, Question question, String contents) { this.contents = contents; } - public Answer setDeleted(boolean deleted) { - this.deleted = deleted; - return this; + public void delete() { + this.deleted = true; } public boolean isDeleted() { diff --git a/src/main/java/qna/domain/AnswerList.java b/src/main/java/qna/domain/AnswerList.java new file mode 100644 index 0000000000..15d88a2782 --- /dev/null +++ b/src/main/java/qna/domain/AnswerList.java @@ -0,0 +1,50 @@ +package qna.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; + +import org.hibernate.annotations.Where; + +import qna.CannotDeleteException; + +@Embeddable +public class AnswerList { + @OneToMany(mappedBy = "question", cascade = CascadeType.ALL) + @Where(clause = "deleted = false") + @OrderBy("id ASC") + private final List answers = new ArrayList<>(); + + public boolean isEmpty() { + return answers.isEmpty(); + } + + public void add(final Answer answer) { + answers.add(answer); + } + + public void deleteAll(final User loginUser) { + this.findAnyAnswerByOtherWriter(loginUser) + .ifPresent( + answer -> { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + }); + + answers.forEach(answer -> answer.delete()); + } + + private Optional findAnyAnswerByOtherWriter(final User loginUser) { + return answers.stream() + .filter(answer -> !answer.isOwner(loginUser)) + .findAny(); + } + + public List getAnswers() { + return answers; + } +} diff --git a/src/main/java/qna/domain/Question.java b/src/main/java/qna/domain/Question.java index 9d965c126e..7407238fcf 100644 --- a/src/main/java/qna/domain/Question.java +++ b/src/main/java/qna/domain/Question.java @@ -1,19 +1,12 @@ package qna.domain; -import java.util.ArrayList; -import java.util.List; - -import javax.persistence.CascadeType; import javax.persistence.Column; +import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.ForeignKey; import javax.persistence.JoinColumn; import javax.persistence.Lob; import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; -import javax.persistence.OrderBy; - -import org.hibernate.annotations.Where; import qna.CannotDeleteException; @@ -29,10 +22,8 @@ public class Question extends AbstractEntity { @JoinColumn(foreignKey = @ForeignKey(name = "fk_question_writer")) private User writer; - @OneToMany(mappedBy = "question", cascade = CascadeType.ALL) - @Where(clause = "deleted = false") - @OrderBy("id ASC") - private List answers = new ArrayList<>(); + @Embedded + private final AnswerList answers = new AnswerList(); private boolean deleted = false; @@ -101,12 +92,7 @@ public void delete(final User loginUser) throws CannotDeleteException { return; } - answers.stream() - .filter(answer -> !answer.isOwner(loginUser)) - .findAny() - .ifPresent(answer -> { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - }); + answers.deleteAll(loginUser); this.deleted = true; } @@ -115,7 +101,7 @@ public boolean isDeleted() { return deleted; } - public List getAnswers() { + public AnswerList getAnswers() { return answers; } diff --git a/src/main/java/qna/service/QnAService.java b/src/main/java/qna/service/QnAService.java index fd8f8ae948..41ac3d8738 100644 --- a/src/main/java/qna/service/QnAService.java +++ b/src/main/java/qna/service/QnAService.java @@ -38,10 +38,9 @@ public void deleteQuestion(User loginUser, long questionId) throws CannotDeleteE question.delete(loginUser); List deleteHistories = new ArrayList<>(); - List answers = question.getAnswers(); deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); - for (Answer answer : answers) { - answer.setDeleted(true); + + for (Answer answer : question.getAnswers().getAnswers()) { deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); } deleteHistoryService.saveAll(deleteHistories); diff --git a/src/test/java/qna/domain/QuestionTest.java b/src/test/java/qna/domain/QuestionTest.java index 6347e8398b..d7dece26bc 100644 --- a/src/test/java/qna/domain/QuestionTest.java +++ b/src/test/java/qna/domain/QuestionTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -47,4 +48,21 @@ public void delete_fail_answer_other() { .hasMessage("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); } + @DisplayName("질문이 삭제되면 답변도 모두 삭제된다.") + @Test + public void delete_success_all_answer_deleted() { + final Answer answer01 = new Answer(UserTest.JAVAJIGI, Q1, "answer contents1"); + final Answer answer02 = new Answer(UserTest.JAVAJIGI, Q1, "answer contents2"); + Q1.addAnswer(answer01); + Q1.addAnswer(answer02); + + Q1.delete(UserTest.JAVAJIGI); + + assertAll( + () -> assertThat(Q1.isDeleted()).isTrue(), + () -> assertThat(answer01.isDeleted()).isTrue(), + () -> assertThat(answer02.isDeleted()).isTrue() + ); + } + } diff --git a/src/test/java/qna/service/QnaServiceTest.java b/src/test/java/qna/service/QnaServiceTest.java index 3c5bf16dfd..83cdb971a3 100644 --- a/src/test/java/qna/service/QnaServiceTest.java +++ b/src/test/java/qna/service/QnaServiceTest.java @@ -1,6 +1,7 @@ package qna.service; 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; @@ -17,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -52,6 +54,23 @@ public void setUp() { verifyDeleteHistories(); } + @DisplayName("질문이 삭제되면 답변도 모두 삭제된다.") + @Test + public void delete_success_all_answer_deleted() { + final Answer answer01 = new Answer(UserTest.JAVAJIGI, question, "answer contents1"); + final Answer answer02 = new Answer(UserTest.JAVAJIGI, question, "answer contents2"); + question.addAnswer(answer01); + question.addAnswer(answer02); + when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); + + qnAService.deleteQuestion(UserTest.JAVAJIGI, question.getId()); + + assertAll( + () -> assertThat(question.isDeleted()).isTrue(), + () -> assertThat(answer01.isDeleted()).isTrue(), + () -> assertThat(answer02.isDeleted()).isTrue() + ); + } @Test public void delete_실패_다른_사람이_쓴_글() { when(questionRepository.findByIdAndDeletedFalse(question.getId())).thenReturn(Optional.of(question)); From fa2eb37323dee69a5e72fa8f763fec068b732ea1 Mon Sep 17 00:00:00 2001 From: blossun Date: Sat, 28 May 2022 19:20:01 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20QuestionTest=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=EB=90=98=EA=B8=B0=20=EC=A0=84=20=ED=99=95=EC=9D=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/qna/domain/QuestionTest.java | 54 +++++++++++++++------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/test/java/qna/domain/QuestionTest.java b/src/test/java/qna/domain/QuestionTest.java index d7dece26bc..27a6f87d3f 100644 --- a/src/test/java/qna/domain/QuestionTest.java +++ b/src/test/java/qna/domain/QuestionTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,37 +14,54 @@ 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); + private Question question; + private Answer answer; + private Answer answer2; + + @BeforeEach + public void setUp() { + question = new Question(1L, "title1", "contents1").writeBy(UserTest.JAVAJIGI); + answer = new Answer(11L, UserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); + answer2 = new Answer(UserTest.JAVAJIGI, question, "answer contents2"); + question.addAnswer(answer); + } + + @DisplayName("질문자가 본인이 아니면 질문을 삭제할 수 없다.") @Test public void delete_fail_not_owner() { - assertThatThrownBy(() -> Q1.delete(UserTest.SANJIGI)) + assertThatThrownBy(() -> question.delete(UserTest.SANJIGI)) .isInstanceOf(CannotDeleteException.class); } @DisplayName("질문자 본인이고, 답변이 없으면 삭제가 가능하다.") @Test public void delete_success_owner_and_no_answer() { - Q1.delete(UserTest.JAVAJIGI); + assertThat(question.isDeleted()).isFalse(); - assertThat(Q1.isDeleted()).isTrue(); + question.delete(UserTest.JAVAJIGI); + + assertThat(question.isDeleted()).isTrue(); } @DisplayName("질문자가 본인이고, 답변이 있는 경우, 모든 답변자가 본인이면 삭제가 가능하다.") @Test public void delete_success_question_and_answer_owner() { - Q1.addAnswer(AnswerTest.A1); + question.addAnswer(answer); + assertThat(question.isDeleted()).isFalse(); + + question.delete(UserTest.JAVAJIGI); - Q1.delete(UserTest.JAVAJIGI); - assertThat(Q1.isDeleted()).isTrue(); + assertThat(question.isDeleted()).isTrue(); } @DisplayName("질문자가 본인이고, 답변이 있는 경우, 모든 답변자가 본인이 아니면 삭제가 불가능하다.") @Test public void delete_fail_answer_other() { - Q1.addAnswer(AnswerTest.A1); - Q1.addAnswer(AnswerTest.A2); + question.addAnswer(AnswerTest.A1); + question.addAnswer(AnswerTest.A2); - assertThatThrownBy(() -> Q1.delete(UserTest.JAVAJIGI)) + assertThatThrownBy(() -> question.delete(UserTest.JAVAJIGI)) .isInstanceOf(CannotDeleteException.class) .hasMessage("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); } @@ -51,17 +69,19 @@ public void delete_fail_answer_other() { @DisplayName("질문이 삭제되면 답변도 모두 삭제된다.") @Test public void delete_success_all_answer_deleted() { - final Answer answer01 = new Answer(UserTest.JAVAJIGI, Q1, "answer contents1"); - final Answer answer02 = new Answer(UserTest.JAVAJIGI, Q1, "answer contents2"); - Q1.addAnswer(answer01); - Q1.addAnswer(answer02); + question.addAnswer(answer2); + assertAll( + () -> assertThat(question.isDeleted()).isFalse(), + () -> assertThat(answer.isDeleted()).isFalse(), + () -> assertThat(answer2.isDeleted()).isFalse() + ); - Q1.delete(UserTest.JAVAJIGI); + question.delete(UserTest.JAVAJIGI); assertAll( - () -> assertThat(Q1.isDeleted()).isTrue(), - () -> assertThat(answer01.isDeleted()).isTrue(), - () -> assertThat(answer02.isDeleted()).isTrue() + () -> assertThat(question.isDeleted()).isTrue(), + () -> assertThat(answer.isDeleted()).isTrue(), + () -> assertThat(answer2.isDeleted()).isTrue() ); } From 0e252f748d5d807f40b6e1263d859cdbc957acac Mon Sep 17 00:00:00 2001 From: blossun Date: Sat, 28 May 2022 19:20:38 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20Answer=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=EA=B0=80=EB=8A=A5=20=EC=97=AC=EB=B6=80?= =?UTF-8?q?=EB=A5=BC=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/qna/domain/Answer.java | 6 +++- src/main/java/qna/domain/AnswerList.java | 14 +-------- src/test/java/qna/domain/AnswerTest.java | 39 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/main/java/qna/domain/Answer.java b/src/main/java/qna/domain/Answer.java index de30315460..fd3ede15bb 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; @@ -43,7 +44,10 @@ public Answer(Long id, User writer, Question question, String contents) { this.contents = contents; } - public void delete() { + public void delete(final User loginUser) { + if (!this.writer.equals(loginUser)) { + throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + } this.deleted = true; } diff --git a/src/main/java/qna/domain/AnswerList.java b/src/main/java/qna/domain/AnswerList.java index 15d88a2782..5d8ec34fb4 100644 --- a/src/main/java/qna/domain/AnswerList.java +++ b/src/main/java/qna/domain/AnswerList.java @@ -29,19 +29,7 @@ public void add(final Answer answer) { } public void deleteAll(final User loginUser) { - this.findAnyAnswerByOtherWriter(loginUser) - .ifPresent( - answer -> { - throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); - }); - - answers.forEach(answer -> answer.delete()); - } - - private Optional findAnyAnswerByOtherWriter(final User loginUser) { - return answers.stream() - .filter(answer -> !answer.isOwner(loginUser)) - .findAny(); + answers.forEach(answer -> answer.delete(loginUser)); } public List getAnswers() { diff --git a/src/test/java/qna/domain/AnswerTest.java b/src/test/java/qna/domain/AnswerTest.java index d858181e31..c0209f1212 100644 --- a/src/test/java/qna/domain/AnswerTest.java +++ b/src/test/java/qna/domain/AnswerTest.java @@ -1,6 +1,45 @@ package qna.domain; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import qna.CannotDeleteException; + 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"); + + private Answer answer; + + @BeforeEach + void setUp() { + answer = new Answer(11L, UserTest.JAVAJIGI, QuestionTest.Q1, "Answers Contents1"); + } + + @DisplayName("작성자가 본인이면 답변을 지울 수 있다.") + @Test + public void delete_success_owner() { + assertThat(answer.isDeleted()).isFalse(); + + answer.delete(UserTest.JAVAJIGI); + + assertThat(answer.isDeleted()).isTrue(); + } + + @DisplayName("작성자가 아니면 답변을 지울 수 없다.") + @Test + public void delete_fail_no_owner() { + assertThat(answer.isDeleted()).isFalse(); + + assertThatThrownBy(() -> answer.delete(UserTest.SANJIGI)) + .isInstanceOf(CannotDeleteException.class) + .hasMessage("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); + + assertThat(answer.isDeleted()).isFalse(); + } + } From 244c35146fc2905dea3914b66288174fad7be1fa Mon Sep 17 00:00:00 2001 From: blossun Date: Sat, 28 May 2022 22:42:24 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EC=82=AD=EC=A0=9C=20=ED=9E=88?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EB=A6=AC=20=EA=B8=B0=EB=A1=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=9D=84=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B0=9C?= =?UTF-8?q?=ED=96=89/=EA=B5=AC=EB=8F=85=20=EA=B5=AC=EC=A1=B0=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + src/main/java/qna/domain/AnswerList.java | 3 -- .../java/qna/domain/DeleteEventHandler.java | 33 +++++++++++++ .../java/qna/domain/DeleteQuestionEvent.java | 13 +++++ src/main/java/qna/service/QnAService.java | 19 ++------ src/test/java/qna/service/QnaServiceTest.java | 47 +++++++++---------- 6 files changed, 74 insertions(+), 42 deletions(-) create mode 100644 src/main/java/qna/domain/DeleteEventHandler.java create mode 100644 src/main/java/qna/domain/DeleteQuestionEvent.java diff --git a/build.gradle b/build.gradle index 81bb93e739..041db8f2c5 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ dependencies { runtimeOnly 'com.h2database:h2' testImplementation 'org.assertj:assertj-core:3.22.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-web' } tasks.named('test') { diff --git a/src/main/java/qna/domain/AnswerList.java b/src/main/java/qna/domain/AnswerList.java index 5d8ec34fb4..22a244063e 100644 --- a/src/main/java/qna/domain/AnswerList.java +++ b/src/main/java/qna/domain/AnswerList.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Optional; import javax.persistence.CascadeType; import javax.persistence.Embeddable; @@ -11,8 +10,6 @@ import org.hibernate.annotations.Where; -import qna.CannotDeleteException; - @Embeddable public class AnswerList { @OneToMany(mappedBy = "question", cascade = CascadeType.ALL) diff --git a/src/main/java/qna/domain/DeleteEventHandler.java b/src/main/java/qna/domain/DeleteEventHandler.java new file mode 100644 index 0000000000..216b253415 --- /dev/null +++ b/src/main/java/qna/domain/DeleteEventHandler.java @@ -0,0 +1,33 @@ +package qna.domain; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Resource; + +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import qna.service.DeleteHistoryService; + +@Component +public class DeleteEventHandler { + + @Resource(name = "deleteHistoryService") + private DeleteHistoryService deleteHistoryService; + + @EventListener + public void saveHistory(DeleteQuestionEvent deleteQuestionEvent) { + System.out.println("Subscribe deleteQuestion event!!!!!"); + final Question question = deleteQuestionEvent.getQuestion(); + + List deleteHistories = new ArrayList<>(); + deleteHistories.add(new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now())); + + for (Answer answer : question.getAnswers().getAnswers()) { + deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); + } + deleteHistoryService.saveAll(deleteHistories); + } +} diff --git a/src/main/java/qna/domain/DeleteQuestionEvent.java b/src/main/java/qna/domain/DeleteQuestionEvent.java new file mode 100644 index 0000000000..15a1cfdd9e --- /dev/null +++ b/src/main/java/qna/domain/DeleteQuestionEvent.java @@ -0,0 +1,13 @@ +package qna.domain; + +public class DeleteQuestionEvent { + private final Question question; + + public DeleteQuestionEvent(final Question question) { + this.question = question; + } + + public Question getQuestion() { + return question; + } +} diff --git a/src/main/java/qna/service/QnAService.java b/src/main/java/qna/service/QnAService.java index 41ac3d8738..a8a1319073 100644 --- a/src/main/java/qna/service/QnAService.java +++ b/src/main/java/qna/service/QnAService.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import qna.CannotDeleteException; @@ -9,9 +10,6 @@ import qna.domain.*; import javax.annotation.Resource; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; @Service("qnaService") public class QnAService { @@ -20,11 +18,8 @@ public class QnAService { @Resource(name = "questionRepository") private QuestionRepository questionRepository; - @Resource(name = "answerRepository") - private AnswerRepository answerRepository; - - @Resource(name = "deleteHistoryService") - private DeleteHistoryService deleteHistoryService; + @Resource(name = "applicationEventPublisher") + private ApplicationEventPublisher applicationEventPublisher; @Transactional(readOnly = true) public Question findQuestionById(Long id) { @@ -37,12 +32,6 @@ public void deleteQuestion(User loginUser, long questionId) throws CannotDeleteE Question question = findQuestionById(questionId); question.delete(loginUser); - List deleteHistories = new ArrayList<>(); - deleteHistories.add(new DeleteHistory(ContentType.QUESTION, questionId, question.getWriter(), LocalDateTime.now())); - - for (Answer answer : question.getAnswers().getAnswers()) { - deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now())); - } - deleteHistoryService.saveAll(deleteHistories); + applicationEventPublisher.publishEvent(new DeleteQuestionEvent(question)); } } diff --git a/src/test/java/qna/service/QnaServiceTest.java b/src/test/java/qna/service/QnaServiceTest.java index 83cdb971a3..17394a79f3 100644 --- a/src/test/java/qna/service/QnaServiceTest.java +++ b/src/test/java/qna/service/QnaServiceTest.java @@ -1,5 +1,15 @@ package qna.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -7,32 +17,28 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; import qna.CannotDeleteException; -import qna.domain.*; - -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import qna.domain.Answer; +import qna.domain.AnswerTest; +import qna.domain.DeleteQuestionEvent; +import qna.domain.Question; +import qna.domain.QuestionRepository; +import qna.domain.QuestionTest; +import qna.domain.UserTest; @ExtendWith(MockitoExtension.class) public class QnaServiceTest { @Mock private QuestionRepository questionRepository; - @Mock - private DeleteHistoryService deleteHistoryService; - @InjectMocks private QnAService qnAService; + @Mock + private ApplicationEventPublisher applicationEventPublisher; + private Question question; private Answer answer; @@ -51,7 +57,7 @@ public void setUp() { qnAService.deleteQuestion(UserTest.JAVAJIGI, question.getId()); assertThat(question.isDeleted()).isTrue(); - verifyDeleteHistories(); + verify(applicationEventPublisher, times(1)).publishEvent(any(DeleteQuestionEvent.class)); } @DisplayName("질문이 삭제되면 답변도 모두 삭제된다.") @@ -89,7 +95,7 @@ public void delete_success_all_answer_deleted() { assertThat(question.isDeleted()).isTrue(); assertThat(answer.isDeleted()).isTrue(); - verifyDeleteHistories(); + verify(applicationEventPublisher, times(1)).publishEvent(any(DeleteQuestionEvent.class)); } @Test @@ -102,11 +108,4 @@ public void delete_success_all_answer_deleted() { }).isInstanceOf(CannotDeleteException.class) .hasMessage("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다."); } - - 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())); - verify(deleteHistoryService).saveAll(deleteHistories); - } }