From e8f5f882b4c9629a498a7f60cd6dec1f88de25b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Sun, 9 Jul 2023 15:35:54 +0900 Subject: [PATCH 01/17] =?UTF-8?q?[fix]=20:=20=EC=A7=88=EB=AC=B8=ED=8F=BC?= =?UTF-8?q?=EC=9D=98=20=ED=83=80=EC=9E=85=EC=97=90=20=ED=95=B4=EB=8B=B9?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20mock=20api=20=EC=88=98=EC=A0=95=20(#308)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/me/nalab/api/mock/MockController.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/api/api-mock/src/main/java/me/nalab/api/mock/MockController.java b/api/api-mock/src/main/java/me/nalab/api/mock/MockController.java index e1d74e7c..a4cdbdcd 100644 --- a/api/api-mock/src/main/java/me/nalab/api/mock/MockController.java +++ b/api/api-mock/src/main/java/me/nalab/api/mock/MockController.java @@ -298,7 +298,7 @@ Object getTargetBySurveyId(@RequestParam("survey-id") Long surveyId) { @ResponseStatus(HttpStatus.OK) Object getFeedbackBySurveyIdAndFormType(@RequestParam("survey-id") Long surveyId, @RequestParam(value = "form-type", required = false) String formType) { - if(formType == null || formType.isBlank()) { + if (formType == null || formType.isBlank()) { return "{\n" + " \"question_feedback\": [\n" + " {\n" @@ -311,17 +311,17 @@ Object getFeedbackBySurveyIdAndFormType(@RequestParam("survey-id") Long surveyId + " {\n" + " \"choice_id\": \"1\",\n" + " \"order\": 1,\n" - + " \"content\": \"UI\"\n" + + " \"content\": \"꼼꼼한\"\n" + " },\n" + " {\n" + " \"choice_id\": \"2\",\n" + " \"order\": 2,\n" - + " \"content\": \"UX\"\n" + + " \"content\": \"도전적인\"\n" + " },\n" + " {\n" + " \"choice_id\": 3,\n" + " \"order\": 3,\n" - + " \"content\": \"BX\"\n" + + " \"content\": \"사교적인\"\n" + " }\n" + " ],\n" + " \"feedbacks\": [\n" @@ -380,7 +380,7 @@ Object getFeedbackBySurveyIdAndFormType(@RequestParam("survey-id") Long surveyId + " ]\n" + "}"; } - return "{\n" + return "{\n" + " \"question_feedback\": [\n" + " {\n" + " \"question_id\": 1,\n" @@ -393,19 +393,19 @@ Object getFeedbackBySurveyIdAndFormType(@RequestParam("survey-id") Long surveyId + " \"choice_id\": 1,\n" + " \"order\": 1,\n" + " \"selected_count\": 5,\n" - + " \"content\": \"UI\"\n" + + " \"content\": \"꼼꼼한\"\n" + " },\n" + " {\n" + " \"choice_id\": 2,\n" + " \"order\": 2,\n" + " \"selected_count\": 10,\n" - + " \"content\": \"UX\"\n" + + " \"content\": \"도전적인\"\n" + " },\n" + " {\n" + " \"choice_id\": 3,\n" + " \"order\": 3,\n" + " \"selected_count\": 0,\n" - + " \"content\": \"BX\"\n" + + " \"content\": \"사교적인\"\n" + " }\n" + " ]\n" + " }\n" @@ -475,5 +475,4 @@ void bookmark(@RequestParam("form-question-feedback-id") Long formQuestionFeedba // 200 OK 반환용 mock api 메소드로 비어있음 } - } From 1d533388d1ce22d5d4558d7f4cda80354a243073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Sat, 8 Jul 2023 03:05:19 +0900 Subject: [PATCH 02/17] =?UTF-8?q?[feat]=20:=20=EC=A7=88=EB=AC=B8=ED=8F=BC?= =?UTF-8?q?=EC=9D=98=20=EB=B6=81=EB=A7=88=ED=81=AC=EB=90=9C=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EA=B5=90=EC=B2=B4=20-=20`=EC=9D=B8?= =?UTF-8?q?=EC=88=98=ED=85=8C=EC=8A=A4=ED=8A=B8`=20=EC=9E=91=EC=84=B1=20(#?= =?UTF-8?q?303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AbstractFeedbackTestSupporter.java | 11 +- .../feedback/FeedbackAcceptanceValidator.java | 4 + .../BookmarkReplaceAcceptanceTest.java | 103 ++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/bookmark/BookmarkReplaceAcceptanceTest.java diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java index 3a0d037f..0a3c967e 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java @@ -59,7 +59,6 @@ protected ResultActions findReviewers(String token, String surveyId) throws Exce ); } - protected ResultActions findFeedback(String token, Long surveyId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders .get(String.format("/v2/surveys/%d/feedbacks", surveyId)) @@ -78,6 +77,16 @@ protected ResultActions findSpecific(String token, Long feedbackId) throws Excep ); } + protected ResultActions replaceBookmark(String token, String form_question_feedback_id) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .patch("/v1/feedbacks/bookmarks") + .queryParam("form-question-feedback-id", form_question_feedback_id) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, token) + ); + } + @Autowired final void setMockMvc(MockMvc mockMvc) { this.mockMvc = mockMvc; diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java index d629e38b..0797a721 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java @@ -126,4 +126,8 @@ public static void assertIsSpecificFound(ResultActions resultActions) throws Exc ); } + public static void assertIsBookmarkReplaced(ResultActions resultActions) throws Exception { + resultActions.andExpectAll(status().isOk()); + } + } diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/bookmark/BookmarkReplaceAcceptanceTest.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/bookmark/BookmarkReplaceAcceptanceTest.java new file mode 100644 index 00000000..9e9884a9 --- /dev/null +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/bookmark/BookmarkReplaceAcceptanceTest.java @@ -0,0 +1,103 @@ +package me.nalab.luffy.api.acceptance.test.feedback.bookmark; + +import static me.nalab.luffy.api.acceptance.test.feedback.FeedbackAcceptanceValidator.*; +import static me.nalab.luffy.api.acceptance.test.feedback.FeedbackCreateRequestFixture.*; + +import java.time.Instant; +import java.util.Map; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.ResultActions; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import me.nalab.auth.mock.api.MockUserRegisterEvent; +import me.nalab.luffy.api.acceptance.test.TargetInitializer; +import me.nalab.luffy.api.acceptance.test.feedback.AbstractFeedbackTestSupporter; +import me.nalab.luffy.api.acceptance.test.feedback.create.request.FeedbackCreateRequest; +import me.nalab.luffy.api.acceptance.test.feedback.create.response.SurveyFindResponse; +import me.nalab.luffy.api.acceptance.test.survey.RequestSample; + +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource("classpath:h2.properties") +@ComponentScan("me.nalab") +@EnableJpaRepositories(basePackages = {"me.nalab"}) +@EntityScan(basePackages = {"me.nalab"}) +class BookmarkReplaceAcceptanceTest extends AbstractFeedbackTestSupporter { + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + @Autowired + private TargetInitializer targetInitializer; + + private static final ObjectMapper OBJECT_MAPPER; + + static { + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + } + + @Test + @DisplayName("북마크 교체 성공 인수테스트") + void REPLACE_BOOKMARK_SUCCESS_TEST() throws Exception { + + // given + Long targetId = targetInitializer.saveTargetAndGetId("target", Instant.now()); + String token = "token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedId(targetId) + .expectedToken(token) + .build()); + + Long surveyId = createAndGetSurveyId(token, RequestSample.DEFAULT_JSON); + SurveyFindResponse surveyFindResponse = getSurveyFindResponse(surveyId); + FeedbackCreateRequest feedbackCreateRequest = getFeedbackCreateRequest(surveyFindResponse, true, "developer"); + createFeedback(surveyId, OBJECT_MAPPER.writeValueAsString(feedbackCreateRequest)); + String formQuestionFeedbackId = findFeedbackAndGetFormQuestionFeedbackId(token, surveyId); + + // when + ResultActions resultActions = replaceBookmark(token, formQuestionFeedbackId); + + // then + assertIsBookmarkReplaced(resultActions); + } + + private Long createAndGetSurveyId(String token, String content) throws Exception { + ResultActions resultActions = createSurvey(token, content); + Map surveyIdMap = OBJECT_MAPPER.readValue( + resultActions.andReturn().getResponse().getContentAsString(), new TypeReference<>() { + }); + return surveyIdMap.get("survey_id"); + } + + private SurveyFindResponse getSurveyFindResponse(Long surveyId) throws Exception { + ResultActions resultActions = findSurvey(surveyId); + return OBJECT_MAPPER.readValue(resultActions.andReturn().getResponse().getContentAsString(), + SurveyFindResponse.class); + } + + private String findFeedbackAndGetFormQuestionFeedbackId(String token, Long surveyId) throws Exception { + String stringResponse = findFeedback(token, surveyId).andReturn() + .getResponse() + .getContentAsString(); + JSONObject jsonObject = new JSONObject(stringResponse); + return jsonObject.getJSONArray("question_feedback").getJSONObject(0).getJSONArray("feedbacks") + .getJSONObject(0).getString("form_question_feedback_id"); + } + +} From b611c2a44d32ea28bc43f8894c191c007353e65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Sun, 9 Jul 2023 16:29:28 +0900 Subject: [PATCH 03/17] =?UTF-8?q?[feat]=20:=20=EC=A7=88=EB=AC=B8=ED=8F=BC?= =?UTF-8?q?=EC=9D=98=20=EB=B6=81=EB=A7=88=ED=81=AC=EB=90=9C=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EA=B5=90=EC=B2=B4=20-=20`application`=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#304)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/feedback/FormFeedbackEntity.java | 14 ++++ ...FormQuestionFeedbackNotExistException.java | 19 +++++ .../web/bookmark/BookmarkReplaceUseCase.java | 17 ++++ .../FormQuestionFeedbackFindPort.java | 20 +++++ .../FormQuestionFeedbackUpdatePort.java | 17 ++++ .../bookmark/ReplaceBookmarkService.java | 29 +++++++ .../bookmark/ReplaceBookmarkServiceTest.java | 80 +++++++++++++++++++ .../survey/domain/feedback/Bookmark.java | 11 ++- .../feedback/FormQuestionFeedbackable.java | 8 +- .../survey/domain/feedback/BookmarkTest.java | 30 +++++++ 10 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/exception/FormQuestionFeedbackNotExistException.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/bookmark/BookmarkReplaceUseCase.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/FormQuestionFeedbackFindPort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/FormQuestionFeedbackUpdatePort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/service/bookmark/ReplaceBookmarkService.java create mode 100644 survey/survey-application/src/test/java/me/nalab/survey/application/service/bookmark/ReplaceBookmarkServiceTest.java create mode 100644 survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/BookmarkTest.java diff --git a/core/data/src/main/java/me/nalab/core/data/feedback/FormFeedbackEntity.java b/core/data/src/main/java/me/nalab/core/data/feedback/FormFeedbackEntity.java index 75d6d6e4..b5fb8158 100644 --- a/core/data/src/main/java/me/nalab/core/data/feedback/FormFeedbackEntity.java +++ b/core/data/src/main/java/me/nalab/core/data/feedback/FormFeedbackEntity.java @@ -7,6 +7,8 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; import javax.persistence.Table; import lombok.AllArgsConstructor; @@ -44,4 +46,16 @@ public abstract class FormFeedbackEntity { @Column(name = "bookmarked_at", columnDefinition = "TIMESTAMP(6)", nullable = false) protected Instant bookmarkedAt; + + @PrePersist + void prePersist() { + Instant now = Instant.now(); + bookmarkedAt = bookmarkedAt != null ? bookmarkedAt : now; + } + + @PreUpdate + void preUpdate() { + Instant now = Instant.now(); + bookmarkedAt = now; + } } diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/exception/FormQuestionFeedbackNotExistException.java b/survey/survey-application/src/main/java/me/nalab/survey/application/exception/FormQuestionFeedbackNotExistException.java new file mode 100644 index 00000000..2ede2fb5 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/exception/FormQuestionFeedbackNotExistException.java @@ -0,0 +1,19 @@ +package me.nalab.survey.application.exception; + +import lombok.Getter; + +public class FormQuestionFeedbackNotExistException extends RuntimeException { + + @Getter + private final Long formQuestionFeedbackId; + + public FormQuestionFeedbackNotExistException(Long formQuestionFeedbackId) { + super("Cannot found any formQuestionFeedback id \"" + formQuestionFeedbackId); + this.formQuestionFeedbackId = formQuestionFeedbackId; + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/bookmark/BookmarkReplaceUseCase.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/bookmark/BookmarkReplaceUseCase.java new file mode 100644 index 00000000..1d8dd994 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/bookmark/BookmarkReplaceUseCase.java @@ -0,0 +1,17 @@ +package me.nalab.survey.application.port.in.web.bookmark; + +/** + * 해당 formQuestionFeedback의 북마크를 교체합니다. + * + * 이미 북마크가 되어있는 formQuestionFeedback일 경우, 요청 시 북마크가 지워집니다.(switch 형식) + */ +public interface BookmarkReplaceUseCase { + + /** + * formQuestionFeedbackId를 인자로 받고, 해당 formQuestionFeedback의 북마크 필드를 교체합니다. + * + * @param formQuestionFeedbackId bookmark를 남길 formQuestionFeedback의 id + */ + void replaceBookmark(Long formQuestionFeedbackId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/FormQuestionFeedbackFindPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/FormQuestionFeedbackFindPort.java new file mode 100644 index 00000000..4dafdbbf --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/FormQuestionFeedbackFindPort.java @@ -0,0 +1,20 @@ +package me.nalab.survey.application.port.out.persistence.bookmark; + +import java.util.Optional; + +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; + +/** + * FormQuestionFeedbackable을 조회하는 인터페이스 입니다. + */ +public interface FormQuestionFeedbackFindPort { + + /** + * formQuestionFeedbackId를 인자로받아 id에 해당하는 FormQuestionFeedbackable을 반환합니다. + * + * @param formQuestionFeedbackId FormQuestionFeedbackable의 id + * @return Optioonal 만약, 어떠한 formQuestionFeedbackable이 없을 경우, Optional.empty()를 반환해야 합니다. + */ + Optional findFormQuestionFeedbackById(Long formQuestionFeedbackId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/FormQuestionFeedbackUpdatePort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/FormQuestionFeedbackUpdatePort.java new file mode 100644 index 00000000..18c9837e --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/bookmark/FormQuestionFeedbackUpdatePort.java @@ -0,0 +1,17 @@ +package me.nalab.survey.application.port.out.persistence.bookmark; + +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; + +/** + * FormQuestionFeedback의 북마크를 교체하는 인터페이스입니다. + */ +public interface FormQuestionFeedbackUpdatePort { + + /** + * FormQuestionFeedbackable를 인자로받아 북마크 필드를 교체합니다. + * + * @param formQuestionFeedbackable 북마크 필드를 교체할 FormQuestionFeedbackable + */ + void updateFormQuestionFeedback(FormQuestionFeedbackable formQuestionFeedbackable); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/bookmark/ReplaceBookmarkService.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/bookmark/ReplaceBookmarkService.java new file mode 100644 index 00000000..1e33dd61 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/bookmark/ReplaceBookmarkService.java @@ -0,0 +1,29 @@ +package me.nalab.survey.application.service.bookmark; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import me.nalab.survey.application.exception.FormQuestionFeedbackNotExistException; +import me.nalab.survey.application.port.in.web.bookmark.BookmarkReplaceUseCase; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackFindPort; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackUpdatePort; +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; + +@Service +@RequiredArgsConstructor +public class ReplaceBookmarkService implements BookmarkReplaceUseCase { + + private final FormQuestionFeedbackFindPort formQuestionFeedbackFindPort; + private final FormQuestionFeedbackUpdatePort formQuestionFeedbackUpdatePort; + + @Transactional + @Override + public void replaceBookmark(Long formQuestionFeedbackId) { + FormQuestionFeedbackable formQuestionFeedback = formQuestionFeedbackFindPort.findFormQuestionFeedbackById( + formQuestionFeedbackId) + .orElseThrow(() -> new FormQuestionFeedbackNotExistException(formQuestionFeedbackId)); + formQuestionFeedback.replaceBookmark(); + formQuestionFeedbackUpdatePort.updateFormQuestionFeedback(formQuestionFeedback); + } +} diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/service/bookmark/ReplaceBookmarkServiceTest.java b/survey/survey-application/src/test/java/me/nalab/survey/application/service/bookmark/ReplaceBookmarkServiceTest.java new file mode 100644 index 00000000..e43354be --- /dev/null +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/service/bookmark/ReplaceBookmarkServiceTest.java @@ -0,0 +1,80 @@ +package me.nalab.survey.application.service.bookmark; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import me.nalab.survey.application.RandomFeedbackDtoFixture; +import me.nalab.survey.application.RandomSurveyDtoFixture; +import me.nalab.survey.application.TestIdGenerator; +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; +import me.nalab.survey.application.common.feedback.mapper.FeedbackDtoMapper; +import me.nalab.survey.application.common.survey.mapper.SurveyDtoMapper; +import me.nalab.survey.application.exception.FormQuestionFeedbackNotExistException; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackFindPort; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackUpdatePort; +import me.nalab.survey.domain.feedback.Feedback; +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; +import me.nalab.survey.domain.survey.Survey; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {TestIdGenerator.class}) +class ReplaceBookmarkServiceTest { + + private ReplaceBookmarkService replaceBookmarkService; + + @Autowired + private TestIdGenerator testIdGenerator; + + @Mock + private FormQuestionFeedbackFindPort formQuestionFeedbackFindPort; + + @Mock + private FormQuestionFeedbackUpdatePort formQuestionFeedbackUpdatePort; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + replaceBookmarkService = new ReplaceBookmarkService(formQuestionFeedbackFindPort, + formQuestionFeedbackUpdatePort); + } + + @Test + void UPDATE_BOOKMARK_BY_FORM_QUESTION_FEEDBACK_ID() { + + RandomSurveyDtoFixture.setRandomIdGenerator(() -> testIdGenerator.generate()); + Survey survey = SurveyDtoMapper.toSurvey(RandomSurveyDtoFixture.createRandomSurveyDto()); + FeedbackDto feedbackDto = RandomFeedbackDtoFixture.getRandomFeedbackDtoBySurvey(survey); + Feedback feedback = FeedbackDtoMapper.toDomain(survey, feedbackDto); + FormQuestionFeedbackable formQuestionFeedback = feedback.getFormQuestionFeedbackableList().get(0); + boolean isBookmarked = formQuestionFeedback.getBookmark().isBookmarked(); + Long formQuestionFeedbackId = formQuestionFeedback.getId(); + + when(formQuestionFeedbackFindPort.findFormQuestionFeedbackById(formQuestionFeedbackId)). + thenReturn(Optional.of(formQuestionFeedback)); + replaceBookmarkService.replaceBookmark(formQuestionFeedbackId); + + verify(formQuestionFeedbackUpdatePort).updateFormQuestionFeedback(formQuestionFeedback); + assertThat(formQuestionFeedback.getBookmark().isBookmarked()).isEqualTo(!isBookmarked); + } + + @Test + void UPDATE_BOOKMARK_BY_NON_EXISTING_FORM_QUESTION_FEEDBACK_ID() { + + Long formQuestionFeedbackId = 123L; + assertThrows(FormQuestionFeedbackNotExistException.class, + () -> replaceBookmarkService.replaceBookmark(formQuestionFeedbackId)); + } + +} diff --git a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/Bookmark.java b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/Bookmark.java index 37812ddf..0692b896 100644 --- a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/Bookmark.java +++ b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/Bookmark.java @@ -9,11 +9,16 @@ @Builder @Getter -@EqualsAndHashCode @ToString +@EqualsAndHashCode public class Bookmark { - private final boolean isBookmarked; - private final Instant bookmarkedAt; + private boolean isBookmarked; + private Instant bookmarkedAt; + + public void replaceIsBookmarked() { + this.isBookmarked = !isBookmarked; + this.bookmarkedAt = Instant.now(); + } } diff --git a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java index c4c464be..f76ae7b0 100644 --- a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java +++ b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java @@ -13,7 +13,7 @@ @SuperBuilder @Getter @ToString -@EqualsAndHashCode +@EqualsAndHashCode(exclude = "bookmark") public abstract class FormQuestionFeedbackable implements IdGeneratable, QuestionFeedbackValidable { private Long id; @@ -25,9 +25,13 @@ public void setRead(boolean read) { isRead = read; } + public void replaceBookmark() { + bookmark.replaceIsBookmarked(); + } + @Override public void withId(LongSupplier idSupplier) { - if(this.id != null) { + if (this.id != null) { throw new IdAlreadyGeneratedException(this); } id = idSupplier.getAsLong(); diff --git a/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/BookmarkTest.java b/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/BookmarkTest.java new file mode 100644 index 00000000..f278d884 --- /dev/null +++ b/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/BookmarkTest.java @@ -0,0 +1,30 @@ +package me.nalab.survey.domain.feedback; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; + +import org.junit.jupiter.api.Test; + +class BookmarkTest { + + @Test + void bookmark_replacedIsBookmarked_test() { + + boolean initialIsBookmarked = false; + Instant initialBookmarkedAt = Instant.now(); + + Bookmark bookmark = Bookmark.builder() + .isBookmarked(initialIsBookmarked) + .bookmarkedAt(initialBookmarkedAt) + .build(); + bookmark.replaceIsBookmarked(); + + boolean updatedIsBookmarked = bookmark.isBookmarked(); + Instant updatedBookmarkedAt = bookmark.getBookmarkedAt(); + + assertEquals(updatedIsBookmarked, !initialIsBookmarked); + assertNotEquals(updatedBookmarkedAt, initialBookmarkedAt); + } + +} From 4e436dfa3ca80ab5c8c3805102207d8b45ff94f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Sun, 9 Jul 2023 20:45:39 +0900 Subject: [PATCH 04/17] =?UTF-8?q?[feat]=20:=20=EC=A7=88=EB=AC=B8=ED=8F=BC?= =?UTF-8?q?=EC=9D=98=20=EB=B6=81=EB=A7=88=ED=81=AC=EB=90=9C=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EA=B5=90=EC=B2=B4=20-=20`jpa-adaptor`=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#311)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/module-info.java | 2 +- .../FormQuestionFeedbackFindAdaptor.java | 30 +++++++++ .../FormQuestionFeedbackUpdateAdaptor.java | 24 +++++++ .../FormQuestionFeedbackFindRepository.java | 8 +++ .../FormQuestionFeedbackUpdateRepository.java | 8 +++ .../common/mapper/FeedbackEntityMapper.java | 14 ++++ .../FormQuestionFeedbackFindAdaptorTest.java | 67 +++++++++++++++++++ ...FormQuestionFeedbackUpdateAdaptorTest.java | 62 +++++++++++++++++ .../TestFeedbackSaveJpaRepository.java | 10 +++ 9 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackFindAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/repository/FormQuestionFeedbackFindRepository.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/repository/FormQuestionFeedbackUpdateRepository.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackFindAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/TestFeedbackSaveJpaRepository.java diff --git a/survey/survey-application/src/main/java/module-info.java b/survey/survey-application/src/main/java/module-info.java index 1f6444c0..376bbd76 100644 --- a/survey/survey-application/src/main/java/module-info.java +++ b/survey/survey-application/src/main/java/module-info.java @@ -27,7 +27,7 @@ exports me.nalab.survey.application.port.out.persistence.authorization; exports me.nalab.survey.application.common.target.dto; exports me.nalab.survey.application.service.authorization; - + exports me.nalab.survey.application.port.out.persistence.bookmark; requires luffy.survey.domain.main; requires luffy.core.id.generator.id.generator.starter.main; diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackFindAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackFindAdaptor.java new file mode 100644 index 00000000..75a758d6 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackFindAdaptor.java @@ -0,0 +1,30 @@ +package me.nalab.survey.jpa.adaptor.bookmark; + +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import me.nalab.core.data.feedback.FormFeedbackEntity; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackFindPort; +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; +import me.nalab.survey.jpa.adaptor.bookmark.repository.FormQuestionFeedbackFindRepository; +import me.nalab.survey.jpa.adaptor.common.mapper.FeedbackEntityMapper; + +@Repository +@RequiredArgsConstructor +public class FormQuestionFeedbackFindAdaptor implements FormQuestionFeedbackFindPort { + + private final FormQuestionFeedbackFindRepository formQuestionFeedbackFindRepository; + + @Override + public Optional findFormQuestionFeedbackById(Long formQuestionFeedbackId) { + Optional formFeedbackEntity = formQuestionFeedbackFindRepository.findById( + formQuestionFeedbackId); + if (formFeedbackEntity.isEmpty()) { + return Optional.empty(); + } + return Optional.of(FeedbackEntityMapper.toFormQuestionFeedbackable(formFeedbackEntity.get())); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java new file mode 100644 index 00000000..d23d752e --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java @@ -0,0 +1,24 @@ +package me.nalab.survey.jpa.adaptor.bookmark; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import me.nalab.core.data.feedback.FormFeedbackEntity; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackUpdatePort; +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; +import me.nalab.survey.jpa.adaptor.bookmark.repository.FormQuestionFeedbackUpdateRepository; +import me.nalab.survey.jpa.adaptor.common.mapper.FeedbackEntityMapper; + +@Repository +@RequiredArgsConstructor +public class FormQuestionFeedbackUpdateAdaptor implements FormQuestionFeedbackUpdatePort { + + private final FormQuestionFeedbackUpdateRepository formQuestionFeedbackUpdateRepository; + + @Override + public void updateFormQuestionFeedback(FormQuestionFeedbackable formQuestionFeedbackable) { + FormFeedbackEntity formFeedbackEntity = FeedbackEntityMapper.toFormFeedbackEntity(formQuestionFeedbackable); + formQuestionFeedbackUpdateRepository.save(formFeedbackEntity); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/repository/FormQuestionFeedbackFindRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/repository/FormQuestionFeedbackFindRepository.java new file mode 100644 index 00000000..b5f5c7ee --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/repository/FormQuestionFeedbackFindRepository.java @@ -0,0 +1,8 @@ +package me.nalab.survey.jpa.adaptor.bookmark.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import me.nalab.core.data.feedback.FormFeedbackEntity; + +public interface FormQuestionFeedbackFindRepository extends JpaRepository { +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/repository/FormQuestionFeedbackUpdateRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/repository/FormQuestionFeedbackUpdateRepository.java new file mode 100644 index 00000000..74d5a691 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/repository/FormQuestionFeedbackUpdateRepository.java @@ -0,0 +1,8 @@ +package me.nalab.survey.jpa.adaptor.bookmark.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import me.nalab.core.data.feedback.FormFeedbackEntity; + +public interface FormQuestionFeedbackUpdateRepository extends JpaRepository { +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/common/mapper/FeedbackEntityMapper.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/common/mapper/FeedbackEntityMapper.java index 275ec490..db4ca442 100644 --- a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/common/mapper/FeedbackEntityMapper.java +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/common/mapper/FeedbackEntityMapper.java @@ -52,6 +52,13 @@ private static List getFormQuestionFeedbackList(Feedba }).collect(Collectors.toList()); } + public static FormQuestionFeedbackable toFormQuestionFeedbackable(FormFeedbackEntity feedbackEntity) { + if (feedbackEntity instanceof ShortFormFeedbackEntity) { + return getShortFormQuestionFeedback((ShortFormFeedbackEntity)feedbackEntity); + } + return getChoiceFormQuestionFeedback((ChoiceFormFeedbackEntity)feedbackEntity); + } + private static ShortFormQuestionFeedback getShortFormQuestionFeedback( ShortFormFeedbackEntity shortFormFeedbackEntity) { return ShortFormQuestionFeedback.builder() @@ -103,6 +110,13 @@ private static ReviewerEntity getReviewerEntity(Reviewer reviewer, Instant now) .build(); } + public static FormFeedbackEntity toFormFeedbackEntity(FormQuestionFeedbackable formQuestionFeedbackable) { + if (formQuestionFeedbackable instanceof ShortFormQuestionFeedback) { + return getShortFormFeedbackEntity((ShortFormQuestionFeedback)formQuestionFeedbackable); + } + return getChoiceFormFeedbackEntity((ChoiceFormQuestionFeedback)formQuestionFeedbackable); + } + private static List getFormFeedbackEntityList(Feedback feedback) { return feedback.getFormQuestionFeedbackableList().stream() .map(f -> { diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackFindAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackFindAdaptorTest.java new file mode 100644 index 00000000..f9401250 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackFindAdaptorTest.java @@ -0,0 +1,67 @@ +package me.nalab.survey.jpa.adaptor.bookmark; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.feedback.FeedbackEntity; +import me.nalab.core.data.feedback.FormFeedbackEntity; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackFindPort; +import me.nalab.survey.domain.feedback.Feedback; +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; +import me.nalab.survey.domain.survey.Survey; +import me.nalab.survey.jpa.adaptor.RandomFeedbackFixture; +import me.nalab.survey.jpa.adaptor.RandomSurveyFixture; +import me.nalab.survey.jpa.adaptor.common.mapper.FeedbackEntityMapper; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = FormQuestionFeedbackFindAdaptor.class) +@TestPropertySource("classpath:h2.properties") +class FormQuestionFeedbackFindAdaptorTest { + + @Autowired + private FormQuestionFeedbackFindPort formQuestionFeedbackFindPort; + + @Autowired + private TestFeedbackSaveJpaRepository testFeedbackSaveJpaRepository; + + @Test + void FIND_FORM_QUESTION_FEEDBACK_WITH_SUCCESS() { + Survey survey = RandomSurveyFixture.createRandomSurvey(); + Feedback feedback = RandomFeedbackFixture.getRandomFeedbackBySurvey(survey); + FeedbackEntity feedbackEntity = FeedbackEntityMapper.toEntity(feedback); + FormFeedbackEntity formFeedbackEntity = feedbackEntity.getFormFeedbackEntityList().get(0); + FormQuestionFeedbackable formQuestionFeedbackable = FeedbackEntityMapper.toFormQuestionFeedbackable( + formFeedbackEntity); + Long formFeedbackEntityId = formFeedbackEntity.getId(); + testFeedbackSaveJpaRepository.save(feedbackEntity); + + Optional resultFormQuestionFeedbackable = formQuestionFeedbackFindPort.findFormQuestionFeedbackById( + formFeedbackEntityId); + + assertTrue(resultFormQuestionFeedbackable.isPresent()); + assertEquals(formQuestionFeedbackable, resultFormQuestionFeedbackable.get()); + } + + @Test + void FIND_FORM_QUESTION_FEEDBACK_WITH_FAIL() { + + Long nonExistentFormFeedbackEntityId = 999L; + + Optional formQuestionFeedbackable = formQuestionFeedbackFindPort.findFormQuestionFeedbackById( + nonExistentFormFeedbackEntityId); + + assertTrue(formQuestionFeedbackable.isEmpty()); + } + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptorTest.java new file mode 100644 index 00000000..a7792dbf --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptorTest.java @@ -0,0 +1,62 @@ +package me.nalab.survey.jpa.adaptor.bookmark; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.feedback.FeedbackEntity; +import me.nalab.core.data.feedback.FormFeedbackEntity; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackFindPort; +import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackUpdatePort; +import me.nalab.survey.domain.feedback.Feedback; +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; +import me.nalab.survey.domain.survey.Survey; +import me.nalab.survey.jpa.adaptor.RandomFeedbackFixture; +import me.nalab.survey.jpa.adaptor.RandomSurveyFixture; +import me.nalab.survey.jpa.adaptor.common.mapper.FeedbackEntityMapper; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = {FormQuestionFeedbackFindAdaptor.class, FormQuestionFeedbackUpdateAdaptor.class}) +@TestPropertySource("classpath:h2.properties") +class FormQuestionFeedbackUpdateAdaptorTest { + + @Autowired + private FormQuestionFeedbackFindPort formQuestionFeedbackFindPort; + + @Autowired + private FormQuestionFeedbackUpdatePort formQuestionFeedbackUpdatePort; + + @Autowired + private TestFeedbackSaveJpaRepository testFeedbackSaveJpaRepository; + + @Test + void UPDATE_FORM_QUESTION_FEEDBACK_WITH_SUCCESS() { + Survey survey = RandomSurveyFixture.createRandomSurvey(); + Feedback feedback = RandomFeedbackFixture.getRandomFeedbackBySurvey(survey); + FeedbackEntity feedbackEntity = FeedbackEntityMapper.toEntity(feedback); + FormFeedbackEntity formFeedbackEntity = feedbackEntity.getFormFeedbackEntityList().get(0); + Long formFeedbackEntityId = FeedbackEntityMapper.toFormQuestionFeedbackable(formFeedbackEntity) + .getFormQuestionId(); + testFeedbackSaveJpaRepository.save(feedbackEntity); + + FormQuestionFeedbackable formQuestionFeedbackable = formQuestionFeedbackFindPort.findFormQuestionFeedbackById( + formFeedbackEntityId).get(); + formQuestionFeedbackable.replaceBookmark(); + formQuestionFeedbackUpdatePort.updateFormQuestionFeedback(formQuestionFeedbackable); + + Optional resultFormQuestionFeedbackable = formQuestionFeedbackFindPort.findFormQuestionFeedbackById( + formFeedbackEntityId); + assertEquals(formQuestionFeedbackable, resultFormQuestionFeedbackable.get()); + } + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/TestFeedbackSaveJpaRepository.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/TestFeedbackSaveJpaRepository.java new file mode 100644 index 00000000..688443ac --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/bookmark/TestFeedbackSaveJpaRepository.java @@ -0,0 +1,10 @@ +package me.nalab.survey.jpa.adaptor.bookmark; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.feedback.FeedbackEntity; + +@Repository("bookmark.TestFeedbackSaveJpaRepository") +public interface TestFeedbackSaveJpaRepository extends JpaRepository { +} From 831b906fb1839c8908f975bbdd159d8a7e91789a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Sun, 9 Jul 2023 23:20:16 +0900 Subject: [PATCH 05/17] =?UTF-8?q?[feat]=20:=20=EC=A7=88=EB=AC=B8=ED=8F=BC?= =?UTF-8?q?=EC=9D=98=20=EB=B6=81=EB=A7=88=ED=81=AC=EB=90=9C=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EA=B5=90=EC=B2=B4=20-=20`web-adaptor`=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C=20(#312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: devxb Co-authored-by: xb205 <62425964+devxb@users.noreply.github.com> --- .../JwtDecryptInterceptorConfigurer.java | 3 +- .../auth/mock/config/MockAuthConfigurer.java | 3 +- coverage-exclude.luffy | 1 + ...FormQuestionFeedbackNotExistException.java | 2 +- .../authorization/TargetIdFindPort.java | 23 ++-- .../FormQuestionFeedbackIdValidator.java | 39 ++++++ ...ormQuestionFeedbackIdValidatorFactory.java | 25 ++++ .../src/main/java/module-info.java | 1 + .../FormQuestionFeedbackIdValidatorTest.java | 112 ++++++++++++++++++ .../survey/domain/feedback/Bookmark.java | 11 +- .../feedback/FormQuestionFeedbackable.java | 6 +- .../survey/domain/feedback/BookmarkTest.java | 24 +++- .../domain/feedback/FeedbackDomainTest.java | 18 +++ .../authorization/TargetIdFindAdaptor.java | 4 + .../repository/TargetIdFindJpaRepository.java | 3 + .../TargetIdFindAdaptorTest.java | 34 +++++- .../bookmark/BookmarkReplaceController.java | 29 +++++ 17 files changed, 321 insertions(+), 17 deletions(-) create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidator.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidatorFactory.java create mode 100644 survey/survey-application/src/test/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidatorTest.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController.java diff --git a/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java b/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java index 7e1192ea..c49e29bc 100644 --- a/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java +++ b/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java @@ -23,7 +23,8 @@ public class JwtDecryptInterceptorConfigurer implements WebMvcConfigurer { "/v1/feedbacks", "/v1/reviewers*", "/v1/reviewers/summary*", - "/v2/surveys/*/feedbacks" + "/v2/surveys/*/feedbacks", + "/v1/feedbacks/bookmarks", }; @Override diff --git a/auth/auth-mock/src/main/java/me/nalab/auth/mock/config/MockAuthConfigurer.java b/auth/auth-mock/src/main/java/me/nalab/auth/mock/config/MockAuthConfigurer.java index 1484fdaa..96e4839f 100644 --- a/auth/auth-mock/src/main/java/me/nalab/auth/mock/config/MockAuthConfigurer.java +++ b/auth/auth-mock/src/main/java/me/nalab/auth/mock/config/MockAuthConfigurer.java @@ -20,7 +20,8 @@ public class MockAuthConfigurer implements WebMvcConfigurer { "/v1/feedbacks", "/v1/reviewers*", "/v1/reviewers/summary*", - "/v2/surveys/*/feedbacks" + "/v2/surveys/*/feedbacks", + "/v1/feedbacks/bookmarks", }; @Override diff --git a/coverage-exclude.luffy b/coverage-exclude.luffy index 61ed760a..0835d8f7 100644 --- a/coverage-exclude.luffy +++ b/coverage-exclude.luffy @@ -51,3 +51,4 @@ exclude me/nalab/core/authorization/aop/advice/* exclude me/nalab/core/secure/cors/CorsConfig exclude me/nalab/core/secure/xss/config/XssConfig exclude me/nalab/core/secure/xss/json/JsonXssFilterException +exclude me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/exception/FormQuestionFeedbackNotExistException.java b/survey/survey-application/src/main/java/me/nalab/survey/application/exception/FormQuestionFeedbackNotExistException.java index 2ede2fb5..8e758362 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/exception/FormQuestionFeedbackNotExistException.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/exception/FormQuestionFeedbackNotExistException.java @@ -8,7 +8,7 @@ public class FormQuestionFeedbackNotExistException extends RuntimeException { private final Long formQuestionFeedbackId; public FormQuestionFeedbackNotExistException(Long formQuestionFeedbackId) { - super("Cannot found any formQuestionFeedback id \"" + formQuestionFeedbackId); + super("Cannot found any formQuestionFeedback id \"" + formQuestionFeedbackId + "\""); this.formQuestionFeedbackId = formQuestionFeedbackId; } diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/authorization/TargetIdFindPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/authorization/TargetIdFindPort.java index 0b057c83..e546536d 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/authorization/TargetIdFindPort.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/authorization/TargetIdFindPort.java @@ -8,21 +8,30 @@ public interface TargetIdFindPort { /** - * surveyId를 인자로 받아, 해당 surveyId를 소유하고있는 target의 Id를 반환합니다. - * 만약, target의 Id를 찾을 수 없다면, Optional.empty() 를 반환합니다. + * surveyId 를 인자로 받아, 해당 surveyId를 소유하고있는 target 의 Id를 반환합니다. + * 만약, target 의 Id를 찾을 수 없다면, Optional.empty() 를 반환합니다. * - * @param surveyId survey의 id - * @return 있다면, surveyId를 소유하고있는 target의 Id + * @param surveyId survey 의 id + * @return 있다면, surveyId를 소유하고있는 target 의 Id */ Optional findTargetIdBySurveyId(Long surveyId); /** - * feedbackId를 인자로 받아, 해당 surveyId를 소유하고있는 feedback의 Id를 반환합니다. + * feedbackId를 인자로 받아, 해당 surveyId를 소유하고있는 target 의 Id를 반환합니다. * 만약, feedback의 Id를 찾을 수 없다면, Optional.empty() 를 반환합니다. * - * @param feedbackId feedback의 id - * @return 있다면, feedbackId를 소유하고있는 target의 Id + * @param feedbackId feedback 의 id + * @return 있다면, feedbackId 를 소유하고있는 target 의 Id */ Optional findTargetIdByFeedbackId(Long feedbackId); + /** + * formQuestionFeedbackId 를 인자로 받아, 해당 surveyId를 소유하고있는 target 의 id를 반환합니다. + * 만약, formQuestionFeedbackId 를 찾을 수 없다면, Optional.empty() 를 반환합니다. + * + * @param formQuestionFeedbackId formQuestionFeedback 의 id + * @return formQuestionFeedbackId를 소유하고있는 target 의 id + */ + Optional findTargetIdByFormQuestionFeedbackId(Long formQuestionFeedbackId); + } diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidator.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidator.java new file mode 100644 index 00000000..a771ab80 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidator.java @@ -0,0 +1,39 @@ +package me.nalab.survey.application.service.authorization; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import me.nalab.core.authorization.spi.Validator; +import me.nalab.survey.application.exception.FormQuestionFeedbackNotExistException; +import me.nalab.survey.application.port.out.persistence.authorization.TargetIdFindPort; + +@Service +@RequiredArgsConstructor +public class FormQuestionFeedbackIdValidator implements Validator { + + private final TargetIdFindPort targetIdFindPort; + + @Override + @Transactional(readOnly = true) + public boolean valid(T expected, S result) { + validType(expected, result); + Long expectedTargetId = (Long)expected; + Long requestFormQuestionFeedbackId = (Long)result; + Long resultTargetId = targetIdFindPort.findTargetIdByFormQuestionFeedbackId(requestFormQuestionFeedbackId) + .orElseThrow(() -> { + throw new FormQuestionFeedbackNotExistException(requestFormQuestionFeedbackId); + }); + return expectedTargetId.equals(resultTargetId); + } + + private void validType(T expected, S result) { + if(expected instanceof Long && result instanceof Long) { + return; + } + throw new IllegalArgumentException( + "Expected \"expected\" type was Long \"" + expected.getClass() + "\"Expected \"result\" type was Long \"" + + result.getClass() + "\""); + } + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidatorFactory.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidatorFactory.java new file mode 100644 index 00000000..5f5b6132 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidatorFactory.java @@ -0,0 +1,25 @@ +package me.nalab.survey.application.service.authorization; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import me.nalab.core.authorization.spi.ValidatorFactory; + +@Service +@RequiredArgsConstructor +public class FormQuestionFeedbackIdValidatorFactory + implements ValidatorFactory { + + private final LongParameterExtractor longParameterExtractor; + private final FormQuestionFeedbackIdValidator formQuestionFeedbackIdValidator; + + @Override + public LongParameterExtractor parameterExtractor() { + return longParameterExtractor; + } + + @Override + public FormQuestionFeedbackIdValidator validator() { + return formQuestionFeedbackIdValidator; + } +} diff --git a/survey/survey-application/src/main/java/module-info.java b/survey/survey-application/src/main/java/module-info.java index 376bbd76..11587456 100644 --- a/survey/survey-application/src/main/java/module-info.java +++ b/survey/survey-application/src/main/java/module-info.java @@ -28,6 +28,7 @@ exports me.nalab.survey.application.common.target.dto; exports me.nalab.survey.application.service.authorization; exports me.nalab.survey.application.port.out.persistence.bookmark; + exports me.nalab.survey.application.port.in.web.bookmark; requires luffy.survey.domain.main; requires luffy.core.id.generator.id.generator.starter.main; diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidatorTest.java b/survey/survey-application/src/test/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidatorTest.java new file mode 100644 index 00000000..75eb7773 --- /dev/null +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/service/authorization/FormQuestionFeedbackIdValidatorTest.java @@ -0,0 +1,112 @@ +package me.nalab.survey.application.service.authorization; + +import java.util.Optional; +import java.util.stream.Stream; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import me.nalab.survey.application.exception.FormQuestionFeedbackNotExistException; +import me.nalab.survey.application.port.out.persistence.authorization.TargetIdFindPort; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {FormQuestionFeedbackIdValidator.class, LongParameterExtractor.class, + FormQuestionFeedbackIdValidatorFactory.class}) +class FormQuestionFeedbackIdValidatorTest { + + @Autowired + private FormQuestionFeedbackIdValidatorFactory feedbackIdValidatorFactory; + + @MockBean + private TargetIdFindPort targetIdFindPort; + + @ParameterizedTest + @DisplayName("expectedTargetId와 requestFormQuestionFeedbackId가 같다면, true를 반환한다") + @MethodSource("authorizationSources") + void FORM_QUESTION_FEEDBACK_ID_AUTHORIZATION_TEST(Long expectedTargetId, Long requestFormQuestionFeedbackId, + Long realTargetId, boolean expectedResult) { + // given + FormQuestionFeedbackIdValidator formQuestionFeedbackIdValidator = feedbackIdValidatorFactory.validator(); + feedbackIdValidatorFactory.parameterExtractor(); + + Mockito.when(targetIdFindPort.findTargetIdByFormQuestionFeedbackId(requestFormQuestionFeedbackId)) + .thenReturn(Optional.of(realTargetId)); + + // when + boolean result = formQuestionFeedbackIdValidator.valid(expectedTargetId, requestFormQuestionFeedbackId); + + // then + Assertions.assertThat(result).isEqualTo(expectedResult); + } + + @Test + @DisplayName("requestFormQuestionFeedbackId에 해당하는 targetId가 없다면, FOrmQuestionFeedbackDoesNotExist 예외를 던진다") + void THROW_FORM_QUESTION_FEEDBACK_DOES_NOT_EXIST_EXCEPTION() { + // given + Long expectedTargetId = 1L; + Long requestFormQuestionFeedbackId = 1L; + FormQuestionFeedbackIdValidator formQuestionFeedbackIdValidator = feedbackIdValidatorFactory.validator(); + feedbackIdValidatorFactory.parameterExtractor(); + + Mockito.when(targetIdFindPort.findTargetIdByFormQuestionFeedbackId(requestFormQuestionFeedbackId)) + .thenReturn(Optional.empty()); + + // when + Throwable throwable = Assertions.catchThrowable( + () -> formQuestionFeedbackIdValidator.valid(expectedTargetId, requestFormQuestionFeedbackId)); + + // then + Assertions.assertThat(throwable.getClass()).isEqualTo(FormQuestionFeedbackNotExistException.class); + } + + @Test + @DisplayName("expectedTargetId로 Long이 아닌 타입이 들어오면 IllegalArgumentException을 던진다") + void THROW_ILLEGAL_ARGUMENT_EXCEPTION_WHEN_WRONG_TYPE_ON_EXPECTED_TARGET_ID() { + // given + String expectedTargetId = "1L"; + Long requestFormQuestionFeedbackId = 1L; + FormQuestionFeedbackIdValidator formQuestionFeedbackIdValidator = feedbackIdValidatorFactory.validator(); + feedbackIdValidatorFactory.parameterExtractor(); + + // when + Throwable throwable = Assertions.catchThrowable( + () -> formQuestionFeedbackIdValidator.valid(expectedTargetId, requestFormQuestionFeedbackId)); + + // then + Assertions.assertThat(throwable.getClass()).isEqualTo(IllegalArgumentException.class); + } + + @Test + @DisplayName("requestFormQuestionFeedbackId로 Long이 아닌 타입이 들어오면 IllegalArgumentException을 던진다") + void THROW_ILLEGAL_ARGUMENT_EXCEPTION_WHEN_WRONG_TYPE_ON_REQUEST_ID() { + // given + Long expectedTargetId = 1L; + String requestFormQuestionFeedbackId = "1L"; + FormQuestionFeedbackIdValidator formQuestionFeedbackIdValidator = feedbackIdValidatorFactory.validator(); + + // when + Throwable throwable = Assertions.catchThrowable( + () -> formQuestionFeedbackIdValidator.valid(expectedTargetId, requestFormQuestionFeedbackId)); + + // then + Assertions.assertThat(throwable.getClass()).isEqualTo(IllegalArgumentException.class); + } + + private static Stream authorizationSources() { + return Stream.of( + Arguments.of(1L, 1L, 1L, true), + Arguments.of(1L, 1L, 2L, false) + ); + } + +} diff --git a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/Bookmark.java b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/Bookmark.java index 0692b896..79bf00ee 100644 --- a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/Bookmark.java +++ b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/Bookmark.java @@ -1,6 +1,7 @@ package me.nalab.survey.domain.feedback; import java.time.Instant; +import java.util.Objects; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -13,9 +14,17 @@ @EqualsAndHashCode public class Bookmark { - private boolean isBookmarked; + private static final boolean BOOKMARK_DEFAULT_STATE = false; + + private boolean isBookmarked = BOOKMARK_DEFAULT_STATE; private Instant bookmarkedAt; + public Bookmark(boolean isBookmarked, Instant bookmarkedAt) { + Objects.requireNonNull(bookmarkedAt, () -> "Null value on new Bookmark(..., bookmarkedAt)"); + this.isBookmarked = isBookmarked; + this.bookmarkedAt = bookmarkedAt; + } + public void replaceIsBookmarked() { this.isBookmarked = !isBookmarked; this.bookmarkedAt = Instant.now(); diff --git a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java index f76ae7b0..c56e1bc9 100644 --- a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java +++ b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java @@ -4,6 +4,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.Setter; import lombok.ToString; import lombok.experimental.SuperBuilder; import me.nalab.survey.domain.exception.IdAlreadyGeneratedException; @@ -18,13 +19,10 @@ public abstract class FormQuestionFeedbackable implements IdGeneratable, Questio private Long id; private Long questionId; + @Setter private boolean isRead; private Bookmark bookmark; - public void setRead(boolean read) { - isRead = read; - } - public void replaceBookmark() { bookmark.replaceIsBookmarked(); } diff --git a/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/BookmarkTest.java b/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/BookmarkTest.java index f278d884..d86f25a5 100644 --- a/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/BookmarkTest.java +++ b/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/BookmarkTest.java @@ -4,12 +4,14 @@ import java.time.Instant; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class BookmarkTest { @Test - void bookmark_replacedIsBookmarked_test() { + @DisplayName("replaceIsBookmarked()메소드는 bookmark가 false라면, true로 변경한다.") + void bookmark_replacedIsBookmarked_true_if_false_test() { boolean initialIsBookmarked = false; Instant initialBookmarkedAt = Instant.now(); @@ -27,4 +29,24 @@ void bookmark_replacedIsBookmarked_test() { assertNotEquals(updatedBookmarkedAt, initialBookmarkedAt); } + @Test + @DisplayName("replaceIsBookmarked()메소드는 bookmark가 true라면, false로 변경한다.") + void bookmark_replacedIsBookmarked_false_if_true_test() { + + boolean initialIsBookmarked = true; + Instant initialBookmarkedAt = Instant.now(); + + Bookmark bookmark = Bookmark.builder() + .isBookmarked(initialIsBookmarked) + .bookmarkedAt(initialBookmarkedAt) + .build(); + bookmark.replaceIsBookmarked(); + + boolean updatedIsBookmarked = bookmark.isBookmarked(); + Instant updatedBookmarkedAt = bookmark.getBookmarkedAt(); + + assertEquals(updatedIsBookmarked, !initialIsBookmarked); + assertNotEquals(updatedBookmarkedAt, initialBookmarkedAt); + } + } diff --git a/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/FeedbackDomainTest.java b/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/FeedbackDomainTest.java index 4ad479f2..d6cfcf65 100644 --- a/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/FeedbackDomainTest.java +++ b/survey/survey-domain/src/test/java/me/nalab/survey/domain/feedback/FeedbackDomainTest.java @@ -17,6 +17,7 @@ import java.util.function.LongSupplier; import java.util.stream.Collectors; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -182,6 +183,23 @@ void FEEDBACK_DOMAIN_SORTING_SUCCESS() { assertIsSorted(feedbackList); } + @Test + @DisplayName("formQuestionFeedback의 bookmarked상태가 true일때, replaceBookmark가 호출되면, 상태가 변경된다.") + void replaceBookmark_change_bookmark_status() { + // given + FormQuestionFeedbackable formQuestionFeedbackable = ShortFormQuestionFeedback.builder() + .bookmark(Bookmark.builder() + .bookmarkedAt(Instant.now()) + .build()) + .build(); + + // when + formQuestionFeedbackable.replaceBookmark(); + + // then + Assertions.assertTrue(formQuestionFeedbackable.getBookmark().isBookmarked()); + } + private void assertIsSorted(List feedbackList) { Feedback before = null; for(Feedback current : feedbackList) { diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/authorization/TargetIdFindAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/authorization/TargetIdFindAdaptor.java index 0228037c..5c6b3992 100644 --- a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/authorization/TargetIdFindAdaptor.java +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/authorization/TargetIdFindAdaptor.java @@ -24,4 +24,8 @@ public Optional findTargetIdByFeedbackId(Long feedbackId) { return targetIdFindJpaRepository.findTargetIdByFeedbackId(feedbackId); } + @Override + public Optional findTargetIdByFormQuestionFeedbackId(Long formQuestionFeedbackId) { + return targetIdFindJpaRepository.findTargetIdByFormQuestionFeedbackId(formQuestionFeedbackId); + } } diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/authorization/repository/TargetIdFindJpaRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/authorization/repository/TargetIdFindJpaRepository.java index 185f9e67..93aa5c46 100644 --- a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/authorization/repository/TargetIdFindJpaRepository.java +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/authorization/repository/TargetIdFindJpaRepository.java @@ -15,4 +15,7 @@ public interface TargetIdFindJpaRepository extends JpaRepository findTargetIdByFeedbackId(@Param("feedbackId") Long feedbackId); + + @Query("select t.id from TargetEntity as t join SurveyEntity as s on s.id = (select ff.feedbackEntity.surveyId from FormFeedbackEntity as ff where ff.id = :formQuestionFeedbackId) and s.targetId = t.id") + Optional findTargetIdByFormQuestionFeedbackId(@Param("formQuestionFeedbackId") Long formQuestionFeedbackId); } diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/authorization/TargetIdFindAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/authorization/TargetIdFindAdaptorTest.java index cfc2bc0b..01595f7d 100644 --- a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/authorization/TargetIdFindAdaptorTest.java +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/authorization/TargetIdFindAdaptorTest.java @@ -1,10 +1,11 @@ package me.nalab.survey.jpa.adaptor.authorization; -import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import java.time.Instant; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -167,4 +168,35 @@ void FIND_ONT_TARGET_WHEN_HAVE_MULTIPLE_TARGET() { assertThat(result).contains(targetId); } + @Test + @DisplayName("formQuestionFeedbackId에 해당하는 target이 있다면, 조회된 타겟의 id를 반환한다.") + void FIND_TARGET_ID_BY_FORM_QUESTION_FEEDBACK_ID() { + // given + Long targetId = 101L; + TargetEntity targetEntity = TargetEntity.builder() + .id(targetId) + .createdAt(Instant.now()) + .updatedAt(Instant.now()) + .nickname("test target") + .build(); + Survey survey = RandomSurveyFixture.createRandomSurvey(); + SurveyEntity surveyEntity = SurveyEntityMapper.toSurveyEntity(targetId, survey); + FeedbackEntity feedbackEntity = FeedbackEntityMapper.toEntity( + RandomFeedbackFixture.getRandomFeedbackBySurvey(survey)); + + testTargetJpaRepository.saveAndFlush(targetEntity); + testSurveyJpaRepository.saveAndFlush(surveyEntity); + testFeedbackJpaRepository.saveAndFlush(feedbackEntity); + + // when + List> resultList = feedbackEntity.getFormFeedbackEntityList().stream() + .map(formQuestionFeedbackEntity -> targetIdFindPort.findTargetIdByFormQuestionFeedbackId( + formQuestionFeedbackEntity.getId())) + .collect(Collectors.toList()); + + // then + resultList.forEach(r -> assertThat(r).isPresent()); + resultList.forEach(r -> assertThat(r).contains(targetId)); + } + } diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController.java new file mode 100644 index 00000000..8cf2a372 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController.java @@ -0,0 +1,29 @@ +package me.nalab.survey.web.adaptor.bookmark; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import me.nalab.core.authorization.aop.meta.Authorization; +import me.nalab.survey.application.port.in.web.bookmark.BookmarkReplaceUseCase; +import me.nalab.survey.application.service.authorization.FormQuestionFeedbackIdValidatorFactory; + +@RestController +@RequestMapping("/v1") +@RequiredArgsConstructor +public class BookmarkReplaceController { + + private final BookmarkReplaceUseCase bookmarkReplaceUseCase; + + @PatchMapping("/feedbacks/bookmarks") + @Authorization(factory = FormQuestionFeedbackIdValidatorFactory.class) + public ResponseEntity replaceBookmark( + @RequestParam("form-question-feedback-id") String formQuestionFeedbackId) { + bookmarkReplaceUseCase.replaceBookmark(Long.valueOf(formQuestionFeedbackId)); + return ResponseEntity.ok().build(); + } + +} From 3fb8c5a821abeee5107dd5961d814dc65335abde Mon Sep 17 00:00:00 2001 From: xb205 <62425964+devxb@users.noreply.github.com> Date: Sun, 9 Jul 2023 23:49:35 +0900 Subject: [PATCH 06/17] =?UTF-8?q?[fix]=20:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=EC=8B=9C=20jpa=20=EB=B3=80=EA=B2=BD=20=EA=B0=90=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EC=9D=B8=EC=A6=9D=EC=97=90=20Auth=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#315)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/FormQuestionFeedbackUpdateAdaptor.java | 3 ++- .../adaptor/bookmark/BookmarkReplaceController.java | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java index d23d752e..d8214d83 100644 --- a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java @@ -18,7 +18,8 @@ public class FormQuestionFeedbackUpdateAdaptor implements FormQuestionFeedbackUp @Override public void updateFormQuestionFeedback(FormQuestionFeedbackable formQuestionFeedbackable) { FormFeedbackEntity formFeedbackEntity = FeedbackEntityMapper.toFormFeedbackEntity(formQuestionFeedbackable); - formQuestionFeedbackUpdateRepository.save(formFeedbackEntity); + formFeedbackEntity.setBookmarked(formQuestionFeedbackable.getBookmark().isBookmarked()); + formFeedbackEntity.setBookmarkedAt(formQuestionFeedbackable.getBookmark().getBookmarkedAt()); } } diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController.java index 8cf2a372..dadc054e 100644 --- a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController.java +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController.java @@ -1,12 +1,15 @@ package me.nalab.survey.web.adaptor.bookmark; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; +import me.nalab.core.authorization.aop.meta.Auth; import me.nalab.core.authorization.aop.meta.Authorization; import me.nalab.survey.application.port.in.web.bookmark.BookmarkReplaceUseCase; import me.nalab.survey.application.service.authorization.FormQuestionFeedbackIdValidatorFactory; @@ -20,10 +23,10 @@ public class BookmarkReplaceController { @PatchMapping("/feedbacks/bookmarks") @Authorization(factory = FormQuestionFeedbackIdValidatorFactory.class) - public ResponseEntity replaceBookmark( - @RequestParam("form-question-feedback-id") String formQuestionFeedbackId) { - bookmarkReplaceUseCase.replaceBookmark(Long.valueOf(formQuestionFeedbackId)); - return ResponseEntity.ok().build(); + @ResponseStatus(HttpStatus.OK) + public void replaceBookmark( + @Auth @RequestParam("form-question-feedback-id") Long formQuestionFeedbackId) { + bookmarkReplaceUseCase.replaceBookmark(formQuestionFeedbackId); } } From 633a08c86d52eb7a0a9019ce4eb8ef14cedc90c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Mon, 10 Jul 2023 15:28:21 +0900 Subject: [PATCH 07/17] =?UTF-8?q?[feat]=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EB=90=9C=20=ED=83=80=EA=B2=9F=EC=9D=98=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20`=EC=9D=B8=EC=88=98=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8`=20=EC=9E=91=EC=84=B1=20(#318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/UserAcceptanceTestSupporter.java | 10 ++++ .../test/user/UserAcceptanceValidator.java | 9 ++- .../TargetPositionUpdateAcceptanceTest.java | 57 +++++++++++++++++++ .../updatetarget/request/RequestSample.java | 32 +++++++++++ .../request/TargetPositionUpdateRequest.java | 17 ++++++ 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/TargetPositionUpdateAcceptanceTest.java create mode 100644 api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/request/RequestSample.java create mode 100644 api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/request/TargetPositionUpdateRequest.java diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/UserAcceptanceTestSupporter.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/UserAcceptanceTestSupporter.java index 3fb08b7a..cf415cb7 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/UserAcceptanceTestSupporter.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/UserAcceptanceTestSupporter.java @@ -23,6 +23,16 @@ protected ResultActions getLoginedUser(String token) throws Exception { ); } + protected ResultActions updateTargetPosition(String token, String content) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .patch(API_VERSION + "/users") + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, token) + .content(content) + ); + } + @Autowired final void setMockMvc(MockMvc mockMvc) { this.mockMvc = mockMvc; diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/UserAcceptanceValidator.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/UserAcceptanceValidator.java index 84d501f1..bf074b3f 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/UserAcceptanceValidator.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/UserAcceptanceValidator.java @@ -1,11 +1,12 @@ package me.nalab.luffy.api.acceptance.test.user; -import org.springframework.test.web.servlet.ResultActions; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import org.springframework.test.web.servlet.ResultActions; + public class UserAcceptanceValidator { - public static void assertIsLogined(ResultActions resultActions, Long targetId, String nickname) throws Exception{ + public static void assertIsLogined(ResultActions resultActions, Long targetId, String nickname) throws Exception { resultActions.andExpectAll( status().isOk(), jsonPath("$.target_id").value(targetId), @@ -13,4 +14,8 @@ public static void assertIsLogined(ResultActions resultActions, Long targetId, S ); } + public static void assertIsTargetPositionUpdated(ResultActions resultActions) throws Exception { + resultActions.andExpectAll(status().isOk()); + } + } diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/TargetPositionUpdateAcceptanceTest.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/TargetPositionUpdateAcceptanceTest.java new file mode 100644 index 00000000..1e6fe2df --- /dev/null +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/TargetPositionUpdateAcceptanceTest.java @@ -0,0 +1,57 @@ +package me.nalab.luffy.api.acceptance.test.user.updatetarget; + +import static me.nalab.luffy.api.acceptance.test.user.UserAcceptanceValidator.*; + +import java.time.Instant; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.ResultActions; + +import me.nalab.auth.mock.api.MockUserRegisterEvent; +import me.nalab.luffy.api.acceptance.test.TargetInitializer; +import me.nalab.luffy.api.acceptance.test.user.UserAcceptanceTestSupporter; +import me.nalab.luffy.api.acceptance.test.user.updatetarget.request.RequestSample; + +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource("classpath:h2.properties") +@ComponentScan("me.nalab") +@EnableJpaRepositories(basePackages = {"me.nalab"}) +@EntityScan(basePackages = {"me.nalab"}) +class TargetPositionUpdateAcceptanceTest extends UserAcceptanceTestSupporter { + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + @Autowired + private TargetInitializer targetInitializer; + + @Test + @DisplayName("로그인된 타겟의 position 수정 성공 테스트") + void TARGET_POSITION_UPDATE_SUCCESS_TEST() throws Exception { + // given + String nickname = "nickname"; + Long targetId = targetInitializer.saveTargetAndGetId(nickname, Instant.now()); + String token = "token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedToken(token) + .expectedId(targetId) + .build()); + + // when + ResultActions resultActions = updateTargetPosition(token, RequestSample.DEFAULT_JSON); + + // then + assertIsTargetPositionUpdated(resultActions); + } + +} diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/request/RequestSample.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/request/RequestSample.java new file mode 100644 index 00000000..9cd8a4ea --- /dev/null +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/request/RequestSample.java @@ -0,0 +1,32 @@ +package me.nalab.luffy.api.acceptance.test.user.updatetarget.request; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +public final class RequestSample { + + private static final ObjectMapper OBJECT_MAPPER; + public static final String DEFAULT_JSON; + + static { + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + DEFAULT_JSON = loadDefaultJson(); + } + + private static String loadDefaultJson() { + + try { + return OBJECT_MAPPER.writeValueAsString(TargetPositionUpdateRequest.builder() + .position("BE developer") + .build() + ); + } catch (JsonProcessingException jpe) { + throw new IllegalStateException( + "Cannot start acceptance test fail to load \"DEFAULT_JSON\" " + jpe.getMessage()); + } + } + +} diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/request/TargetPositionUpdateRequest.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/request/TargetPositionUpdateRequest.java new file mode 100644 index 00000000..b7c5a6b5 --- /dev/null +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/user/updatetarget/request/TargetPositionUpdateRequest.java @@ -0,0 +1,17 @@ +package me.nalab.luffy.api.acceptance.test.user.updatetarget.request; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TargetPositionUpdateRequest { + + @JsonProperty("position") + private String position; + +} From 8ed0a14eeb3d497873200b9cc97e54c2f74b43cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Mon, 10 Jul 2023 21:21:16 +0900 Subject: [PATCH 08/17] =?UTF-8?q?[feat]=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EB=90=9C=20=ED=83=80=EA=B2=9F=EC=9D=98=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20`application`=20=EC=B6=94=EA=B0=80=20(?= =?UTF-8?q?#319)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../update/TargetPositionUpdateUseCase.java | 16 +++++ .../target/update/TargetFindPort.java | 17 +++++ .../update/TargetPositionUpdatePort.java | 18 +++++ .../TargetPositionUpdateService.java | 30 ++++++++ .../TargetPositionUpdateServiceTest.java | 70 +++++++++++++++++++ .../me/nalab/survey/domain/target/Target.java | 7 +- .../domain/target/TargetDomainTest.java | 21 ++++-- 7 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/target/update/TargetPositionUpdateUseCase.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetFindPort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetPositionUpdatePort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateService.java create mode 100644 survey/survey-application/src/test/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateServiceTest.java diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/target/update/TargetPositionUpdateUseCase.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/target/update/TargetPositionUpdateUseCase.java new file mode 100644 index 00000000..9d321af8 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/target/update/TargetPositionUpdateUseCase.java @@ -0,0 +1,16 @@ +package me.nalab.survey.application.port.in.web.target.update; + +/** + * + * targetId에 해당하는 타겟의 position을 수정합니다. + */ +public interface TargetPositionUpdateUseCase { + + /** + * + * @param targetId 해당 타겟의 id + * @param position 변경할 position + */ + void updateTargetPosition(Long targetId, String position); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetFindPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetFindPort.java new file mode 100644 index 00000000..881a3c44 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetFindPort.java @@ -0,0 +1,17 @@ +package me.nalab.survey.application.port.out.persistence.target.update; + +import java.util.Optional; + +import me.nalab.survey.domain.target.Target; + +public interface TargetFindPort { + + /** + * targetId에 해당하는 Target을 조회합니다. + * + * @param targetId Target을 조회할 Target의 ID + * @return Optional 만약, 어떠한 targetId도 없을 경우, Optional.empty() 를 반환해야 합니다. + */ + Optional findTarget(Long targetId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetPositionUpdatePort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetPositionUpdatePort.java new file mode 100644 index 00000000..8a270874 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetPositionUpdatePort.java @@ -0,0 +1,18 @@ +package me.nalab.survey.application.port.out.persistence.target.update; + +import me.nalab.survey.domain.target.Target; + +/** + * Target의 position 필드를 변경하는 역할을 하는 인터페이스 + * + * 이 인터페이스의 구현체는 target의 position을 업데이트 합니다 + */ +public interface TargetPositionUpdatePort { + + /** + * 이 메서드는 target의 position을 업데이트 합니다 + * @param target 업데이트할 target + */ + void updateTargetPosition(Target target); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateService.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateService.java new file mode 100644 index 00000000..686e434c --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateService.java @@ -0,0 +1,30 @@ +package me.nalab.survey.application.service.updatetarget; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import me.nalab.survey.application.exception.TargetDoesNotExistException; +import me.nalab.survey.application.port.in.web.target.update.TargetPositionUpdateUseCase; +import me.nalab.survey.application.port.out.persistence.target.update.TargetFindPort; +import me.nalab.survey.application.port.out.persistence.target.update.TargetPositionUpdatePort; +import me.nalab.survey.domain.target.Target; + +@Service +@RequiredArgsConstructor +public class TargetPositionUpdateService implements TargetPositionUpdateUseCase { + + private final TargetFindPort targetFindPort; + private final TargetPositionUpdatePort targetPositionUpdatePort; + + @Override + @Transactional + public void updateTargetPosition(Long targetId, String position) { + Target target = targetFindPort.findTarget(targetId).orElseThrow( + () -> new TargetDoesNotExistException(targetId) + ); + target.setPosition(position); + targetPositionUpdatePort.updateTargetPosition(target); + } + +} diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateServiceTest.java b/survey/survey-application/src/test/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateServiceTest.java new file mode 100644 index 00000000..526cc133 --- /dev/null +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateServiceTest.java @@ -0,0 +1,70 @@ +package me.nalab.survey.application.service.updatetarget; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import me.nalab.survey.application.exception.TargetDoesNotExistException; +import me.nalab.survey.application.port.out.persistence.target.update.TargetFindPort; +import me.nalab.survey.application.port.out.persistence.target.update.TargetPositionUpdatePort; +import me.nalab.survey.domain.target.Target; + +@ExtendWith(SpringExtension.class) +class TargetPositionUpdateServiceTest { + + private TargetPositionUpdateService targetPositionUpdateService; + + @Mock + private TargetFindPort targetFindPort; + + @Mock + private TargetPositionUpdatePort targetPositionUpdatePort; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + targetPositionUpdateService = new TargetPositionUpdateService(targetFindPort, targetPositionUpdatePort); + } + + @Test + void UPDATE_TARGET_POSITION_SERVICE_SUCCESS_TEST() { + + Long targetId = 123L; + Target target = Target.builder() + .id(targetId) + .nickname("sujin") + .build(); + + when(targetFindPort.findTarget(targetId)).thenReturn(Optional.of(target)); + + assertDoesNotThrow(() -> targetPositionUpdateService.updateTargetPosition(targetId, target.getPosition())); + + } + + @Test + void UPDATE_TARGET_POSITION_SERVICE_FAIL_TEST() { + + Long targetId = 123L; + Target target = Target.builder() + .id(targetId) + .nickname("sujin") + .build(); + String position = target.getPosition(); + + when(targetFindPort.findTarget(targetId)).thenReturn(Optional.empty()); + + assertThrows(TargetDoesNotExistException.class, + () -> targetPositionUpdateService.updateTargetPosition(targetId, position) + ); + + } + +} diff --git a/survey/survey-domain/src/main/java/me/nalab/survey/domain/target/Target.java b/survey/survey-domain/src/main/java/me/nalab/survey/domain/target/Target.java index 1bf3de68..b109a3d0 100644 --- a/survey/survey-domain/src/main/java/me/nalab/survey/domain/target/Target.java +++ b/survey/survey-domain/src/main/java/me/nalab/survey/domain/target/Target.java @@ -20,14 +20,17 @@ public class Target implements IdGeneratable { private Long id; private final List surveyList; private final String nickname; - private final String position; + private String position; @Override public void withId(LongSupplier idSupplier) { - if(id != null) { + if (id != null) { throw new IdAlreadyGeneratedException(this); } id = idSupplier.getAsLong(); } + public void setPosition(String position) { + this.position = position; + } } diff --git a/survey/survey-domain/src/test/java/me/nalab/survey/domain/target/TargetDomainTest.java b/survey/survey-domain/src/test/java/me/nalab/survey/domain/target/TargetDomainTest.java index 63b32b07..64a9a7d6 100644 --- a/survey/survey-domain/src/test/java/me/nalab/survey/domain/target/TargetDomainTest.java +++ b/survey/survey-domain/src/test/java/me/nalab/survey/domain/target/TargetDomainTest.java @@ -1,9 +1,6 @@ package me.nalab.survey.domain.target; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import java.util.List; import java.util.function.Function; @@ -88,4 +85,20 @@ void TARGET_WITH_ID_FAIL_DUPLICATED_REQUEST() { assertThrows(IdAlreadyGeneratedException.class, () -> target.withId(longSupplier)); } + @Test + @DisplayName("Target position 수정 테스트") + void TARGET_POSITION_UPDATE_TEST() { + + Target target = Target.builder() + .nickname("target") + .position("designer") + .build(); + String newPosition = "developer"; + + target.setPosition(newPosition); + + assertEquals(newPosition, target.getPosition()); + + } + } From 1bb77ddd2fbc70671d6f78d8c4c7b31a777061c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Tue, 11 Jul 2023 02:03:43 +0900 Subject: [PATCH 09/17] =?UTF-8?q?[feat]=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EB=90=9C=20=ED=83=80=EA=B2=9F=EC=9D=98=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20`jpa-adaptor`=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20(#320)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../update/TargetPositionUpdatePort.java | 4 +- .../TargetPositionUpdateService.java | 6 +- .../src/main/java/module-info.java | 1 + .../TargetPositionUpdateServiceTest.java | 42 ++++++++- .../updatetarget/TargetFindAdaptor.java | 35 ++++++++ .../TargetPositionUpdateAdaptor.java | 35 ++++++++ .../repository/TargetFindRepository.java | 11 +++ .../TargetPositionUpdateRepository.java | 11 +++ .../updatetarget/TargetFindAdaptorTest.java | 69 +++++++++++++++ .../TargetPositionUpdateAdaptorTest.java | 88 +++++++++++++++++++ .../TestTargetFindRepository.java | 11 +++ .../TestTargetPositionUpdateRepository.java | 11 +++ 12 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetFindAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetPositionUpdateAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/repository/TargetFindRepository.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/repository/TargetPositionUpdateRepository.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetFindAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetPositionUpdateAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TestTargetFindRepository.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TestTargetPositionUpdateRepository.java diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetPositionUpdatePort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetPositionUpdatePort.java index 8a270874..751ca263 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetPositionUpdatePort.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/target/update/TargetPositionUpdatePort.java @@ -10,9 +10,11 @@ public interface TargetPositionUpdatePort { /** + * * 이 메서드는 target의 position을 업데이트 합니다 * @param target 업데이트할 target + * @return */ - void updateTargetPosition(Target target); + boolean updateTargetPosition(Target target); } diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateService.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateService.java index 686e434c..49f9ac03 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateService.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateService.java @@ -24,7 +24,11 @@ public void updateTargetPosition(Long targetId, String position) { () -> new TargetDoesNotExistException(targetId) ); target.setPosition(position); - targetPositionUpdatePort.updateTargetPosition(target); + + boolean updatedResult = targetPositionUpdatePort.updateTargetPosition(target); + if (!updatedResult) { + throw new TargetDoesNotExistException(target.getId()); + } } } diff --git a/survey/survey-application/src/main/java/module-info.java b/survey/survey-application/src/main/java/module-info.java index 11587456..240fb702 100644 --- a/survey/survey-application/src/main/java/module-info.java +++ b/survey/survey-application/src/main/java/module-info.java @@ -29,6 +29,7 @@ exports me.nalab.survey.application.service.authorization; exports me.nalab.survey.application.port.out.persistence.bookmark; exports me.nalab.survey.application.port.in.web.bookmark; + exports me.nalab.survey.application.port.out.persistence.target.update; requires luffy.survey.domain.main; requires luffy.core.id.generator.id.generator.starter.main; diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateServiceTest.java b/survey/survey-application/src/test/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateServiceTest.java index 526cc133..805849dd 100644 --- a/survey/survey-application/src/test/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateServiceTest.java +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/service/updatetarget/TargetPositionUpdateServiceTest.java @@ -44,13 +44,52 @@ void UPDATE_TARGET_POSITION_SERVICE_SUCCESS_TEST() { .build(); when(targetFindPort.findTarget(targetId)).thenReturn(Optional.of(target)); + when(targetPositionUpdatePort.updateTargetPosition(target)).thenReturn(true); assertDoesNotThrow(() -> targetPositionUpdateService.updateTargetPosition(targetId, target.getPosition())); } @Test - void UPDATE_TARGET_POSITION_SERVICE_FAIL_TEST() { + void UPDATE_TARGET_POSITION_SERVICE_FAIL_TEST_CASE_1() { + + Long targetId = 123L; + Target target = Target.builder() + .id(targetId) + .nickname("sujin") + .build(); + String position = target.getPosition(); + + when(targetFindPort.findTarget(targetId)).thenReturn(Optional.of(target)); + when(targetPositionUpdatePort.updateTargetPosition(target)).thenReturn(false); + + assertThrows(TargetDoesNotExistException.class, + () -> targetPositionUpdateService.updateTargetPosition(targetId, position) + ); + + } + + @Test + void UPDATE_TARGET_POSITION_SERVICE_FAIL_TEST_CASE_2() { + + Long targetId = 123L; + Target target = Target.builder() + .id(targetId) + .nickname("sujin") + .build(); + String position = target.getPosition(); + + when(targetFindPort.findTarget(targetId)).thenReturn(Optional.empty()); + when(targetPositionUpdatePort.updateTargetPosition(target)).thenReturn(true); + + assertThrows(TargetDoesNotExistException.class, + () -> targetPositionUpdateService.updateTargetPosition(targetId, position) + ); + + } + + @Test + void UPDATE_TARGET_POSITION_SERVICE_FAIL_TEST_CASE_3() { Long targetId = 123L; Target target = Target.builder() @@ -60,6 +99,7 @@ void UPDATE_TARGET_POSITION_SERVICE_FAIL_TEST() { String position = target.getPosition(); when(targetFindPort.findTarget(targetId)).thenReturn(Optional.empty()); + when(targetPositionUpdatePort.updateTargetPosition(target)).thenReturn(false); assertThrows(TargetDoesNotExistException.class, () -> targetPositionUpdateService.updateTargetPosition(targetId, position) diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetFindAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetFindAdaptor.java new file mode 100644 index 00000000..deaf956d --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetFindAdaptor.java @@ -0,0 +1,35 @@ +package me.nalab.survey.jpa.adaptor.updatetarget; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.application.port.out.persistence.target.update.TargetFindPort; +import me.nalab.survey.domain.target.Target; +import me.nalab.survey.jpa.adaptor.common.mapper.TargetEntityMapper; +import me.nalab.survey.jpa.adaptor.updatetarget.repository.TargetFindRepository; + +@Repository("updatetarget.TargetFindAdaptor") +public class TargetFindAdaptor implements TargetFindPort { + + private final TargetFindRepository targetFindRepository; + + @Autowired + TargetFindAdaptor( + @Qualifier("updatetarget.TargetFindRepository") TargetFindRepository targetFindRepository) { + this.targetFindRepository = targetFindRepository; + } + + @Override + public Optional findTarget(Long targetId) { + Optional targetEntity = targetFindRepository.findById(targetId); + if (targetEntity.isEmpty()) { + return Optional.empty(); + } + return Optional.of(TargetEntityMapper.toTarget(targetEntity.get())); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetPositionUpdateAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetPositionUpdateAdaptor.java new file mode 100644 index 00000000..3de96006 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetPositionUpdateAdaptor.java @@ -0,0 +1,35 @@ +package me.nalab.survey.jpa.adaptor.updatetarget; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.application.port.out.persistence.target.update.TargetPositionUpdatePort; +import me.nalab.survey.domain.target.Target; +import me.nalab.survey.jpa.adaptor.updatetarget.repository.TargetPositionUpdateRepository; + +@Repository("updatetarget.TargetPositionUpdateAdaptor") +public class TargetPositionUpdateAdaptor implements TargetPositionUpdatePort { + + private final TargetPositionUpdateRepository targetPositionUpdateRepository; + + @Autowired + TargetPositionUpdateAdaptor( + @Qualifier("updatetarget.TargetPositionUpdateRepository") TargetPositionUpdateRepository targetPositionUpdateRepository) { + this.targetPositionUpdateRepository = targetPositionUpdateRepository; + } + + @Override + public boolean updateTargetPosition(Target target) { + Optional targetEntity = targetPositionUpdateRepository.findById(target.getId()); + if (targetEntity.isEmpty()) { + return false; + } + targetEntity.get().setPosition(target.getPosition()); + return true; + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/repository/TargetFindRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/repository/TargetFindRepository.java new file mode 100644 index 00000000..85d9cb6a --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/repository/TargetFindRepository.java @@ -0,0 +1,11 @@ +package me.nalab.survey.jpa.adaptor.updatetarget.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.target.TargetEntity; + +@Repository("updatetarget.TargetFindRepository") +public interface TargetFindRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/repository/TargetPositionUpdateRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/repository/TargetPositionUpdateRepository.java new file mode 100644 index 00000000..41e866b4 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/updatetarget/repository/TargetPositionUpdateRepository.java @@ -0,0 +1,11 @@ +package me.nalab.survey.jpa.adaptor.updatetarget.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.target.TargetEntity; + +@Repository("updatetarget.TargetPositionUpdateRepository") +public interface TargetPositionUpdateRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetFindAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetFindAdaptorTest.java new file mode 100644 index 00000000..eea6339a --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetFindAdaptorTest.java @@ -0,0 +1,69 @@ +package me.nalab.survey.jpa.adaptor.updatetarget; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.Optional; + +import javax.persistence.EntityManager; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.domain.target.Target; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = TargetFindAdaptor.class) +@TestPropertySource("classpath:h2.properties") +class TargetFindAdaptorTest { + + @Autowired + private TargetFindAdaptor targetFindAdaptor; + + @Autowired + private TestTargetFindRepository testTargetFindRepository; + + @Autowired + private EntityManager entityManager; + + @Test + @DisplayName("타겟 조회 성공 테스트") + void FIND_TARGET_BY_ID_SUCCESS() { + + Long targetId = 1L; + TargetEntity targetEntity = TargetEntity.builder() + .id(targetId) + .createdAt(Instant.now()) + .updatedAt(Instant.now()) + .nickname("sujin") + .build(); + + testTargetFindRepository.saveAndFlush(targetEntity); + entityManager.clear(); + Optional resultTarget = targetFindAdaptor.findTarget(targetId); + + assertTrue(resultTarget.isPresent()); + assertEquals(targetEntity.getId(), resultTarget.get().getId()); + } + + @Test + @DisplayName("타겟 조회 실패 테스트") + void FIND_TARGET_BY_ID_FAIL() { + + Long targetId = 1L; + + Optional target = targetFindAdaptor.findTarget(targetId); + + assertTrue(target.isEmpty()); + } + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetPositionUpdateAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetPositionUpdateAdaptorTest.java new file mode 100644 index 00000000..16d7ba24 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TargetPositionUpdateAdaptorTest.java @@ -0,0 +1,88 @@ +package me.nalab.survey.jpa.adaptor.updatetarget; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.Optional; + +import javax.persistence.EntityManager; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.domain.target.Target; +import me.nalab.survey.jpa.adaptor.common.mapper.TargetEntityMapper; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = TargetPositionUpdateAdaptor.class) +@TestPropertySource("classpath:h2.properties") +class TargetPositionUpdateAdaptorTest { + + @Autowired + private TargetPositionUpdateAdaptor targetPositionUpdateAdaptor; + + @Autowired + private TestTargetPositionUpdateRepository testTargetPositionUpdateRepository; + + @Autowired + private EntityManager entityManager; + + @Test + @DisplayName("타겟 position 수정 성공 테스트") + void UPDATE_TARGET_POSITION_SUCCESS() { + + Long targetId = 1L; + TargetEntity targetEntity = TargetEntity.builder() + .id(targetId) + .createdAt(Instant.now()) + .updatedAt(Instant.now()) + .nickname("sujin") + .position("designer") + .build(); + Target target = TargetEntityMapper.toTarget(targetEntity); + testTargetPositionUpdateRepository.saveAndFlush(targetEntity); + entityManager.clear(); + + String updatePosition = "developer"; + target.setPosition(updatePosition); + boolean updatedResult = targetPositionUpdateAdaptor.updateTargetPosition(target); + Optional resultTargetEntity = testTargetPositionUpdateRepository.findById(targetId); + + assertTrue(updatedResult); + assertTrue(resultTargetEntity.isPresent()); + assertEquals(updatePosition, resultTargetEntity.get().getPosition()); + + } + + @Test + @DisplayName("타겟 position 수정 실패 테스트") + void UPDATE_TARGET_POSITION_FAIL() { + + Long targetId = 1L; + TargetEntity targetEntity = TargetEntity.builder() + .id(targetId) + .createdAt(Instant.now()) + .updatedAt(Instant.now()) + .nickname("sujin") + .position("designer") + .build(); + Target target = TargetEntityMapper.toTarget(targetEntity); + + String updatePosition = "developer"; + target.setPosition(updatePosition); + boolean updatedResult = targetPositionUpdateAdaptor.updateTargetPosition(target); + + assertFalse(updatedResult); + + } + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TestTargetFindRepository.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TestTargetFindRepository.java new file mode 100644 index 00000000..2450a412 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TestTargetFindRepository.java @@ -0,0 +1,11 @@ +package me.nalab.survey.jpa.adaptor.updatetarget; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.target.TargetEntity; + +@Repository("updatetarget.TestTargetFindRepository") +public interface TestTargetFindRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TestTargetPositionUpdateRepository.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TestTargetPositionUpdateRepository.java new file mode 100644 index 00000000..acc34b7a --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/updatetarget/TestTargetPositionUpdateRepository.java @@ -0,0 +1,11 @@ +package me.nalab.survey.jpa.adaptor.updatetarget; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.target.TargetEntity; + +@Repository("updatetarget.TestTargetPositionUpdateRepository") +public interface TestTargetPositionUpdateRepository extends JpaRepository { + +} From a8fd648e68fa950d7a7afc73b74949e2b1a30056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Tue, 11 Jul 2023 17:31:06 +0900 Subject: [PATCH 10/17] =?UTF-8?q?[feat]=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EB=90=9C=20=ED=83=80=EA=B2=9F=EC=9D=98=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20`web-adaptor`=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20(#321)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JwtDecryptInterceptorConfigurer.java | 1 + coverage-exclude.luffy | 1 + .../src/main/java/module-info.java | 1 + .../TargetPositionUpdateController.java | 30 +++++++++++++++++++ .../request/TargetPositionUpdateRequest.java | 20 +++++++++++++ 5 files changed, 53 insertions(+) create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/updatetarget/TargetPositionUpdateController.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/updatetarget/request/TargetPositionUpdateRequest.java diff --git a/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java b/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java index c49e29bc..7d9dfdd9 100644 --- a/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java +++ b/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptorConfigurer.java @@ -25,6 +25,7 @@ public class JwtDecryptInterceptorConfigurer implements WebMvcConfigurer { "/v1/reviewers/summary*", "/v2/surveys/*/feedbacks", "/v1/feedbacks/bookmarks", + "/v1/users" }; @Override diff --git a/coverage-exclude.luffy b/coverage-exclude.luffy index 0835d8f7..e2e92bc5 100644 --- a/coverage-exclude.luffy +++ b/coverage-exclude.luffy @@ -52,3 +52,4 @@ exclude me/nalab/core/secure/cors/CorsConfig exclude me/nalab/core/secure/xss/config/XssConfig exclude me/nalab/core/secure/xss/json/JsonXssFilterException exclude me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController +exclude me/nalab/survey/web/adaptor/updatetarget/* diff --git a/survey/survey-application/src/main/java/module-info.java b/survey/survey-application/src/main/java/module-info.java index 240fb702..e943f6da 100644 --- a/survey/survey-application/src/main/java/module-info.java +++ b/survey/survey-application/src/main/java/module-info.java @@ -30,6 +30,7 @@ exports me.nalab.survey.application.port.out.persistence.bookmark; exports me.nalab.survey.application.port.in.web.bookmark; exports me.nalab.survey.application.port.out.persistence.target.update; + exports me.nalab.survey.application.port.in.web.target.update; requires luffy.survey.domain.main; requires luffy.core.id.generator.id.generator.starter.main; diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/updatetarget/TargetPositionUpdateController.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/updatetarget/TargetPositionUpdateController.java new file mode 100644 index 00000000..9bed6be1 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/updatetarget/TargetPositionUpdateController.java @@ -0,0 +1,30 @@ +package me.nalab.survey.web.adaptor.updatetarget; + +import javax.validation.Valid; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import me.nalab.survey.application.port.in.web.target.update.TargetPositionUpdateUseCase; +import me.nalab.survey.web.adaptor.updatetarget.request.TargetPositionUpdateRequest; + +@RestController +@RequestMapping("/v1") +@RequiredArgsConstructor +public class TargetPositionUpdateController { + + private final TargetPositionUpdateUseCase targetPositionUpdateUseCase; + + @PatchMapping("/users") + public ResponseEntity updateTargetPosition(@RequestAttribute("logined") Long loginId, + @Valid @RequestBody TargetPositionUpdateRequest targetPositionUpdateRequest) { + targetPositionUpdateUseCase.updateTargetPosition(loginId, targetPositionUpdateRequest.getPosition()); + return ResponseEntity.ok().build(); + } + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/updatetarget/request/TargetPositionUpdateRequest.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/updatetarget/request/TargetPositionUpdateRequest.java new file mode 100644 index 00000000..f1ca7ed7 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/updatetarget/request/TargetPositionUpdateRequest.java @@ -0,0 +1,20 @@ +package me.nalab.survey.web.adaptor.updatetarget.request; + +import javax.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@EqualsAndHashCode +public class TargetPositionUpdateRequest { + + @JsonProperty("position") + @Size(max = 16, message = "직군은 공백포함 최대 16자까지 입력가능합니다.") + private String position; + +} From c8d4f5a6240a88ceb055834f1c911a0cd283f7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Wed, 12 Jul 2023 01:34:29 +0900 Subject: [PATCH 11/17] =?UTF-8?q?[feat]=20:=20`=EC=A7=88=EB=AC=B8=ED=8F=BC?= =?UTF-8?q?=EC=97=90=20=ED=95=B4=EB=8B=B9=ED=95=98=EB=8A=94=20=ED=83=80?= =?UTF-8?q?=EA=B2=9F=EC=9D=98=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C`?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20=EC=99=84=EB=A3=8C?= =?UTF-8?q?=20(#328)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../survey/AbstractSurveyTestSupporter.java | 13 ++- .../survey/SurveyAcceptanceValidator.java | 15 ++- .../findtarget/TargetFindAcceptanceTest.java | 84 +++++++++++++++++ .../interceptor/JwtDecryptInterceptor.java | 33 ++++++- .../mock/interceptor/MockAuthInterceptor.java | 32 ++++++- .../interceptor/MockAuthInterceptorTest.java | 7 +- coverage-exclude.luffy | 2 + .../TargetFindBySurveyIdUseCase.java | 18 ++++ .../findtarget/TargetFindPort.java | 20 ++++ .../findtarget/TargetIdFindPort.java | 17 ++++ .../TargetFindBySurveyIdService.java | 35 +++++++ .../src/main/java/module-info.java | 3 + .../TargetFindBySurveyIdServiceTest.java | 93 +++++++++++++++++++ .../adaptor/findtarget/TargetFindAdaptor.java | 36 +++++++ .../findtarget/TargetIdFindAdaptor.java | 35 +++++++ .../repository/TargetFindJpaRepository.java | 11 +++ .../repository/TargetIdFindJpaRepository.java | 11 +++ .../findtarget/TargetFindAdaptorTest.java | 71 ++++++++++++++ .../findtarget/TargetIdFindAdaptorTest.java | 71 ++++++++++++++ .../TestTargetFindJpaRepository.java | 9 ++ .../TestTargetIdFindJpaRepository.java | 9 ++ .../TargetFindBySurveyIdController.java | 29 ++++++ .../findtarget/TargetResponseMapper.java | 20 ++++ .../findtarget/response/TargetResponse.java | 25 +++++ 24 files changed, 679 insertions(+), 20 deletions(-) create mode 100644 api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/findtarget/TargetFindAcceptanceTest.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findtarget/TargetFindBySurveyIdUseCase.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findtarget/TargetFindPort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findtarget/TargetIdFindPort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/service/findtarget/TargetFindBySurveyIdService.java create mode 100644 survey/survey-application/src/test/java/me/nalab/survey/application/service/findtarget/TargetFindBySurveyIdServiceTest.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/TargetFindAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/TargetIdFindAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/repository/TargetFindJpaRepository.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/repository/TargetIdFindJpaRepository.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TargetFindAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TargetIdFindAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TestTargetFindJpaRepository.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TestTargetIdFindJpaRepository.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/TargetFindBySurveyIdController.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/TargetResponseMapper.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/response/TargetResponse.java diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/AbstractSurveyTestSupporter.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/AbstractSurveyTestSupporter.java index b997fd52..0d866c73 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/AbstractSurveyTestSupporter.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/AbstractSurveyTestSupporter.java @@ -1,6 +1,7 @@ package me.nalab.luffy.api.acceptance.test.survey; -import me.nalab.luffy.api.acceptance.test.DatabaseCleaner; +import java.util.Set; + import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; @@ -9,7 +10,7 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.util.Set; +import me.nalab.luffy.api.acceptance.test.DatabaseCleaner; public abstract class AbstractSurveyTestSupporter { @@ -44,6 +45,14 @@ protected ResultActions findSurvey(Long survey_Id) throws Exception { ); } + protected ResultActions findTargetBySurveyId(Long survey_Id) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get(API_VERSION + "/users?survey-id=" + survey_Id) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + ); + } + @Autowired final void setMockMvc(MockMvc mockMvc) { this.mockMvc = mockMvc; diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/SurveyAcceptanceValidator.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/SurveyAcceptanceValidator.java index 711ffb32..7deb2984 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/SurveyAcceptanceValidator.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/SurveyAcceptanceValidator.java @@ -1,8 +1,6 @@ package me.nalab.luffy.api.acceptance.test.survey; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; @@ -52,7 +50,6 @@ public static void assertIsSurveyAndQuestionFound(ResultActions resultActions) t .andExpect(jsonPath("$.question[0].choices[2].order").value(3)) .andExpect(jsonPath("$.question[0].choices[3].order").value(4)) - .andExpect(jsonPath("$.question[1].type").value("choice")) .andExpect(jsonPath("$.question[1].title").value("예진님의 성향은 어떤 것이 돋보이나요?")) .andExpect(jsonPath("$.question[1].order").value(4)) @@ -77,4 +74,14 @@ public static void assertIsSurveyAndQuestionFound(ResultActions resultActions) t .andExpect(jsonPath("$.question[3].order").value(6)); } + public static void assertIsTargetFound(ResultActions resultActions) throws Exception { + resultActions.andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.target_id").isNumber(), + jsonPath("$.nickname").isString(), + jsonPath("$.position").hasJsonPath() + ); + } + } diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/findtarget/TargetFindAcceptanceTest.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/findtarget/TargetFindAcceptanceTest.java new file mode 100644 index 00000000..3f16bfaa --- /dev/null +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/survey/findtarget/TargetFindAcceptanceTest.java @@ -0,0 +1,84 @@ +package me.nalab.luffy.api.acceptance.test.survey.findtarget; + +import static me.nalab.luffy.api.acceptance.test.survey.SurveyAcceptanceValidator.*; + +import java.time.Instant; +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.ResultActions; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import me.nalab.auth.mock.api.MockUserRegisterEvent; +import me.nalab.luffy.api.acceptance.test.TargetInitializer; +import me.nalab.luffy.api.acceptance.test.survey.AbstractSurveyTestSupporter; +import me.nalab.luffy.api.acceptance.test.survey.RequestSample; + +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource("classpath:h2.properties") +@ComponentScan("me.nalab") +@EnableJpaRepositories(basePackages = {"me.nalab"}) +@EntityScan(basePackages = {"me.nalab"}) +class TargetFindAcceptanceTest extends AbstractSurveyTestSupporter { + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + @Autowired + private TargetInitializer targetInitializer; + + @PersistenceContext + private EntityManager entityManager; + + private static final ObjectMapper OBJECT_MAPPER; + + static { + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + } + + @Test + @DisplayName("survey_id에 해당하는 타겟의 정보 조회") + void FIND_TARGET_BY_SURVEY_ID_WITH_NO_AUTHORIZATION() throws Exception { + // given + Long targetId = targetInitializer.saveTargetAndGetId("target", Instant.now()); + String token = "token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedToken(token) + .expectedId(targetId) + .build()); + Long surveyId = createAndGetSurveyId(token, RequestSample.DEFAULT_JSON); + + // when + ResultActions resultActions = findTargetBySurveyId(surveyId); + + // then + assertIsTargetFound(resultActions); + } + + private Long createAndGetSurveyId(String token, String content) throws Exception { + ResultActions resultActions = createSurvey(token, content); + Map surveyIdMap = OBJECT_MAPPER.readValue( + resultActions.andReturn().getResponse().getContentAsString(), new TypeReference<>() { + }); + return surveyIdMap.get("survey_id"); + } + +} diff --git a/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptor.java b/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptor.java index 99bca4dd..4604ad64 100644 --- a/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptor.java +++ b/auth/auth-interceptor/src/main/java/me/nalab/auth/interceptor/JwtDecryptInterceptor.java @@ -9,6 +9,13 @@ public class JwtDecryptInterceptor implements HandlerInterceptor { + private static final String[][] EXCLUDED_URI_LIST = new String[][] { + {"POST", "/v1/feedbacks"}, + {"GET", "/v1/feedbacks/bookmarks"} + }; + private static final String[][] EXCLUDED_URI_WITH_QUERY_STRING_LIST = new String[][] { + {"GET", "/v1/users"} + }; private final TargetIdGetPort targetIdGetPort; JwtDecryptInterceptor(TargetIdGetPort targetIdGetPort) { @@ -17,10 +24,10 @@ public class JwtDecryptInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - if(isPreflight(request)) { + if (isPreflight(request)) { return true; } - if(!isExcludedURI(request)) { + if (!isExcludedURI(request)) { String token = request.getHeader("Authorization"); throwIfCannotValidToken(token); Long targetId = targetIdGetPort.getTargetId(token.split(" ")[1]); @@ -34,12 +41,28 @@ private boolean isPreflight(HttpServletRequest request) { } private boolean isExcludedURI(HttpServletRequest httpServletRequest) { - return httpServletRequest.getMethod().equals("POST") && httpServletRequest.getRequestURI() - .contains("/v1/feedbacks"); + String httpMethod = httpServletRequest.getMethod(); + String requestURI = httpServletRequest.getRequestURI(); + String queryString = httpServletRequest.getQueryString(); + + for (String[] excludedURI : EXCLUDED_URI_LIST) { + if (excludedURI[0].equals(httpMethod) && excludedURI[1].equals(requestURI)) { + return true; + } + } + + for (String[] excludedURI : EXCLUDED_URI_WITH_QUERY_STRING_LIST) { + if (excludedURI[0].equals(httpMethod) && excludedURI[1].equals(requestURI) + && queryString.length() > 0) { + return true; + } + } + + return false; } private void throwIfCannotValidToken(String token) { - if(token == null) { + if (token == null) { throw new CannotValidMockTokenException(); } } diff --git a/auth/auth-mock/src/main/java/me/nalab/auth/mock/interceptor/MockAuthInterceptor.java b/auth/auth-mock/src/main/java/me/nalab/auth/mock/interceptor/MockAuthInterceptor.java index 88ccb6f7..3ce67db5 100644 --- a/auth/auth-mock/src/main/java/me/nalab/auth/mock/interceptor/MockAuthInterceptor.java +++ b/auth/auth-mock/src/main/java/me/nalab/auth/mock/interceptor/MockAuthInterceptor.java @@ -10,12 +10,19 @@ public class MockAuthInterceptor implements HandlerInterceptor { + private static final String[][] EXCLUDED_URI_LIST = new String[][] { + {"POST", "/v1/feedbacks"}, + {"GET", "/v1/feedbacks/bookmarks"} + }; + private static final String[][] EXCLUDED_URI_WITH_QUERY_STRING_LIST = new String[][] { + {"GET", "/v1/users"} + }; private String expectedToken = null; private Long expectedId = null; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - if(!isExcludedURI(request)) { + if (!isExcludedURI(request)) { String token = request.getHeader("Authorization"); throwIfCannotValidToken(token); request.setAttribute("logined", expectedId); @@ -24,12 +31,27 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } private boolean isExcludedURI(HttpServletRequest httpServletRequest) { - return httpServletRequest.getMethod().equals("POST") && httpServletRequest.getRequestURI() - .contains("/v1/feedbacks"); + String httpMethod = httpServletRequest.getMethod(); + String requestURI = httpServletRequest.getRequestURI(); + String queryString = httpServletRequest.getQueryString(); + + for (String[] excludedURI : EXCLUDED_URI_LIST) { + if (excludedURI[0].equals(httpMethod) && excludedURI[1].equals(requestURI)) { + return true; + } + } + for (String[] excludedURI : EXCLUDED_URI_WITH_QUERY_STRING_LIST) { + if (excludedURI[0].equals(httpMethod) && excludedURI[1].equals(requestURI) + && queryString.length() > 0) { + return true; + } + } + + return false; } private void throwIfCannotValidToken(String token) { - if(expectedToken == null || expectedId == null || !expectedToken.equals(token)) { + if (expectedToken == null || expectedId == null || !expectedToken.equals(token)) { throw new CannotValidMockTokenException(); } } @@ -40,4 +62,4 @@ void mockUserRegister(MockUserRegisterEvent mockUserRegisterEvent) { expectedId = mockUserRegisterEvent.getExpectedId(); } -} +} \ No newline at end of file diff --git a/auth/auth-mock/src/test/java/me/nalab/auth/mock/interceptor/MockAuthInterceptorTest.java b/auth/auth-mock/src/test/java/me/nalab/auth/mock/interceptor/MockAuthInterceptorTest.java index 32a171ea..b18b58da 100644 --- a/auth/auth-mock/src/test/java/me/nalab/auth/mock/interceptor/MockAuthInterceptorTest.java +++ b/auth/auth-mock/src/test/java/me/nalab/auth/mock/interceptor/MockAuthInterceptorTest.java @@ -1,8 +1,7 @@ package me.nalab.auth.mock.interceptor; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.Arguments.of; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.params.provider.Arguments.*; import java.util.stream.Stream; @@ -64,7 +63,7 @@ void FAIL_LOGIN(HttpMethod httpMethod, String url, String requestToken, String e static Stream successSources() { return Stream.of( of(HttpMethod.POST, "/v1/surveys", "token1", 1L) - , of(HttpMethod.GET, "/v1/users", "token2", 2L) + , of(HttpMethod.POST, "/v1/surveys", "token1", 2L) , of(HttpMethod.GET, "/v1/surveys-id", "token3", 3L) , of(HttpMethod.GET, "/v1/questions?survey-id=4", "token4", 4L) , of(HttpMethod.GET, "/v1/feedbacks?survey-id=5", "token5", 5L) diff --git a/coverage-exclude.luffy b/coverage-exclude.luffy index e2e92bc5..9aae8eda 100644 --- a/coverage-exclude.luffy +++ b/coverage-exclude.luffy @@ -52,4 +52,6 @@ exclude me/nalab/core/secure/cors/CorsConfig exclude me/nalab/core/secure/xss/config/XssConfig exclude me/nalab/core/secure/xss/json/JsonXssFilterException exclude me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController +exclude me/nalab/survey/web/adaptor/findtarget/* +exclude me/nalab/auth/mock/interceptor/* exclude me/nalab/survey/web/adaptor/updatetarget/* diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findtarget/TargetFindBySurveyIdUseCase.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findtarget/TargetFindBySurveyIdUseCase.java new file mode 100644 index 00000000..f8d2593a --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findtarget/TargetFindBySurveyIdUseCase.java @@ -0,0 +1,18 @@ +package me.nalab.survey.application.port.in.web.findtarget; + +import me.nalab.survey.application.common.survey.dto.TargetDto; + +/** + * SurveyID를 이용해 Target을 조회하는 Usecase + * 이 인터페이스를 이용해서 Target을 조회할 수 있음 + */ +public interface TargetFindBySurveyIdUseCase { + + /** + * + * @param surveyId 질문폼의 id + * @return 질문폼에 해당하는 타겟을 TargetDto 형식으로 반환해서 가져옴 + */ + TargetDto findTargetBySurveyId(Long surveyId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findtarget/TargetFindPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findtarget/TargetFindPort.java new file mode 100644 index 00000000..1a1f79fa --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findtarget/TargetFindPort.java @@ -0,0 +1,20 @@ +package me.nalab.survey.application.port.out.persistence.findtarget; + +import java.util.Optional; + +import me.nalab.survey.domain.target.Target; + +/** + * targetId를 받아 Target을 반환하는 인터페이스입니다. + */ +public interface TargetFindPort { + + /** + * targetId에 해당하는 Target을 조회합니다 + * + * @param targetId 조회할 Target의 ID + * @return Optional 만약, 어떠한 targetId도 없을 경우, Optional.empty() 를 반환해야 합니다 + */ + Optional findTargetById(Long targetId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findtarget/TargetIdFindPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findtarget/TargetIdFindPort.java new file mode 100644 index 00000000..23f732a2 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findtarget/TargetIdFindPort.java @@ -0,0 +1,17 @@ +package me.nalab.survey.application.port.out.persistence.findtarget; + +import java.util.Optional; + +/** + * surveyId를 받아 survey에 해당하는 target의 id를 반환하는 인터페이스입니다. + */ +public interface TargetIdFindPort { + + /** + * + * @param surveyId survey를 조회할 survey의 ID + * @return Optional 만약, 어떠한 targetId도 없을 경우, Optional.empty() + */ + Optional findTargetIdBySurveyId(Long surveyId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/findtarget/TargetFindBySurveyIdService.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/findtarget/TargetFindBySurveyIdService.java new file mode 100644 index 00000000..0f89b3ba --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/findtarget/TargetFindBySurveyIdService.java @@ -0,0 +1,35 @@ +package me.nalab.survey.application.service.findtarget; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import me.nalab.survey.application.common.survey.dto.TargetDto; +import me.nalab.survey.application.common.survey.mapper.TargetDtoMapper; +import me.nalab.survey.application.exception.SurveyDoesNotHasTargetException; +import me.nalab.survey.application.exception.TargetDoesNotExistException; +import me.nalab.survey.application.port.in.web.findtarget.TargetFindBySurveyIdUseCase; +import me.nalab.survey.application.port.out.persistence.findtarget.TargetFindPort; +import me.nalab.survey.application.port.out.persistence.findtarget.TargetIdFindPort; +import me.nalab.survey.domain.target.Target; + +@Service +@RequiredArgsConstructor +public class TargetFindBySurveyIdService implements TargetFindBySurveyIdUseCase { + + private final TargetFindPort targetFindPort; + private final TargetIdFindPort targetIdFindPort; + + @Override + @Transactional + public TargetDto findTargetBySurveyId(Long surveyId) { + Long targetId = targetIdFindPort.findTargetIdBySurveyId(surveyId).orElseThrow(() -> { + throw new SurveyDoesNotHasTargetException(surveyId); + }); + Target target = targetFindPort.findTargetById(targetId).orElseThrow(() -> { + throw new TargetDoesNotExistException(targetId); + }); + return TargetDtoMapper.toTargetDto(target); + } + +} diff --git a/survey/survey-application/src/main/java/module-info.java b/survey/survey-application/src/main/java/module-info.java index e943f6da..d8a26e9e 100644 --- a/survey/survey-application/src/main/java/module-info.java +++ b/survey/survey-application/src/main/java/module-info.java @@ -32,6 +32,9 @@ exports me.nalab.survey.application.port.out.persistence.target.update; exports me.nalab.survey.application.port.in.web.target.update; + exports me.nalab.survey.application.port.out.persistence.findtarget; + exports me.nalab.survey.application.port.in.web.findtarget; + requires luffy.survey.domain.main; requires luffy.core.id.generator.id.generator.starter.main; requires luffy.core.authorization.authorization.core.main; diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/service/findtarget/TargetFindBySurveyIdServiceTest.java b/survey/survey-application/src/test/java/me/nalab/survey/application/service/findtarget/TargetFindBySurveyIdServiceTest.java new file mode 100644 index 00000000..aefec5e4 --- /dev/null +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/service/findtarget/TargetFindBySurveyIdServiceTest.java @@ -0,0 +1,93 @@ +package me.nalab.survey.application.service.findtarget; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import me.nalab.survey.application.common.survey.dto.TargetDto; +import me.nalab.survey.application.common.survey.mapper.TargetDtoMapper; +import me.nalab.survey.application.exception.SurveyDoesNotHasTargetException; +import me.nalab.survey.application.exception.TargetDoesNotExistException; +import me.nalab.survey.application.port.out.persistence.findtarget.TargetFindPort; +import me.nalab.survey.application.port.out.persistence.findtarget.TargetIdFindPort; +import me.nalab.survey.domain.target.Target; + +@ExtendWith(SpringExtension.class) +class TargetFindBySurveyIdServiceTest { + + private TargetFindBySurveyIdService targetFindBySurveyIdService; + + @Mock + private TargetIdFindPort targetIdFindPort; + + @Mock + private TargetFindPort targetFindPort; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + targetFindBySurveyIdService = new TargetFindBySurveyIdService(targetFindPort, targetIdFindPort); + } + + @Test + void FIND_TARGET_BY_SURVEY_ID_SUCCESS_TEST() { + + Long targetId = 123L; + Long surveyId = 12L; + Target target = Target.builder() + .id(targetId) + .nickname("sujin") + .build(); + + when(targetIdFindPort.findTargetIdBySurveyId(surveyId)).thenReturn(Optional.of(targetId)); + when(targetFindPort.findTargetById(targetId)).thenReturn(Optional.of(target)); + TargetDto targetDto = targetFindBySurveyIdService.findTargetBySurveyId(surveyId); + + assertEquals(TargetDtoMapper.toTarget(targetDto), target); + } + + @Test + void FIND_TARGET_BY_SURVEY_ID_FAILED_TEST_WITH_NO_TARGET_ID() { + + Long targetId = 123L; + Long surveyId = 12L; + Target target = Target.builder() + .id(targetId) + .nickname("sujin") + .build(); + + when(targetIdFindPort.findTargetIdBySurveyId(surveyId)).thenReturn(Optional.empty()); + when(targetFindPort.findTargetById(targetId)).thenReturn(Optional.of(target)); + + assertThrows(SurveyDoesNotHasTargetException.class, + () -> targetFindBySurveyIdService.findTargetBySurveyId(surveyId)); + + } + + @Test + void FIND_TARGET_BY_SURVEY_ID_FAILED_TEST_WITH_NO_TARGET() { + + Long targetId = 123L; + Long surveyId = 12L; + Target target = Target.builder() + .id(targetId) + .nickname("sujin") + .build(); + + when(targetIdFindPort.findTargetIdBySurveyId(surveyId)).thenReturn(Optional.of(targetId)); + when(targetFindPort.findTargetById(targetId)).thenReturn(Optional.empty()); + + assertThrows(TargetDoesNotExistException.class, + () -> targetFindBySurveyIdService.findTargetBySurveyId(surveyId)); + + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/TargetFindAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/TargetFindAdaptor.java new file mode 100644 index 00000000..d01f9d91 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/TargetFindAdaptor.java @@ -0,0 +1,36 @@ +package me.nalab.survey.jpa.adaptor.findtarget; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.application.port.out.persistence.findtarget.TargetFindPort; +import me.nalab.survey.domain.target.Target; +import me.nalab.survey.jpa.adaptor.common.mapper.TargetEntityMapper; +import me.nalab.survey.jpa.adaptor.findtarget.repository.TargetFindJpaRepository; + +@Repository("findtarget.TargetFindAdaptor") +public class TargetFindAdaptor implements TargetFindPort { + + private final TargetFindJpaRepository targetFindJpaRepository; + + @Autowired + TargetFindAdaptor( + @Qualifier("findtarget.TargetFindJpaRepository") TargetFindJpaRepository targetFindJpaRepository) { + this.targetFindJpaRepository = targetFindJpaRepository; + } + + @Override + public Optional findTargetById(Long targetId) { + Optional targetEntity = targetFindJpaRepository.findById(targetId); + if (targetEntity.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(TargetEntityMapper.toTarget(targetEntity.get())); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/TargetIdFindAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/TargetIdFindAdaptor.java new file mode 100644 index 00000000..9004e751 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/TargetIdFindAdaptor.java @@ -0,0 +1,35 @@ +package me.nalab.survey.jpa.adaptor.findtarget; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.survey.SurveyEntity; +import me.nalab.survey.application.port.out.persistence.findtarget.TargetIdFindPort; +import me.nalab.survey.jpa.adaptor.findtarget.repository.TargetIdFindJpaRepository; + +@Repository("findtarget.TargetIdFindAdaptor") +public class TargetIdFindAdaptor implements TargetIdFindPort { + + private final TargetIdFindJpaRepository targetIdFindJpaRepository; + + @Autowired + TargetIdFindAdaptor( + @Qualifier("findtarget.TargetIdFindJpaRepository") TargetIdFindJpaRepository targetIdFindJpaRepository) { + this.targetIdFindJpaRepository = targetIdFindJpaRepository; + } + + @Override + public Optional findTargetIdBySurveyId(Long surveyId) { + Optional surveyEntity = targetIdFindJpaRepository.findById(surveyId); + if (surveyEntity.isEmpty()) { + return Optional.empty(); + } + + Long targetId = surveyEntity.get().getTargetId(); + return Optional.of(targetId); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/repository/TargetFindJpaRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/repository/TargetFindJpaRepository.java new file mode 100644 index 00000000..0cee5878 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/repository/TargetFindJpaRepository.java @@ -0,0 +1,11 @@ +package me.nalab.survey.jpa.adaptor.findtarget.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.target.TargetEntity; + +@Repository("findtarget.TargetFindJpaRepository") +public interface TargetFindJpaRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/repository/TargetIdFindJpaRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/repository/TargetIdFindJpaRepository.java new file mode 100644 index 00000000..7b9772c8 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findtarget/repository/TargetIdFindJpaRepository.java @@ -0,0 +1,11 @@ +package me.nalab.survey.jpa.adaptor.findtarget.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.survey.SurveyEntity; + +@Repository("findtarget.TargetIdFindJpaRepository") +public interface TargetIdFindJpaRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TargetFindAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TargetFindAdaptorTest.java new file mode 100644 index 00000000..c9d2606a --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TargetFindAdaptorTest.java @@ -0,0 +1,71 @@ +package me.nalab.survey.jpa.adaptor.findtarget; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Optional; + +import javax.persistence.EntityManager; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.domain.target.Target; +import me.nalab.survey.jpa.adaptor.common.mapper.TargetEntityMapper; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = TargetFindAdaptor.class) +@TestPropertySource("classpath:h2.properties") +class TargetFindAdaptorTest { + + @Autowired + private TargetFindAdaptor targetFindAdaptor; + + @Autowired + private TestTargetFindJpaRepository targetFindJpaRepository; + + @Autowired + private EntityManager entityManager; + + @Test + @DisplayName("targetId로 target 조회 성공테스트") + void FIND_TARGET_BY_TARGET_ID_SUCCESS_TEST() { + + Long targetId = 1L; + TargetEntity targetEntity = TargetEntity.builder() + .id(targetId) + .nickname("nickname") + .position("developer") + .build(); + Target target = TargetEntityMapper.toTarget(targetEntity); + + targetFindJpaRepository.saveAndFlush(targetEntity); + entityManager.clear(); + + Optional resultTarget = targetFindAdaptor.findTargetById(targetId); + + assertTrue(resultTarget.isPresent()); + assertEquals(targetId, resultTarget.get().getId()); + } + + @Test + @DisplayName("targetId로 target 조회 실패테스트") + void FIND_TARGET_BY_TARGET_ID_FAIL_TEST() { + + Long targetId = 1L; + + Optional resultTarget = targetFindAdaptor.findTargetById(targetId); + + assertTrue(resultTarget.isEmpty()); + + } + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TargetIdFindAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TargetIdFindAdaptorTest.java new file mode 100644 index 00000000..e19b5191 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TargetIdFindAdaptorTest.java @@ -0,0 +1,71 @@ +package me.nalab.survey.jpa.adaptor.findtarget; + +import static me.nalab.survey.jpa.adaptor.RandomSurveyFixture.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Optional; + +import javax.persistence.EntityManager; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.survey.SurveyEntity; +import me.nalab.survey.domain.survey.Survey; +import me.nalab.survey.jpa.adaptor.common.mapper.SurveyEntityMapper; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = TargetIdFindAdaptor.class) +@TestPropertySource("classpath:h2.properties") +class TargetIdFindAdaptorTest { + + @Autowired + private TargetIdFindAdaptor targetIdFindAdaptor; + + @Autowired + private TestTargetIdFindJpaRepository targetIdFindJpaRepository; + + @Autowired + private EntityManager entityManager; + + @Test + @DisplayName("surveyId로 targetId 조회 성공테스트") + void FIND_TARGET_ID_BY_SURVEY_ID_SUCCESS_TEST() { + + Long targetId = 1L; + Survey randomSurvey = createRandomSurvey(); + Long surveyId = randomSurvey.getId(); + SurveyEntity surveyEntity = SurveyEntityMapper.toSurveyEntity(targetId, randomSurvey); + + targetIdFindJpaRepository.saveAndFlush(surveyEntity); + entityManager.clear(); + + Optional targetIdBySurveyId = targetIdFindAdaptor.findTargetIdBySurveyId(surveyId); + + assertTrue(targetIdBySurveyId.isPresent()); + assertEquals(targetId, targetIdBySurveyId.get()); + + } + + @Test + @DisplayName("surveyId로 targetId 조회 실패테스트") + void FIND_TARGET_ID_BY_SURVEY_ID_FAIL_TEST() { + + Survey randomSurvey = createRandomSurvey(); + Long surveyId = randomSurvey.getId(); + + Optional targetIdBySurveyId = targetIdFindAdaptor.findTargetIdBySurveyId(surveyId); + + assertTrue(targetIdBySurveyId.isEmpty()); + + } + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TestTargetFindJpaRepository.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TestTargetFindJpaRepository.java new file mode 100644 index 00000000..6aacd467 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TestTargetFindJpaRepository.java @@ -0,0 +1,9 @@ +package me.nalab.survey.jpa.adaptor.findtarget; + +import org.springframework.data.jpa.repository.JpaRepository; + +import me.nalab.core.data.target.TargetEntity; + +public interface TestTargetFindJpaRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TestTargetIdFindJpaRepository.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TestTargetIdFindJpaRepository.java new file mode 100644 index 00000000..08487b0c --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findtarget/TestTargetIdFindJpaRepository.java @@ -0,0 +1,9 @@ +package me.nalab.survey.jpa.adaptor.findtarget; + +import org.springframework.data.jpa.repository.JpaRepository; + +import me.nalab.core.data.survey.SurveyEntity; + +public interface TestTargetIdFindJpaRepository extends JpaRepository { + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/TargetFindBySurveyIdController.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/TargetFindBySurveyIdController.java new file mode 100644 index 00000000..c3a8e267 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/TargetFindBySurveyIdController.java @@ -0,0 +1,29 @@ +package me.nalab.survey.web.adaptor.findtarget; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import me.nalab.survey.application.common.survey.dto.TargetDto; +import me.nalab.survey.application.port.in.web.findtarget.TargetFindBySurveyIdUseCase; +import me.nalab.survey.web.adaptor.findtarget.response.TargetResponse; + +@RestController +@RequestMapping("/v1") +@RequiredArgsConstructor +public class TargetFindBySurveyIdController { + + private final TargetFindBySurveyIdUseCase targetFindBySurveyIdUseCase; + + @GetMapping("/users") + @ResponseStatus(HttpStatus.OK) + public TargetResponse findTargetBySurveyId(@RequestParam("survey-id") Long surveyId) { + TargetDto targetDto = targetFindBySurveyIdUseCase.findTargetBySurveyId(surveyId); + return TargetResponseMapper.toTargetResponse(targetDto); + } + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/TargetResponseMapper.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/TargetResponseMapper.java new file mode 100644 index 00000000..b287fff6 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/TargetResponseMapper.java @@ -0,0 +1,20 @@ +package me.nalab.survey.web.adaptor.findtarget; + +import me.nalab.survey.application.common.survey.dto.TargetDto; +import me.nalab.survey.web.adaptor.findtarget.response.TargetResponse; + +class TargetResponseMapper { + + private TargetResponseMapper() { + throw new UnsupportedOperationException("Cannot invoke constructor \"TargetResponseMapper()\""); + } + + static TargetResponse toTargetResponse(TargetDto targetDto) { + return TargetResponse.builder() + .targetId(targetDto.getId()) + .nickname(targetDto.getNickname()) + .position(targetDto.getPosition()) + .build(); + } + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/response/TargetResponse.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/response/TargetResponse.java new file mode 100644 index 00000000..0e9bd793 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findtarget/response/TargetResponse.java @@ -0,0 +1,25 @@ +package me.nalab.survey.web.adaptor.findtarget.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +@EqualsAndHashCode +public class TargetResponse { + + @JsonProperty("target_id") + private final long targetId; + + @JsonProperty("nickname") + private final String nickname; + + @JsonProperty("position") + private final String position; + +} From 52164e8ad43d63444814600c8a4930491aa67626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:40:28 +0900 Subject: [PATCH 12/17] =?UTF-8?q?[test]=20:=20e2e=20test=20v1=5F5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#332)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- support/e2e/v1_5_target.hurl | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 support/e2e/v1_5_target.hurl diff --git a/support/e2e/v1_5_target.hurl b/support/e2e/v1_5_target.hurl new file mode 100644 index 00000000..e0a9edff --- /dev/null +++ b/support/e2e/v1_5_target.hurl @@ -0,0 +1,88 @@ +POST http://nalab-server:8080/v1/oauth/default # 회원가입을 한다 +{ + "nickname": "sujin", + "email": "sujin@email.com" +} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.access_token" exists +jsonpath "$.token_type" exists + +[Captures] +token_type: jsonpath "$.token_type" +auth_token: jsonpath "$.access_token" + +########## + +POST http://nalab-server:8080/v1/surveys # 발급받은 토큰으로 survey를 생성한다 +Authorization: {{ token_type }} {{ auth_token }} +{ + "question_count": 2, + "question": [ + { + "type": "choice", + "form_type": "tendency", + "title": "저는 UI, UI, GUI 중에 어떤 분야를 가장 잘하는 것 같나요?", + "choices": [ + { + "content": "UI", + "order": 1 + }, + { + "content": "UX", + "order": 2 + }, + { + "content": "GUI", + "order": 3 + } + ], + "max_selectable_count": 1, + "order": 1 + }, + { + "type": "short", + "form_type": "strength", + "title": "저는 UX, UI, GUI 중에 어떤 분야에 더 강점이 있나요?", + "order": 2 + } + ] +} + +HTTP 201 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.survey_id" exists + +[Captures] +survey_id: jsonpath "$.survey_id" + +########## + +PATCH http://nalab-server:8080/v1/users # 로그인된 타겟의 정보를 수정한다 +Authorization: {{ token_type }} {{ auth_token }} +{ + "position": "BE developer" +} + +HTTP 200 +[Asserts] + +########## + +GET http://nalab-server:8080/v1/users # survey-id를 이용해 수정된 타겟의 정보를 확인한다 + +[QueryStringParams] +survey-id: {{ survey_id }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.target_id" exists +jsonpath "$.nickname" matches "sujin" +jsonpath "$.position" matches "BE developer" From b58784cda46828e41e53d7d7b477eae449090c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Wed, 12 Jul 2023 22:03:46 +0900 Subject: [PATCH 13/17] =?UTF-8?q?[feat]=20:=20`=EC=A7=88=EB=AC=B8=ED=8F=BC?= =?UTF-8?q?=EC=9D=98=20=ED=83=80=EC=9E=85=EC=97=90=20=ED=95=B4=EB=8B=B9?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C`=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20(#334)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AbstractFeedbackTestSupporter.java | 9 + .../feedback/FeedbackAcceptanceValidator.java | 49 +++++ .../FeedbackFindByTypeAcceptanceTest.java | 207 ++++++++++++++++++ coverage-exclude.luffy | 1 + .../formtype/FeedbackFindByTypeUseCase.java | 29 +++ .../formtype/FeedbackFindPort.java | 20 ++ .../findfeedback/formtype/SurveyFindPort.java | 19 ++ .../formtype/TargetIdFindPort.java | 17 ++ .../formtype/FeedbackFindByTypeService.java | 80 +++++++ .../src/main/java/module-info.java | 2 + .../FeedbackFindByTypeServiceTest.java | 192 ++++++++++++++++ .../common/mapper/SurveyEntityMapper.java | 8 +- .../formtype/FeedbackFindAdaptor.java | 35 +++ .../formtype/SurveyFindAdaptor.java | 35 +++ .../formtype/TargetIdFindAdaptor.java | 33 +++ .../repository/FeedbackFindRepository.java | 18 ++ .../repository/SurveyFindRepository.java | 11 + .../formtype/FeedbackFindAdaptorTest.java | 97 ++++++++ .../formtype/SurveyFindAdaptorTest.java | 83 +++++++ .../formtype/TargetIdFindAdaptorTest.java | 83 +++++++ .../formtype/TestFeedbackFindRepository.java | 9 + .../formtype/TestSurveyFindRepository.java | 9 + .../formtype/TestTargetRepository.java | 9 + .../FeedbackFindByFormTypeController.java | 37 ++++ .../QuestionFeedbackResponseMapper.java | 124 +++++++++++ .../response/QuestionFeedbackResponse.java | 20 ++ .../AbstractFormQuestionResponse.java | 26 +++ .../ChoiceFormQuestionResponse.java | 19 ++ .../response/formquestion/ChoiceResponse.java | 28 +++ .../ShortFormQuestionResponse.java | 19 ++ .../response/formquestion/ShortResponse.java | 26 +++ 31 files changed, 1350 insertions(+), 4 deletions(-) create mode 100644 api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/find/FeedbackFindByTypeAcceptanceTest.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findfeedback/formtype/FeedbackFindByTypeUseCase.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/FeedbackFindPort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/SurveyFindPort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/TargetIdFindPort.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/service/findfeedback/formtype/FeedbackFindByTypeService.java create mode 100644 survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/formtype/FeedbackFindByTypeServiceTest.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/FeedbackFindAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/SurveyFindAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TargetIdFindAdaptor.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/repository/FeedbackFindRepository.java create mode 100644 survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/repository/SurveyFindRepository.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/FeedbackFindAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/SurveyFindAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TargetIdFindAdaptorTest.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestFeedbackFindRepository.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestSurveyFindRepository.java create mode 100644 survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestTargetRepository.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/FeedbackFindByFormTypeController.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/QuestionFeedbackResponseMapper.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/QuestionFeedbackResponse.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/AbstractFormQuestionResponse.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ChoiceFormQuestionResponse.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ChoiceResponse.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ShortFormQuestionResponse.java create mode 100644 survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ShortResponse.java diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java index 0a3c967e..fffa1e18 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java @@ -87,6 +87,15 @@ protected ResultActions replaceBookmark(String token, String form_question_feedb ); } + protected ResultActions findFeedbackByType(Long surveyId, String formType) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get("/v2/feedbacks?survey-id=" + surveyId) + .queryParam("form-type", formType) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + ); + } + @Autowired final void setMockMvc(MockMvc mockMvc) { this.mockMvc = mockMvc; diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java index 0797a721..4d4d6690 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java @@ -130,4 +130,53 @@ public static void assertIsBookmarkReplaced(ResultActions resultActions) throws resultActions.andExpectAll(status().isOk()); } + public static void assertIsFeedbackFoundByTendency(ResultActions resultActions) throws Exception { + resultActions.andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON), + + jsonPath("$.question_feedback").isArray(), + jsonPath("$.question_feedback").isNotEmpty(), + + jsonPath("$.question_feedback.[0].question_id").isString(), + jsonPath("$.question_feedback.[0].order").isNumber(), + jsonPath("$.question_feedback.[0].type").isString(), + jsonPath("$.question_feedback.[0].form_type").value("tendency"), + jsonPath("$.question_feedback.[0].title").isString(), + jsonPath("$.question_feedback.[0].choices").isArray() + ); + } + + public static void assertIsFeedbackFoundByCustom(ResultActions resultActions) throws Exception { + resultActions.andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON), + + jsonPath("$.question_feedback").isArray(), + jsonPath("$.question_feedback").isNotEmpty(), + + jsonPath("$.question_feedback.[0].question_id").isString(), + jsonPath("$.question_feedback.[0].order").isNumber(), + jsonPath("$.question_feedback.[0].type").isString(), + jsonPath("$.question_feedback.[0].form_type").value("custom"), + jsonPath("$.question_feedback.[0].title").isString() + ); + } + + public static void assertIsFeedbackFoundByFormTypeWithNoFeedback(ResultActions resultActions) throws Exception { + resultActions.andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON), + + jsonPath("$.question_feedback").isArray(), + jsonPath("$.question_feedback").isNotEmpty(), + + jsonPath("$.question_feedback.[0].question_id").isString(), + jsonPath("$.question_feedback.[0].order").isNumber(), + jsonPath("$.question_feedback.[0].type").isString(), + jsonPath("$.question_feedback.[0].form_type").isString(), + jsonPath("$.question_feedback.[0].title").isString() + ); + } + } diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/find/FeedbackFindByTypeAcceptanceTest.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/find/FeedbackFindByTypeAcceptanceTest.java new file mode 100644 index 00000000..289c8dc5 --- /dev/null +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/find/FeedbackFindByTypeAcceptanceTest.java @@ -0,0 +1,207 @@ +package me.nalab.luffy.api.acceptance.test.feedback.find; + +import static me.nalab.luffy.api.acceptance.test.feedback.FeedbackAcceptanceValidator.*; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.ResultActions; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import me.nalab.auth.mock.api.MockUserRegisterEvent; +import me.nalab.luffy.api.acceptance.test.TargetInitializer; +import me.nalab.luffy.api.acceptance.test.feedback.AbstractFeedbackTestSupporter; +import me.nalab.luffy.api.acceptance.test.feedback.create.request.AbstractQuestionFeedbackRequest; +import me.nalab.luffy.api.acceptance.test.feedback.create.request.ChoiceQuestionFeedbackRequest; +import me.nalab.luffy.api.acceptance.test.feedback.create.request.FeedbackCreateRequest; +import me.nalab.luffy.api.acceptance.test.feedback.create.request.ReviewerRequest; +import me.nalab.luffy.api.acceptance.test.feedback.create.request.ShortQuestionFeedbackRequest; +import me.nalab.luffy.api.acceptance.test.feedback.create.response.ChoiceFormQuestionResponse; +import me.nalab.luffy.api.acceptance.test.feedback.create.response.ShortFormQuestionResponse; +import me.nalab.luffy.api.acceptance.test.feedback.create.response.SurveyFindResponse; +import me.nalab.luffy.api.acceptance.test.survey.RequestSample; + +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource("classpath:h2.properties") +@ComponentScan("me.nalab") +@EnableJpaRepositories(basePackages = {"me.nalab"}) +@EntityScan(basePackages = {"me.nalab"}) +class FeedbackFindByTypeAcceptanceTest extends AbstractFeedbackTestSupporter { + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + @Autowired + private TargetInitializer targetInitializer; + + private static final ObjectMapper OBJECT_MAPPER; + + static { + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + } + + @Test + @DisplayName("타입에 해당하는 피드백 조회 성공 인수테스트 - formType이 tendency일 때") + void FIND_FEEDBACK_BY_FORM_TYPE_TENDENCY() throws Exception { + // given + Long targetId = targetInitializer.saveTargetAndGetId("hello world", Instant.now()); + String token = "mock token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedId(targetId) + .expectedToken(token) + .build()); + Long surveyId = createAndGetSurveyId(token, RequestSample.DEFAULT_JSON); + SurveyFindResponse surveyFindResponse = getSurveyFindResponse(surveyId); + FeedbackCreateRequest feedbackCreateRequest = getFeedbackCreateRequest(surveyFindResponse, true, "developer"); + createFeedback(surveyId, OBJECT_MAPPER.writeValueAsString(feedbackCreateRequest)); + + // when + ResultActions resultActions = findFeedbackByType(surveyId, "tendency"); + + // then + assertIsFeedbackFoundByTendency(resultActions); + + } + + @Test + @DisplayName("타입에 해당하는 피드백 조회 성공 인수테스트 - formType이 custom일 때") + void FIND_FEEDBACK_BY_FORM_TYPE_CUSTOM() throws Exception { + // given + Long targetId = targetInitializer.saveTargetAndGetId("hello world", Instant.now()); + String token = "mock token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedId(targetId) + .expectedToken(token) + .build()); + Long surveyId = createAndGetSurveyId(token, RequestSample.DEFAULT_JSON); + SurveyFindResponse surveyFindResponse = getSurveyFindResponse(surveyId); + FeedbackCreateRequest feedbackCreateRequest = getFeedbackCreateRequest(surveyFindResponse, true, "developer"); + createFeedback(surveyId, OBJECT_MAPPER.writeValueAsString(feedbackCreateRequest)); + + // when + ResultActions resultActions = findFeedbackByType(surveyId, "custom"); + + // then + assertIsFeedbackFoundByCustom(resultActions); + + } + + @Test + @DisplayName("타입에 해당하는 피드백 조회 성공 인수테스트 - 피드백이 없을때") + void FIND_FEEDBACK_BY_TYPE_TENDENCY_WITH_NO_FEEDBACK() throws Exception { + // given + Long targetId = targetInitializer.saveTargetAndGetId("hello world", Instant.now()); + String token = "mock token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedId(targetId) + .expectedToken(token) + .build()); + Long surveyId = createAndGetSurveyId(token, RequestSample.DEFAULT_JSON); + + // when + ResultActions resultActions = findFeedbackByType(surveyId, "tendency"); + + // then + assertIsFeedbackFoundByFormTypeWithNoFeedback(resultActions); + + } + + @Test + @DisplayName("타입에 해당하는 피드백 조회 성공 인수테스트 - 피드백이 없을때") + void FIND_FEEDBACK_BY_TYPE_CUSTOM_WITH_NO_FEEDBACK() throws Exception { + // given + Long targetId = targetInitializer.saveTargetAndGetId("hello world", Instant.now()); + String token = "mock token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedId(targetId) + .expectedToken(token) + .build()); + Long surveyId = createAndGetSurveyId(token, RequestSample.DEFAULT_JSON); + + // when + ResultActions resultActions = findFeedbackByType(surveyId, "custom"); + + // then + assertIsFeedbackFoundByFormTypeWithNoFeedback(resultActions); + + } + + private Long createAndGetSurveyId(String token, String content) throws Exception { + ResultActions resultActions = createSurvey(token, content); + Map surveyIdMap = OBJECT_MAPPER.readValue( + resultActions.andReturn().getResponse().getContentAsString(), new TypeReference<>() { + }); + return surveyIdMap.get("survey_id"); + } + + private SurveyFindResponse getSurveyFindResponse(Long surveyId) throws Exception { + ResultActions resultActions = findSurvey(surveyId); + return OBJECT_MAPPER.readValue(resultActions.andReturn().getResponse().getContentAsString(), + SurveyFindResponse.class); + } + + private static FeedbackCreateRequest getFeedbackCreateRequest(SurveyFindResponse surveyFindResponse, + boolean collaborationExperience, String position) { + return FeedbackCreateRequest.builder() + .reviewerRequest(getReviewerRequest(collaborationExperience, position)) + .abstractQuestionFeedbackRequests(getQuestionFeedbackRequestList(surveyFindResponse)) + .build(); + } + + private static ReviewerRequest getReviewerRequest(boolean collaborationExperience, String position) { + return ReviewerRequest.builder() + .collaborationExperience(collaborationExperience) + .position(position) + .build(); + } + + private static List getQuestionFeedbackRequestList( + SurveyFindResponse surveyFindResponse) { + + return surveyFindResponse.getQuestion().stream() + .map(q -> { + if (q instanceof ShortFormQuestionResponse) { + return getShortQuestionFeedbackRequest((ShortFormQuestionResponse)q); + } + return getChoiceQuestionFeedbackRequest((ChoiceFormQuestionResponse)q); + }) + .collect(Collectors.toList()); + } + + private static ShortQuestionFeedbackRequest getShortQuestionFeedbackRequest( + ShortFormQuestionResponse shortFormQuestionResponse) { + return ShortQuestionFeedbackRequest.builder() + .questionId(shortFormQuestionResponse.getQuestionId()) + .replyList(List.of("mocking", "words", "hello!")) + .type("short") + .build(); + } + + private static ChoiceQuestionFeedbackRequest getChoiceQuestionFeedbackRequest( + ChoiceFormQuestionResponse choiceFormQuestionResponse) { + return ChoiceQuestionFeedbackRequest.builder() + .type("choice") + .questionId(choiceFormQuestionResponse.getQuestionId()) + .choiceList(List.of(choiceFormQuestionResponse.getChoices().get(0).getChoiceId())) + .build(); + } + +} diff --git a/coverage-exclude.luffy b/coverage-exclude.luffy index 9aae8eda..831c14b3 100644 --- a/coverage-exclude.luffy +++ b/coverage-exclude.luffy @@ -55,3 +55,4 @@ exclude me/nalab/survey/web/adaptor/bookmark/BookmarkReplaceController exclude me/nalab/survey/web/adaptor/findtarget/* exclude me/nalab/auth/mock/interceptor/* exclude me/nalab/survey/web/adaptor/updatetarget/* +exclude me/nalab/survey/web/adaptor/findfeedback/formtype/* \ No newline at end of file diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findfeedback/formtype/FeedbackFindByTypeUseCase.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findfeedback/formtype/FeedbackFindByTypeUseCase.java new file mode 100644 index 00000000..0c1cb893 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findfeedback/formtype/FeedbackFindByTypeUseCase.java @@ -0,0 +1,29 @@ +package me.nalab.survey.application.port.in.web.findfeedback.formtype; + +import java.util.List; + +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; +import me.nalab.survey.application.common.survey.dto.FormQuestionDtoable; + +/** + * type으로 Feedback을 찾는 UseCase 입니다. + */ +public interface FeedbackFindByTypeUseCase { + + /** + * surveyId를 입력으로 받아, surveyId에 해당하는 모든 FeedbackDto를 반환합니다. + * + * @param surveyId 질문폼에 해당하는 id + * @return 해당하는 모든 FeedbackDto 만약, 어떠한 FeedbackDto도 없다면, 빈 List를 반환합니다. + */ + List findFeedbackBySurveyId(Long surveyId); + + /** + * + * @param surveyId survey의 id + * @param formType + * @return 질문 폼의 질문 중 formType에 해당하는 FormQuestionDtoable을 List의 형태로 반환합니다. + */ + List formQuestionMatchingWithType(Long surveyId, String formType); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/FeedbackFindPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/FeedbackFindPort.java new file mode 100644 index 00000000..918b7d37 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/FeedbackFindPort.java @@ -0,0 +1,20 @@ +package me.nalab.survey.application.port.out.persistence.findfeedback.formtype; + +import java.util.List; + +import me.nalab.survey.domain.feedback.Feedback; + +/** + * Feedback을 찾는 인터페이스입니다. + */ +public interface FeedbackFindPort { + + /** + * surveyId를 입력으로 받아, surveyId에 해당하는 모든 Feedback을 반환합니다. + * + * @param surveyId 질문폼에 해당하는 id + * @return 해당하는 모든 Feedback 만약, 어떠한 FeedbackDto도 없다면, 빈 List를 반환합니다. + */ + List findFeedbackBySurveyId(Long surveyId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/SurveyFindPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/SurveyFindPort.java new file mode 100644 index 00000000..6088e480 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/SurveyFindPort.java @@ -0,0 +1,19 @@ +package me.nalab.survey.application.port.out.persistence.findfeedback.formtype; + +import java.util.Optional; + +import me.nalab.survey.domain.survey.Survey; + +/** + * Survey를 찾는 인터페이스입니다. + */ +public interface SurveyFindPort { + + /** + * + * @param surveyId surveyId 질문폼에 해당하는 id + * @return Optional 만약, survey가 존재하지 않으면 Optional.empty() 를 반환합니다. + */ + Optional findSurveyById(Long surveyId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/TargetIdFindPort.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/TargetIdFindPort.java new file mode 100644 index 00000000..5cac252a --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/out/persistence/findfeedback/formtype/TargetIdFindPort.java @@ -0,0 +1,17 @@ +package me.nalab.survey.application.port.out.persistence.findfeedback.formtype; + +import java.util.Optional; + +/** + * surveyId를 받아 survey에 해당하는 target의 id를 반환하는 인터페이스입니다. + */ +public interface TargetIdFindPort { + + /** + * + * @param surveyId survey를 조회할 survey의 ID + * @return Optional 만약, 어떠한 targetId도 없을 경우, Optional.empty() + */ + Optional findTargetIdBySurveyId(Long surveyId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/findfeedback/formtype/FeedbackFindByTypeService.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/findfeedback/formtype/FeedbackFindByTypeService.java new file mode 100644 index 00000000..41db8704 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/findfeedback/formtype/FeedbackFindByTypeService.java @@ -0,0 +1,80 @@ +package me.nalab.survey.application.service.findfeedback.formtype; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; +import me.nalab.survey.application.common.feedback.mapper.FeedbackDtoMapper; +import me.nalab.survey.application.common.survey.dto.ChoiceFormQuestionDto; +import me.nalab.survey.application.common.survey.dto.FormQuestionDtoable; +import me.nalab.survey.application.common.survey.dto.ShortFormQuestionDto; +import me.nalab.survey.application.common.survey.dto.SurveyDto; +import me.nalab.survey.application.common.survey.mapper.SurveyDtoMapper; +import me.nalab.survey.application.exception.SurveyDoesNotExistException; +import me.nalab.survey.application.exception.SurveyDoesNotHasTargetException; +import me.nalab.survey.application.port.in.web.findfeedback.formtype.FeedbackFindByTypeUseCase; +import me.nalab.survey.application.port.out.persistence.findfeedback.formtype.FeedbackFindPort; +import me.nalab.survey.application.port.out.persistence.findfeedback.formtype.SurveyFindPort; +import me.nalab.survey.application.port.out.persistence.findfeedback.formtype.TargetIdFindPort; +import me.nalab.survey.domain.feedback.Feedback; +import me.nalab.survey.domain.survey.Survey; + +@Service +@RequiredArgsConstructor +public class FeedbackFindByTypeService implements FeedbackFindByTypeUseCase { + + private final SurveyFindPort surveyFindPort; + private final FeedbackFindPort feedbackFindPort; + private final TargetIdFindPort targetIdFindPort; + + @Transactional(readOnly = true) + @Override + public List findFeedbackBySurveyId(Long surveyId) { + List feedbackList = feedbackFindPort.findFeedbackBySurveyId(surveyId); + return feedbackList.stream() + .map(FeedbackDtoMapper::toDto) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + @Override + public List formQuestionMatchingWithType(Long surveyId, String formType) { + SurveyDto surveyDto = findSurvey(surveyId); + List formQuestionDtoableList = new ArrayList<>(); + surveyDto.getFormQuestionDtoableList() + .forEach(q -> + validateFormType(formType, formQuestionDtoableList, q) + ); + return formQuestionDtoableList; + } + + private SurveyDto findSurvey(Long surveyId) { + Survey survey = surveyFindPort.findSurveyById(surveyId).orElseThrow( + () -> new SurveyDoesNotExistException(surveyId) + ); + Long targetId = targetIdFindPort.findTargetIdBySurveyId(surveyId).orElseThrow( + () -> new SurveyDoesNotHasTargetException(surveyId) + ); + return SurveyDtoMapper.toSurveyDto(targetId, survey); + } + + private static void validateFormType(String formType, List formQuestionDtoableList, + FormQuestionDtoable q) { + if (q instanceof ChoiceFormQuestionDto && ((ChoiceFormQuestionDto)q).getChoiceFormQuestionDtoType() + .toString() + .toLowerCase() + .equals(formType) + || q instanceof ShortFormQuestionDto && ((ShortFormQuestionDto)q).getShortFormQuestionDtoType() + .toString() + .toLowerCase() + .equals(formType)) { + formQuestionDtoableList.add(q); + } + } + +} diff --git a/survey/survey-application/src/main/java/module-info.java b/survey/survey-application/src/main/java/module-info.java index d8a26e9e..0528180f 100644 --- a/survey/survey-application/src/main/java/module-info.java +++ b/survey/survey-application/src/main/java/module-info.java @@ -34,6 +34,8 @@ exports me.nalab.survey.application.port.out.persistence.findtarget; exports me.nalab.survey.application.port.in.web.findtarget; + exports me.nalab.survey.application.port.in.web.findfeedback.formtype; + exports me.nalab.survey.application.port.out.persistence.findfeedback.formtype; requires luffy.survey.domain.main; requires luffy.core.id.generator.id.generator.starter.main; diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/formtype/FeedbackFindByTypeServiceTest.java b/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/formtype/FeedbackFindByTypeServiceTest.java new file mode 100644 index 00000000..783063d7 --- /dev/null +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/formtype/FeedbackFindByTypeServiceTest.java @@ -0,0 +1,192 @@ +package me.nalab.survey.application.service.findfeedback.formtype; + +import static me.nalab.survey.application.RandomSurveyDtoFixture.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import me.nalab.survey.application.RandomFeedbackDtoFixture; +import me.nalab.survey.application.RandomSurveyDtoFixture; +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; +import me.nalab.survey.application.common.feedback.mapper.FeedbackDtoMapper; +import me.nalab.survey.application.common.survey.dto.FormQuestionDtoable; +import me.nalab.survey.application.common.survey.dto.SurveyDto; +import me.nalab.survey.application.common.survey.mapper.SurveyDtoMapper; +import me.nalab.survey.application.exception.SurveyDoesNotExistException; +import me.nalab.survey.application.exception.SurveyDoesNotHasTargetException; +import me.nalab.survey.application.port.out.persistence.findfeedback.formtype.FeedbackFindPort; +import me.nalab.survey.application.port.out.persistence.findfeedback.formtype.SurveyFindPort; +import me.nalab.survey.application.port.out.persistence.findfeedback.formtype.TargetIdFindPort; +import me.nalab.survey.domain.feedback.Feedback; +import me.nalab.survey.domain.survey.ChoiceFormQuestion; +import me.nalab.survey.domain.survey.ShortFormQuestion; +import me.nalab.survey.domain.survey.Survey; + +@ExtendWith(SpringExtension.class) +class FeedbackFindByTypeServiceTest { + + private FeedbackFindByTypeService feedbackFindByTypeService; + + @Mock + private SurveyFindPort surveyFindPort; + + @Mock + private FeedbackFindPort feedbackFindPort; + + @Mock + private TargetIdFindPort targetIdFindPort; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + feedbackFindByTypeService = new FeedbackFindByTypeService(surveyFindPort, feedbackFindPort, targetIdFindPort); + } + + @Test + @DisplayName("surveyId로 모든 feedback 찾기 - 피드백이 있는 경우") + void FIND_ALL_FEEDBACK_BY_SURVEY_ID_WITH_FEEDBACK() { + + Survey survey = SurveyDtoMapper.toSurvey(RandomSurveyDtoFixture.createRandomSurveyDto()); + Long surveyId = survey.getId(); + FeedbackDto feedbackDto1 = RandomFeedbackDtoFixture.getRandomFeedbackDtoBySurvey(survey); + FeedbackDto feedbackDto2 = RandomFeedbackDtoFixture.getRandomFeedbackDtoBySurvey(survey); + Feedback feedback1 = FeedbackDtoMapper.toDomain(survey, feedbackDto1); + Feedback feedback2 = FeedbackDtoMapper.toDomain(survey, feedbackDto2); + + when(feedbackFindPort.findFeedbackBySurveyId(surveyId)).thenReturn(List.of(feedback1, feedback2)); + + List feedbackDtoList = feedbackFindByTypeService.findFeedbackBySurveyId(surveyId); + + assertEquals(List.of(feedbackDto1, feedbackDto2), feedbackDtoList); + + } + + @Test + @DisplayName("surveyId로 모든 feedback 찾기 - 피드백이 없는 경우") + void FIND_ALL_FEEDBACK_BY_SURVEY_ID_WITH_NO_FEEDBACK() { + + Survey survey = SurveyDtoMapper.toSurvey(RandomSurveyDtoFixture.createRandomSurveyDto()); + Long surveyId = survey.getId(); + when(feedbackFindPort.findFeedbackBySurveyId(surveyId)).thenReturn(List.of()); + + List feedbackDtoBySurveyId = feedbackFindByTypeService.findFeedbackBySurveyId(surveyId); + + assertEquals(List.of(), feedbackDtoBySurveyId); + + } + + @ParameterizedTest + @ValueSource(strings = {"tendency", "strength", "custom"}) + @DisplayName("formType에 맞는 FormQuestionDtoable 찾기") + void FIND_ALL_FORMQUESTIONDTOABLE_WITH_FORM_TYPE(String formType) { + + SurveyDto surveyDto = createRandomSurveyDto(); + Survey survey = SurveyDtoMapper.toSurvey(surveyDto); + Long surveyId = surveyDto.getId(); + Long targetId = surveyDto.getTargetId(); + AtomicInteger expectectedCount = new AtomicInteger(0); + + survey.getFormQuestionableList() + .forEach(q -> { + if (q instanceof ChoiceFormQuestion && ((ChoiceFormQuestion)q).getChoiceFormQuestionType() + .toString() + .toLowerCase() + .equals( + formType)) + expectectedCount.addAndGet(1); + if (q instanceof ShortFormQuestion && ((ShortFormQuestion)q).getShortFormQuestionType() + .toString() + .toLowerCase() + .equals( + formType)) + expectectedCount.getAndIncrement(); + }); + when(surveyFindPort.findSurveyById(surveyId)).thenReturn(Optional.of(survey)); + when(targetIdFindPort.findTargetIdBySurveyId(surveyId)).thenReturn(Optional.of(targetId)); + + List formQuestionDtoableList = feedbackFindByTypeService.formQuestionMatchingWithType( + surveyDto.getId(), formType); + + assertEquals(expectectedCount.get(), formQuestionDtoableList.size()); + + } + + @Test + @DisplayName("formType에 맞는 FormQuestionDtoable 찾기 - survey가 존재하지 않을 때") + void FIND_ALL_FORMQUESTIONDTOABLE_FAIL_WHEN_SURVEY_IS_NOT_EXIST() { + + SurveyDto surveyDto = createRandomSurveyDto(); + Survey survey = SurveyDtoMapper.toSurvey(surveyDto); + Long surveyId = surveyDto.getId(); + Long targetId = surveyDto.getTargetId(); + String formType = "custom"; + AtomicInteger expectectedCount = new AtomicInteger(0); + + survey.getFormQuestionableList() + .forEach(q -> { + if (q instanceof ChoiceFormQuestion && ((ChoiceFormQuestion)q).getChoiceFormQuestionType() + .toString() + .toLowerCase() + .equals( + formType)) + expectectedCount.addAndGet(1); + if (q instanceof ShortFormQuestion && ((ShortFormQuestion)q).getShortFormQuestionType() + .toString() + .toLowerCase() + .equals( + formType)) + expectectedCount.getAndIncrement(); + }); + when(surveyFindPort.findSurveyById(surveyId)).thenReturn(Optional.empty()); + when(targetIdFindPort.findTargetIdBySurveyId(surveyId)).thenReturn(Optional.of(targetId)); + + assertThrows(SurveyDoesNotExistException.class, + () -> feedbackFindByTypeService.formQuestionMatchingWithType(surveyId, "custom")); + } + + @Test + @DisplayName("formType에 맞는 FormQuestionDtoable 찾기 - target이 존재하지 않을 때") + void FIND_ALL_FORMQUESTIONDTOABLE_FAIL_WHEN_TARGET_IS_NOT_EXIST() { + + SurveyDto surveyDto = createRandomSurveyDto(); + Survey survey = SurveyDtoMapper.toSurvey(surveyDto); + Long surveyId = surveyDto.getId(); + String formType = "custom"; + AtomicInteger expectectedCount = new AtomicInteger(0); + + survey.getFormQuestionableList() + .forEach(q -> { + if (q instanceof ChoiceFormQuestion && ((ChoiceFormQuestion)q).getChoiceFormQuestionType() + .toString() + .toLowerCase() + .equals( + formType)) + expectectedCount.addAndGet(1); + if (q instanceof ShortFormQuestion && ((ShortFormQuestion)q).getShortFormQuestionType() + .toString() + .toLowerCase() + .equals( + formType)) + expectectedCount.getAndIncrement(); + }); + when(surveyFindPort.findSurveyById(surveyId)).thenReturn(Optional.of(survey)); + when(targetIdFindPort.findTargetIdBySurveyId(surveyId)).thenReturn(Optional.empty()); + + assertThrows(SurveyDoesNotHasTargetException.class, + () -> feedbackFindByTypeService.formQuestionMatchingWithType(surveyId, "custom")); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/common/mapper/SurveyEntityMapper.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/common/mapper/SurveyEntityMapper.java index 05c5fc87..60ab42b1 100644 --- a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/common/mapper/SurveyEntityMapper.java +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/common/mapper/SurveyEntityMapper.java @@ -23,7 +23,7 @@ public final class SurveyEntityMapper { - private SurveyEntityMapper(){ + private SurveyEntityMapper() { throw new UnsupportedOperationException("Cannot invoke constructor \"SurveyEntityMapper()\""); } @@ -44,7 +44,7 @@ private static List toFormQuestionEntityList(List toFormQuestionableList(List findFeedbackBySurveyId(Long surveyId) { + List feedbackEntityList = feedbackFindRepository.findAllFeedbackEntityBySurveyId(surveyId); + return feedbackEntityList.stream() + .map(FeedbackEntityMapper::toDomain) + .collect(Collectors.toList()); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/SurveyFindAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/SurveyFindAdaptor.java new file mode 100644 index 00000000..55afeed0 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/SurveyFindAdaptor.java @@ -0,0 +1,35 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.survey.SurveyEntity; +import me.nalab.survey.application.port.out.persistence.findfeedback.formtype.SurveyFindPort; +import me.nalab.survey.domain.survey.Survey; +import me.nalab.survey.jpa.adaptor.common.mapper.SurveyEntityMapper; +import me.nalab.survey.jpa.adaptor.findfeedback.formtype.repository.SurveyFindRepository; + +@Repository("findfeedback.formtype.SurveyFindAdaptor") +public class SurveyFindAdaptor implements SurveyFindPort { + + private final SurveyFindRepository surveyFindRepository; + + @Autowired + SurveyFindAdaptor( + @Qualifier("findfeedback.formtype.SurveyFindRepository") SurveyFindRepository surveyFindRepository) { + this.surveyFindRepository = surveyFindRepository; + } + + @Override + public Optional findSurveyById(Long surveyId) { + Optional surveyEntity = surveyFindRepository.findById(surveyId); + if (surveyEntity.isEmpty()) { + return Optional.empty(); + } + return Optional.of(SurveyEntityMapper.toSurvey(surveyEntity.get())); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TargetIdFindAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TargetIdFindAdaptor.java new file mode 100644 index 00000000..9dc3bc3d --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TargetIdFindAdaptor.java @@ -0,0 +1,33 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.survey.SurveyEntity; +import me.nalab.survey.application.port.out.persistence.findfeedback.formtype.TargetIdFindPort; +import me.nalab.survey.jpa.adaptor.findfeedback.formtype.repository.SurveyFindRepository; + +@Repository("findfeedback.formtype.TargetIdFindAdaptor") +public class TargetIdFindAdaptor implements TargetIdFindPort { + + private final SurveyFindRepository surveyFindRepository; + + @Autowired + TargetIdFindAdaptor( + @Qualifier("findfeedback.formtype.SurveyFindRepository") SurveyFindRepository surveyFindRepository) { + this.surveyFindRepository = surveyFindRepository; + } + + @Override + public Optional findTargetIdBySurveyId(Long surveyId) { + Optional surveyEntity = surveyFindRepository.findById(surveyId); + if (surveyEntity.isEmpty()) { + return Optional.empty(); + } + return Optional.of(surveyEntity.get().getTargetId()); + } + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/repository/FeedbackFindRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/repository/FeedbackFindRepository.java new file mode 100644 index 00000000..8ed92509 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/repository/FeedbackFindRepository.java @@ -0,0 +1,18 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.feedback.FeedbackEntity; + +@Repository("findfeedback.formtype.FeedbackFindRepository") +public interface FeedbackFindRepository extends JpaRepository { + + @Query("select f from FeedbackEntity as f where f.surveyId = :surveyId") + List findAllFeedbackEntityBySurveyId(@Param("surveyId") Long surveyId); + +} diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/repository/SurveyFindRepository.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/repository/SurveyFindRepository.java new file mode 100644 index 00000000..564e6df8 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/repository/SurveyFindRepository.java @@ -0,0 +1,11 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import me.nalab.core.data.survey.SurveyEntity; + +@Repository("findfeedback.formtype.SurveyFindRepository") +public interface SurveyFindRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/FeedbackFindAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/FeedbackFindAdaptorTest.java new file mode 100644 index 00000000..585159f8 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/FeedbackFindAdaptorTest.java @@ -0,0 +1,97 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.List; + +import javax.persistence.EntityManager; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.feedback.FeedbackEntity; +import me.nalab.core.data.survey.SurveyEntity; +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.domain.feedback.Feedback; +import me.nalab.survey.domain.survey.Survey; +import me.nalab.survey.jpa.adaptor.RandomFeedbackFixture; +import me.nalab.survey.jpa.adaptor.RandomSurveyFixture; +import me.nalab.survey.jpa.adaptor.common.mapper.FeedbackEntityMapper; +import me.nalab.survey.jpa.adaptor.common.mapper.SurveyEntityMapper; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = FeedbackFindAdaptor.class) +@TestPropertySource("classpath:h2.properties") +class FeedbackFindAdaptorTest { + + @Autowired + private FeedbackFindAdaptor feedbackFindAdaptor; + + @Autowired + private TestTargetRepository testTargetRepository; + + @Autowired + private TestSurveyFindRepository testSurveyFindRepository; + + @Autowired + private TestFeedbackFindRepository testFeedbackFindRepository; + + @Autowired + private EntityManager entityManager; + + @Test + @DisplayName("surveyId에 해당하는 모든 feedback 조회 - 피드백이 있는 경우") + void FIND_ALL_FEEDBACK_WITH_SURVEY_ID_WITH_FEEDBACK() { + + TargetEntity targetEntity = getTargetEntity(); + Survey survey = RandomSurveyFixture.createRandomSurvey(); + SurveyEntity surveyEntity = SurveyEntityMapper.toSurveyEntity(targetEntity.getId(), survey); + FeedbackEntity feedbackEntity = FeedbackEntityMapper.toEntity( + RandomFeedbackFixture.getRandomFeedbackBySurvey(survey)); + Feedback feedback = FeedbackEntityMapper.toDomain(feedbackEntity); + + testTargetRepository.saveAndFlush(targetEntity); + testSurveyFindRepository.saveAndFlush(surveyEntity); + testFeedbackFindRepository.saveAndFlush(feedbackEntity); + entityManager.clear(); + + List feedbackList = feedbackFindAdaptor.findFeedbackBySurveyId(survey.getId()); + + assertIterableEquals(List.of(feedback), feedbackList); + } + + @Test + @DisplayName("surveyId에 해당하는 모든 feedback 조회 - 피드백이 없는 경우") + void FIND_ALL_FEEDBACK_WITH_SURVEY_ID_WITH_NO_FEEDBACK() { + + TargetEntity targetEntity = getTargetEntity(); + Survey survey = RandomSurveyFixture.createRandomSurvey(); + SurveyEntity surveyEntity = SurveyEntityMapper.toSurveyEntity(targetEntity.getId(), survey); + + testTargetRepository.saveAndFlush(targetEntity); + testSurveyFindRepository.saveAndFlush(surveyEntity); + entityManager.clear(); + + List feedbackList = feedbackFindAdaptor.findFeedbackBySurveyId(survey.getId()); + + assertEquals(List.of(), feedbackList); + } + + private TargetEntity getTargetEntity() { + return TargetEntity.builder() + .id(1L) + .createdAt(Instant.now()) + .updatedAt(Instant.now()) + .nickname("nalab") + .build(); + } +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/SurveyFindAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/SurveyFindAdaptorTest.java new file mode 100644 index 00000000..cbb7812e --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/SurveyFindAdaptorTest.java @@ -0,0 +1,83 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.Optional; + +import javax.persistence.EntityManager; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.survey.SurveyEntity; +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.domain.survey.Survey; +import me.nalab.survey.jpa.adaptor.RandomSurveyFixture; +import me.nalab.survey.jpa.adaptor.common.mapper.SurveyEntityMapper; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = SurveyFindAdaptor.class) +@TestPropertySource("classpath:h2.properties") +class SurveyFindAdaptorTest { + + @Autowired + private SurveyFindAdaptor surveyFindAdaptor; + + @Autowired + private TestTargetRepository testTargetRepository; + + @Autowired + private TestSurveyFindRepository testSurveyFindRepository; + + @Autowired + private EntityManager entityManager; + + @Test + @DisplayName("surveyId에 해당하는 survey 조회 - survey가 존재하는 경우") + void FIND_SURVEY_WITH_SURVEY_ID_WITH_SURVEY() { + + TargetEntity targetEntity = getTargetEntity(); + Survey survey = RandomSurveyFixture.createRandomSurvey(); + SurveyEntity surveyEntity = SurveyEntityMapper.toSurveyEntity(targetEntity.getId(), survey); + + testTargetRepository.saveAndFlush(targetEntity); + testSurveyFindRepository.saveAndFlush(surveyEntity); + entityManager.clear(); + + Optional resultSurvey = surveyFindAdaptor.findSurveyById(survey.getId()); + + assertFalse(resultSurvey.isEmpty()); + assertEquals(survey, resultSurvey.get()); + } + + @Test + @DisplayName("surveyId에 해당하는 survey 조회 - survey가 존재하지 않는 경우") + void FIND_SURVEY_WITH_SURVEY_ID_WITH_NO_SURVEY() { + + Survey survey = RandomSurveyFixture.createRandomSurvey(); + + Optional resultSurvey = surveyFindAdaptor.findSurveyById(survey.getId()); + + assertTrue(resultSurvey.isEmpty()); + + } + + private TargetEntity getTargetEntity() { + return TargetEntity.builder() + .id(1L) + .createdAt(Instant.now()) + .updatedAt(Instant.now()) + .nickname("nalab") + .build(); + } + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TargetIdFindAdaptorTest.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TargetIdFindAdaptorTest.java new file mode 100644 index 00000000..42319cd8 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TargetIdFindAdaptorTest.java @@ -0,0 +1,83 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.Optional; + +import javax.persistence.EntityManager; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import me.nalab.core.data.survey.SurveyEntity; +import me.nalab.core.data.target.TargetEntity; +import me.nalab.survey.domain.survey.Survey; +import me.nalab.survey.jpa.adaptor.RandomSurveyFixture; +import me.nalab.survey.jpa.adaptor.common.mapper.SurveyEntityMapper; + +@DataJpaTest +@EnableJpaRepositories +@EntityScan("me.nalab.core.data") +@ContextConfiguration(classes = TargetIdFindAdaptor.class) +@TestPropertySource("classpath:h2.properties") +class TargetIdFindAdaptorTest { + + @Autowired + private TargetIdFindAdaptor targetIdFindAdaptor; + + @Autowired + private TestTargetRepository testTargetRepository; + + @Autowired + private TestSurveyFindRepository testSurveyFindRepository; + + @Autowired + private EntityManager entityManager; + + @Test + @DisplayName("surveyId에 해당하는 targetId 조회 - targetId가 존재하는 경우") + void FIND_TARGET_ID_WITH_SURVEY_ID_WITH_TARGETID() { + + TargetEntity targetEntity = getTargetEntity(); + Survey survey = RandomSurveyFixture.createRandomSurvey(); + SurveyEntity surveyEntity = SurveyEntityMapper.toSurveyEntity(targetEntity.getId(), survey); + + testTargetRepository.saveAndFlush(targetEntity); + testSurveyFindRepository.saveAndFlush(surveyEntity); + entityManager.clear(); + + Optional targetIdBySurveyId = targetIdFindAdaptor.findTargetIdBySurveyId(survey.getId()); + + assertFalse(targetIdBySurveyId.isEmpty()); + assertEquals(targetEntity.getId(), targetIdBySurveyId.get()); + } + + @Test + @DisplayName("surveyId에 해당하는 targetId 조회 - targetId가 존재하지 않는 경우") + void FIND_TARGET_ID_WITH_SURVEY_ID_WITH_NO_TARGETID() { + + Survey survey = RandomSurveyFixture.createRandomSurvey(); + + Optional targetIdBySurveyId = targetIdFindAdaptor.findTargetIdBySurveyId(survey.getId()); + + assertTrue(targetIdBySurveyId.isEmpty()); + + } + + private TargetEntity getTargetEntity() { + return TargetEntity.builder() + .id(1L) + .createdAt(Instant.now()) + .updatedAt(Instant.now()) + .nickname("nalab") + .build(); + } + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestFeedbackFindRepository.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestFeedbackFindRepository.java new file mode 100644 index 00000000..3f42261f --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestFeedbackFindRepository.java @@ -0,0 +1,9 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype; + +import org.springframework.data.jpa.repository.JpaRepository; + +import me.nalab.core.data.feedback.FeedbackEntity; + +public interface TestFeedbackFindRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestSurveyFindRepository.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestSurveyFindRepository.java new file mode 100644 index 00000000..46e3d123 --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestSurveyFindRepository.java @@ -0,0 +1,9 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype; + +import org.springframework.data.jpa.repository.JpaRepository; + +import me.nalab.core.data.survey.SurveyEntity; + +public interface TestSurveyFindRepository extends JpaRepository { + +} diff --git a/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestTargetRepository.java b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestTargetRepository.java new file mode 100644 index 00000000..6e03701a --- /dev/null +++ b/survey/survey-jpa-adaptor/src/test/java/me/nalab/survey/jpa/adaptor/findfeedback/formtype/TestTargetRepository.java @@ -0,0 +1,9 @@ +package me.nalab.survey.jpa.adaptor.findfeedback.formtype; + +import org.springframework.data.jpa.repository.JpaRepository; + +import me.nalab.core.data.target.TargetEntity; + +public interface TestTargetRepository extends JpaRepository { + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/FeedbackFindByFormTypeController.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/FeedbackFindByFormTypeController.java new file mode 100644 index 00000000..4c54820f --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/FeedbackFindByFormTypeController.java @@ -0,0 +1,37 @@ +package me.nalab.survey.web.adaptor.findfeedback.formtype; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; +import me.nalab.survey.application.common.survey.dto.FormQuestionDtoable; +import me.nalab.survey.application.port.in.web.findfeedback.formtype.FeedbackFindByTypeUseCase; +import me.nalab.survey.web.adaptor.findfeedback.formtype.response.QuestionFeedbackResponse; + +@RestController +@RequestMapping("/v2") +@RequiredArgsConstructor +public class FeedbackFindByFormTypeController { + + private final FeedbackFindByTypeUseCase feedbackFindByTypeUseCase; + + @GetMapping("/feedbacks") + @ResponseStatus(HttpStatus.OK) + public QuestionFeedbackResponse findFeedbackByFormType( + @RequestParam("survey-id") Long surveyId, + @RequestParam("form-type") String formType) { + List formQuestionDtoableList = feedbackFindByTypeUseCase.formQuestionMatchingWithType( + surveyId, formType); + List feedbackDtoList = feedbackFindByTypeUseCase.findFeedbackBySurveyId(surveyId); + return QuestionFeedbackResponseMapper.toFeedbackQuestionResponse( + formQuestionDtoableList, feedbackDtoList); + } + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/QuestionFeedbackResponseMapper.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/QuestionFeedbackResponseMapper.java new file mode 100644 index 00000000..0648681f --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/QuestionFeedbackResponseMapper.java @@ -0,0 +1,124 @@ +package me.nalab.survey.web.adaptor.findfeedback.formtype; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import me.nalab.survey.application.common.feedback.dto.ChoiceFormQuestionFeedbackDto; +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; +import me.nalab.survey.application.common.feedback.dto.ShortFormQuestionFeedbackDto; +import me.nalab.survey.application.common.survey.dto.ChoiceDto; +import me.nalab.survey.application.common.survey.dto.ChoiceFormQuestionDto; +import me.nalab.survey.application.common.survey.dto.FormQuestionDtoable; +import me.nalab.survey.application.common.survey.dto.QuestionDtoType; +import me.nalab.survey.application.common.survey.dto.ShortFormQuestionDto; +import me.nalab.survey.web.adaptor.findfeedback.formtype.response.QuestionFeedbackResponse; +import me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion.ChoiceFormQuestionResponse; +import me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion.ChoiceResponse; +import me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion.ShortFormQuestionResponse; +import me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion.ShortResponse; + +final class QuestionFeedbackResponseMapper { + + private QuestionFeedbackResponseMapper() { + throw new UnsupportedOperationException("Cannot invoke constructor \"QuestionFeedbackResponseMapper()\""); + } + + static QuestionFeedbackResponse toFeedbackQuestionResponse(List formQuestionDtoableList, + List feedbackDtoList) { + return QuestionFeedbackResponse.builder() + .abstractFormQuestionResponseList(formQuestionDtoableList.stream() + .map(q -> { + if (q.getQuestionDtoType() == QuestionDtoType.CHOICE) { + return toChoiceFormQuestionResponse((ChoiceFormQuestionDto)q, feedbackDtoList); + } + return toShortFormQuestionResponse((ShortFormQuestionDto)q, feedbackDtoList); + } + ).collect(Collectors.toList())).build(); + } + + private static ChoiceFormQuestionResponse toChoiceFormQuestionResponse(ChoiceFormQuestionDto choiceFormQuestionDto, + List feedbackDtoList) { + ChoiceFormQuestionResponse choiceFormQuestionResponse = ChoiceFormQuestionResponse.builder() + .questionId(String.valueOf(choiceFormQuestionDto.getId())) + .order(choiceFormQuestionDto.getOrder()) + .type(choiceFormQuestionDto.getQuestionDtoType().name().toLowerCase()) + .title(choiceFormQuestionDto.getQuestionDtoType().name().toLowerCase()) + .formType(choiceFormQuestionDto.getChoiceFormQuestionDtoType().name().toLowerCase()) + .title(choiceFormQuestionDto.getTitle()) + .choiceResponseList(toChoiceResponseList(choiceFormQuestionDto.getChoiceDtoList())) + .build(); + updateSelectedCount(choiceFormQuestionResponse, feedbackDtoList); + return choiceFormQuestionResponse; + } + + private static void updateSelectedCount(ChoiceFormQuestionResponse choiceFormQuestionResponse, + List feedbackDtoList) { + Long questionId = Long.valueOf(choiceFormQuestionResponse.getQuestionId()); + + feedbackDtoList + .forEach(c -> c.getFormQuestionFeedbackDtoableList() + .forEach(q -> { + if (Objects.equals(q.getQuestionId(), questionId)) { + Set selectedChoiceIdSet = ((ChoiceFormQuestionFeedbackDto)q).getSelectedChoiceIdSet(); + selectedChoiceIdSet + .forEach(sc -> choiceFormQuestionResponse.getChoiceResponseList() + .forEach(cq -> { + if (Objects.equals(sc, Long.valueOf(cq.getChoiceId()))) + cq.updateSelectedCount(); + })); + } + }) + ); + } + + private static List toChoiceResponseList(List choiceDtoList) { + return choiceDtoList.stream() + .map(c -> ChoiceResponse.builder() + .choiceId(String.valueOf(c.getId())) + .order(c.getOrder()) + .selectedCount(0) + .content(c.getContent()) + .build()) + .collect(Collectors.toList()); + } + + private static ShortFormQuestionResponse toShortFormQuestionResponse(ShortFormQuestionDto shortFormQuestionDto, + List feedbackDtoList) { + List shortResponseList = new ArrayList<>(); + ShortFormQuestionResponse shortFormQuestionResponse = ShortFormQuestionResponse.builder() + .questionId(String.valueOf(shortFormQuestionDto.getId())) + .order(shortFormQuestionDto.getOrder()) + .type(shortFormQuestionDto.getQuestionDtoType().name().toLowerCase()) + .title(shortFormQuestionDto.getQuestionDtoType().name().toLowerCase()) + .formType(shortFormQuestionDto.getShortFormQuestionDtoType().name().toLowerCase()) + .title(shortFormQuestionDto.getTitle()) + .shortResponseList(shortResponseList) + .build(); + toShortResponseList(shortFormQuestionResponse, feedbackDtoList); + return shortFormQuestionResponse; + } + + private static void toShortResponseList(ShortFormQuestionResponse shortFormQuestionResponse, + List feedbackDtoList) { + feedbackDtoList + .forEach(f -> f.getFormQuestionFeedbackDtoableList().stream() + .filter(q -> Objects.equals(q.getQuestionId(), + Long.valueOf(shortFormQuestionResponse.getQuestionId()))) + .forEach(sq -> shortFormQuestionResponse.getShortResponseList().add( + ShortResponse.builder() + .feedbackId(String.valueOf(f.getId())) + .createdAt( + ZonedDateTime.ofInstant(f.getCreatedAt(), ZoneId.of("Asia/Seoul")).toLocalDateTime()) + .replyList(((ShortFormQuestionFeedbackDto)sq).getReplyList()) + .build() + + ))); + + } + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/QuestionFeedbackResponse.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/QuestionFeedbackResponse.java new file mode 100644 index 00000000..49cfe797 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/QuestionFeedbackResponse.java @@ -0,0 +1,20 @@ +package me.nalab.survey.web.adaptor.findfeedback.formtype.response; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion.AbstractFormQuestionResponse; + +@Getter +@ToString +@SuperBuilder +public class QuestionFeedbackResponse { + + @JsonProperty("question_feedback") + private List abstractFormQuestionResponseList; + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/AbstractFormQuestionResponse.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/AbstractFormQuestionResponse.java new file mode 100644 index 00000000..3a9e040c --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/AbstractFormQuestionResponse.java @@ -0,0 +1,26 @@ +package me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Getter +@ToString +@SuperBuilder +public abstract class AbstractFormQuestionResponse { + + @JsonProperty("question_id") + private final String questionId; + + private final Integer order; + + private final String type; + + @JsonProperty("form_type") + private final String formType; + + private final String title; + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ChoiceFormQuestionResponse.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ChoiceFormQuestionResponse.java new file mode 100644 index 00000000..bc52a0fe --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ChoiceFormQuestionResponse.java @@ -0,0 +1,19 @@ +package me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Getter +@ToString +@SuperBuilder +public class ChoiceFormQuestionResponse extends AbstractFormQuestionResponse { + + @JsonProperty("choices") + private final List choiceResponseList; + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ChoiceResponse.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ChoiceResponse.java new file mode 100644 index 00000000..6b366de9 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ChoiceResponse.java @@ -0,0 +1,28 @@ +package me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class ChoiceResponse { + + @JsonProperty("choice_id") + private final String choiceId; + + private final int order; + + @JsonProperty("selected_count") + private int selectedCount; + + private final String content; + + public void updateSelectedCount() { + this.selectedCount++; + } + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ShortFormQuestionResponse.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ShortFormQuestionResponse.java new file mode 100644 index 00000000..b4233e03 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ShortFormQuestionResponse.java @@ -0,0 +1,19 @@ +package me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Getter +@ToString +@SuperBuilder +public class ShortFormQuestionResponse extends AbstractFormQuestionResponse { + + @JsonProperty("feedbacks") + private final List shortResponseList; + +} diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ShortResponse.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ShortResponse.java new file mode 100644 index 00000000..f391ce93 --- /dev/null +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/formtype/response/formquestion/ShortResponse.java @@ -0,0 +1,26 @@ +package me.nalab.survey.web.adaptor.findfeedback.formtype.response.formquestion; + +import java.time.LocalDateTime; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class ShortResponse { + + @JsonProperty("feedback_id") + private final String feedbackId; + + @JsonProperty("created_at") + private final LocalDateTime createdAt; + + @JsonProperty("reply") + List replyList; + +} From c998741154ce5d2db26a4c9c82fbee14f4695058 Mon Sep 17 00:00:00 2001 From: xb205 <62425964+devxb@users.noreply.github.com> Date: Wed, 12 Jul 2023 22:19:37 +0900 Subject: [PATCH 14/17] =?UTF-8?q?[fix]=20:=20`/v2=20API`=20CORS=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95=20(#336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../me/nalab/core/secure/cors/CorsConfig.java | 22 +++++++++++++++++-- core/secure/src/main/java/module-info.java | 1 + 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/secure/src/main/java/me/nalab/core/secure/cors/CorsConfig.java b/core/secure/src/main/java/me/nalab/core/secure/cors/CorsConfig.java index 342e07c8..a8c2bc1b 100644 --- a/core/secure/src/main/java/me/nalab/core/secure/cors/CorsConfig.java +++ b/core/secure/src/main/java/me/nalab/core/secure/cors/CorsConfig.java @@ -1,6 +1,7 @@ package me.nalab.core.secure.cors; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -11,16 +12,33 @@ class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/v1/**") + addCorsMappingsV1(registry); + addCorsMappingsV2(registry); + addCorsMappingsMock(registry); + } + + private void addCorsMappingsV1(CorsRegistry corsRegistry) { + corsRegistry.addMapping("/v1/**") + .allowedOrigins("*") + .allowedHeaders("*") + .allowedMethods(ALLOWED_METHOD_NAMES) + .maxAge(3600); + } + + private CorsRegistration addCorsMappingsV2(CorsRegistry corsRegistry) { + return corsRegistry.addMapping("/v2/**") .allowedOrigins("*") .allowedHeaders("*") .allowedMethods(ALLOWED_METHOD_NAMES) .maxAge(3600); + } - registry.addMapping("/mock/**") + private CorsRegistration addCorsMappingsMock(CorsRegistry corsRegistry) { + return corsRegistry.addMapping("/mock/**") .allowedOrigins("*") .allowedHeaders("*") .allowedMethods(ALLOWED_METHOD_NAMES) .maxAge(3600); } + } diff --git a/core/secure/src/main/java/module-info.java b/core/secure/src/main/java/module-info.java index 3f16e354..60d5390e 100644 --- a/core/secure/src/main/java/module-info.java +++ b/core/secure/src/main/java/module-info.java @@ -4,6 +4,7 @@ requires spring.context; requires com.fasterxml.jackson.databind; requires org.apache.commons.text; + requires spring.webmvc; exports me.nalab.core.secure.xss.meta; From 19b970886c599f138436cf07411d3306a9a03a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=8F=84=EC=A7=84?= Date: Wed, 12 Jul 2023 23:18:42 +0900 Subject: [PATCH 15/17] =?UTF-8?q?[feat]=20:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=EB=90=9C=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1=20(#313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: devxb --- .../AbstractFeedbackTestSupporter.java | 13 +- .../feedback/FeedbackAcceptanceValidator.java | 41 +++++- .../find/FeedbackFindAcceptanceTest.java | 92 +++++++++--- .../auth/mock/config/MockAuthConfigurer.java | 1 - .../mock/interceptor/MockAuthInterceptor.java | 2 +- .../feedback/mapper/FeedbackDtoMapper.java | 37 ++++- .../BookmarkedFeedbackFindUseCase.java | 20 +++ .../BookmarkedFeedbackFindService.java | 35 +++++ .../application/RandomFeedbackDtoFixture.java | 2 +- .../BookmarkedFeedbackFindServiceTest.java | 131 ++++++++++++++++++ .../findfeedback/FeedbackFindServiceTest.java | 2 +- .../feedback/FormQuestionFeedbackable.java | 4 + .../FormQuestionFeedbackUpdateAdaptor.java | 6 +- .../findfeedback/FeedbackFindController.java | 10 ++ .../adaptor/findfeedback/ResponseMapper.java | 33 +++-- 15 files changed, 386 insertions(+), 43 deletions(-) create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findfeedback/BookmarkedFeedbackFindUseCase.java create mode 100644 survey/survey-application/src/main/java/me/nalab/survey/application/service/findfeedback/BookmarkedFeedbackFindService.java create mode 100644 survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/BookmarkedFeedbackFindServiceTest.java diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java index fffa1e18..8652ba27 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/AbstractFeedbackTestSupporter.java @@ -61,10 +61,19 @@ protected ResultActions findReviewers(String token, String surveyId) throws Exce protected ResultActions findFeedback(String token, Long surveyId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders - .get(String.format("/v2/surveys/%d/feedbacks", surveyId)) + .get(String.format("/v2/surveys/%d/feedbacks", surveyId)) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, token) + ); + } + + protected ResultActions findBookmarkedFeedback(Long surveyId) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get("/v1/feedbacks/bookmarks") + .queryParam("survey-id", surveyId.toString()) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) - .header(HttpHeaders.AUTHORIZATION, token) ); } diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java index 4d4d6690..c4823bb4 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/FeedbackAcceptanceValidator.java @@ -55,7 +55,6 @@ public static void assertIsFeedbackFound(ResultActions resultActions) throws Exc jsonPath("$.question_feedback.[0].feedbacks.[0].reviewer.position").isString() ); } - public static void assertIsFeedbackNotFound(ResultActions resultActions) throws Exception { resultActions.andExpectAll( status().isOk(), @@ -75,6 +74,46 @@ public static void assertIsFeedbackNotFound(ResultActions resultActions) throws ); } + public static void assertIsBookmarkedFeedbackFound(ResultActions resultActions) throws Exception { + resultActions.andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON), + + jsonPath("$.question_feedback").isArray(), + jsonPath("$.question_feedback").isNotEmpty(), + jsonPath("$.question_feedback.[0].question_id").isString(), + jsonPath("$.question_feedback.[0].order").isNumber(), + jsonPath("$.question_feedback.[0].type").isString(), + jsonPath("$.question_feedback.[0].form_type").isString(), + jsonPath("$.question_feedback.[0].title").isString(), + + jsonPath("$.question_feedback.[0].feedbacks").isArray(), + jsonPath("$.question_feedback.[0].feedbacks").isNotEmpty(), + jsonPath("$.question_feedback.[0].feedbacks.[0].feedback_id").isString(), + jsonPath("$.question_feedback.[0].feedbacks.[0].form_question_feedback_id").isString(), + jsonPath("$.question_feedback.[0].feedbacks.[0].created_at").isString(), + jsonPath("$.question_feedback.[0].feedbacks.[0].is_read").isBoolean(), + jsonPath("$.question_feedback.[0].feedbacks.[0].bookmark").isNotEmpty(), + jsonPath("$.question_feedback.[0].feedbacks.[0].bookmark.is_bookmarked").isBoolean(), + jsonPath("$.question_feedback.[0].feedbacks.[0].bookmark.bookmarked_at").isString(), + + jsonPath("$.question_feedback.[0].feedbacks.[0].reviewer.reviewer_id").isString(), + jsonPath("$.question_feedback.[0].feedbacks.[0].reviewer.nickname").isString(), + jsonPath("$.question_feedback.[0].feedbacks.[0].reviewer.collaboration_experience").isBoolean(), + jsonPath("$.question_feedback.[0].feedbacks.[0].reviewer.position").isString() + ); + } + + public static void assertIsBookmarkedFeedbackNotFound(ResultActions resultActions) throws Exception { + resultActions.andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON), + + jsonPath("$.question_feedback").isArray(), + jsonPath("$.question_feedback").isEmpty() + ); + } + public static void assertIsFeedbackSummaryFound(ResultActions resultActions) throws Exception { resultActions.andExpectAll( status().isOk(), diff --git a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/find/FeedbackFindAcceptanceTest.java b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/find/FeedbackFindAcceptanceTest.java index 03419310..b8de5cce 100644 --- a/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/find/FeedbackFindAcceptanceTest.java +++ b/api/acceptance-test/src/test/java/me/nalab/luffy/api/acceptance/test/feedback/find/FeedbackFindAcceptanceTest.java @@ -1,11 +1,16 @@ package me.nalab.luffy.api.acceptance.test.feedback.find; -import static me.nalab.luffy.api.acceptance.test.feedback.FeedbackAcceptanceValidator.*; -import static me.nalab.luffy.api.acceptance.test.feedback.FeedbackCreateRequestFixture.*; - -import java.time.Instant; -import java.util.Map; - +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import me.nalab.auth.mock.api.MockUserRegisterEvent; +import me.nalab.luffy.api.acceptance.test.TargetInitializer; +import me.nalab.luffy.api.acceptance.test.feedback.AbstractFeedbackTestSupporter; +import me.nalab.luffy.api.acceptance.test.feedback.create.request.FeedbackCreateRequest; +import me.nalab.luffy.api.acceptance.test.feedback.create.response.SurveyFindResponse; +import me.nalab.luffy.api.acceptance.test.survey.RequestSample; +import org.json.JSONObject; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -18,17 +23,11 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.ResultActions; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.Instant; +import java.util.Map; -import me.nalab.auth.mock.api.MockUserRegisterEvent; -import me.nalab.luffy.api.acceptance.test.TargetInitializer; -import me.nalab.luffy.api.acceptance.test.feedback.AbstractFeedbackTestSupporter; -import me.nalab.luffy.api.acceptance.test.feedback.create.request.FeedbackCreateRequest; -import me.nalab.luffy.api.acceptance.test.feedback.create.response.SurveyFindResponse; -import me.nalab.luffy.api.acceptance.test.survey.RequestSample; +import static me.nalab.luffy.api.acceptance.test.feedback.FeedbackAcceptanceValidator.*; +import static me.nalab.luffy.api.acceptance.test.feedback.FeedbackCreateRequestFixture.getFeedbackCreateRequest; @SpringBootTest @AutoConfigureMockMvc @@ -95,6 +94,67 @@ void FIND_FEED_BACK_SUCCESS_ANY_FEEDBACK() throws Exception { assertIsFeedbackNotFound(resultActions); } + @Test + @DisplayName("북마크된 피드백 조회 성공 인수테스트") + void FIND_BOOKMARKED_FEED_BACK_SUCCESS() throws Exception { + // given + Long surveyId = setUpSurveyAndFeedbackAndBookmark(); + + // when + ResultActions resultActions = findBookmarkedFeedback(surveyId); + + // then + assertIsBookmarkedFeedbackFound(resultActions); + } + + @Test + @DisplayName("북마크된 피드백 조회 성공 인수테스트 - 피드백이 없을때") + void FIND_BOOKMARKED_FEED_BACK_SUCCESS_ANY_FEEDBACK() throws Exception { + // given + Long targetId = targetInitializer.saveTargetAndGetId("hello world", Instant.now()); + String token = "mock token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedId(targetId) + .expectedToken(token) + .build()); + + Long surveyId = createAndGetSurveyId(token, RequestSample.DEFAULT_JSON); + + // when + ResultActions resultActions = findBookmarkedFeedback(surveyId); + + // then + assertIsBookmarkedFeedbackNotFound(resultActions); + } + + private Long setUpSurveyAndFeedbackAndBookmark() throws Exception { + // 유저 생성 + Long targetId = targetInitializer.saveTargetAndGetId("hello world", Instant.now()); + String token = "mock token"; + applicationEventPublisher.publishEvent(MockUserRegisterEvent.builder() + .expectedId(targetId) + .expectedToken(token) + .build()); + // 서베이 생성 + Long surveyId = createAndGetSurveyId(token, RequestSample.DEFAULT_JSON); + SurveyFindResponse surveyFindResponse = getSurveyFindResponse(surveyId); + FeedbackCreateRequest feedbackCreateRequest = getFeedbackCreateRequest(surveyFindResponse, true, "developer"); + // 피드백 생성 + createFeedback(surveyId, OBJECT_MAPPER.writeValueAsString(feedbackCreateRequest)); + + // 피드백 조회 + String stringResponse = findFeedback(token, surveyId).andReturn() + .getResponse() + .getContentAsString(); + JSONObject jsonObject = new JSONObject(stringResponse); + String feedbackId = jsonObject.getJSONArray("question_feedback").getJSONObject(2).getJSONArray("feedbacks") + .getJSONObject(0).getString("form_question_feedback_id"); + // 북마크 처리 + replaceBookmark(token, feedbackId); + + return surveyId; + } + private Long createAndGetSurveyId(String token, String content) throws Exception { ResultActions resultActions = createSurvey(token, content); Map surveyIdMap = OBJECT_MAPPER.readValue( diff --git a/auth/auth-mock/src/main/java/me/nalab/auth/mock/config/MockAuthConfigurer.java b/auth/auth-mock/src/main/java/me/nalab/auth/mock/config/MockAuthConfigurer.java index 96e4839f..0f26147c 100644 --- a/auth/auth-mock/src/main/java/me/nalab/auth/mock/config/MockAuthConfigurer.java +++ b/auth/auth-mock/src/main/java/me/nalab/auth/mock/config/MockAuthConfigurer.java @@ -21,7 +21,6 @@ public class MockAuthConfigurer implements WebMvcConfigurer { "/v1/reviewers*", "/v1/reviewers/summary*", "/v2/surveys/*/feedbacks", - "/v1/feedbacks/bookmarks", }; @Override diff --git a/auth/auth-mock/src/main/java/me/nalab/auth/mock/interceptor/MockAuthInterceptor.java b/auth/auth-mock/src/main/java/me/nalab/auth/mock/interceptor/MockAuthInterceptor.java index 3ce67db5..3c1c0827 100644 --- a/auth/auth-mock/src/main/java/me/nalab/auth/mock/interceptor/MockAuthInterceptor.java +++ b/auth/auth-mock/src/main/java/me/nalab/auth/mock/interceptor/MockAuthInterceptor.java @@ -62,4 +62,4 @@ void mockUserRegister(MockUserRegisterEvent mockUserRegisterEvent) { expectedId = mockUserRegisterEvent.getExpectedId(); } -} \ No newline at end of file +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/common/feedback/mapper/FeedbackDtoMapper.java b/survey/survey-application/src/main/java/me/nalab/survey/application/common/feedback/mapper/FeedbackDtoMapper.java index 13607b12..6dc30edb 100644 --- a/survey/survey-application/src/main/java/me/nalab/survey/application/common/feedback/mapper/FeedbackDtoMapper.java +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/common/feedback/mapper/FeedbackDtoMapper.java @@ -98,12 +98,17 @@ private static ReviewerDto getReviewerDto(Reviewer reviewer) { } private static List getFormQuestionFeedbackDtoList(Feedback feedback) { - return feedback.getFormQuestionFeedbackableList().stream().map(q -> { - if (q instanceof ShortFormQuestionFeedback) { - return getShortFormQuestionFeedbackDto((ShortFormQuestionFeedback)q); - } - return getChoiceFormQuestionFeedbackDto((ChoiceFormQuestionFeedback)q); - }).collect(Collectors.toList()); + return feedback.getFormQuestionFeedbackableList() + .stream() + .map(FeedbackDtoMapper::getFormQuestionFeedbackDtoable) + .collect(Collectors.toList()); + } + + private static FormQuestionFeedbackDtoable getFormQuestionFeedbackDtoable(FormQuestionFeedbackable q) { + if (q instanceof ShortFormQuestionFeedback) { + return getShortFormQuestionFeedbackDto((ShortFormQuestionFeedback) q); + } + return getChoiceFormQuestionFeedbackDto((ChoiceFormQuestionFeedback) q); } private static ShortFormQuestionFeedbackDto getShortFormQuestionFeedbackDto( @@ -134,4 +139,24 @@ private static ChoiceFormQuestionFeedbackDto getChoiceFormQuestionFeedbackDto( .build(); } + public static FeedbackDto toDtoWithBookmarkedForm(Feedback feedback) { + return FeedbackDto.builder() + .id(feedback.getId()) + .surveyId(feedback.getSurveyId()) + .reviewerDto(getReviewerDto(feedback.getReviewer())) + .formQuestionFeedbackDtoableList(getBookmakredFormQuestionFeedbackDtoList(feedback)) + .createdAt(feedback.getCreatedAt()) + .updatedAt(feedback.getUpdatedAt()) + .isRead(feedback.isRead()) + .build(); + } + + private static List getBookmakredFormQuestionFeedbackDtoList(Feedback feedback) { + return feedback.getFormQuestionFeedbackableList() + .stream() + .filter(FormQuestionFeedbackable::isBookmarked) + .map(FeedbackDtoMapper::getFormQuestionFeedbackDtoable) + .collect(Collectors.toList()); + } + } diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findfeedback/BookmarkedFeedbackFindUseCase.java b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findfeedback/BookmarkedFeedbackFindUseCase.java new file mode 100644 index 00000000..73598592 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/port/in/web/findfeedback/BookmarkedFeedbackFindUseCase.java @@ -0,0 +1,20 @@ +package me.nalab.survey.application.port.in.web.findfeedback; + +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; + +import java.util.List; + +/** + * 북마크된 피드백을 조회. 북마크된 피드백은 전체 응답이 아닌 북마크된 응답만 가지고 있습니다. + */ +public interface BookmarkedFeedbackFindUseCase { + + /** + * 특정 survey의 피드백 중 북마크된 피드백만 조회합니다. + * + * @param surveyId 조회하고자 하는 Feedback이 속한 Survey의 식별자 + * @return 북마크된 모든 피드백들 + */ + List findAllBySurveyId(Long surveyId); + +} diff --git a/survey/survey-application/src/main/java/me/nalab/survey/application/service/findfeedback/BookmarkedFeedbackFindService.java b/survey/survey-application/src/main/java/me/nalab/survey/application/service/findfeedback/BookmarkedFeedbackFindService.java new file mode 100644 index 00000000..18daa551 --- /dev/null +++ b/survey/survey-application/src/main/java/me/nalab/survey/application/service/findfeedback/BookmarkedFeedbackFindService.java @@ -0,0 +1,35 @@ +package me.nalab.survey.application.service.findfeedback; + +import lombok.RequiredArgsConstructor; +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; +import me.nalab.survey.application.common.feedback.mapper.FeedbackDtoMapper; +import me.nalab.survey.application.port.in.web.findfeedback.BookmarkedFeedbackFindUseCase; +import me.nalab.survey.application.port.out.persistence.findfeedback.FeedbackFindPort; +import me.nalab.survey.domain.feedback.Feedback; +import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class BookmarkedFeedbackFindService implements BookmarkedFeedbackFindUseCase { + + private final FeedbackFindPort feedbackFindPort; + + @Override + @Transactional(readOnly = true) + public List findAllBySurveyId(Long surveyId) { + List allFeedbacks = feedbackFindPort.findAllFeedbackBySurveyId(surveyId); + + return allFeedbacks.stream() + .filter(fb -> { + List allQuestionFeedbackValidable = fb.getAllQuestionFeedbackValidable(); + return !allQuestionFeedbackValidable.isEmpty() && allQuestionFeedbackValidable.stream().anyMatch(FormQuestionFeedbackable::isBookmarked); + }) + .map(FeedbackDtoMapper::toDtoWithBookmarkedForm) + .collect(Collectors.toList()); + } +} diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/RandomFeedbackDtoFixture.java b/survey/survey-application/src/test/java/me/nalab/survey/application/RandomFeedbackDtoFixture.java index 5ee9d381..793847f9 100644 --- a/survey/survey-application/src/test/java/me/nalab/survey/application/RandomFeedbackDtoFixture.java +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/RandomFeedbackDtoFixture.java @@ -61,7 +61,7 @@ public static FeedbackDto getRandomFeedbackDtoBySurvey(Survey survey) { .build(); } - private static ReviewerDto getRandomReviewerDto() { + public static ReviewerDto getRandomReviewerDto() { return ReviewerDto.builder() .collaborationExperience(RANDOM_BOOLEAN_SUPPLIER.getAsBoolean()) .position(RANDOM_STRING_SUPPLIER.get()) diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/BookmarkedFeedbackFindServiceTest.java b/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/BookmarkedFeedbackFindServiceTest.java new file mode 100644 index 00000000..159698af --- /dev/null +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/BookmarkedFeedbackFindServiceTest.java @@ -0,0 +1,131 @@ +package me.nalab.survey.application.service.findfeedback; + +import me.nalab.survey.application.RandomSurveyDtoFixture; +import me.nalab.survey.application.TestIdGenerator; +import me.nalab.survey.application.common.feedback.dto.BookmarkDto; +import me.nalab.survey.application.common.feedback.dto.FeedbackDto; +import me.nalab.survey.application.common.feedback.dto.ShortFormQuestionFeedbackDto; +import me.nalab.survey.application.common.feedback.mapper.FeedbackDtoMapper; +import me.nalab.survey.application.common.survey.mapper.SurveyDtoMapper; +import me.nalab.survey.application.port.out.persistence.findfeedback.FeedbackFindPort; +import me.nalab.survey.domain.feedback.Feedback; +import me.nalab.survey.domain.survey.FormQuestionable; +import me.nalab.survey.domain.survey.QuestionType; +import me.nalab.survey.domain.survey.Survey; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static me.nalab.survey.application.RandomFeedbackDtoFixture.getRandomFeedbackDtoBySurvey; +import static me.nalab.survey.application.RandomFeedbackDtoFixture.getRandomReviewerDto; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {BookmarkedFeedbackFindService.class, TestIdGenerator.class}) +class BookmarkedFeedbackFindServiceTest { + + @Autowired + private BookmarkedFeedbackFindService bookmarkedFeedbackFindService; + + @MockBean + private FeedbackFindPort feedbackFindPort; + + @ParameterizedTest + @MethodSource("feedbackFindSources") + @DisplayName("Feedback 조회 성공 테스트 - Bookmark 된 응답 없음") + void FIND_EMPTY_FEEDBACK(Survey survey, List feedbackDtoList) { + // given + List feedbackList = feedbackDtoList.stream() + .map(f -> FeedbackDtoMapper.toDomain(survey, f)) + .collect(Collectors.toList()); + when(feedbackFindPort.findAllFeedbackBySurveyId(anyLong())).thenReturn(feedbackList); + + // when + + List result = bookmarkedFeedbackFindService.findAllBySurveyId(survey.getId()); + + // then + org.assertj.core.api.Assertions.assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Feedback 조회 성공 테스트 - Bookmark 된 응답 없음") + void FIND_FEEDBACK() { + // given + Survey survey = SurveyDtoMapper.toSurvey(RandomSurveyDtoFixture.createRandomSurveyDto()); + List feedbackList = getBookmarkedFeedbackList(survey); + when(feedbackFindPort.findAllFeedbackBySurveyId(anyLong())).thenReturn(feedbackList); + + // when + List result = bookmarkedFeedbackFindService.findAllBySurveyId(survey.getId()); + + // then + org.assertj.core.api.Assertions.assertThat(result) + .isNotEmpty() + .allMatch(feedbackDto -> feedbackDto.getFormQuestionFeedbackDtoableList() + .stream() + .allMatch(formQuestionFeedbackDtoable -> formQuestionFeedbackDtoable.getBookmarkDto().isBookmarked())); + } + + private static List getBookmarkedFeedbackList(Survey survey) { + return Stream.of(getRandomFeedbackDtoBySurvey(survey), + getRandomFeedbackDtoBySurvey(survey), + getBookmarkedFeedbackDtoBySurvey(survey)) + .map(feedbackDto -> FeedbackDtoMapper.toDomain(survey, feedbackDto)) + .collect(Collectors.toList()); + } + + private static FeedbackDto getBookmarkedFeedbackDtoBySurvey(Survey survey) { + FormQuestionable targetQuestion = survey.getFormQuestionableList() + .stream() + .filter(formQuestionable -> formQuestionable.getQuestionType() == QuestionType.SHORT) + .findFirst() + .orElseThrow(); + ShortFormQuestionFeedbackDto bookmarkedForm = ShortFormQuestionFeedbackDto.builder() + .questionId(targetQuestion.getId()) + .bookmarkDto( + BookmarkDto.builder() + .isBookmarked(true) + .bookmarkedAt(Instant.now()) + .build()) + .isRead(true) + .replyList(List.of("북마크된 텍스트")) + .build(); + + return FeedbackDto.builder() + .surveyId(survey.getId()) + .isRead(false) + .reviewerDto(getRandomReviewerDto()) + .formQuestionFeedbackDtoableList(List.of(bookmarkedForm)) + .createdAt(Instant.now()) + .updatedAt(Instant.now()) + .build(); + } + + private static Stream feedbackFindSources() { + Survey survey = SurveyDtoMapper.toSurvey(RandomSurveyDtoFixture.createRandomSurveyDto()); + return Stream.of( + Arguments.of(survey, + Arrays.asList(getRandomFeedbackDtoBySurvey(survey), getRandomFeedbackDtoBySurvey(survey), + getRandomFeedbackDtoBySurvey(survey))), // feedback이 여러개 + Arguments.of(survey, Collections.singletonList(getRandomFeedbackDtoBySurvey(survey))), // feedback이 하나 + Arguments.of(survey, List.of()) // feedback이 없을때 + ); + } + +} diff --git a/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/FeedbackFindServiceTest.java b/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/FeedbackFindServiceTest.java index 0e14d65e..d89de97e 100644 --- a/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/FeedbackFindServiceTest.java +++ b/survey/survey-application/src/test/java/me/nalab/survey/application/service/findfeedback/FeedbackFindServiceTest.java @@ -123,7 +123,7 @@ private void assertIsSorted(List result) { continue; } assertTrue(before.getUpdatedAt().isAfter(current.getUpdatedAt()) - || before.getUpdatedAt() == current.getUpdatedAt()); + || before.getUpdatedAt().equals(current.getUpdatedAt())); } } diff --git a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java index c56e1bc9..0d1b062c 100644 --- a/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java +++ b/survey/survey-domain/src/main/java/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.java @@ -39,4 +39,8 @@ public void withId(LongSupplier idSupplier) { public Long getFormQuestionId() { return questionId; } + + public boolean isBookmarked() { + return bookmark.isBookmarked(); + } } diff --git a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java index d8214d83..087b8af0 100644 --- a/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java +++ b/survey/survey-jpa-adaptor/src/main/java/me/nalab/survey/jpa/adaptor/bookmark/FormQuestionFeedbackUpdateAdaptor.java @@ -1,13 +1,11 @@ package me.nalab.survey.jpa.adaptor.bookmark; -import org.springframework.stereotype.Repository; - import lombok.RequiredArgsConstructor; import me.nalab.core.data.feedback.FormFeedbackEntity; import me.nalab.survey.application.port.out.persistence.bookmark.FormQuestionFeedbackUpdatePort; import me.nalab.survey.domain.feedback.FormQuestionFeedbackable; import me.nalab.survey.jpa.adaptor.bookmark.repository.FormQuestionFeedbackUpdateRepository; -import me.nalab.survey.jpa.adaptor.common.mapper.FeedbackEntityMapper; +import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor @@ -17,7 +15,7 @@ public class FormQuestionFeedbackUpdateAdaptor implements FormQuestionFeedbackUp @Override public void updateFormQuestionFeedback(FormQuestionFeedbackable formQuestionFeedbackable) { - FormFeedbackEntity formFeedbackEntity = FeedbackEntityMapper.toFormFeedbackEntity(formQuestionFeedbackable); + FormFeedbackEntity formFeedbackEntity = formQuestionFeedbackUpdateRepository.findById(formQuestionFeedbackable.getId()).orElseThrow(); formFeedbackEntity.setBookmarked(formQuestionFeedbackable.getBookmark().isBookmarked()); formFeedbackEntity.setBookmarkedAt(formQuestionFeedbackable.getBookmark().getBookmarkedAt()); } diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/FeedbackFindController.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/FeedbackFindController.java index d304eee2..3dbc6d1f 100644 --- a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/FeedbackFindController.java +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/FeedbackFindController.java @@ -2,6 +2,7 @@ import java.util.List; +import me.nalab.survey.application.port.in.web.findfeedback.BookmarkedFeedbackFindUseCase; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -25,6 +26,7 @@ public class FeedbackFindController { private final SurveyFindUseCase surveyFindUseCase; private final FeedbackFindUseCase feedbackFindUseCase; + private final BookmarkedFeedbackFindUseCase bookmarkedFeedbackFindUseCase; @Authorization(factory = SurveyIdValidatorFactory.class) @GetMapping("/v1/feedbacks") @@ -48,4 +50,12 @@ public QuestionFeedbackResponse findFeedback(@Auth @PathVariable("survey-id") Lo return ResponseMapper.toQuestionFeedbackResponse(surveyDto, feedbackDto); } + @GetMapping("/v1/feedbacks/bookmarks") + @ResponseStatus(HttpStatus.OK) + public QuestionFeedbackResponse findAllBookmarked(@RequestParam("survey-id") Long surveyId) { + SurveyDto surveyDto = surveyFindUseCase.findSurvey(surveyId); + List feedbackDto = bookmarkedFeedbackFindUseCase.findAllBySurveyId(surveyId); + return ResponseMapper.toBookmarkedQuestionFeedbackResponse(surveyDto, feedbackDto); + } + } diff --git a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/ResponseMapper.java b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/ResponseMapper.java index 87056cb3..a3232e3a 100644 --- a/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/ResponseMapper.java +++ b/survey/survey-web-adaptor/src/main/java/me/nalab/survey/web/adaptor/findfeedback/ResponseMapper.java @@ -7,16 +7,8 @@ import java.util.Set; import java.util.stream.Collectors; -import me.nalab.survey.application.common.feedback.dto.BookmarkDto; -import me.nalab.survey.application.common.feedback.dto.ChoiceFormQuestionFeedbackDto; -import me.nalab.survey.application.common.feedback.dto.FeedbackDto; -import me.nalab.survey.application.common.feedback.dto.ReviewerDto; -import me.nalab.survey.application.common.feedback.dto.ShortFormQuestionFeedbackDto; -import me.nalab.survey.application.common.survey.dto.ChoiceDto; -import me.nalab.survey.application.common.survey.dto.ChoiceFormQuestionDto; -import me.nalab.survey.application.common.survey.dto.QuestionDtoType; -import me.nalab.survey.application.common.survey.dto.ShortFormQuestionDto; -import me.nalab.survey.application.common.survey.dto.SurveyDto; +import me.nalab.survey.application.common.feedback.dto.*; +import me.nalab.survey.application.common.survey.dto.*; import me.nalab.survey.web.adaptor.findfeedback.response.QuestionFeedbackResponse; import me.nalab.survey.web.adaptor.findfeedback.response.feedback.BookmarkResponse; import me.nalab.survey.web.adaptor.findfeedback.response.feedback.ChoiceFeedbackResponse; @@ -44,6 +36,27 @@ static QuestionFeedbackResponse toQuestionFeedbackResponse(SurveyDto surveyDto, ).collect(Collectors.toList())).build(); } + static QuestionFeedbackResponse toBookmarkedQuestionFeedbackResponse(SurveyDto surveyDto, List feedbackDtoList) { + Set questionIds = feedbackDtoList.stream() + .flatMap(feedbackDto -> feedbackDto.getFormQuestionFeedbackDtoableList().stream()) + .map(FormQuestionFeedbackDtoable::getQuestionId) + .collect(Collectors.toSet()); + + List formQuestionDtoableList = surveyDto.getFormQuestionDtoableList() + .stream() + .filter(formQuestionDtoable -> questionIds.contains(formQuestionDtoable.getId())) + .collect(Collectors.toList()); + return QuestionFeedbackResponse.builder() + .abstractSurveyResponse(formQuestionDtoableList.stream().map( + f -> { + if (f.getQuestionDtoType() == QuestionDtoType.CHOICE) { + return toChoiceSurveyResponse((ChoiceFormQuestionDto)f, feedbackDtoList); + } + return toShortSurveyResponse((ShortFormQuestionDto)f, feedbackDtoList); + } + ).collect(Collectors.toList())).build(); + } + private static AbstractSurveyResponse toChoiceSurveyResponse(ChoiceFormQuestionDto choiceFormQuestionDto, List feedbackDto) { return ChoiceSurveyResponse.builder() From 59340fb1c02c2d51a86688e3209b448179fa6758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=88=98=EC=A7=80=EB=8B=88?= <71487608+ssssujini99@users.noreply.github.com> Date: Wed, 12 Jul 2023 23:36:45 +0900 Subject: [PATCH 16/17] =?UTF-8?q?[test]=20:=20`=EC=A7=88=EB=AC=B8=ED=8F=BC?= =?UTF-8?q?=EC=9D=98=20=ED=83=80=EC=9E=85=EC=97=90=20=ED=95=B4=EB=8B=B9?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C`=20e2e=20test=20=EC=B6=94=EA=B0=80=20(#337)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- support/e2e/v1_3_feedback_find.hurl | 110 ++++++++++++++++++++++++++++ support/e2e/v2_3_feedback_find.hurl | 109 +++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) diff --git a/support/e2e/v1_3_feedback_find.hurl b/support/e2e/v1_3_feedback_find.hurl index 60878f7a..87352df0 100644 --- a/support/e2e/v1_3_feedback_find.hurl +++ b/support/e2e/v1_3_feedback_find.hurl @@ -103,6 +103,10 @@ short_strength_question_id: jsonpath "$.question.[1].question_id" choice_custom_question_id: jsonpath "$.question.[2].question_id" choice_custom_choice_id: jsonpath "$.question.[2].choices.[0].choice_id" short_custom_question_id: jsonpath "$.question.[3].question_id" +formtype_tendency: jsonpath "$.question.[0].form_type" +formtype_strength: jsonpath "$.question.[1].form_type" +formtype_custom: jsonpath "$.question.[2].form_type" + ########## @@ -314,3 +318,109 @@ header "Content-type" == "application/json" jsonpath "$.all_feedback_count" == 2 jsonpath "$.new_feedback_count" == 1 + +########## + +GET http://nalab-server:8080/v2/feedbacks # 생성된 survey와 feedback 2개를 바탕으로, 질문폼의 타입 "tendency"에 해당하는 피드백을 조회한다 + +[QueryStringParams] +survey-id: {{ survey_id }} +form-type: {{ formtype_tendency }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.question_feedback" count == 1 +jsonpath "$.question_feedback.[0].question_id" exists +jsonpath "$.question_feedback.[0].order" exists +jsonpath "$.question_feedback.[0].type" matches "choice" +jsonpath "$.question_feedback.[0].form_type" matches "tendency" +jsonpath "$.question_feedback.[0].title" exists + +jsonpath "$.question_feedback.[0].choices" count == 4 +jsonpath "$.question_feedback.[0].choices.[0].order" exists +jsonpath "$.question_feedback.[0].choices.[0].content" exists +jsonpath "$.question_feedback.[0].choices.[0].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[0].selected_count" == 2 +jsonpath "$.question_feedback.[0].choices.[1].order" exists +jsonpath "$.question_feedback.[0].choices.[1].content" exists +jsonpath "$.question_feedback.[0].choices.[1].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[1].selected_count" == 0 +jsonpath "$.question_feedback.[0].choices.[2].order" exists +jsonpath "$.question_feedback.[0].choices.[2].content" exists +jsonpath "$.question_feedback.[0].choices.[2].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[2].selected_count" == 0 +jsonpath "$.question_feedback.[0].choices.[3].order" exists +jsonpath "$.question_feedback.[0].choices.[3].content" exists +jsonpath "$.question_feedback.[0].choices.[3].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[3].selected_count" == 0 + +########## + +GET http://nalab-server:8080/v2/feedbacks # 생성된 survey와 feedback 2개를 바탕으로, 질문폼의 타입 "strength"에 해당하는 피드백을 조회한다 + +[QueryStringParams] +survey-id: {{ survey_id }} +form-type: {{ formtype_strength }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.question_feedback" count == 1 +jsonpath "$.question_feedback.[0].question_id" exists +jsonpath "$.question_feedback.[0].order" exists +jsonpath "$.question_feedback.[0].type" matches "short" +jsonpath "$.question_feedback.[0].form_type" matches "strength" +jsonpath "$.question_feedback.[0].title" exists + +jsonpath "$.question_feedback.[0].feedbacks" count == 2 +jsonpath "$.question_feedback.[0].feedbacks.[0].feedback_id" exists +jsonpath "$.question_feedback.[0].feedbacks.[0].created_at" exists +jsonpath "$.question_feedback.[0].feedbacks.[0].reply" exists +jsonpath "$.question_feedback.[0].feedbacks.[1].feedback_id" exists +jsonpath "$.question_feedback.[0].feedbacks.[1].created_at" exists +jsonpath "$.question_feedback.[0].feedbacks.[1].reply" exists + +########## + +GET http://nalab-server:8080/v2/feedbacks # 생성된 survey와 feedback 2개를 바탕으로, 질문폼의 타입 "custom"에 해당하는 피드백을 조회한다 + +[QueryStringParams] +survey-id: {{ survey_id }} +form-type: {{ formtype_custom }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.question_feedback" count == 2 + +jsonpath "$.question_feedback.[0].question_id" exists +jsonpath "$.question_feedback.[0].order" exists +jsonpath "$.question_feedback.[0].type" matches "choice" +jsonpath "$.question_feedback.[0].form_type" matches "custom" +jsonpath "$.question_feedback.[0].title" exists +jsonpath "$.question_feedback.[0].choices" count == 2 +jsonpath "$.question_feedback.[0].choices.[0].order" exists +jsonpath "$.question_feedback.[0].choices.[0].content" exists +jsonpath "$.question_feedback.[0].choices.[0].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[0].selected_count" == 2 +jsonpath "$.question_feedback.[0].choices.[1].order" exists +jsonpath "$.question_feedback.[0].choices.[1].content" exists +jsonpath "$.question_feedback.[0].choices.[1].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[1].selected_count" == 0 + +jsonpath "$.question_feedback.[1].question_id" exists +jsonpath "$.question_feedback.[1].order" exists +jsonpath "$.question_feedback.[1].type" matches "short" +jsonpath "$.question_feedback.[1].form_type" matches "custom" +jsonpath "$.question_feedback.[1].title" exists +jsonpath "$.question_feedback.[1].feedbacks" count == 2 +jsonpath "$.question_feedback.[1].feedbacks.[0].feedback_id" exists +jsonpath "$.question_feedback.[1].feedbacks.[0].created_at" exists +jsonpath "$.question_feedback.[1].feedbacks.[0].reply" exists +jsonpath "$.question_feedback.[1].feedbacks.[1].feedback_id" exists +jsonpath "$.question_feedback.[1].feedbacks.[1].created_at" exists +jsonpath "$.question_feedback.[1].feedbacks.[1].reply" exists diff --git a/support/e2e/v2_3_feedback_find.hurl b/support/e2e/v2_3_feedback_find.hurl index 530ff510..363bdda6 100644 --- a/support/e2e/v2_3_feedback_find.hurl +++ b/support/e2e/v2_3_feedback_find.hurl @@ -103,6 +103,9 @@ short_strength_question_id: jsonpath "$.question.[1].question_id" choice_custom_question_id: jsonpath "$.question.[2].question_id" choice_custom_choice_id: jsonpath "$.question.[2].choices.[0].choice_id" short_custom_question_id: jsonpath "$.question.[3].question_id" +formtype_tendency: jsonpath "$.question.[0].form_type" +formtype_strength: jsonpath "$.question.[1].form_type" +formtype_custom: jsonpath "$.question.[2].form_type" ########## @@ -311,3 +314,109 @@ header "Content-type" == "application/json" jsonpath "$.all_feedback_count" == 2 jsonpath "$.new_feedback_count" == 1 + +########## + +GET http://nalab-server:8080/v2/feedbacks # 생성된 survey와 feedback 2개를 바탕으로, 질문폼의 타입 "tendency"에 해당하는 피드백을 조회한다 + +[QueryStringParams] +survey-id: {{ survey_id }} +form-type: {{ formtype_tendency }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.question_feedback" count == 1 +jsonpath "$.question_feedback.[0].question_id" exists +jsonpath "$.question_feedback.[0].order" exists +jsonpath "$.question_feedback.[0].type" matches "choice" +jsonpath "$.question_feedback.[0].form_type" matches "tendency" +jsonpath "$.question_feedback.[0].title" exists + +jsonpath "$.question_feedback.[0].choices" count == 4 +jsonpath "$.question_feedback.[0].choices.[0].order" exists +jsonpath "$.question_feedback.[0].choices.[0].content" exists +jsonpath "$.question_feedback.[0].choices.[0].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[0].selected_count" == 2 +jsonpath "$.question_feedback.[0].choices.[1].order" exists +jsonpath "$.question_feedback.[0].choices.[1].content" exists +jsonpath "$.question_feedback.[0].choices.[1].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[1].selected_count" == 0 +jsonpath "$.question_feedback.[0].choices.[2].order" exists +jsonpath "$.question_feedback.[0].choices.[2].content" exists +jsonpath "$.question_feedback.[0].choices.[2].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[2].selected_count" == 0 +jsonpath "$.question_feedback.[0].choices.[3].order" exists +jsonpath "$.question_feedback.[0].choices.[3].content" exists +jsonpath "$.question_feedback.[0].choices.[3].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[3].selected_count" == 0 + +########## + +GET http://nalab-server:8080/v2/feedbacks # 생성된 survey와 feedback 2개를 바탕으로, 질문폼의 타입 "strength"에 해당하는 피드백을 조회한다 + +[QueryStringParams] +survey-id: {{ survey_id }} +form-type: {{ formtype_strength }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.question_feedback" count == 1 +jsonpath "$.question_feedback.[0].question_id" exists +jsonpath "$.question_feedback.[0].order" exists +jsonpath "$.question_feedback.[0].type" matches "short" +jsonpath "$.question_feedback.[0].form_type" matches "strength" +jsonpath "$.question_feedback.[0].title" exists + +jsonpath "$.question_feedback.[0].feedbacks" count == 2 +jsonpath "$.question_feedback.[0].feedbacks.[0].feedback_id" exists +jsonpath "$.question_feedback.[0].feedbacks.[0].created_at" exists +jsonpath "$.question_feedback.[0].feedbacks.[0].reply" exists +jsonpath "$.question_feedback.[0].feedbacks.[1].feedback_id" exists +jsonpath "$.question_feedback.[0].feedbacks.[1].created_at" exists +jsonpath "$.question_feedback.[0].feedbacks.[1].reply" exists + +########## + +GET http://nalab-server:8080/v2/feedbacks # 생성된 survey와 feedback 2개를 바탕으로, 질문폼의 타입 "custom"에 해당하는 피드백을 조회한다 + +[QueryStringParams] +survey-id: {{ survey_id }} +form-type: {{ formtype_custom }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.question_feedback" count == 2 + +jsonpath "$.question_feedback.[0].question_id" exists +jsonpath "$.question_feedback.[0].order" exists +jsonpath "$.question_feedback.[0].type" matches "choice" +jsonpath "$.question_feedback.[0].form_type" matches "custom" +jsonpath "$.question_feedback.[0].title" exists +jsonpath "$.question_feedback.[0].choices" count == 2 +jsonpath "$.question_feedback.[0].choices.[0].order" exists +jsonpath "$.question_feedback.[0].choices.[0].content" exists +jsonpath "$.question_feedback.[0].choices.[0].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[0].selected_count" == 2 +jsonpath "$.question_feedback.[0].choices.[1].order" exists +jsonpath "$.question_feedback.[0].choices.[1].content" exists +jsonpath "$.question_feedback.[0].choices.[1].choice_id" exists +jsonpath "$.question_feedback.[0].choices.[1].selected_count" == 0 + +jsonpath "$.question_feedback.[1].question_id" exists +jsonpath "$.question_feedback.[1].order" exists +jsonpath "$.question_feedback.[1].type" matches "short" +jsonpath "$.question_feedback.[1].form_type" matches "custom" +jsonpath "$.question_feedback.[1].title" exists +jsonpath "$.question_feedback.[1].feedbacks" count == 2 +jsonpath "$.question_feedback.[1].feedbacks.[0].feedback_id" exists +jsonpath "$.question_feedback.[1].feedbacks.[0].created_at" exists +jsonpath "$.question_feedback.[1].feedbacks.[0].reply" exists +jsonpath "$.question_feedback.[1].feedbacks.[1].feedback_id" exists +jsonpath "$.question_feedback.[1].feedbacks.[1].created_at" exists +jsonpath "$.question_feedback.[1].feedbacks.[1].reply" exists From a9b31ccda0918189c5dc4829e6b0ff21e507d0be Mon Sep 17 00:00:00 2001 From: xb205 <62425964+devxb@users.noreply.github.com> Date: Wed, 12 Jul 2023 23:42:41 +0900 Subject: [PATCH 17/17] =?UTF-8?q?[test]=20:=20`=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80,=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C`=20e2e=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#329)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- support/e2e/v1_4_bookmark.hurl | 166 +++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 support/e2e/v1_4_bookmark.hurl diff --git a/support/e2e/v1_4_bookmark.hurl b/support/e2e/v1_4_bookmark.hurl new file mode 100644 index 00000000..5703918e --- /dev/null +++ b/support/e2e/v1_4_bookmark.hurl @@ -0,0 +1,166 @@ +POST http://nalab-server:8080/v1/oauth/default # Default provider를 통해서 로그인 진행 +{ + "nickname": "devxb", + "email": "hello@12345" +} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.access_token" exists +jsonpath "$.token_type" exists + +[Captures] +token_type: jsonpath "$.token_type" +auth_token: jsonpath "$.access_token" + +########## + +POST http://nalab-server:8080/v1/surveys # 발급받은 토큰으로 survey를 생성한다. +Authorization: {{ token_type }} {{ auth_token }} +{ + "question_count": 2, + "question": [ + { + "type": "choice", + "form_type": "tendency", + "title": "저는 UI, UI, GUI 중에 어떤 분야를 가장 잘하는 것 같나요?", + "choices": [ + { + "content": "UI", + "order": 1 + }, + { + "content": "UX", + "order": 2 + }, + { + "content": "GUI", + "order": 3 + } + ], + "max_selectable_count": 1, + "order": 1 + }, + { + "type": "short", + "form_type": "strength", + "title": "저는 UX, UI, GUI 중에 어떤 분야에 더 강점이 있나요?", + "order": 2 + } + ] +} + +HTTP 201 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.survey_id" exists + +[Captures] +survey_id: jsonpath "$.survey_id" + +########## + +GET http://nalab-server:8080/v1/surveys/{{ survey_id }} # 생성된 survey를 조회하고, feedback을 남기기 위해 id를 저장한다. + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +[Captures] +tendency_question_id: jsonpath "$.question.[0].question_id" +tendency_question_choice_id: jsonpath "$.question.[0].choices.[0].choice_id" +strength_question_id: jsonpath "$.question.[1].question_id" + +########## + +POST http://nalab-server:8080/v1/feedbacks # 생성된 survey에 feedback을 남긴다. + +[QueryStringParams] +survey-id: {{ survey_id }} + +{ + "reviewer": { + "collaboration_experience": true, + "position": "pm" + }, + "question_feedback": [ + { + "question_id": {{ tendency_question_id }}, + "type": "choice", + "choices": [ + {{ tendency_question_choice_id }} + ] + }, + { + "question_id": {{ strength_question_id }}, + "type": "short", + "reply": [ + "Hello world" + ] + } + ] +} + +HTTP 201 +[Asserts] + +########## + +GET http://nalab-server:8080/v1/feedbacks # 북마크를 위해 feedback id 저장 +Authorization: {{ token_type }} {{ auth_token }} + +[QueryStringParams] +survey-id: {{ survey_id }} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +[Captures] +form_question_feedback_id: jsonpath "$.question_feedback.[0].feedbacks.[0].form_question_feedback_id" + +########## + +PATCH http://nalab-server:8080/v1/feedbacks/bookmarks # 북마크를 진행한다. +Authorization: {{ token_type }} {{ auth_token }} + +[QueryStringParams] +form-question-feedback-id: {{form_question_feedback_id}} + +########## + +GET http://nalab-server:8080/v1/feedbacks/bookmarks # 북마크된 피드백을 조회하면, 북마크된 피드백이 1개 조회된다. + +[QueryStringParams] +survey-id: {{survey_id}} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.question_feedback" count == 1 +jsonpath "$.question_feedback.[0].feedbacks.[0].bookmark.is_bookmarked" == true + +########## + +PATCH http://nalab-server:8080/v1/feedbacks/bookmarks # 북마크를 취소한다. +Authorization: {{ token_type }} {{ auth_token }} + +[QueryStringParams] +form-question-feedback-id: {{form_question_feedback_id}} + +########## + +GET http://nalab-server:8080/v1/feedbacks/bookmarks # 북마크된 피드백을 조회하면, 북마크된 피드백이 0개 조회된다. + +[QueryStringParams] +survey-id: {{survey_id}} + +HTTP 200 +[Asserts] +header "Content-type" == "application/json" + +jsonpath "$.question_feedback" count == 0