diff --git a/src/main/java/daybyquest/global/error/ExceptionCode.java b/src/main/java/daybyquest/global/error/ExceptionCode.java index c19b295..cf5282e 100644 --- a/src/main/java/daybyquest/global/error/ExceptionCode.java +++ b/src/main/java/daybyquest/global/error/ExceptionCode.java @@ -42,6 +42,7 @@ public enum ExceptionCode { INVALID_POST_IMAGE("POT-05", BAD_REQUEST, "게시물 사진은 1~5장이어야 합니다"), INVALID_POST_CONTENT("POT-06", BAD_REQUEST, "게시물 내용은 500자 이하여야 합니다"), ALREADY_JUDGED_POST("POT-07", BAD_REQUEST, "이미 판정된 게시물 입니다"), + NOT_LINKED_POST("POT-08", BAD_REQUEST, "퀘스트와 관련이 없는 게시물 입니다"), // Comment NOT_EXIST_COMMENT("COM-00", BAD_REQUEST, "존재하지 않는 댓글입니다"), diff --git a/src/main/java/daybyquest/global/error/exception/InternalServerException.java b/src/main/java/daybyquest/global/error/exception/InternalServerException.java new file mode 100644 index 0000000..da0afe5 --- /dev/null +++ b/src/main/java/daybyquest/global/error/exception/InternalServerException.java @@ -0,0 +1,10 @@ +package daybyquest.global.error.exception; + +import static daybyquest.global.error.ExceptionCode.INTERNAL_SERVER; + +public class InternalServerException extends CustomException { + + public InternalServerException() { + super(INTERNAL_SERVER); + } +} diff --git a/src/main/java/daybyquest/post/application/JudgePostService.java b/src/main/java/daybyquest/post/application/JudgePostService.java index 30a7264..8b5d1ba 100644 --- a/src/main/java/daybyquest/post/application/JudgePostService.java +++ b/src/main/java/daybyquest/post/application/JudgePostService.java @@ -1,6 +1,5 @@ package daybyquest.post.application; -import daybyquest.global.error.exception.BadRequestException; import daybyquest.post.domain.Judgement; import daybyquest.post.domain.Post; import daybyquest.post.domain.Posts; @@ -25,9 +24,6 @@ public JudgePostService(final Posts posts, final ApplicationEventPublisher publi @Transactional public void invoke(final Long postId, final JudgePostRequest request) { final Post post = posts.getById(postId); - if (post.getQuestId() == null) { - throw new BadRequestException(); - } final Judgement judgement = Judgement.valueOf(request.getJudgement()); if (judgement == Judgement.SUCCESS) { post.success(); diff --git a/src/main/java/daybyquest/post/application/PostClient.java b/src/main/java/daybyquest/post/application/PostClient.java new file mode 100644 index 0000000..8fccfbf --- /dev/null +++ b/src/main/java/daybyquest/post/application/PostClient.java @@ -0,0 +1,8 @@ +package daybyquest.post.application; + +import java.util.List; + +public interface PostClient { + + void requestJudge(final Long postId, final String label, final List identifiers); +} diff --git a/src/main/java/daybyquest/post/application/SavePostService.java b/src/main/java/daybyquest/post/application/SavePostService.java index 5feddba..91e79eb 100644 --- a/src/main/java/daybyquest/post/application/SavePostService.java +++ b/src/main/java/daybyquest/post/application/SavePostService.java @@ -7,6 +7,7 @@ import daybyquest.post.domain.Post; import daybyquest.post.domain.Posts; import daybyquest.post.dto.request.SavePostRequest; +import daybyquest.quest.domain.Quests; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,18 +24,25 @@ public class SavePostService { private final ImageIdentifierGenerator generator; + private final PostClient postClient; + + private final Quests quests; + public SavePostService(final Posts posts, final Images images, - final ImageIdentifierGenerator generator) { + final ImageIdentifierGenerator generator, final PostClient postClient, final Quests quests) { this.posts = posts; this.images = images; this.generator = generator; + this.postClient = postClient; + this.quests = quests; } @Transactional public Long invoke(final Long loginId, final SavePostRequest request, final List files) { - final Post post = toEntity(loginId, request, toImageList(files)); - return posts.save(post); + final Post post = posts.save(toEntity(loginId, request, toImageList(files))); + requestJudge(post); + return post.getId(); } private List toImageList(final List files) { @@ -47,4 +55,11 @@ private List toImageList(final List files) { private Post toEntity(final Long loginId, final SavePostRequest request, final List images) { return new Post(loginId, request.getQuestId(), request.getContent(), images); } + + private void requestJudge(final Post post) { + if (post.isQuestLinked()) { + postClient.requestJudge(post.getId(), quests.getLabelById(post.getQuestId()), + post.getImages().stream().map(Image::getIdentifier).toList()); + } + } } diff --git a/src/main/java/daybyquest/post/domain/Post.java b/src/main/java/daybyquest/post/domain/Post.java index 6bd3a61..d96ffdd 100644 --- a/src/main/java/daybyquest/post/domain/Post.java +++ b/src/main/java/daybyquest/post/domain/Post.java @@ -4,6 +4,7 @@ import static daybyquest.global.error.ExceptionCode.INVALID_POST_CONTENT; import static daybyquest.global.error.ExceptionCode.INVALID_POST_IMAGE; import static daybyquest.global.error.ExceptionCode.NOT_EXIST_USER; +import static daybyquest.global.error.ExceptionCode.NOT_LINKED_POST; import static daybyquest.post.domain.PostState.NEED_CHECK; import static daybyquest.post.domain.PostState.NOT_DECIDED; import static daybyquest.post.domain.PostState.SUCCESS; @@ -79,7 +80,7 @@ public Post(Long userId, Long questId, String content, List images) { validateImages(); validateContent(); if (questId == null) { - success(); + this.state = SUCCESS; } } @@ -102,6 +103,7 @@ private void validateContent() { } public void success() { + validateQuestLink(); if (state != NOT_DECIDED) { throw new InvalidDomainException(ALREADY_JUDGED_POST); } @@ -109,9 +111,20 @@ public void success() { } public void needCheck() { + validateQuestLink(); if (state != NOT_DECIDED) { throw new InvalidDomainException(ALREADY_JUDGED_POST); } state = NEED_CHECK; } + + public boolean isQuestLinked() { + return questId != null; + } + + private void validateQuestLink() { + if (!isQuestLinked()) { + throw new InvalidDomainException(NOT_LINKED_POST); + } + } } diff --git a/src/main/java/daybyquest/post/domain/Posts.java b/src/main/java/daybyquest/post/domain/Posts.java index 60e5051..c3f790f 100644 --- a/src/main/java/daybyquest/post/domain/Posts.java +++ b/src/main/java/daybyquest/post/domain/Posts.java @@ -20,12 +20,12 @@ public class Posts { this.participants = participants; } - public Long save(final Post post) { + public Post save(final Post post) { users.validateExistentById(post.getUserId()); if (post.getQuestId() != null) { participants.validateExistent(post.getUserId(), post.getQuestId()); } - return postRepository.save(post).getId(); + return postRepository.save(post); } public Post getById(final Long id) { diff --git a/src/main/java/daybyquest/post/dto/request/PostJudgeRequest.java b/src/main/java/daybyquest/post/dto/request/PostJudgeRequest.java new file mode 100644 index 0000000..e8a324e --- /dev/null +++ b/src/main/java/daybyquest/post/dto/request/PostJudgeRequest.java @@ -0,0 +1,7 @@ +package daybyquest.post.dto.request; + +import java.util.List; + +public record PostJudgeRequest(String label, List imageIdentifiers) { + +} diff --git a/src/main/java/daybyquest/post/infra/PostStubClient.java b/src/main/java/daybyquest/post/infra/PostStubClient.java new file mode 100644 index 0000000..4ba1cdf --- /dev/null +++ b/src/main/java/daybyquest/post/infra/PostStubClient.java @@ -0,0 +1,15 @@ +package daybyquest.post.infra; + +import daybyquest.post.application.PostClient; +import java.util.List; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component +@Profile("!prod") +public class PostStubClient implements PostClient { + + @Override + public void requestJudge(final Long postId, final String label, final List identifiers) { + } +} diff --git a/src/main/java/daybyquest/post/infra/PostWebClient.java b/src/main/java/daybyquest/post/infra/PostWebClient.java new file mode 100644 index 0000000..b9d2f5c --- /dev/null +++ b/src/main/java/daybyquest/post/infra/PostWebClient.java @@ -0,0 +1,38 @@ +package daybyquest.post.infra; + +import daybyquest.global.error.exception.BadRequestException; +import daybyquest.post.application.PostClient; +import daybyquest.post.dto.request.PostJudgeRequest; +import java.util.List; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpStatusCode; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +@Profile("prod") +public class PostWebClient implements PostClient { + + private final WebClient webClient; + + public PostWebClient(final WebClient webClient) { + this.webClient = webClient; + } + + @Override + public void requestJudge(final Long postId, final String label, final List identifiers) { + final PostJudgeRequest request = new PostJudgeRequest(label, identifiers); + webClient.post() + .uri(uriBuilder -> uriBuilder.pathSegment("post", postId.toString(), "judge").build()) + .bodyValue(request) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, response -> { + throw new BadRequestException(); + }) + .onStatus(HttpStatusCode::is5xxServerError, response -> { + throw new BadRequestException(); + }) + .toBodilessEntity() + .block(); + } +} diff --git a/src/main/java/daybyquest/quest/application/GroupQuestValidator.java b/src/main/java/daybyquest/quest/application/GroupQuestValidator.java new file mode 100644 index 0000000..94b8b15 --- /dev/null +++ b/src/main/java/daybyquest/quest/application/GroupQuestValidator.java @@ -0,0 +1,26 @@ +package daybyquest.quest.application; + +import daybyquest.group.domain.GroupUsers; +import daybyquest.quest.domain.Quest; +import daybyquest.quest.domain.Quests; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class GroupQuestValidator { + + private final Quests quests; + + private final GroupUsers groupUsers; + + public GroupQuestValidator(final Quests quests, final GroupUsers groupUsers) { + this.quests = quests; + this.groupUsers = groupUsers; + } + + @Transactional(readOnly = true) + public void validate(final Long loginId, final Long questId) { + final Quest quest = quests.getById(questId); + groupUsers.validateGroupManager(loginId, quest.getGroupId()); + } +} diff --git a/src/main/java/daybyquest/quest/application/QuestSseEmitters.java b/src/main/java/daybyquest/quest/application/QuestSseEmitters.java new file mode 100644 index 0000000..35591c3 --- /dev/null +++ b/src/main/java/daybyquest/quest/application/QuestSseEmitters.java @@ -0,0 +1,78 @@ +package daybyquest.quest.application; + +import daybyquest.global.error.exception.InternalServerException; +import daybyquest.quest.dto.response.QuestLabelsResponse; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@Component +@Slf4j +public class QuestSseEmitters { + + private static final Long SSE_TIMEOUT = 10L * 1000 * 60; + + private static final String MESSAGE_NAME = "labels"; + + private static final String CONNECT_MESSAGE_NAME = "connect"; + + private static final String CONNECT_MESSAGE_CONTENT = "success"; + + private final Map emitters; + + private final Map cache; + + public QuestSseEmitters() { + this.emitters = new ConcurrentHashMap<>(); + this.cache = new ConcurrentHashMap<>(); + } + + public SseEmitter subscribe(final Long questId) { + final SseEmitter emitter = new SseEmitter(SSE_TIMEOUT); + emitter.onCompletion(() -> { + log.debug("QuestSseEmiiter finished with completiong. Quest ID: {}", questId); + emitters.remove(questId); + }); + emitter.onTimeout(() -> { + log.debug("QuestSseEmiiter finished with timeout. Quest ID: {}", questId); + emitter.complete(); + }); + emitters.put(questId, emitter); + sendConnectedMessage(emitter); + sendCachedMessage(questId); + return emitter; + } + + private void sendConnectedMessage(final SseEmitter emitter) { + try { + emitter.send(SseEmitter.event().name(CONNECT_MESSAGE_NAME).data(CONNECT_MESSAGE_CONTENT)); + } catch (final IOException e) { + log.error("IOException occurred while sending connected message."); + log.error(e.getMessage()); + } + } + + private void sendCachedMessage(final Long questId) { + if (cache.containsKey(questId)) { + send(questId, cache.get(questId)); + cache.remove(questId); + } + } + + public void send(final Long questId, final QuestLabelsResponse content) { + if (emitters.containsKey(questId)) { + final SseEmitter emitter = emitters.get(questId); + try { + emitter.send(SseEmitter.event().name(MESSAGE_NAME).data(content)); + } catch (final IOException e) { + log.error("IOException occurred while sending quest labels.: quest id {}", questId); + throw new InternalServerException(); + } + return; + } + cache.put(questId, content); + } +} diff --git a/src/main/java/daybyquest/quest/application/SendQuestLabelsService.java b/src/main/java/daybyquest/quest/application/SendQuestLabelsService.java new file mode 100644 index 0000000..779b471 --- /dev/null +++ b/src/main/java/daybyquest/quest/application/SendQuestLabelsService.java @@ -0,0 +1,29 @@ +package daybyquest.quest.application; + +import daybyquest.quest.domain.Quests; +import daybyquest.quest.dto.request.QuestLabelsRequest; +import daybyquest.quest.dto.response.QuestLabelsResponse; +import org.springframework.stereotype.Service; + +@Service +public class SendQuestLabelsService { + + private final Quests quests; + + private final QuestSseEmitters questSseEmitters; + + public SendQuestLabelsService(final Quests quests, final QuestSseEmitters questSseEmitters) { + this.quests = quests; + this.questSseEmitters = questSseEmitters; + } + + public void invoke(final Long questId, final QuestLabelsRequest request) { + quests.validateExistentById(questId); + questSseEmitters.send(questId, requestToResponse(request)); + } + + private QuestLabelsResponse requestToResponse(final QuestLabelsRequest request) { + return new QuestLabelsResponse(request.getLabels()); + } + +} diff --git a/src/main/java/daybyquest/quest/application/SubscribeGroupQuestLabelsService.java b/src/main/java/daybyquest/quest/application/SubscribeGroupQuestLabelsService.java new file mode 100644 index 0000000..bf4b973 --- /dev/null +++ b/src/main/java/daybyquest/quest/application/SubscribeGroupQuestLabelsService.java @@ -0,0 +1,23 @@ +package daybyquest.quest.application; + +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@Service +public class SubscribeGroupQuestLabelsService { + + private final GroupQuestValidator groupQuestValidator; + + private final QuestSseEmitters questSseEmitters; + + public SubscribeGroupQuestLabelsService(final GroupQuestValidator groupQuestValidator, + final QuestSseEmitters questSseEmitters) { + this.groupQuestValidator = groupQuestValidator; + this.questSseEmitters = questSseEmitters; + } + + public SseEmitter invoke(final Long loginId, final Long questId) { + groupQuestValidator.validate(loginId, questId); + return questSseEmitters.subscribe(questId); + } +} diff --git a/src/main/java/daybyquest/quest/application/SubscribeQuestLabelsService.java b/src/main/java/daybyquest/quest/application/SubscribeQuestLabelsService.java new file mode 100644 index 0000000..afa55c1 --- /dev/null +++ b/src/main/java/daybyquest/quest/application/SubscribeQuestLabelsService.java @@ -0,0 +1,23 @@ +package daybyquest.quest.application; + +import daybyquest.quest.domain.Quests; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@Service +public class SubscribeQuestLabelsService { + + private final Quests quests; + + private final QuestSseEmitters questSseEmitters; + + public SubscribeQuestLabelsService(final Quests quests, final QuestSseEmitters questSseEmitters) { + this.quests = quests; + this.questSseEmitters = questSseEmitters; + } + + public SseEmitter invoke(final Long questId) { + quests.validateExistentById(questId); + return questSseEmitters.subscribe(questId); + } +} diff --git a/src/main/java/daybyquest/quest/domain/Quests.java b/src/main/java/daybyquest/quest/domain/Quests.java index f7146c7..88a8fc1 100644 --- a/src/main/java/daybyquest/quest/domain/Quests.java +++ b/src/main/java/daybyquest/quest/domain/Quests.java @@ -31,9 +31,19 @@ public Quest getById(final Long id) { return questRepository.findById(id).orElseThrow(NotExistQuestException::new); } + public void validateExistentById(final Long id) { + if (!questRepository.existsById(id)) { + throw new NotExistQuestException(); + } + } + private void validateNotExistentByBadgeId(final Long badgeId) { if (questRepository.existsByBadgeId(badgeId)) { throw new InvalidDomainException(ALREADY_EXIST_REWARD); } } + + public String getLabelById(final Long id) { + return getById(id).getLabel(); + } } diff --git a/src/main/java/daybyquest/quest/dto/request/QuestLabelsRequest.java b/src/main/java/daybyquest/quest/dto/request/QuestLabelsRequest.java new file mode 100644 index 0000000..cbeec1f --- /dev/null +++ b/src/main/java/daybyquest/quest/dto/request/QuestLabelsRequest.java @@ -0,0 +1,12 @@ +package daybyquest.quest.dto.request; + +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class QuestLabelsRequest { + + private List labels; +} diff --git a/src/main/java/daybyquest/quest/dto/response/QuestLabelsResponse.java b/src/main/java/daybyquest/quest/dto/response/QuestLabelsResponse.java new file mode 100644 index 0000000..cdf8120 --- /dev/null +++ b/src/main/java/daybyquest/quest/dto/response/QuestLabelsResponse.java @@ -0,0 +1,7 @@ +package daybyquest.quest.dto.response; + +import java.util.List; + +public record QuestLabelsResponse(List labels) { + +} diff --git a/src/main/java/daybyquest/quest/infra/QuestStubClient.java b/src/main/java/daybyquest/quest/infra/QuestStubClient.java index 46b1631..51652c1 100644 --- a/src/main/java/daybyquest/quest/infra/QuestStubClient.java +++ b/src/main/java/daybyquest/quest/infra/QuestStubClient.java @@ -4,20 +4,12 @@ import java.util.List; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; @Component @Profile("!prod") public class QuestStubClient implements QuestClient { - private final WebClient webClient; - - public QuestStubClient(final WebClient webClient) { - this.webClient = webClient; - } - @Override public void requestLabels(final Long questId, final List identifiers) { - return; } } diff --git a/src/main/java/daybyquest/quest/presentation/QuestCommandApi.java b/src/main/java/daybyquest/quest/presentation/QuestCommandApi.java index 30f68cd..057623b 100644 --- a/src/main/java/daybyquest/quest/presentation/QuestCommandApi.java +++ b/src/main/java/daybyquest/quest/presentation/QuestCommandApi.java @@ -1,11 +1,17 @@ package daybyquest.quest.presentation; +import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE; + import daybyquest.auth.Authorization; import daybyquest.auth.domain.AccessUser; import daybyquest.quest.application.SaveGroupQuestDetailService; import daybyquest.quest.application.SaveGroupQuestService; import daybyquest.quest.application.SaveQuestDetailService; import daybyquest.quest.application.SaveQuestService; +import daybyquest.quest.application.SendQuestLabelsService; +import daybyquest.quest.application.SubscribeGroupQuestLabelsService; +import daybyquest.quest.application.SubscribeQuestLabelsService; +import daybyquest.quest.dto.request.QuestLabelsRequest; import daybyquest.quest.dto.request.SaveGroupQuestDetailRequest; import daybyquest.quest.dto.request.SaveGroupQuestRequest; import daybyquest.quest.dto.request.SaveQuestDetailRequest; @@ -14,12 +20,15 @@ import jakarta.validation.Valid; import java.util.List; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; @RestController public class QuestCommandApi { @@ -32,14 +41,26 @@ public class QuestCommandApi { private final SaveGroupQuestDetailService saveGroupQuestDetailService; + private final SubscribeQuestLabelsService subscribeQuestLabelsService; + + private final SubscribeGroupQuestLabelsService subscribeGroupQuestLabelsService; + + private final SendQuestLabelsService sendQuestLabelsService; + public QuestCommandApi(final SaveQuestService saveQuestService, final SaveGroupQuestService saveGroupQuestService, final SaveQuestDetailService saveQuestDetailService, - final SaveGroupQuestDetailService saveGroupQuestDetailService) { + final SaveGroupQuestDetailService saveGroupQuestDetailService, + final SubscribeQuestLabelsService subscribeQuestLabelsService, + final SubscribeGroupQuestLabelsService subscribeGroupQuestLabelsService, + final SendQuestLabelsService sendQuestLabelsService) { this.saveQuestService = saveQuestService; this.saveGroupQuestService = saveGroupQuestService; this.saveQuestDetailService = saveQuestDetailService; this.saveGroupQuestDetailService = saveGroupQuestDetailService; + this.subscribeQuestLabelsService = subscribeQuestLabelsService; + this.subscribeGroupQuestLabelsService = subscribeGroupQuestLabelsService; + this.sendQuestLabelsService = sendQuestLabelsService; } @PostMapping("/quest") @@ -75,4 +96,28 @@ public ResponseEntity saveGroupQuestDetail(final AccessUser a saveGroupQuestDetailService.invoke(accessUser.getId(), questId, request); return ResponseEntity.ok(new SaveQuestResponse(questId)); } + + @GetMapping(value = "/quest/{questId}/labels", produces = TEXT_EVENT_STREAM_VALUE) + @Authorization(admin = true) + public ResponseEntity subscribeQuestLabels(final AccessUser accessUser, + @PathVariable final Long questId) { + final SseEmitter emitter = subscribeQuestLabelsService.invoke(questId); + return ResponseEntity.ok(emitter); + } + + @GetMapping(value = "/group/{questId}/quest/labels", produces = TEXT_EVENT_STREAM_VALUE) + @Authorization + public ResponseEntity subscribeGroupQuestLabels(final AccessUser accessUser, + @PathVariable final Long questId) { + final SseEmitter emitter = subscribeGroupQuestLabelsService.invoke(accessUser.getId(), questId); + return ResponseEntity.ok(emitter); + } + + @PatchMapping(value = "/quest/{questId}/shot") + @Authorization(admin = true) + public ResponseEntity sendQuestLabels(final AccessUser accessUser, + @PathVariable final Long questId, @RequestBody final QuestLabelsRequest request) { + sendQuestLabelsService.invoke(questId, request); + return ResponseEntity.ok().build(); + } } diff --git a/src/test/java/daybyquest/post/domain/PostTest.java b/src/test/java/daybyquest/post/domain/PostTest.java index ac1d8b4..4d57e55 100644 --- a/src/test/java/daybyquest/post/domain/PostTest.java +++ b/src/test/java/daybyquest/post/domain/PostTest.java @@ -2,7 +2,9 @@ import static daybyquest.support.fixture.PostFixtures.POST_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.domain.Image; @@ -52,4 +54,87 @@ class 생성자는 { .isInstanceOf(InvalidDomainException.class); } } + + @Test + void 퀘스트_링크를_성공_처리한다() { + // given + final Post post = POST_1.생성(1L, 2L); + + // when + post.success(); + + // then + assertThat(post.getState()).isEqualTo(PostState.SUCCESS); + } + + @Test + void 퀘스트_링크_성공_처리_시_이미_판정된_퀘스트라면_예외를_던진다() { + // given + final Post post = POST_1.생성(1L, 2L); + post.needCheck(); + + // when & then + assertThatThrownBy(post::success) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 퀘스트_링크_성공_처리_시_퀘스트와_연관이_없다면_예외를_던진다() { + // given + final Post post = POST_1.생성(1L); + + // when & then + assertThatThrownBy(post::success) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 퀘스트_링크를_확인_필요_처리_한다() { + // given + final Post post = POST_1.생성(1L, 2L); + + // when + post.needCheck(); + + // then + assertThat(post.getState()).isEqualTo(PostState.NEED_CHECK); + } + + @Test + void 퀘스트_링크_확인_필요_처리_시_이미_판정된_퀘스트라면_예외를_던진다() { + // given + final Post post = POST_1.생성(1L, 2L); + post.success(); + + // when & then + assertThatThrownBy(post::needCheck) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 퀘스트_링크_확인_필요_처리_시_퀘스트와_연관이_없다면_예외를_던진다() { + // given + final Post post = POST_1.생성(1L); + + // when & then + assertThatThrownBy(post::needCheck) + .isInstanceOf(InvalidDomainException.class); + } + + @Test + void 퀘스트가_링크_되었는지_확인한다() { + // given + final Post linkedPost = POST_1.생성(1L, 2L); + final Post notLinkedPost = POST_1.생성(1L); + + // when + final boolean linkedActual = linkedPost.isQuestLinked(); + final boolean notLinkedActual = notLinkedPost.isQuestLinked(); + + // then + assertAll(() -> { + assertThat(linkedActual).isTrue(); + assertThat(notLinkedActual).isFalse(); + }); + } } diff --git a/src/test/java/daybyquest/quest/domain/QuestsTest.java b/src/test/java/daybyquest/quest/domain/QuestsTest.java index d285c44..c3b1d72 100644 --- a/src/test/java/daybyquest/quest/domain/QuestsTest.java +++ b/src/test/java/daybyquest/quest/domain/QuestsTest.java @@ -96,4 +96,46 @@ public class QuestsTest { assertThatThrownBy(() -> quests.getById(1L)) .isInstanceOf(NotExistQuestException.class); } + + @Test + void ID를_통해_퀘스트_존재를_검증한다() { + // given + final Long questId = 1L; + given(questRepository.existsById(questId)).willReturn(true); + + // when + quests.validateExistentById(questId); + + // then + then(questRepository).should().existsById(questId); + } + + @Test + void ID를_통한_퀘스트_존재_검증_시_없다면_예외를_던진다() { + // given & when & then + assertThatThrownBy(() -> quests.validateExistentById(1L)) + .isInstanceOf(NotExistQuestException.class); + } + + @Test + void ID를_통해_라벨을_조회한다() { + // given + final Long questId = 1L; + final Quest quest = QUEST_1.일반_퀘스트_생성(); + QUEST_1.보상_없이_세부사항을_설정한다(quest); + given(questRepository.findById(questId)).willReturn(Optional.of(quest)); + + // when + final String label = quests.getLabelById(questId); + + // then + assertThat(label).isEqualTo(QUEST_1.label); + } + + @Test + void ID를_통한_라벨_조회_시_퀘스트가_없다면_예외를_던진다() { + // given & when & then + assertThatThrownBy(() -> quests.getLabelById(1L)) + .isInstanceOf(NotExistQuestException.class); + } }