Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] feat: 리뷰 등록 요청 dto 형식 검증 로직을 SpringBeanValidator 를 통해 구현 #668

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scale이라는 타입이 추가되면 어떡해요? EitherTextOrCheckboxOrScale이라고 이름 바꿔야 하나요..? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"해당하는 질문 타입에 대해서만 답변할 수 있다" 이런 의미를 담도록 바꿔보겠습니다 ㅎㅎ


String message() default "선택형 응답과 서술형 응답 중 하나만 입력해주세요.";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아루의 코멘트에 이어서 에러 메세지도 질문 타입 추가에 영향을 받지 않도록 수정되면 좋을 것 같아요.


Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package reviewme.review.service.dto.request;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class EitherTextOrCheckboxValidator
implements ConstraintValidator<EitherTextOrCheckbox, ReviewAnswerRequest> {

@Override
public boolean isValid(ReviewAnswerRequest request, ConstraintValidatorContext context) {
if (request.selectedOptionIds() != null) {
return request.text() == null;
}
return request.text() != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import jakarta.validation.constraints.NotNull;
import java.util.List;

@EitherTextOrCheckbox
public record ReviewAnswerRequest(

@NotNull(message = "질문 ID를 입력해주세요.")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,6 +10,7 @@ public record ReviewRegisterRequest(
@NotBlank(message = "리뷰 요청 코드를 입력해주세요.")
String reviewRequestCode,

@Valid
@NotEmpty(message = "답변 내용을 입력해주세요.")
List<ReviewAnswerRequest> answers
) {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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<Long> selectedOptionIds, String text) {
// given
ReviewAnswerRequest request = new ReviewAnswerRequest(questionId, selectedOptionIds, text);

// when
Set<ConstraintViolation<ReviewAnswerRequest>> violations = validator.validate(request);

// then
assertThat(violations).isEmpty();
}

static Stream<Arguments> 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<ConstraintViolation<ReviewAnswerRequest>> violations = validator.validate(request);

// then
assertThat(violations).hasSize(1);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}
Loading