From 50f5e6efe77ad28b588aee269f122143260e6be5 Mon Sep 17 00:00:00 2001 From: Wo-ogie Date: Sun, 31 Mar 2024 19:58:51 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20#37=20=EB=B3=B8=EC=9D=B8=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=BD=94=EB=93=9C=20=EA=B2=80=EC=A6=9D=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 23 +++++++- .../request/VerifyUserAuthCodeRequest.java | 25 ++++++++ .../response/VerifyUserAUthCodeResponse.java | 14 +++++ .../common/auth/entity/UserAuthCode.java | 9 +++ .../exception/InvalidAuthCodeException.java | 11 ++++ .../UserAuthCodeNotFoundException.java | 11 ++++ .../UserAuthCodeRedisRepository.java | 12 +++- .../repository/UserAuthCodeRepository.java | 6 +- .../auth/service/UserAuthCodeService.java | 24 ++++++++ .../hertz/common/config/SecurityConfig.java | 1 + .../constant/CustomExceptionType.java | 2 + .../message/service/MessageService.java | 9 +++ .../UserAuthCodeRedisRepositoryTest.java | 43 +++++++++++--- .../auth/controller/AuthControllerTest.java | 26 +++++++++ .../auth/service/UserAuthCodeServiceTest.java | 58 +++++++++++++++++++ .../com/ajou/hertz/util/ReflectionUtils.java | 12 ++++ 16 files changed, 272 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/ajou/hertz/common/auth/dto/request/VerifyUserAuthCodeRequest.java create mode 100644 src/main/java/com/ajou/hertz/common/auth/dto/response/VerifyUserAUthCodeResponse.java create mode 100644 src/main/java/com/ajou/hertz/common/auth/exception/InvalidAuthCodeException.java create mode 100644 src/main/java/com/ajou/hertz/common/auth/exception/UserAuthCodeNotFoundException.java diff --git a/src/main/java/com/ajou/hertz/common/auth/controller/AuthController.java b/src/main/java/com/ajou/hertz/common/auth/controller/AuthController.java index 8b9de82..987121d 100644 --- a/src/main/java/com/ajou/hertz/common/auth/controller/AuthController.java +++ b/src/main/java/com/ajou/hertz/common/auth/controller/AuthController.java @@ -2,7 +2,6 @@ import static com.ajou.hertz.common.constant.GlobalConstants.*; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,8 +11,10 @@ import com.ajou.hertz.common.auth.dto.request.KakaoLoginRequest; import com.ajou.hertz.common.auth.dto.request.LoginRequest; import com.ajou.hertz.common.auth.dto.request.SendUserAuthCodeRequest; +import com.ajou.hertz.common.auth.dto.request.VerifyUserAuthCodeRequest; import com.ajou.hertz.common.auth.dto.response.JwtTokenInfoResponse; import com.ajou.hertz.common.auth.dto.response.SendUserAuthCodeResponse; +import com.ajou.hertz.common.auth.dto.response.VerifyUserAUthCodeResponse; import com.ajou.hertz.common.auth.service.AuthService; import com.ajou.hertz.common.auth.service.UserAuthCodeService; import com.ajou.hertz.common.kakao.service.KakaoService; @@ -86,4 +87,24 @@ public SendUserAuthCodeResponse sendUserAuthCodeV1(@RequestBody @Valid SendUserA userAuthCodeService.sendUserAuthCodeViaSms(sendCodeRequest.getPhoneNumber()); return new SendUserAuthCodeResponse(true); } + + @Operation( + summary = "일회용 본인 인증 코드 검증", + description = """ +

전달된 코드가 '일회용 본인 인증 코드 발송 API'를 통해 사용자에게 발송된 본인 인증 코드인지 검증합니다. +

검증에 성공한 경우, 생성된 본인 인증코드는 더 이상 사용할 수 없습니다. (일회용) + """ + ) + @ApiResponses({ + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "400", description = "[2005] 전달된 인증 코드 또는 전화번호가 유효하지 않은 경우. 인증 코드가 만료되었거나 잘못된 코드가 전송되었을 수 있습니다", content = @Content), + @ApiResponse(responseCode = "404", description = "[2004] 본인 인증 코드 발행 이력을 찾을 수 없는 경우. 인증 코드가 만료되었거나 잘못된 코드가 전송되었을 수 있습니다.", content = @Content) + }) + @PostMapping(value = "/codes/verify", headers = API_VERSION_HEADER_NAME + "=" + 1) + public VerifyUserAUthCodeResponse verifyUserAUthCodeV1( + @RequestBody @Valid VerifyUserAuthCodeRequest verifyRequest + ) { + userAuthCodeService.verifyUserAuthCode(verifyRequest.getCode(), verifyRequest.getPhoneNumber()); + return new VerifyUserAUthCodeResponse(true); + } } diff --git a/src/main/java/com/ajou/hertz/common/auth/dto/request/VerifyUserAuthCodeRequest.java b/src/main/java/com/ajou/hertz/common/auth/dto/request/VerifyUserAuthCodeRequest.java new file mode 100644 index 0000000..ddaacf1 --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/auth/dto/request/VerifyUserAuthCodeRequest.java @@ -0,0 +1,25 @@ +package com.ajou.hertz.common.auth.dto.request; + +import com.ajou.hertz.common.validator.PhoneNumber; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class VerifyUserAuthCodeRequest { + + @Schema(description = "본인 인증 코드를 전달받은 사용자의 전화번호", example = "01012345678") + @NotBlank + @PhoneNumber + private String phoneNumber; + + @Schema(description = "사용자가 전달받은 인증 코드", example = "1a2b3c4d") + @NotBlank + private String code; +} diff --git a/src/main/java/com/ajou/hertz/common/auth/dto/response/VerifyUserAUthCodeResponse.java b/src/main/java/com/ajou/hertz/common/auth/dto/response/VerifyUserAUthCodeResponse.java new file mode 100644 index 0000000..bdd3415 --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/auth/dto/response/VerifyUserAUthCodeResponse.java @@ -0,0 +1,14 @@ +package com.ajou.hertz.common.auth.dto.response; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class VerifyUserAUthCodeResponse { + + private Boolean isSuccess; +} diff --git a/src/main/java/com/ajou/hertz/common/auth/entity/UserAuthCode.java b/src/main/java/com/ajou/hertz/common/auth/entity/UserAuthCode.java index c95746c..68e4f97 100644 --- a/src/main/java/com/ajou/hertz/common/auth/entity/UserAuthCode.java +++ b/src/main/java/com/ajou/hertz/common/auth/entity/UserAuthCode.java @@ -3,6 +3,8 @@ import java.io.Serializable; import java.time.LocalDateTime; +import com.ajou.hertz.common.auth.exception.InvalidAuthCodeException; + import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -19,4 +21,11 @@ public class UserAuthCode implements Serializable { private String code; private String phoneNumber; private LocalDateTime createdAt; + + public void verify(String code, String phoneNumber) { + if (!this.code.equals(code) + || !this.phoneNumber.equals(phoneNumber)) { + throw new InvalidAuthCodeException(); + } + } } diff --git a/src/main/java/com/ajou/hertz/common/auth/exception/InvalidAuthCodeException.java b/src/main/java/com/ajou/hertz/common/auth/exception/InvalidAuthCodeException.java new file mode 100644 index 0000000..790c54a --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/auth/exception/InvalidAuthCodeException.java @@ -0,0 +1,11 @@ +package com.ajou.hertz.common.auth.exception; + +import com.ajou.hertz.common.exception.BadRequestException; +import com.ajou.hertz.common.exception.constant.CustomExceptionType; + +public class InvalidAuthCodeException extends BadRequestException { + + public InvalidAuthCodeException() { + super(CustomExceptionType.USER_AUTH_CODE_NOT_FOUND); + } +} diff --git a/src/main/java/com/ajou/hertz/common/auth/exception/UserAuthCodeNotFoundException.java b/src/main/java/com/ajou/hertz/common/auth/exception/UserAuthCodeNotFoundException.java new file mode 100644 index 0000000..d264529 --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/auth/exception/UserAuthCodeNotFoundException.java @@ -0,0 +1,11 @@ +package com.ajou.hertz.common.auth.exception; + +import com.ajou.hertz.common.exception.NotFoundException; +import com.ajou.hertz.common.exception.constant.CustomExceptionType; + +public class UserAuthCodeNotFoundException extends NotFoundException { + + public UserAuthCodeNotFoundException() { + super(CustomExceptionType.USER_AUTH_CODE_NOT_FOUND); + } +} diff --git a/src/main/java/com/ajou/hertz/common/auth/repository/UserAuthCodeRedisRepository.java b/src/main/java/com/ajou/hertz/common/auth/repository/UserAuthCodeRedisRepository.java index 4177e95..0e8d166 100644 --- a/src/main/java/com/ajou/hertz/common/auth/repository/UserAuthCodeRedisRepository.java +++ b/src/main/java/com/ajou/hertz/common/auth/repository/UserAuthCodeRedisRepository.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Repository; import com.ajou.hertz.common.auth.entity.UserAuthCode; +import com.ajou.hertz.common.auth.exception.UserAuthCodeNotFoundException; import lombok.RequiredArgsConstructor; @@ -28,8 +29,13 @@ public UserAuthCode save(UserAuthCode userAuthCode) { } @Override - public Optional findByCode(String code) { - UserAuthCode userAuthCode = redisTemplate.opsForValue().get(code); - return Optional.ofNullable(userAuthCode); + public UserAuthCode getByCode(String code) { + return Optional.ofNullable(redisTemplate.opsForValue().get(code)) + .orElseThrow(UserAuthCodeNotFoundException::new); + } + + @Override + public void delete(UserAuthCode userAuthCode) { + redisTemplate.delete(userAuthCode.getCode()); } } diff --git a/src/main/java/com/ajou/hertz/common/auth/repository/UserAuthCodeRepository.java b/src/main/java/com/ajou/hertz/common/auth/repository/UserAuthCodeRepository.java index da39d5e..e7d39be 100644 --- a/src/main/java/com/ajou/hertz/common/auth/repository/UserAuthCodeRepository.java +++ b/src/main/java/com/ajou/hertz/common/auth/repository/UserAuthCodeRepository.java @@ -1,12 +1,12 @@ package com.ajou.hertz.common.auth.repository; -import java.util.Optional; - import com.ajou.hertz.common.auth.entity.UserAuthCode; public interface UserAuthCodeRepository { UserAuthCode save(UserAuthCode userAuthCode); - Optional findByCode(String code); + UserAuthCode getByCode(String code); + + void delete(UserAuthCode userAuthCode); } diff --git a/src/main/java/com/ajou/hertz/common/auth/service/UserAuthCodeService.java b/src/main/java/com/ajou/hertz/common/auth/service/UserAuthCodeService.java index 71e66df..ff2654a 100644 --- a/src/main/java/com/ajou/hertz/common/auth/service/UserAuthCodeService.java +++ b/src/main/java/com/ajou/hertz/common/auth/service/UserAuthCodeService.java @@ -22,6 +22,11 @@ public class UserAuthCodeService { private final MessageService messageService; private final UserAuthCodeRepository userAuthCodeRepository; + /** + * 본인 인증 코드를 발송한다. + * + * @param targetPhoneNumber 인증 코드를 발송할 사용자의 전화번호 + */ @Transactional public void sendUserAuthCodeViaSms(@NonNull String targetPhoneNumber) { String authCode = generateRandomAuthCode(); @@ -33,6 +38,25 @@ public void sendUserAuthCodeViaSms(@NonNull String targetPhoneNumber) { ); } + /** + * 전달된 코드가 유효한 코드인지 검증한다. + * 만약 성공적으로 유효성이 검증되었다면, 저장된 본인 인증 코드 발급 이력(UserAuthCode)을 삭제한다. + * + * @param code 유효한 코드인지 검증할 인증 코드 + * @param phoneNumber 인증 코드를 전달받은 사용자의 전화번호 + */ + @Transactional + public void verifyUserAuthCode(String code, String phoneNumber) { + UserAuthCode userAuthCode = userAuthCodeRepository.getByCode(code); + userAuthCode.verify(code, phoneNumber); + userAuthCodeRepository.delete(userAuthCode); + } + + /** + * 영문과 숫자가 포함된 랜덤한 8자리 인증 코드를 생성한다. + * + * @return 생성된 랜덤 인증 코드 + */ private String generateRandomAuthCode() { return UUID.randomUUID().toString().substring(0, USER_AUTH_CODE_LEN); } diff --git a/src/main/java/com/ajou/hertz/common/config/SecurityConfig.java b/src/main/java/com/ajou/hertz/common/config/SecurityConfig.java index 17aa806..3b50b6c 100644 --- a/src/main/java/com/ajou/hertz/common/config/SecurityConfig.java +++ b/src/main/java/com/ajou/hertz/common/config/SecurityConfig.java @@ -47,6 +47,7 @@ public class SecurityConfig { AUTH_WHITE_LIST.put("/api/auth/kakao/login", POST); AUTH_WHITE_LIST.put("/api/users", POST); AUTH_WHITE_LIST.put("/api/auth/codes/send", POST); + AUTH_WHITE_LIST.put("/api/auth/codes/verify", POST); AUTH_WHITE_LIST.put("/api/users/existence", GET); AUTH_WHITE_LIST.put("/api/users/email", GET); AUTH_WHITE_LIST.put("/api/administrative-areas/sido", GET); diff --git a/src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java b/src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java index 6193219..a86cfc2 100644 --- a/src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java +++ b/src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java @@ -30,6 +30,8 @@ public enum CustomExceptionType { UNAUTHORIZED(2001, "유효하지 않은 인증 정보로 인해 인증 과정에서 문제가 발생하였습니다."), TOKEN_VALIDATE(2002, "유효하지 않은 token입니다. Token 값이 잘못되었거나 만료되어 유효하지 않은 경우로 token 갱신이 필요합니다."), PASSWORD_MISMATCH(2003, "비밀번호가 일치하지 않습니다."), + USER_AUTH_CODE_NOT_FOUND(2004, "본인 인증 코드 발행 이력을 찾을 수 없습니다. 인증 코드가 만료되었거나 잘못된 코드가 전송되었을 수 있습니다. 다시 시도해주세요."), + INVALID_AUTH_CODE(2005, "유효하지 않은 인증 코드 또는 전화번호입니다. 인증 코드가 만료되었거나 잘못된 정보가 전송되었을 수 있습니다. 다시 시도해주세요."), /** * 유저 관련 예외 diff --git a/src/main/java/com/ajou/hertz/common/message/service/MessageService.java b/src/main/java/com/ajou/hertz/common/message/service/MessageService.java index 4365a0e..ee34de0 100644 --- a/src/main/java/com/ajou/hertz/common/message/service/MessageService.java +++ b/src/main/java/com/ajou/hertz/common/message/service/MessageService.java @@ -1,6 +1,15 @@ package com.ajou.hertz.common.message.service; +import com.ajou.hertz.common.message.exception.SendMessageException; + public interface MessageService { + /** + * 전달된 메시지 내용으로 사용자에게 문자를 발송한다. + * + * @param message 발송할 문자 내용 + * @param targetPhoneNumber 발송하고자 하는 사용자의 전화번호 + * @throws SendMessageException 메시지 발송 중 오류가 발생한 경우 + */ void sendShortMessage(String message, String targetPhoneNumber); } diff --git a/src/test/java/com/ajou/hertz/integration/common/auth/repository/UserAuthCodeRedisRepositoryTest.java b/src/test/java/com/ajou/hertz/integration/common/auth/repository/UserAuthCodeRedisRepositoryTest.java index 7f98902..9c09ed1 100644 --- a/src/test/java/com/ajou/hertz/integration/common/auth/repository/UserAuthCodeRedisRepositoryTest.java +++ b/src/test/java/com/ajou/hertz/integration/common/auth/repository/UserAuthCodeRedisRepositoryTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.*; import java.time.LocalDateTime; -import java.util.Optional; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; @@ -13,9 +12,10 @@ import org.springframework.test.context.ActiveProfiles; import com.ajou.hertz.common.auth.entity.UserAuthCode; +import com.ajou.hertz.common.auth.exception.UserAuthCodeNotFoundException; import com.ajou.hertz.common.auth.repository.UserAuthCodeRedisRepository; -@Disabled("TODO: 추후 testcontainers 또는 embedded redis 환경 구축 후 테스트 대상에 포함시킬 예정") +// @Disabled("TODO: 추후 testcontainers 또는 embedded redis 환경 구축 후 테스트 대상에 포함시킬 예정") @DisplayName("[Integration] Repository(Redis) - user auth code") @ActiveProfiles("test") @SpringBootTest @@ -37,8 +37,8 @@ public UserAuthCodeRedisRepositoryTest(UserAuthCodeRedisRepository sut) { sut.save(userAuthCode); // then - Optional result = sut.findByCode(userAuthCode.getCode()); - assertThat(result).isNotNull(); + UserAuthCode result = sut.getByCode(userAuthCode.getCode()); + assertThat(result.getCode()).isEqualTo(userAuthCode.getCode()); } @Test @@ -49,9 +49,38 @@ public UserAuthCodeRedisRepositoryTest(UserAuthCodeRedisRepository sut) { sut.save(userAuthCode); // when - Optional result = sut.findByCode(code); + UserAuthCode result = sut.getByCode(code); // then - assertThat(result).isNotNull(); + assertThat(result.getCode()).isEqualTo(userAuthCode.getCode()); } -} \ No newline at end of file + + @Test + void 존재하지_않는_코드로_유저_인증_코드를_단건_조회하면_예외가_발생한다() { + // given + sut.save( + new UserAuthCode("code", "01012345678", LocalDateTime.now()) + ); + + // when + Throwable ex = catchThrowable(() -> sut.getByCode("code-not-exists")); + + // then + assertThat(ex).isInstanceOf(UserAuthCodeNotFoundException.class); + } + + @Test + void 전달된_코드로_유저_인증_코드를_삭제한다() { + // given + String code = "code"; + UserAuthCode userAuthCode = new UserAuthCode(code, "01012345678", LocalDateTime.now()); + sut.save(userAuthCode); + + // when + sut.delete(userAuthCode); + + // then + assertThatThrownBy(() -> sut.getByCode(code)) + .isInstanceOf(UserAuthCodeNotFoundException.class); + } +} diff --git a/src/test/java/com/ajou/hertz/unit/common/auth/controller/AuthControllerTest.java b/src/test/java/com/ajou/hertz/unit/common/auth/controller/AuthControllerTest.java index 0f2afc1..bdaaeeb 100644 --- a/src/test/java/com/ajou/hertz/unit/common/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/ajou/hertz/unit/common/auth/controller/AuthControllerTest.java @@ -21,6 +21,7 @@ import com.ajou.hertz.common.auth.dto.request.KakaoLoginRequest; import com.ajou.hertz.common.auth.dto.request.LoginRequest; import com.ajou.hertz.common.auth.dto.request.SendUserAuthCodeRequest; +import com.ajou.hertz.common.auth.dto.request.VerifyUserAuthCodeRequest; import com.ajou.hertz.common.auth.service.AuthService; import com.ajou.hertz.common.auth.service.UserAuthCodeService; import com.ajou.hertz.common.kakao.service.KakaoService; @@ -111,6 +112,26 @@ public AuthControllerTest(MockMvc mvc, ObjectMapper objectMapper) { verifyEveryMocksShouldHaveNoMoreInteractions(); } + @Test + void 주어진_코드가_유효한_본인_인증_코드인지_검증한다() throws Exception { + // given + String code = "code"; + String phoneNumber = "01012345678"; + VerifyUserAuthCodeRequest verifyUserAuthCodeRequest = createVerifyUserAuthCodeRequest(phoneNumber, code); + willDoNothing().given(userAuthCodeService).verifyUserAuthCode(code, phoneNumber); + + // when & then + mvc.perform( + post("/api/auth/codes/verify") + .header(API_VERSION_HEADER_NAME, 1) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(verifyUserAuthCodeRequest)) + ) + .andExpect(status().isOk()); + then(userAuthCodeService).should().verifyUserAuthCode(code, phoneNumber); + verifyEveryMocksShouldHaveNoMoreInteractions(); + } + private void verifyEveryMocksShouldHaveNoMoreInteractions() { then(authService).shouldHaveNoMoreInteractions(); then(kakaoService).shouldHaveNoMoreInteractions(); @@ -129,6 +150,11 @@ private SendUserAuthCodeRequest createSendUserAuthCodeRequest(String targetPhone return ReflectionUtils.createSendUserAuthCodeRequest(targetPhoneNumber); } + private VerifyUserAuthCodeRequest createVerifyUserAuthCodeRequest(String phoneNumber, String code) throws + Exception { + return ReflectionUtils.createVerifyUserAuthCodeRequest(phoneNumber, code); + } + private JwtTokenInfoDto createJwtTokenInfoDto() { return new JwtTokenInfoDto( "access-token", diff --git a/src/test/java/com/ajou/hertz/unit/common/auth/service/UserAuthCodeServiceTest.java b/src/test/java/com/ajou/hertz/unit/common/auth/service/UserAuthCodeServiceTest.java index 8c96498..a9c03e2 100644 --- a/src/test/java/com/ajou/hertz/unit/common/auth/service/UserAuthCodeServiceTest.java +++ b/src/test/java/com/ajou/hertz/unit/common/auth/service/UserAuthCodeServiceTest.java @@ -1,5 +1,6 @@ package com.ajou.hertz.unit.common.auth.service; +import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; import java.time.LocalDateTime; @@ -12,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.ajou.hertz.common.auth.entity.UserAuthCode; +import com.ajou.hertz.common.auth.exception.InvalidAuthCodeException; import com.ajou.hertz.common.auth.repository.UserAuthCodeRepository; import com.ajou.hertz.common.auth.service.UserAuthCodeService; import com.ajou.hertz.common.message.service.MessageService; @@ -46,6 +48,62 @@ class UserAuthCodeServiceTest { verifyEveryMocksShouldHaveNoMoreInteractions(); } + @Test + void 주어진_코드가_유효한_본인_인증_코드인지_검증한다() { + // given + String code = "code"; + String phoneNumber = "01012345678"; + UserAuthCode userAuthCode = new UserAuthCode(code, phoneNumber, LocalDateTime.now()); + given(userAuthCodeRepository.getByCode(code)) + .willReturn(userAuthCode); + willDoNothing().given(userAuthCodeRepository).delete(userAuthCode); + + // when + sut.verifyUserAuthCode(code, phoneNumber); + + // then + then(userAuthCodeRepository).should().getByCode(code); + then(userAuthCodeRepository).should().delete(userAuthCode); + verifyEveryMocksShouldHaveNoMoreInteractions(); + } + + @Test + void 유효하지_않은_코드가_주어지고_유효한_본인_인증_코드인지_검증하면_예외가_발생한다() { + // given + String code = "code"; + String invalidCode = "invalid-code"; + String phoneNumber = "01012345678"; + UserAuthCode userAuthCode = new UserAuthCode(code, phoneNumber, LocalDateTime.now()); + given(userAuthCodeRepository.getByCode(invalidCode)) + .willReturn(userAuthCode); + + // when + Throwable ex = catchThrowable(() -> sut.verifyUserAuthCode(invalidCode, phoneNumber)); + + // then + then(userAuthCodeRepository).should().getByCode(invalidCode); + assertThat(ex).isInstanceOf(InvalidAuthCodeException.class); + verifyEveryMocksShouldHaveNoMoreInteractions(); + } + + @Test + void 본인_인증_코드를_전달받은_사용자가_아닌_다른_사용자의_전화번호가_주어지고_주어진_전화번호로_유효한_본인_인증_코드인지_검증하면_예외가_발생한다() { + // given + String code = "code"; + String phoneNumber = "01012345678"; + UserAuthCode userAuthCode = new UserAuthCode(code, phoneNumber, LocalDateTime.now()); + given(userAuthCodeRepository.getByCode(code)) + .willReturn(userAuthCode); + + // when + Throwable ex = catchThrowable(() -> sut.verifyUserAuthCode(code, "01000000000")); + + // then + then(userAuthCodeRepository).should().getByCode(code); + assertThat(ex).isInstanceOf(InvalidAuthCodeException.class); + verifyEveryMocksShouldHaveNoMoreInteractions(); + } + private void verifyEveryMocksShouldHaveNoMoreInteractions() { then(messageService).shouldHaveNoMoreInteractions(); then(userAuthCodeRepository).shouldHaveNoMoreInteractions(); diff --git a/src/test/java/com/ajou/hertz/util/ReflectionUtils.java b/src/test/java/com/ajou/hertz/util/ReflectionUtils.java index 50e383b..92846ad 100644 --- a/src/test/java/com/ajou/hertz/util/ReflectionUtils.java +++ b/src/test/java/com/ajou/hertz/util/ReflectionUtils.java @@ -12,6 +12,7 @@ import com.ajou.hertz.common.auth.dto.request.KakaoLoginRequest; import com.ajou.hertz.common.auth.dto.request.LoginRequest; import com.ajou.hertz.common.auth.dto.request.SendUserAuthCodeRequest; +import com.ajou.hertz.common.auth.dto.request.VerifyUserAuthCodeRequest; import com.ajou.hertz.common.dto.AddressDto; import com.ajou.hertz.common.dto.request.AddressRequest; import com.ajou.hertz.common.entity.Address; @@ -888,6 +889,17 @@ public static SendUserAuthCodeRequest createSendUserAuthCodeRequest(String phone return constructor.newInstance(phoneNumber); } + public static VerifyUserAuthCodeRequest createVerifyUserAuthCodeRequest( + String phoneNumber, + String code + ) throws Exception { + Constructor constructor = VerifyUserAuthCodeRequest.class.getDeclaredConstructor( + String.class, String.class + ); + constructor.setAccessible(true); + return constructor.newInstance(phoneNumber, code); + } + /** * DTO(Response) */