From 0ec7be45e47be29c5dfabcf84f6e82b2c80b3c5c Mon Sep 17 00:00:00 2001 From: DH_Choi <58378676+vectorch9@users.noreply.github.com> Date: Sat, 4 Nov 2023 15:18:02 +0900 Subject: [PATCH] =?UTF-8?q?[Test]=20=ED=80=98=EC=8A=A4=ED=8A=B8,=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EB=8F=84=EB=A9=94=EC=9D=B8=EA=B3=BC=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=9C=EB=8B=A4=20(#85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 퀘스트 검증 로직을 수정하고 추가한다 * test: 퀘스트 도메인 로직 테스트를 작성한다 * test: Quests 테스트를 작성한다 * refactor: Participant 도메인에 검증 로직을 추가한다 * test: Participant 도메인 테스트를 작성한다 * refactor: IncreaseLinkedCountListener가 하나의 트랜잭션에서 실행되도록 수정한다 * test: Participants 테스트를 작성한다 * test: QuestDaoQuerydslImpl 테스트를 작성한다 --- .../participant/domain/Participant.java | 11 +- .../listener/IncreaseLinkedCountListener.java | 10 +- .../java/daybyquest/quest/domain/Quest.java | 52 +++- .../participant/domain/ParticipantTest.java | 266 +++++++++++++++++ .../participant/domain/ParticipantsTest.java | 162 +++++++++++ .../daybyquest/quest/domain/QuestTest.java | 267 ++++++++++++++++++ .../daybyquest/quest/domain/QuestsTest.java | 86 ++++++ .../quest/query/QuestDaoQuerydslImplTest.java | 165 +++++++++++ .../support/fixture/PostFixtures.java | 8 + .../support/fixture/QuestFixtures.java | 91 ++++++ .../support/util/ParticipantUtils.java | 23 ++ 11 files changed, 1117 insertions(+), 24 deletions(-) create mode 100644 src/test/java/daybyquest/participant/domain/ParticipantTest.java create mode 100644 src/test/java/daybyquest/participant/domain/ParticipantsTest.java create mode 100644 src/test/java/daybyquest/quest/domain/QuestTest.java create mode 100644 src/test/java/daybyquest/quest/domain/QuestsTest.java create mode 100644 src/test/java/daybyquest/quest/query/QuestDaoQuerydslImplTest.java create mode 100644 src/test/java/daybyquest/support/fixture/QuestFixtures.java create mode 100644 src/test/java/daybyquest/support/util/ParticipantUtils.java diff --git a/src/main/java/daybyquest/participant/domain/Participant.java b/src/main/java/daybyquest/participant/domain/Participant.java index e1c8d49..a5785ed 100644 --- a/src/main/java/daybyquest/participant/domain/Participant.java +++ b/src/main/java/daybyquest/participant/domain/Participant.java @@ -37,13 +37,13 @@ public class Participant { @Enumerated(EnumType.STRING) private ParticipantState state; - private int linkedCount; + private Long linkedCount; public Participant(Long userId, Quest quest) { this.userId = userId; this.quest = quest; this.state = DOING; - this.linkedCount = 0; + this.linkedCount = 0L; } public Long getQuestId() { @@ -64,8 +64,8 @@ private void validateDidNotTakeReward() { } private void validateRewardCount() { - if (quest.getRewardCount() != null && quest.getRewardCount() != 0 - && linkedCount < quest.getRewardCount()) { + if (quest.getRewardCount() == null || quest.getRewardCount() == 0 + || linkedCount < quest.getRewardCount()) { throw new InvalidDomainException(NOT_FINISHABLE_QUEST); } } @@ -85,6 +85,9 @@ public void doContinue() { } public void increaseLinkedCount() { + if (state != DOING && state != CONTINUE) { + throw new InvalidDomainException(); + } linkedCount += 1; } } diff --git a/src/main/java/daybyquest/participant/listener/IncreaseLinkedCountListener.java b/src/main/java/daybyquest/participant/listener/IncreaseLinkedCountListener.java index 9742c81..890a2b2 100644 --- a/src/main/java/daybyquest/participant/listener/IncreaseLinkedCountListener.java +++ b/src/main/java/daybyquest/participant/listener/IncreaseLinkedCountListener.java @@ -1,14 +1,11 @@ package daybyquest.participant.listener; -import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; - import daybyquest.participant.domain.Participant; import daybyquest.participant.domain.Participants; import daybyquest.post.domain.SuccessfullyPostLinkedEvent; -import org.springframework.scheduling.annotation.Async; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.event.TransactionalEventListener; @Component public class IncreaseLinkedCountListener { @@ -19,9 +16,8 @@ public IncreaseLinkedCountListener(final Participants participants) { this.participants = participants; } - @Async - @Transactional(propagation = REQUIRES_NEW) - @TransactionalEventListener(fallbackExecution = true) + @Transactional + @EventListener public void listenSuccessfullyPostLinkedEvent(final SuccessfullyPostLinkedEvent event) { final Participant participant = participants.getByUserIdAndQuestId(event.getUserId(), event.getQuestId()); diff --git a/src/main/java/daybyquest/quest/domain/Quest.java b/src/main/java/daybyquest/quest/domain/Quest.java index 22d175d..7dc766d 100644 --- a/src/main/java/daybyquest/quest/domain/Quest.java +++ b/src/main/java/daybyquest/quest/domain/Quest.java @@ -30,13 +30,15 @@ @EntityListeners(AuditingEntityListener.class) public class Quest { - private static final int MAX_TITLE_LENGTH = 15; + private static final int MAX_TITLE_LENGTH = 30; private static final int MAX_CONTENT_LENGTH = 300; - private static final int MAX_IMAGE_DESCRIPTION_LENGTH = 300; + private static final int MAX_IMAGE_DESCRIPTION_LENGTH = 100; - private static final int MAX_REWARD_COUNT = 100; + private static final Long MIN_REWARD_COUNT = 1L; + + private static final Long MAX_REWARD_COUNT = 365L; private static final int IMAGE_COUNT = 3; @@ -81,9 +83,9 @@ public class Quest { @CollectionTable(name = "quest_image", joinColumns = @JoinColumn(name = "quest_id")) private List images; - private Quest(Long groupId, Long badgeId, String interestName, String title, String content, - QuestCategory category, Long rewardCount, LocalDateTime expiredAt, - String imageDescription, List images) { + private Quest(final Long groupId, final Long badgeId, final String interestName, final String title, + final String content, final QuestCategory category, final Long rewardCount, + final LocalDateTime expiredAt, final String imageDescription, final List images) { this.groupId = groupId; this.badgeId = badgeId; this.interestName = interestName; @@ -98,26 +100,35 @@ private Quest(Long groupId, Long badgeId, String interestName, String title, Str validate(); } - public static Quest createNormalQuest(Long badgeId, String interestName, String title, - String content, - Long rewardCount, String imageDescription, List images) { + public static Quest createNormalQuest(final Long badgeId, final String interestName, final String title, + final String content, final Long rewardCount, final String imageDescription, + final List images) { return new Quest(null, badgeId, interestName, title, content, QuestCategory.NORMAL, rewardCount , null, imageDescription, images); } - public static Quest createGroupQuest(Long groupId, String interestName, String title, - String content, - LocalDateTime expiredAt, String imageDescription, List images) { + public static Quest createGroupQuest(final Long groupId, final String interestName, final String title, + final String content, final LocalDateTime expiredAt, final String imageDescription, + final List images) { + validateGroupId(groupId); return new Quest(groupId, null, interestName, title, content, QuestCategory.GROUP, null , expiredAt, imageDescription, images); } + private static void validateGroupId(final Long groupId) { + if (groupId == null) { + throw new InvalidDomainException(); + } + } + private void validate() { validateTitle(); validateContent(); validateImageDescription(); validateRewardCount(); validateImageCount(); + validateReward(); + validateExpiredAt(); } private void validateTitle() { @@ -139,7 +150,10 @@ private void validateImageDescription() { } private void validateRewardCount() { - if (rewardCount > MAX_REWARD_COUNT) { + if (rewardCount == null) { + return; + } + if (rewardCount < MIN_REWARD_COUNT || rewardCount > MAX_REWARD_COUNT) { throw new InvalidDomainException(); } } @@ -150,6 +164,18 @@ private void validateImageCount() { } } + private void validateReward() { + if (rewardCount == null ^ badgeId == null) { + throw new InvalidDomainException(); + } + } + + private void validateExpiredAt() { + if (expiredAt != null && expiredAt.isBefore(LocalDateTime.now())) { + throw new InvalidDomainException(); + } + } + public void setLabel(final String label) { if (this.state != NEED_LABEL) { throw new InvalidDomainException(); diff --git a/src/test/java/daybyquest/participant/domain/ParticipantTest.java b/src/test/java/daybyquest/participant/domain/ParticipantTest.java new file mode 100644 index 0000000..cffc48d --- /dev/null +++ b/src/test/java/daybyquest/participant/domain/ParticipantTest.java @@ -0,0 +1,266 @@ +package daybyquest.participant.domain; + +import static daybyquest.participant.domain.ParticipantState.CONTINUE; +import static daybyquest.participant.domain.ParticipantState.DOING; +import static daybyquest.participant.domain.ParticipantState.FINISHED; +import static daybyquest.support.fixture.QuestFixtures.QUEST_1; +import static daybyquest.support.fixture.QuestFixtures.QUEST_WITHOUT_REWARD; +import static daybyquest.support.util.ParticipantUtils.게시물_연결_횟수를_지정한다; +import static daybyquest.support.util.ParticipantUtils.퀘스트를_계속한다; +import static daybyquest.support.util.ParticipantUtils.퀘스트를_끝낸다; +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 daybyquest.global.error.exception.InvalidDomainException; +import daybyquest.quest.domain.Quest; +import org.junit.jupiter.api.Test; + +public class ParticipantTest { + + @Test + void 퀘스트_참여_시_진행_중_상태가_설정된다() { + // given + final Long questId = 1L; + final Long badgeId = 1L; + final Long userId = 1L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + + // when + final Participant participant = new Participant(userId, quest); + + // then + assertThat(participant.getState()).isEqualTo(DOING); + } + + @Test + void 퀘스트_참여_시_연결된_게시물_수는_0개로_설정된다() { + // given + final Long questId = 1L; + final Long badgeId = 1L; + final Long userId = 1L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + + // when + final Participant participant = new Participant(userId, quest); + + // then + assertThat(participant.getLinkedCount()).isZero(); + } + + @Test + void 퀘스트_ID를_조회한다() { + // given + final Long questId = 1L; + final Long badgeId = 1L; + final Long userId = 1L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + + // when + final Participant participant = new Participant(userId, quest); + + // then + assertThat(participant.getQuestId()).isEqualTo(questId); + } + + @Test + void 퀘스트_보상을_받는다() { + // given + final Long questId = 1L; + final Long badgeId = 1L; + final Long userId = 1L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant participant = new Participant(userId, quest); + 게시물_연결_횟수를_지정한다(participant, QUEST_1.rewardCount); + + // when + final Long actualBadgeId = participant.takeReward(); + + // then + assertAll(() -> { + assertThat(actualBadgeId).isEqualTo(badgeId); + assertThat(participant.getState()).isEqualTo(FINISHED); + }); + } + + @Test + void 퀘스트_목표치를_달성하지_못했다면_예외를_던진다() { + // given + final Long questId = 1L; + final Long badgeId = 1L; + final Long userId = 1L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant participant = new Participant(userId, quest); + 게시물_연결_횟수를_지정한다(participant, QUEST_1.rewardCount - 1); + + // when & then + assertThatThrownBy(participant::takeReward) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 퀘스트_보상이_없다면_예외를_던진다() { + // given + final Long questId = 1L; + final Long userId = 1L; + final Quest quest = QUEST_WITHOUT_REWARD.일반_퀘스트_생성(questId, null); + final Participant participant = new Participant(userId, quest); + 게시물_연결_횟수를_지정한다(participant, QUEST_1.rewardCount); + + // when & then + assertThatThrownBy(participant::takeReward) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 이미_보상을_받았다면_예외를_던진다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long bobId = 3L; + final Long aliceId = 4L; + + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant bob = new Participant(bobId, quest); + final Participant alice = new Participant(aliceId, quest); + + 퀘스트를_계속한다(bob); + 퀘스트를_끝낸다(bob); + + // when & then + assertAll(() -> { + assertThatThrownBy(bob::takeReward).isInstanceOf(InvalidDomainException.class); + assertThatThrownBy(alice::takeReward).isInstanceOf(InvalidDomainException.class); + }); + } + + @Test + void 퀘스트가_계속_상태일_때_끝낸다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long bobId = 3L; + + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant participant = new Participant(bobId, quest); + + 퀘스트를_계속한다(participant); + + // when + participant.finish(); + + // then + assertThat(participant.getState()).isEqualTo(FINISHED); + } + + @Test + void 퀘스트가_계속_상태가_아닐_때_끝내려하면_예외를_던진다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long bobId = 3L; + final Long aliceId = 4L; + + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant bob = new Participant(bobId, quest); + final Participant alice = new Participant(aliceId, quest); + + 퀘스트를_끝낸다(bob); + + // when & then + assertAll(() -> { + assertThatThrownBy(bob::finish).isInstanceOf(InvalidDomainException.class); + assertThatThrownBy(alice::finish).isInstanceOf(InvalidDomainException.class); + }); + } + + @Test + void 퀘스트가_끝난_상태일_때_계속한다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long bobId = 3L; + + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant participant = new Participant(bobId, quest); + + 퀘스트를_끝낸다(participant); + + // when + participant.doContinue(); + + // then + assertThat(participant.getState()).isEqualTo(CONTINUE); + } + + @Test + void 퀘스트가_끝난_상태가_아닐_때_계속하려_하면_예외를_던진다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long bobId = 3L; + final Long aliceId = 4L; + + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant bob = new Participant(bobId, quest); + final Participant alice = new Participant(aliceId, quest); + + 퀘스트를_계속한다(bob); + + // when & then + assertAll(() -> { + assertThatThrownBy(bob::doContinue).isInstanceOf(InvalidDomainException.class); + assertThatThrownBy(alice::doContinue).isInstanceOf(InvalidDomainException.class); + }); + } + + @Test + void 퀘스트_수행_횟수를_증가시킨다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long bobId = 3L; + + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant participant = new Participant(bobId, quest); + + // when + participant.increaseLinkedCount(); + + // then + assertThat(participant.getLinkedCount()).isOne(); + } + + @Test + void 퀘스트가_계속_상태일_때_퀘스트_수행_횟수를_증가시킨다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long bobId = 3L; + + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant participant = new Participant(bobId, quest); + 퀘스트를_계속한다(participant); + + // when + participant.increaseLinkedCount(); + + // then + assertThat(participant.getLinkedCount()).isOne(); + } + + @Test + void 퀘스트가_끝난_상태라면_수행_횟수를_증가시킬_수_없다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long bobId = 3L; + + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant participant = new Participant(bobId, quest); + 퀘스트를_끝낸다(participant); + + // when & then + assertThatThrownBy(participant::increaseLinkedCount).isInstanceOf(InvalidDomainException.class); + } +} diff --git a/src/test/java/daybyquest/participant/domain/ParticipantsTest.java b/src/test/java/daybyquest/participant/domain/ParticipantsTest.java new file mode 100644 index 0000000..d94a300 --- /dev/null +++ b/src/test/java/daybyquest/participant/domain/ParticipantsTest.java @@ -0,0 +1,162 @@ +package daybyquest.participant.domain; + +import static daybyquest.support.fixture.QuestFixtures.QUEST_1; +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.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +import daybyquest.global.error.exception.InvalidDomainException; +import daybyquest.quest.domain.Quest; +import daybyquest.quest.domain.Quests; +import daybyquest.user.domain.Users; +import java.util.Optional; +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; + +@ExtendWith(MockitoExtension.class) +public class ParticipantsTest { + + @Mock + private ParticipantRepository participantRepository; + + @Mock + private Users users; + + @Mock + private Quests quests; + + @InjectMocks + private Participants participants; + + @Test + void 사용자와_퀘스트_ID를_검증하고_저장한다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long userId = 3L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + given(quests.getById(questId)).willReturn(quest); + + // when + participants.saveWithUserIdAndQuestId(userId, questId); + + // then + assertAll(() -> { + then(users).should().validateExistentById(userId); + then(quests).should().getById(questId); + then(participantRepository).should().existsByUserIdAndQuestId(userId, questId); + then(participantRepository).should().save(any(Participant.class)); + }); + } + + @Test + void 이미_참여_중인_퀘스트_라면_예외를_던진다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long userId = 3L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + given(quests.getById(questId)).willReturn(quest); + given(participantRepository.existsByUserIdAndQuestId(userId, questId)).willReturn(true); + + // when & then + assertThatThrownBy(() -> participants.saveWithUserIdAndQuestId(userId, questId)) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사용자와_퀘스트_ID로_존재_여부를_검증한다() { + // given + final Long questId = 1L; + final Long userId = 2L; + given(participantRepository.existsByUserIdAndQuestId(userId, questId)).willReturn(true); + + // when + participants.validateExistent(userId, questId); + + // then + then(participantRepository).should().existsByUserIdAndQuestId(userId, questId); + } + + @Test + void 사용자와_퀘스트_ID로_존재_여부를_검증_시_없다면_예외를_던진다() { + // given + final Long questId = 1L; + final Long userId = 2L; + + // when & then + assertThatThrownBy(() -> participants.validateExistent(questId, userId)) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사용자와_퀘스트_ID를_통해_조회한다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long userId = 3L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant expected = new Participant(userId, quest); + given(participantRepository.findByUserIdAndQuestId(userId, questId)).willReturn( + Optional.of(expected)); + + // when + final Participant actual = participants.getByUserIdAndQuestId(userId, questId); + + // then + assertAll(() -> { + then(participantRepository).should().findByUserIdAndQuestId(userId, questId); + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + }); + } + + @Test + void 사용자와_퀘스트_ID를_통한_조회_시_없다면_예외를_던진다() { + // given + final Long questId = 1L; + final Long userId = 3L; + given(participantRepository.findByUserIdAndQuestId(userId, questId)).willReturn( + Optional.empty()); + + // when & then + assertThatThrownBy(() -> participants.getByUserIdAndQuestId(userId, questId)) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사용자와_퀘스트_ID를_통해_삭제한다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Long userId = 3L; + final Quest quest = QUEST_1.일반_퀘스트_생성(questId, badgeId); + final Participant expected = new Participant(userId, quest); + given(participantRepository.findByUserIdAndQuestId(userId, questId)).willReturn( + Optional.of(expected)); + + // when + participants.deleteByUserIdAndQuestId(userId, questId); + + // then + then(participantRepository).should().delete(expected); + } + + @Test + void 사용자와_퀘스트_ID를_통한_삭제_시_없다면_예외를_던진다() { + // given + final Long questId = 1L; + final Long userId = 3L; + given(participantRepository.findByUserIdAndQuestId(userId, questId)).willReturn( + Optional.empty()); + + // when & then + assertThatThrownBy(() -> participants.deleteByUserIdAndQuestId(userId, questId)) + .isInstanceOf(InvalidDomainException.class); + } +} diff --git a/src/test/java/daybyquest/quest/domain/QuestTest.java b/src/test/java/daybyquest/quest/domain/QuestTest.java new file mode 100644 index 0000000..ecc7d54 --- /dev/null +++ b/src/test/java/daybyquest/quest/domain/QuestTest.java @@ -0,0 +1,267 @@ +package daybyquest.quest.domain; + +import static daybyquest.quest.domain.QuestState.ACTIVE; +import static daybyquest.support.fixture.QuestFixtures.QUEST_1; +import static daybyquest.support.util.StringUtils.문자열을_만든다; +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 daybyquest.global.error.exception.InvalidDomainException; +import daybyquest.image.vo.Image; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class QuestTest { + + @Nested + class 일반_퀘스트_생성은 { + + @Test + void 제목이_비어있으면_예외를_던진다() { + // given + final String title = ""; + + // when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, title, + QUEST_1.content, null, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 제목이_30자를_넘으면_예외를_던진다() { + // given + final String title = 문자열을_만든다(31); + + // when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, title, + QUEST_1.content, null, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 내용이_300자를_넘으면_예외를_던진다() { + // given + final String content = 문자열을_만든다(501); + + // when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, QUEST_1.title, + content, null, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사진_설명이_비어있으면_예외를_던진다() { + // given + final String imageDescription = ""; + + // when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사진_설명이_100자를_넘으면_예외를_던진다() { + // given + final String imageDescription = 문자열을_만든다(101); + + // when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @ParameterizedTest + @ValueSource(longs = {0L, -1L, -1111L}) + void 목표치가_1보다_작으면_예외를_던진다(final Long rewardCount) { + // given & when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, rewardCount, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @ParameterizedTest + @ValueSource(longs = {366L, 1000L}) + void 목표치가_365보다_크면_예외를_던진다(final Long rewardCount) { + // given & when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, rewardCount, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사진이_3개_보다_많으면_예외를_던진다() { + // given + final List images = List.of(new Image("사진1"), new Image("사진2"), + new Image("사진3"), new Image("사진4")); + + // when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, QUEST_1.imageDescription, images)) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사진이_3개_보다_적으면_예외를_던진다() { + // given + final List images = List.of(new Image("사진1"), new Image("사진2")); + + // when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, QUEST_1.imageDescription, images)) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 보상이_없는데_목표치가_있으면_예외를_던진다() { + // given & when & then + assertThatThrownBy(() -> Quest.createNormalQuest(null, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, 1L, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 보상이_있는데_목표치가_없으면_예외를_던진다() { + // given & when & then + assertThatThrownBy(() -> Quest.createNormalQuest(1L, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + } + + @Nested + class 그룹_퀘스트_생성은 { + + @Test + void 그룹_ID가_없으면_예외를_던진다() { + // given & when & then + assertThatThrownBy(() -> Quest.createGroupQuest(null, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 제목이_비어있으면_예외를_던진다() { + // given + final String title = ""; + + // when & then + assertThatThrownBy(() -> Quest.createGroupQuest(1L, QUEST_1.interest, title, + QUEST_1.content, null, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 제목이_30자를_넘으면_예외를_던진다() { + // given + final String title = 문자열을_만든다(31); + + // when & then + assertThatThrownBy(() -> Quest.createGroupQuest(1L, QUEST_1.interest, title, + QUEST_1.content, null, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 내용이_300자를_넘으면_예외를_던진다() { + // given + final String content = 문자열을_만든다(501); + + // when & then + assertThatThrownBy(() -> Quest.createGroupQuest(1L, QUEST_1.interest, QUEST_1.title, + content, null, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사진_설명이_비어있으면_예외를_던진다() { + // given + final String imageDescription = ""; + + // when & then + assertThatThrownBy(() -> Quest.createGroupQuest(1L, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사진_설명이_100자를_넘으면_예외를_던진다() { + // given + final String imageDescription = 문자열을_만든다(101); + + // when & then + assertThatThrownBy(() -> Quest.createGroupQuest(1L, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사진이_3개_보다_많으면_예외를_던진다() { + // given + final List images = List.of(new Image("사진1"), new Image("사진2"), + new Image("사진3"), new Image("사진4")); + + // when & then + assertThatThrownBy(() -> Quest.createGroupQuest(1L, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, QUEST_1.imageDescription, images)) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 사진이_3개_보다_적으면_예외를_던진다() { + // given + final List images = List.of(new Image("사진1"), new Image("사진2")); + + // when & then + assertThatThrownBy(() -> Quest.createGroupQuest(1L, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, null, QUEST_1.imageDescription, images)) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 만료_시간이_현재보다_전이면_예외를_던진다() { + // given + final LocalDateTime expiredAt = LocalDateTime.now().minusDays(1); + + // when & then + assertThatThrownBy(() -> Quest.createGroupQuest(1L, QUEST_1.interest, QUEST_1.title, + QUEST_1.content, expiredAt, QUEST_1.imageDescription, QUEST_1.사진_목록())) + .isInstanceOf(InvalidDomainException.class); + } + } + + @Nested + class 라벨_설정은 { + + @Test + void 새로_만든_퀘스트였다면_퀘스트를_활성화하고_라벨을_설정한다() { + // given + final Quest quest = QUEST_1.일반_퀘스트_생성(); + + // when + quest.setLabel(QUEST_1.label); + + // then + assertAll(() -> { + assertThat(quest.getState()).isEqualTo(ACTIVE); + assertThat(quest.getLabel()).isEqualTo(QUEST_1.label); + }); + } + + @Test + void 이미_활성화된_퀘스트라면_예외를_던진다() { + // given + final Quest quest = QUEST_1.일반_퀘스트_생성(); + quest.setLabel(QUEST_1.label); + + // when & then + assertThatThrownBy(() -> quest.setLabel(QUEST_1.label)) + .isInstanceOf(InvalidDomainException.class); + } + } +} diff --git a/src/test/java/daybyquest/quest/domain/QuestsTest.java b/src/test/java/daybyquest/quest/domain/QuestsTest.java new file mode 100644 index 0000000..f9e1307 --- /dev/null +++ b/src/test/java/daybyquest/quest/domain/QuestsTest.java @@ -0,0 +1,86 @@ +package daybyquest.quest.domain; + +import static daybyquest.support.fixture.QuestFixtures.QUEST_1; +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.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +import daybyquest.badge.domain.Badges; +import daybyquest.global.error.exception.NotExistQuestException; +import java.util.Optional; +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; + +@ExtendWith(MockitoExtension.class) +public class QuestsTest { + + @Mock + private QuestRepository questRepository; + + @Mock + private Badges badges; + + @InjectMocks + private Quests quests; + + @Test + void 뱃지_ID를_검증하고_게시물을_저장한다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + given(questRepository.save(any(Quest.class))).willReturn(QUEST_1.일반_퀘스트_생성(questId, badgeId)); + + // when + quests.save(QUEST_1.일반_퀘스트_생성(badgeId)); + + // then + assertAll(() -> { + then(badges).should().validateExistentById(badgeId); + then(questRepository).should().save(any(Quest.class)); + }); + } + + @Test + void 뱃지_ID가_없으면_그냥_게시물을_저장한다() { + // given + final Long questId = 1L; + given(questRepository.save(any(Quest.class))).willReturn(QUEST_1.일반_퀘스트_생성(questId, null)); + + // when + quests.save(QUEST_1.일반_퀘스트_생성()); + + // then + then(questRepository).should().save(any(Quest.class)); + } + + @Test + void ID를_통해_퀘스트를_조회한다() { + // given + final Long questId = 1L; + final Long badgeId = 2L; + final Quest expected = QUEST_1.일반_퀘스트_생성(questId, badgeId); + given(questRepository.findById(questId)).willReturn(Optional.of(expected)); + + // when + final Quest actual = quests.getById(questId); + + // then + assertAll(() -> { + then(questRepository).should().findById(questId); + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + }); + } + + @Test + void ID를_통한_조회_시_퀘스트가_없다면_예외를_던진다() { + // given & when & then + assertThatThrownBy(() -> quests.getById(1L)) + .isInstanceOf(NotExistQuestException.class); + } +} diff --git a/src/test/java/daybyquest/quest/query/QuestDaoQuerydslImplTest.java b/src/test/java/daybyquest/quest/query/QuestDaoQuerydslImplTest.java new file mode 100644 index 0000000..48407f4 --- /dev/null +++ b/src/test/java/daybyquest/quest/query/QuestDaoQuerydslImplTest.java @@ -0,0 +1,165 @@ +package daybyquest.quest.query; + +import static daybyquest.participant.domain.ParticipantState.CONTINUE; +import static daybyquest.participant.domain.ParticipantState.DOING; +import static daybyquest.participant.domain.ParticipantState.FINISHED; +import static daybyquest.participant.domain.ParticipantState.NOT; +import static daybyquest.quest.domain.QuestCategory.GROUP; +import static daybyquest.quest.domain.QuestCategory.NORMAL; +import static daybyquest.support.fixture.BadgeFixtures.BADGE_1; +import static daybyquest.support.fixture.GroupFixtures.GROUP_1; +import static daybyquest.support.fixture.PostFixtures.POST_1; +import static daybyquest.support.fixture.PostFixtures.POST_2; +import static daybyquest.support.fixture.PostFixtures.POST_3; +import static daybyquest.support.fixture.QuestFixtures.QUEST_1; +import static daybyquest.support.fixture.UserFixtures.ALICE; +import static daybyquest.support.fixture.UserFixtures.BOB; +import static daybyquest.support.fixture.UserFixtures.CHARLIE; +import static daybyquest.support.fixture.UserFixtures.DAVID; +import static daybyquest.support.util.ParticipantUtils.퀘스트를_계속한다; +import static daybyquest.support.util.ParticipantUtils.퀘스트를_끝낸다; +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 daybyquest.badge.domain.Badge; +import daybyquest.global.error.exception.NotExistQuestException; +import daybyquest.group.domain.Group; +import daybyquest.participant.domain.Participant; +import daybyquest.quest.domain.Quest; +import daybyquest.support.test.QuerydslTest; +import daybyquest.user.domain.User; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +@Import(QuestDaoQuerydslImpl.class) +public class QuestDaoQuerydslImplTest extends QuerydslTest { + + @Autowired + private QuestDaoQuerydslImpl questDao; + + @Test + void 일반_퀘스트_ID로_데이터를_조회한다() { + // given + final Long userId = 1L; + final Quest quest = 저장한다(QUEST_1.일반_퀘스트_생성()); + + // when + final QuestData questData = questDao.getById(userId, quest.getId()); + + // then + assertAll(() -> { + assertThat(questData.getTitle()).isEqualTo(QUEST_1.title); + assertThat(questData.getContent()).isEqualTo(QUEST_1.content); + assertThat(questData.getInterestName()).isEqualTo(QUEST_1.interest); + assertThat(questData.getCategory()).isEqualTo(NORMAL); + }); + } + + @Test + void 일반_퀘스트_ID로_데이터_조회_시_보상이_있다면_뱃지_정보를_포함한다() { + // given + final Long userId = 1L; + final Badge badge = 저장한다(BADGE_1.생성()); + final Quest quest = 저장한다(QUEST_1.일반_퀘스트_생성(badge)); + + // when + final QuestData questData = questDao.getById(userId, quest.getId()); + + // then + assertAll(() -> { + assertThat(questData.getRewardCount()).isEqualTo(QUEST_1.rewardCount); + assertThat(questData.getBadgeId()).isEqualTo(badge.getId()); + assertThat(questData.getImage()).isEqualTo(badge.getImage()); + }); + } + + @Test + void 그룹_퀘스트_ID로_데이터를_조회한다() { + // given + final Long userId = 1L; + final Long groupId = 2L; + final Quest quest = 저장한다(QUEST_1.그룹_퀘스트_생성(groupId)); + + // when + final QuestData questData = questDao.getById(userId, quest.getId()); + + // then + assertAll(() -> { + assertThat(questData.getTitle()).isEqualTo(QUEST_1.title); + assertThat(questData.getContent()).isEqualTo(QUEST_1.content); + assertThat(questData.getInterestName()).isEqualTo(QUEST_1.interest); + assertThat(questData.getCategory()).isEqualTo(GROUP); + assertThat(questData.getGroupId()).isEqualTo(groupId); + }); + } + + @Test + void 그룹_퀘스트_ID로_데이터_조회_시_그룹_정보를_포함한다() { + // given + final Long userId = 1L; + final Group group = 저장한다(GROUP_1.생성()); + final Quest quest = 저장한다(QUEST_1.그룹_퀘스트_생성(group)); + + // when + final QuestData questData = questDao.getById(userId, quest.getId()); + + // then + assertAll(() -> { + assertThat(questData.getGroupId()).isEqualTo(group.getId()); + assertThat(questData.getImage()).isEqualTo(group.getImage()); + }); + } + + @Test + void 데이터_조회_시_링크_성공한_게시물_수를_포함한다() { + // given + final User user = 저장한다(ALICE.생성()); + final Quest quest = 저장한다(QUEST_1.일반_퀘스트_생성()); + 저장한다(POST_1.링크_성공_상태로_생성(user, quest)); + 저장한다(POST_2.링크_성공_상태로_생성(user, quest)); + 저장한다(POST_3.생성(user, quest)); + + // when + final QuestData questData = questDao.getById(user.getId(), quest.getId()); + + // then + assertThat(questData.getCurrentCount()).isEqualTo(2); + } + + @Test + void 데이터_조회_시_참여_정보를_포함한다() { + // given + final User alice = 저장한다(ALICE.생성()); + final User bob = 저장한다(BOB.생성()); + final User charlie = 저장한다(CHARLIE.생성()); + final User david = 저장한다(DAVID.생성()); + final Quest quest = 저장한다(QUEST_1.일반_퀘스트_생성()); + + 저장한다(new Participant(alice.getId(), quest)); + 저장한다(퀘스트를_끝낸다(new Participant(bob.getId(), quest))); + 저장한다(퀘스트를_계속한다(new Participant(charlie.getId(), quest))); + + // when + final QuestData aliceData = questDao.getById(alice.getId(), quest.getId()); + final QuestData bobData = questDao.getById(bob.getId(), quest.getId()); + final QuestData charlieData = questDao.getById(charlie.getId(), quest.getId()); + final QuestData davidData = questDao.getById(david.getId(), quest.getId()); + + // then + assertAll(() -> { + assertThat(aliceData.getState()).isEqualTo(DOING); + assertThat(bobData.getState()).isEqualTo(FINISHED); + assertThat(charlieData.getState()).isEqualTo(CONTINUE); + assertThat(davidData.getState()).isEqualTo(NOT); + }); + } + + @Test + void ID에_해당하는_퀘스트가_없으면_예외를_던진다() { + // given & when & then + assertThatThrownBy(() -> questDao.getById(1L, 2L)) + .isInstanceOf(NotExistQuestException.class); + } +} diff --git a/src/test/java/daybyquest/support/fixture/PostFixtures.java b/src/test/java/daybyquest/support/fixture/PostFixtures.java index 612549c..9f69ad1 100644 --- a/src/test/java/daybyquest/support/fixture/PostFixtures.java +++ b/src/test/java/daybyquest/support/fixture/PostFixtures.java @@ -1,6 +1,8 @@ package daybyquest.support.fixture; +import static daybyquest.post.domain.PostState.SUCCESS; + import daybyquest.image.vo.Image; import daybyquest.post.domain.Post; import daybyquest.quest.domain.Quest; @@ -43,6 +45,12 @@ public enum PostFixtures { return 생성(null, user.getId(), quest.getId()); } + public Post 링크_성공_상태로_생성(final User user, final Quest quest) { + final Post post = 생성(null, user.getId(), quest.getId()); + ReflectionTestUtils.setField(post, "state", SUCCESS); + return post; + } + public Post 생성(final User user) { return 생성(null, user.getId(), null); } diff --git a/src/test/java/daybyquest/support/fixture/QuestFixtures.java b/src/test/java/daybyquest/support/fixture/QuestFixtures.java new file mode 100644 index 0000000..65f5cc2 --- /dev/null +++ b/src/test/java/daybyquest/support/fixture/QuestFixtures.java @@ -0,0 +1,91 @@ +package daybyquest.support.fixture; + +import daybyquest.badge.domain.Badge; +import daybyquest.group.domain.Group; +import daybyquest.image.vo.Image; +import daybyquest.quest.domain.Quest; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.test.util.ReflectionTestUtils; + +public enum QuestFixtures { + QUEST_1("일반퀘스트1", "퀘스트1입니다", "관심사", 5L, null, + "퀘스트1 사진 설명입니다", List.of("퀘스트사진1_1", "퀘스트사진1_2", "퀘스트사진1_3"), "라벨1"), + QUEST_2("일반퀘스트2", "퀘스트2입니다", "관심사", 10L, null, + "퀘스트2 사진 설명입니다", List.of("퀘스트사진2_1", "퀘스트사진2_2", "퀘스트사진2_3"), "라벨2"), + QUEST_3("일반퀘스트3", "퀘스트3입니다", "관심사", 15L, null, + "퀘스트3 사진 설명입니다", List.of("퀘스트사진3_1", "퀘스트사진3_2", "퀘스트사진3_3"), "라벨3"), + QUEST_WITHOUT_REWARD("일반퀘스트", "퀘스트입니다", "관심사", null, null, + "퀘스트 사진 설명입니다", List.of("퀘스트사진_1", "퀘스트사진_2", "퀘스트사진_3"), "라벨4"); + + public final String title; + + public final String content; + + public final String interest; + + public final Long rewardCount; + + public final LocalDateTime expiredAt; + + public final String imageDescription; + + public final List imageIdentifiers; + + public final String label; + + QuestFixtures(final String title, final String content, final String interest, + final Long rewardCount, final LocalDateTime expiredAt, + final String imageDescription, final List imageIdentifiers, final String label) { + this.title = title; + this.content = content; + this.interest = interest; + this.rewardCount = rewardCount; + this.expiredAt = expiredAt; + this.imageDescription = imageDescription; + this.imageIdentifiers = imageIdentifiers; + this.label = label; + } + + public Quest 일반_퀘스트_생성(final Long id, final Long badgeId) { + Long rewardCount = this.rewardCount; + if (badgeId == null) { + rewardCount = null; + } + final Quest quest = Quest.createNormalQuest(badgeId, interest, title, content, rewardCount, + imageDescription, 사진_목록()); + ReflectionTestUtils.setField(quest, "id", id); + return quest; + } + + public Quest 일반_퀘스트_생성() { + return 일반_퀘스트_생성(null, null); + } + + public Quest 일반_퀘스트_생성(final Long badgeId) { + return 일반_퀘스트_생성(null, badgeId); + } + + public Quest 일반_퀘스트_생성(final Badge badge) { + return 일반_퀘스트_생성(badge.getId()); + } + + public Quest 그룹_퀘스트_생성(final Long id, final Long groupId) { + final Quest quest = Quest.createGroupQuest(groupId, interest, title, content, expiredAt, + imageDescription, 사진_목록()); + ReflectionTestUtils.setField(quest, "id", id); + return quest; + } + + public Quest 그룹_퀘스트_생성(final Long groupId) { + return 그룹_퀘스트_생성(null, groupId); + } + + public Quest 그룹_퀘스트_생성(final Group group) { + return 그룹_퀘스트_생성(group.getId()); + } + + public List 사진_목록() { + return imageIdentifiers.stream().map(Image::new).toList(); + } +} diff --git a/src/test/java/daybyquest/support/util/ParticipantUtils.java b/src/test/java/daybyquest/support/util/ParticipantUtils.java new file mode 100644 index 0000000..2520590 --- /dev/null +++ b/src/test/java/daybyquest/support/util/ParticipantUtils.java @@ -0,0 +1,23 @@ +package daybyquest.support.util; + +import daybyquest.participant.domain.Participant; +import daybyquest.participant.domain.ParticipantState; +import org.springframework.test.util.ReflectionTestUtils; + +public class ParticipantUtils { + + public static Participant 게시물_연결_횟수를_지정한다(final Participant participant, final Long linkedCount) { + ReflectionTestUtils.setField(participant, "linkedCount", linkedCount); + return participant; + } + + public static Participant 퀘스트를_끝낸다(final Participant participant) { + ReflectionTestUtils.setField(participant, "state", ParticipantState.FINISHED); + return participant; + } + + public static Participant 퀘스트를_계속한다(final Participant participant) { + ReflectionTestUtils.setField(participant, "state", ParticipantState.CONTINUE); + return participant; + } +}