From 10d3de1e43a823c1ab67f90d196e2b361d060f4f Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 23 Sep 2024 16:06:18 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=9A=94=EC=B2=AD=20dto=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20validator=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/EitherTextOrCheckbox.java | 20 +++++++++++++++++++ .../EitherTextOrCheckboxValidator.java | 16 +++++++++++++++ .../dto/request/ReviewAnswerRequest.java | 1 + 3 files changed, 37 insertions(+) create mode 100644 backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckbox.java create mode 100644 backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidator.java diff --git a/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckbox.java b/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckbox.java new file mode 100644 index 000000000..7512992fa --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckbox.java @@ -0,0 +1,20 @@ +package reviewme.review.service.dto.request; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = EitherTextOrCheckboxValidator.class) +public @interface EitherTextOrCheckbox { + + String message() default "선택형 응답과 서술형 응답 중 하나만 입력해주세요."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidator.java b/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidator.java new file mode 100644 index 000000000..b0324fb10 --- /dev/null +++ b/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidator.java @@ -0,0 +1,16 @@ +package reviewme.review.service.dto.request; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class EitherTextOrCheckboxValidator + implements ConstraintValidator { + + @Override + public boolean isValid(ReviewAnswerRequest request, ConstraintValidatorContext context) { + if (request.selectedOptionIds() != null) { + return request.text() == null; + } + return request.text() != null; + } +} diff --git a/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java b/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java index 2da5ab5ec..ea653eb12 100644 --- a/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java +++ b/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotNull; import java.util.List; +@EitherTextOrCheckbox public record ReviewAnswerRequest( @NotNull(message = "질문 ID를 입력해주세요.") From d865f8b91cb485a9a07eb24d1f7c64aa0718bcca Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 23 Sep 2024 16:06:28 +0900 Subject: [PATCH 2/5] =?UTF-8?q?test:=20=EC=9A=94=EC=B2=AD=20dto=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20validator=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EitherTextOrCheckboxValidatorTest.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 backend/src/test/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidatorTest.java diff --git a/backend/src/test/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidatorTest.java b/backend/src/test/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidatorTest.java new file mode 100644 index 000000000..93f1f52eb --- /dev/null +++ b/backend/src/test/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidatorTest.java @@ -0,0 +1,60 @@ +package reviewme.review.service.dto.request; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class EitherTextOrCheckboxValidatorTest { + + private Validator validator; + + @BeforeEach + void setUp() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("validRequests") + void 유효한_응답에_대한_검증(String title, Long questionId, List selectedOptionIds, String text) { + // given + ReviewAnswerRequest request = new ReviewAnswerRequest(questionId, selectedOptionIds, text); + + // when + Set> violations = validator.validate(request); + + // then + assertThat(violations).isEmpty(); + } + + static Stream validRequests() { + return Stream.of( + Arguments.of("선택형 응답만 존재", 1L, List.of(1L), null), + Arguments.of("서술형 응답만 존재", 1L, null, "답"), + Arguments.of("필수 아닌 질문 (둘 다 없음)", 1L, null, "") + ); + } + + @Test + void 유효하지_않은_응답에_대한_검증() { + // given + ReviewAnswerRequest request = new ReviewAnswerRequest(1L, List.of(1L), "답"); + + // when + Set> violations = validator.validate(request); + + // then + assertThat(violations).hasSize(1); + } +} From eeacf74898ccf7551b16551af78f3eb6b6266a9b Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 23 Sep 2024 16:12:51 +0900 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EC=97=90=20=EC=9E=88=EC=97=88=EB=8D=98=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CheckBoxAnswerIncludedTextException.java | 13 --------- ...TextAnswerIncludedOptionItemException.java | 13 --------- .../review/service/mapper/AnswerMapper.java | 10 ------- .../service/mapper/AnswerMapperTest.java | 29 ------------------- 4 files changed, 65 deletions(-) delete mode 100644 backend/src/main/java/reviewme/review/service/exception/CheckBoxAnswerIncludedTextException.java delete mode 100644 backend/src/main/java/reviewme/review/service/exception/TextAnswerIncludedOptionItemException.java diff --git a/backend/src/main/java/reviewme/review/service/exception/CheckBoxAnswerIncludedTextException.java b/backend/src/main/java/reviewme/review/service/exception/CheckBoxAnswerIncludedTextException.java deleted file mode 100644 index a563acbf0..000000000 --- a/backend/src/main/java/reviewme/review/service/exception/CheckBoxAnswerIncludedTextException.java +++ /dev/null @@ -1,13 +0,0 @@ -package reviewme.review.service.exception; - -import lombok.extern.slf4j.Slf4j; -import reviewme.global.exception.BadRequestException; - -@Slf4j -public class CheckBoxAnswerIncludedTextException extends BadRequestException { - - public CheckBoxAnswerIncludedTextException(long questionId) { - super("체크박스형 응답은 텍스트를 포함할 수 없어요."); - log.info("CheckBox type answer cannot have option items - questionId: {}", questionId); - } -} diff --git a/backend/src/main/java/reviewme/review/service/exception/TextAnswerIncludedOptionItemException.java b/backend/src/main/java/reviewme/review/service/exception/TextAnswerIncludedOptionItemException.java deleted file mode 100644 index ba9310ee6..000000000 --- a/backend/src/main/java/reviewme/review/service/exception/TextAnswerIncludedOptionItemException.java +++ /dev/null @@ -1,13 +0,0 @@ -package reviewme.review.service.exception; - -import lombok.extern.slf4j.Slf4j; -import reviewme.global.exception.BadRequestException; - -@Slf4j -public class TextAnswerIncludedOptionItemException extends BadRequestException { - - public TextAnswerIncludedOptionItemException(long questionId) { - super("텍스트형 응답은 옵션 항목을 포함할 수 없어요."); - log.info("Text type answer cannot have option items - questionId: {}", questionId); - } -} diff --git a/backend/src/main/java/reviewme/review/service/mapper/AnswerMapper.java b/backend/src/main/java/reviewme/review/service/mapper/AnswerMapper.java index ae444da6d..d79a8826c 100644 --- a/backend/src/main/java/reviewme/review/service/mapper/AnswerMapper.java +++ b/backend/src/main/java/reviewme/review/service/mapper/AnswerMapper.java @@ -4,25 +4,15 @@ import reviewme.review.domain.CheckboxAnswer; import reviewme.review.domain.TextAnswer; import reviewme.review.service.dto.request.ReviewAnswerRequest; -import reviewme.review.service.exception.CheckBoxAnswerIncludedTextException; -import reviewme.review.service.exception.TextAnswerIncludedOptionItemException; @Component public class AnswerMapper { public TextAnswer mapToTextAnswer(ReviewAnswerRequest answerRequest) { - if (answerRequest.selectedOptionIds() != null) { - throw new TextAnswerIncludedOptionItemException(answerRequest.questionId()); - } - return new TextAnswer(answerRequest.questionId(), answerRequest.text()); } public CheckboxAnswer mapToCheckBoxAnswer(ReviewAnswerRequest answerRequest) { - if (answerRequest.text() != null) { - throw new CheckBoxAnswerIncludedTextException(answerRequest.questionId()); - } - return new CheckboxAnswer(answerRequest.questionId(), answerRequest.selectedOptionIds()); } } diff --git a/backend/src/test/java/reviewme/review/service/mapper/AnswerMapperTest.java b/backend/src/test/java/reviewme/review/service/mapper/AnswerMapperTest.java index f9557dde3..2f4043d41 100644 --- a/backend/src/test/java/reviewme/review/service/mapper/AnswerMapperTest.java +++ b/backend/src/test/java/reviewme/review/service/mapper/AnswerMapperTest.java @@ -1,7 +1,6 @@ package reviewme.review.service.mapper; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import java.util.List; @@ -11,8 +10,6 @@ import reviewme.review.domain.CheckboxAnswer; import reviewme.review.domain.TextAnswer; import reviewme.review.service.dto.request.ReviewAnswerRequest; -import reviewme.review.service.exception.CheckBoxAnswerIncludedTextException; -import reviewme.review.service.exception.TextAnswerIncludedOptionItemException; import reviewme.support.ServiceTest; @ServiceTest @@ -56,30 +53,4 @@ class AnswerMapperTest { .containsOnly(selectedOptionsId) ); } - - @Test - void 서술형_답변_매핑시_선택형_답변이_존재할_경우_예외가_발생한다() { - // given - long questionId = 1L; - String text = "답변"; - long selectedOptionsId = 2L; - ReviewAnswerRequest answerRequest = new ReviewAnswerRequest(questionId, List.of(selectedOptionsId), text); - - // when, then - assertThatThrownBy(() -> answerMapper.mapToTextAnswer(answerRequest)) - .isInstanceOf(TextAnswerIncludedOptionItemException.class); - } - - @Test - void 선택형_답변_매핑시_서술형_답변이_존재할_경우_예외가_발생한다() { - // given - long questionId = 1L; - String text = "답변"; - long selectedOptionsId = 2L; - ReviewAnswerRequest answerRequest = new ReviewAnswerRequest(questionId, List.of(selectedOptionsId), text); - - // when, then - assertThatThrownBy(() -> answerMapper.mapToCheckBoxAnswer(answerRequest)) - .isInstanceOf(CheckBoxAnswerIncludedTextException.class); - } } From 265e7afb5d034a4c6bfb0fabf5303ea064f48ff2 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 23 Sep 2024 16:24:38 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20Valid=20=EA=B0=80=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/service/dto/request/ReviewRegisterRequest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/reviewme/review/service/dto/request/ReviewRegisterRequest.java b/backend/src/main/java/reviewme/review/service/dto/request/ReviewRegisterRequest.java index 1b7a6f896..612be2fec 100644 --- a/backend/src/main/java/reviewme/review/service/dto/request/ReviewRegisterRequest.java +++ b/backend/src/main/java/reviewme/review/service/dto/request/ReviewRegisterRequest.java @@ -1,5 +1,6 @@ package reviewme.review.service.dto.request; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import java.util.List; @@ -9,6 +10,7 @@ public record ReviewRegisterRequest( @NotBlank(message = "리뷰 요청 코드를 입력해주세요.") String reviewRequestCode, + @Valid @NotEmpty(message = "답변 내용을 입력해주세요.") List answers ) { From e6905d9570a967880376d514d5368d7b6086654c Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Wed, 25 Sep 2024 18:12:59 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{EitherTextOrCheckbox.java => OneTypeAnswer.java} | 6 +++--- ...OrCheckboxValidator.java => OntTypeAnswerValidator.java} | 4 ++-- .../review/service/dto/request/ReviewAnswerRequest.java | 2 +- ...oxValidatorTest.java => OneTypeAnswerValidatorTest.java} | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename backend/src/main/java/reviewme/review/service/dto/request/{EitherTextOrCheckbox.java => OneTypeAnswer.java} (67%) rename backend/src/main/java/reviewme/review/service/dto/request/{EitherTextOrCheckboxValidator.java => OntTypeAnswerValidator.java} (76%) rename backend/src/test/java/reviewme/review/service/dto/request/{EitherTextOrCheckboxValidatorTest.java => OneTypeAnswerValidatorTest.java} (97%) diff --git a/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckbox.java b/backend/src/main/java/reviewme/review/service/dto/request/OneTypeAnswer.java similarity index 67% rename from backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckbox.java rename to backend/src/main/java/reviewme/review/service/dto/request/OneTypeAnswer.java index 7512992fa..40cc9a5ce 100644 --- a/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckbox.java +++ b/backend/src/main/java/reviewme/review/service/dto/request/OneTypeAnswer.java @@ -9,10 +9,10 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = EitherTextOrCheckboxValidator.class) -public @interface EitherTextOrCheckbox { +@Constraint(validatedBy = OntTypeAnswerValidator.class) +public @interface OneTypeAnswer { - String message() default "선택형 응답과 서술형 응답 중 하나만 입력해주세요."; + String message() default ""; Class[] groups() default {}; diff --git a/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidator.java b/backend/src/main/java/reviewme/review/service/dto/request/OntTypeAnswerValidator.java similarity index 76% rename from backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidator.java rename to backend/src/main/java/reviewme/review/service/dto/request/OntTypeAnswerValidator.java index b0324fb10..c39577031 100644 --- a/backend/src/main/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidator.java +++ b/backend/src/main/java/reviewme/review/service/dto/request/OntTypeAnswerValidator.java @@ -3,8 +3,8 @@ import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; -public class EitherTextOrCheckboxValidator - implements ConstraintValidator { +public class OntTypeAnswerValidator + implements ConstraintValidator { @Override public boolean isValid(ReviewAnswerRequest request, ConstraintValidatorContext context) { diff --git a/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java b/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java index ea653eb12..0fd959097 100644 --- a/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java +++ b/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java @@ -4,7 +4,7 @@ import jakarta.validation.constraints.NotNull; import java.util.List; -@EitherTextOrCheckbox +@OneTypeAnswer(message = "한 가지 유형의 답변만 가능해요.") public record ReviewAnswerRequest( @NotNull(message = "질문 ID를 입력해주세요.") diff --git a/backend/src/test/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidatorTest.java b/backend/src/test/java/reviewme/review/service/dto/request/OneTypeAnswerValidatorTest.java similarity index 97% rename from backend/src/test/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidatorTest.java rename to backend/src/test/java/reviewme/review/service/dto/request/OneTypeAnswerValidatorTest.java index 93f1f52eb..35517798f 100644 --- a/backend/src/test/java/reviewme/review/service/dto/request/EitherTextOrCheckboxValidatorTest.java +++ b/backend/src/test/java/reviewme/review/service/dto/request/OneTypeAnswerValidatorTest.java @@ -15,7 +15,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class EitherTextOrCheckboxValidatorTest { +class OneTypeAnswerValidatorTest { private Validator validator;