From 8d1224d4732342ebc86ff39fe41246eec04cb523 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:22:36 +0900 Subject: [PATCH] =?UTF-8?q?feat-be:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#848)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Kwoun Ki Ho --- backend/docker-compose.dev.yml | 14 ++ backend/src/docs/asciidoc/email.adoc | 28 ++++ .../java/com/cruru/config/WebMvcConfig.java | 2 + .../email/controller/EmailController.java | 19 ++- .../{dto => request}/EmailRequest.java | 2 +- .../request/SendVerificationCodeRequest.java | 12 ++ .../controller/request/VerifyCodeRequest.java | 15 ++ .../VerificationCodeMismatchException.java | 12 ++ .../VerificationCodeNotFoundException.java | 12 ++ .../com/cruru/email/facade/EmailFacade.java | 23 ++- .../cruru/email/service/EmailRedisClient.java | 25 +++ .../com/cruru/email/service/EmailService.java | 18 +++ .../com/cruru/email/util/EmailTemplate.java | 28 ++++ .../email/util/VerificationCodeUtil.java | 28 ++++ backend/src/main/resources/application.yml | 6 +- .../email/controller/EmailControllerTest.java | 143 ++++++++++++++++++ .../cruru/email/facade/EmailFacadeTest.java | 64 +++++++- 17 files changed, 444 insertions(+), 7 deletions(-) rename backend/src/main/java/com/cruru/email/controller/{dto => request}/EmailRequest.java (95%) create mode 100644 backend/src/main/java/com/cruru/email/controller/request/SendVerificationCodeRequest.java create mode 100644 backend/src/main/java/com/cruru/email/controller/request/VerifyCodeRequest.java create mode 100644 backend/src/main/java/com/cruru/email/exception/badrequest/VerificationCodeMismatchException.java create mode 100644 backend/src/main/java/com/cruru/email/exception/badrequest/VerificationCodeNotFoundException.java create mode 100644 backend/src/main/java/com/cruru/email/service/EmailRedisClient.java create mode 100644 backend/src/main/java/com/cruru/email/util/EmailTemplate.java create mode 100644 backend/src/main/java/com/cruru/email/util/VerificationCodeUtil.java diff --git a/backend/docker-compose.dev.yml b/backend/docker-compose.dev.yml index 270764bbd..161f1ce86 100644 --- a/backend/docker-compose.dev.yml +++ b/backend/docker-compose.dev.yml @@ -23,6 +23,7 @@ services: platform: linux/arm64 depends_on: - database-mysql + - redis restart: always image: ${DOCKER_REPO_NAME}/cruru:${DOCKER_IMAGE_VERSION_TAG} ports: @@ -39,6 +40,19 @@ services: cruru_network: ipv4_address: ${APP_IP_ADDRESS} + redis: + container_name: redis-container + image: redis:latest + environment: + TZ: Asia/Seoul + REDIS_PASSWORD: ${REDIS_PASSWORD} + ports: + - ${REDIS_PORT}:6379 + command: [ "redis-server", "--requirepass", "${REDIS_PASSWORD}" ] + networks: + cruru_network: + ipv4_address: ${REDIS_IP_ADDRESS} + promtail: environment: TZ: Asia/Seoul diff --git a/backend/src/docs/asciidoc/email.adoc b/backend/src/docs/asciidoc/email.adoc index ea8f7179d..dcd669256 100644 --- a/backend/src/docs/asciidoc/email.adoc +++ b/backend/src/docs/asciidoc/email.adoc @@ -13,3 +13,31 @@ operation::email/send-fail/invalid-email[snippets="http-request,request-cookies, ==== 실패: 존재하지 않는 동아리 operation::email/send-fail/club-not-found[snippets="http-request,request-cookies,request-parts,http-response"] + +=== 이메일 인증 번호 발송 + +==== 성공 + +operation::email/verification-code[snippets="http-request,request-fields,http-response"] + +==== 실패: 이메일 형식이 올바르지 않은 이메일 형식 + +operation::email/verification-code-fail/invalid-email[snippets="http-request,request-fields,http-response"] + +=== 이메일 인증 확인 + +==== 성공 + +operation::email/verify-code[snippets="http-request,request-fields,http-response"] + +==== 실패: 이메일 형식이 올바르지 않은 이메일 형식 + +operation::email/verify-code-fail/invalid-email[snippets="http-request,request-fields,http-response"] + +==== 실패: 인증 번호가 없는 이메일 + +operation::email/verify-code-fail/code-not-found[snippets="http-request,request-fields,http-response"] + +==== 실패: 인증 번호가 다른 이메일 + +operation::email/verify-code-fail/code-mismatch[snippets="http-request,request-fields,http-response"] diff --git a/backend/src/main/java/com/cruru/config/WebMvcConfig.java b/backend/src/main/java/com/cruru/config/WebMvcConfig.java index c896bf63e..0d331cd04 100644 --- a/backend/src/main/java/com/cruru/config/WebMvcConfig.java +++ b/backend/src/main/java/com/cruru/config/WebMvcConfig.java @@ -42,6 +42,8 @@ public void addInterceptors(InterceptorRegistry registry) { .excludePathPatterns("/**/signup") .excludePathPatterns("/**/login") .excludePathPatterns("/**/applyform/*/submit") + .excludePathPatterns("/**/emails/verification-code") + .excludePathPatterns("/**/emails/verify-code") .excludePathPatterns("/"); } diff --git a/backend/src/main/java/com/cruru/email/controller/EmailController.java b/backend/src/main/java/com/cruru/email/controller/EmailController.java index 8f624b8f8..f478d24e1 100644 --- a/backend/src/main/java/com/cruru/email/controller/EmailController.java +++ b/backend/src/main/java/com/cruru/email/controller/EmailController.java @@ -1,7 +1,9 @@ package com.cruru.email.controller; import com.cruru.auth.annotation.ValidAuth; -import com.cruru.email.controller.dto.EmailRequest; +import com.cruru.email.controller.request.EmailRequest; +import com.cruru.email.controller.request.SendVerificationCodeRequest; +import com.cruru.email.controller.request.VerifyCodeRequest; import com.cruru.email.facade.EmailFacade; import com.cruru.global.LoginProfile; import jakarta.validation.Valid; @@ -9,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -28,4 +31,18 @@ public ResponseEntity send( emailFacade.send(request); return ResponseEntity.ok().build(); } + + @PostMapping("/verification-code") + public ResponseEntity sendVerificationCode( + @RequestBody @Valid SendVerificationCodeRequest request + ) { + emailFacade.sendVerificationCode(request); + return ResponseEntity.ok().build(); + } + + @PostMapping("/verify-code") + public ResponseEntity verifyCode(@Valid @RequestBody VerifyCodeRequest request) { + emailFacade.verifyCode(request); + return ResponseEntity.ok().build(); + } } diff --git a/backend/src/main/java/com/cruru/email/controller/dto/EmailRequest.java b/backend/src/main/java/com/cruru/email/controller/request/EmailRequest.java similarity index 95% rename from backend/src/main/java/com/cruru/email/controller/dto/EmailRequest.java rename to backend/src/main/java/com/cruru/email/controller/request/EmailRequest.java index 946b29d09..c3356907d 100644 --- a/backend/src/main/java/com/cruru/email/controller/dto/EmailRequest.java +++ b/backend/src/main/java/com/cruru/email/controller/request/EmailRequest.java @@ -1,4 +1,4 @@ -package com.cruru.email.controller.dto; +package com.cruru.email.controller.request; import com.cruru.applicant.domain.Applicant; import com.cruru.auth.annotation.RequireAuth; diff --git a/backend/src/main/java/com/cruru/email/controller/request/SendVerificationCodeRequest.java b/backend/src/main/java/com/cruru/email/controller/request/SendVerificationCodeRequest.java new file mode 100644 index 000000000..1d002e8ba --- /dev/null +++ b/backend/src/main/java/com/cruru/email/controller/request/SendVerificationCodeRequest.java @@ -0,0 +1,12 @@ +package com.cruru.email.controller.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +public record SendVerificationCodeRequest( + @NotBlank + @Email + String email +) { + +} diff --git a/backend/src/main/java/com/cruru/email/controller/request/VerifyCodeRequest.java b/backend/src/main/java/com/cruru/email/controller/request/VerifyCodeRequest.java new file mode 100644 index 000000000..9caaae36a --- /dev/null +++ b/backend/src/main/java/com/cruru/email/controller/request/VerifyCodeRequest.java @@ -0,0 +1,15 @@ +package com.cruru.email.controller.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +public record VerifyCodeRequest( + @NotBlank + @Email + String email, + + @NotBlank + String verificationCode +) { + +} diff --git a/backend/src/main/java/com/cruru/email/exception/badrequest/VerificationCodeMismatchException.java b/backend/src/main/java/com/cruru/email/exception/badrequest/VerificationCodeMismatchException.java new file mode 100644 index 000000000..5cf6f9bfd --- /dev/null +++ b/backend/src/main/java/com/cruru/email/exception/badrequest/VerificationCodeMismatchException.java @@ -0,0 +1,12 @@ +package com.cruru.email.exception.badrequest; + +import com.cruru.advice.badrequest.BadRequestException; + +public class VerificationCodeMismatchException extends BadRequestException { + + private static final String MESSAGE = "인증 코드가 일치하지 않습니다."; + + public VerificationCodeMismatchException() { + super(MESSAGE); + } +} diff --git a/backend/src/main/java/com/cruru/email/exception/badrequest/VerificationCodeNotFoundException.java b/backend/src/main/java/com/cruru/email/exception/badrequest/VerificationCodeNotFoundException.java new file mode 100644 index 000000000..c37b14395 --- /dev/null +++ b/backend/src/main/java/com/cruru/email/exception/badrequest/VerificationCodeNotFoundException.java @@ -0,0 +1,12 @@ +package com.cruru.email.exception.badrequest; + +import com.cruru.advice.badrequest.BadRequestException; + +public class VerificationCodeNotFoundException extends BadRequestException { + + private static final String MESSAGE = "인증 코드가 존재하지 않거나 만료되었습니다."; + + public VerificationCodeNotFoundException() { + super(MESSAGE); + } +} diff --git a/backend/src/main/java/com/cruru/email/facade/EmailFacade.java b/backend/src/main/java/com/cruru/email/facade/EmailFacade.java index b68ee189a..1461b6fc4 100644 --- a/backend/src/main/java/com/cruru/email/facade/EmailFacade.java +++ b/backend/src/main/java/com/cruru/email/facade/EmailFacade.java @@ -4,10 +4,14 @@ import com.cruru.applicant.service.ApplicantService; import com.cruru.club.domain.Club; import com.cruru.club.service.ClubService; -import com.cruru.email.controller.dto.EmailRequest; +import com.cruru.email.controller.request.EmailRequest; +import com.cruru.email.controller.request.SendVerificationCodeRequest; +import com.cruru.email.controller.request.VerifyCodeRequest; import com.cruru.email.exception.EmailAttachmentsException; +import com.cruru.email.service.EmailRedisClient; import com.cruru.email.service.EmailService; import com.cruru.email.util.FileUtil; +import com.cruru.email.util.VerificationCodeUtil; import java.io.File; import java.io.IOException; import java.util.List; @@ -23,6 +27,7 @@ public class EmailFacade { private final EmailService emailService; private final ClubService clubService; private final ApplicantService applicantService; + private final EmailRedisClient emailRedisClient; public void send(EmailRequest request) { Club from = clubService.findById(request.clubId()); @@ -52,4 +57,20 @@ private List saveTempFiles(Club from, String subject, List throw new EmailAttachmentsException(from.getId(), subject); } } + + public void sendVerificationCode(SendVerificationCodeRequest request) { + String email = request.email(); + String verificationCode = VerificationCodeUtil.generateVerificationCode(); + + emailRedisClient.saveVerificationCode(email, verificationCode); + emailService.sendVerificationCode(email, verificationCode); + } + + public void verifyCode(VerifyCodeRequest request) { + String email = request.email(); + String inputVerificationCode = request.verificationCode(); + String storedVerificationCode = emailRedisClient.getVerificationCode(email); + + VerificationCodeUtil.verify(storedVerificationCode, inputVerificationCode); + } } diff --git a/backend/src/main/java/com/cruru/email/service/EmailRedisClient.java b/backend/src/main/java/com/cruru/email/service/EmailRedisClient.java new file mode 100644 index 000000000..70115bdf1 --- /dev/null +++ b/backend/src/main/java/com/cruru/email/service/EmailRedisClient.java @@ -0,0 +1,25 @@ +package com.cruru.email.service; + +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class EmailRedisClient { + + private static final String REDIS_PREFIX = "email_verification:"; + private static final long VERIFICATION_CODE_EXPIRATION = 10; + + private final RedisTemplate redisTemplate; + + public void saveVerificationCode(String email, String verificationCode) { + redisTemplate.opsForValue() + .set(REDIS_PREFIX + email, verificationCode, VERIFICATION_CODE_EXPIRATION, TimeUnit.MINUTES); + } + + public String getVerificationCode(String email) { + return redisTemplate.opsForValue().get(REDIS_PREFIX + email); + } +} diff --git a/backend/src/main/java/com/cruru/email/service/EmailService.java b/backend/src/main/java/com/cruru/email/service/EmailService.java index 1321d2e14..09c148010 100644 --- a/backend/src/main/java/com/cruru/email/service/EmailService.java +++ b/backend/src/main/java/com/cruru/email/service/EmailService.java @@ -4,6 +4,7 @@ import com.cruru.club.domain.Club; import com.cruru.email.domain.Email; import com.cruru.email.domain.repository.EmailRepository; +import com.cruru.email.util.EmailTemplate; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import java.io.File; @@ -79,4 +80,21 @@ public void save(Email email) { public void deleteAllByTos(List applicants) { emailRepository.deleteAllByTos(applicants); } + + @Async + public void sendVerificationCode(String to, String verificationCode) { + try { + String subject = "[크루루] 인증 코드 안내"; + String content = EmailTemplate.generateVerificationEmailContent(verificationCode); + + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8"); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(content, true); + mailSender.send(message); + } catch (MessagingException | MailException e) { + log.error("이메일 전송 실패: to={}, subject={}", to, e.getMessage()); + } + } } diff --git a/backend/src/main/java/com/cruru/email/util/EmailTemplate.java b/backend/src/main/java/com/cruru/email/util/EmailTemplate.java new file mode 100644 index 000000000..b24a4bb21 --- /dev/null +++ b/backend/src/main/java/com/cruru/email/util/EmailTemplate.java @@ -0,0 +1,28 @@ +package com.cruru.email.util; + +public class EmailTemplate { + + public static String generateVerificationEmailContent(String verificationCode) { + return """ +
+
+

크루루

+
+
+

[크루루] 인증 코드 안내

+

안녕하세요,

+

아래 인증 코드를 입력해 주세요:

+
+ %s +
+

이 코드는 10분 후에 만료됩니다.

+
+
+
+

본 메일은 크루루 시스템에 의해 자동 발송되었습니다.

+

© 2024 크루루. All Rights Reserved.

+
+
+ """.formatted(verificationCode); + } +} diff --git a/backend/src/main/java/com/cruru/email/util/VerificationCodeUtil.java b/backend/src/main/java/com/cruru/email/util/VerificationCodeUtil.java new file mode 100644 index 000000000..4d166b822 --- /dev/null +++ b/backend/src/main/java/com/cruru/email/util/VerificationCodeUtil.java @@ -0,0 +1,28 @@ +package com.cruru.email.util; + +import com.cruru.email.exception.badrequest.VerificationCodeMismatchException; +import com.cruru.email.exception.badrequest.VerificationCodeNotFoundException; +import java.util.Random; + +public class VerificationCodeUtil { + + private static final Random random = new Random(); + private static final int CODE_LENGTH = 6; + + private VerificationCodeUtil() { + } + + public static String generateVerificationCode() { + return String.format("%0" + CODE_LENGTH + "d", random.nextInt((int) Math.pow(10, CODE_LENGTH))); + } + + public static void verify(String storedVerificationCode, String inputVerificationCode) { + if (storedVerificationCode == null) { + throw new VerificationCodeNotFoundException(); + } + + if (!storedVerificationCode.equals(inputVerificationCode)) { + throw new VerificationCodeMismatchException(); + } + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 20919a2dc..f41091e0c 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -47,9 +47,9 @@ spring: max-request-size: 50MB data: redis: - port: ${REDIS_PORT} - host: ${REDIS_HOST} - password: ${REDIS_PASSWORD} + port: 6379 + host: localhost + password: password security: jwt: diff --git a/backend/src/test/java/com/cruru/email/controller/EmailControllerTest.java b/backend/src/test/java/com/cruru/email/controller/EmailControllerTest.java index aaa4026a0..5b02351c6 100644 --- a/backend/src/test/java/com/cruru/email/controller/EmailControllerTest.java +++ b/backend/src/test/java/com/cruru/email/controller/EmailControllerTest.java @@ -1,13 +1,20 @@ package com.cruru.email.controller; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.request.RequestDocumentation.partWithName; import static org.springframework.restdocs.request.RequestDocumentation.requestParts; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; import com.cruru.applicant.domain.Applicant; import com.cruru.applicant.domain.repository.ApplicantRepository; +import com.cruru.email.controller.request.SendVerificationCodeRequest; +import com.cruru.email.controller.request.VerifyCodeRequest; +import com.cruru.email.service.EmailRedisClient; import com.cruru.util.ControllerTest; import com.cruru.util.fixture.ApplicantFixture; import com.cruru.util.fixture.EmailFixture; @@ -17,6 +24,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; @DisplayName("이메일 컨트롤러 테스트") class EmailControllerTest extends ControllerTest { @@ -24,6 +32,9 @@ class EmailControllerTest extends ControllerTest { @Autowired private ApplicantRepository applicantRepository; + @MockBean + private EmailRedisClient emailRedisClient; + @DisplayName("이메일 발송 성공 시, 200을 응답한다.") @Test void send() { @@ -121,4 +132,136 @@ void send_clubNotExist() { .when().post("/v1/emails/send") .then().log().all().statusCode(404); } + + @DisplayName("이메일 인증 번호 발송 성공 시, 200을 응답한다.") + @Test + void sendVerificationCode() { + // given + SendVerificationCodeRequest request = new SendVerificationCodeRequest("email@email.com"); + + // when & then + RestAssured.given(spec).log().all() + .contentType(ContentType.JSON) + .body(request) + .filter(document("email/verification-code", + requestFields(fieldWithPath("email").description("인증 번호를 전송할 이메일")) + )) + .when().post("/v1/emails/verification-code") + .then().log().all().statusCode(200); + } + + @DisplayName("이메일 인증 번호 발송 시, 이메일 형식이 올바르지 않을 경우 400을 응답한다.") + @Test + void sendVerificationCode_invalidEmailFormat() { + // given + String email = "invalidEmail"; + SendVerificationCodeRequest request = new SendVerificationCodeRequest(email); + + // when & then + RestAssured.given(spec).log().all() + .contentType(ContentType.JSON) + .body(request) + .filter(document("email/verification-code-fail/invalid-email", + requestFields(fieldWithPath("email").description("부적절한 이메일")) + )) + .when().post("/v1/emails/verification-code") + .then().log().all().statusCode(400); + } + + @DisplayName("이메일 인증 성공 시, 200을 응답한다.") + @Test + void verifyCode() { + // given + String email = "email@email.com"; + String verificationCode = "123456"; + + doNothing().when(emailRedisClient).saveVerificationCode(email, verificationCode); + when(emailRedisClient.getVerificationCode(email)).thenReturn(verificationCode); + + VerifyCodeRequest request = new VerifyCodeRequest(email, verificationCode); + + // when & then + RestAssured.given(spec).log().all() + .contentType(ContentType.JSON) + .body(request) + .filter(document("email/verify-code", + requestFields( + fieldWithPath("email").description("인증할 이메일"), + fieldWithPath("verificationCode").description("인증 번호") + ) + )) + .when().post("/v1/emails/verify-code") + .then().log().all().statusCode(200); + } + + @DisplayName("이메일 인증 시, 이메일 형식이 올바르지 않을 경우 400을 응답한다.") + @Test + void verifyCode_invalidEmailFormat() { + // given + String email = "invalidEmail"; + String verificationCode = "123456"; + + emailRedisClient.saveVerificationCode(email, verificationCode); + VerifyCodeRequest request = new VerifyCodeRequest(email, verificationCode); + + // when & then + RestAssured.given(spec).log().all() + .contentType(ContentType.JSON) + .body(request) + .filter(document("email/verify-code-fail/invalid-email", + requestFields( + fieldWithPath("email").description("부적절한 이메일 형식"), + fieldWithPath("verificationCode").description("인증 번호")) + )) + .when().post("/v1/emails/verify-code") + .then().log().all().statusCode(400); + } + + @DisplayName("이메일 인증 시, 인증 번호가 없는 이메일일 경우 400을 응답한다.") + @Test + void verifyCode_codeNotFound() { + // given + String email = "email@email.com"; + String invalidEmail = "no-code@mail.com"; + String verificationCode = "123456"; + + emailRedisClient.saveVerificationCode(email, verificationCode); + VerifyCodeRequest request = new VerifyCodeRequest(invalidEmail, verificationCode); + + // when & then + RestAssured.given(spec).log().all() + .contentType(ContentType.JSON) + .body(request) + .filter(document("email/verify-code-fail/code-not-found", + requestFields( + fieldWithPath("email").description("인증 번호가 없는 이메일"), + fieldWithPath("verificationCode").description("인증 번호")) + )) + .when().post("/v1/emails/verify-code") + .then().log().all().statusCode(400); + } + + @DisplayName("이메일 인증 시, 인증 번호가 다른 이메일일 경우 400을 응답한다.") + @Test + void verifyCode_codeMisMatch() { + // given + String email = "email@email.com"; + String verificationCode = "123456"; + String invalidVerificationCode = "654321"; + + emailRedisClient.saveVerificationCode(email, verificationCode); + VerifyCodeRequest request = new VerifyCodeRequest(email, invalidVerificationCode); + + // when & then + RestAssured.given(spec).log().all() + .contentType(ContentType.JSON) + .body(request) + .filter(document("email/verify-code-fail/code-mismatch", + requestFields( + fieldWithPath("email").description("인증할 이메일"), + fieldWithPath("verificationCode").description("적절하지 않은 인증 번호")) + )) + .when().post("/v1/emails/verify-code") + .then().log().all().statusCode(400); + } } diff --git a/backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java b/backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java index 6c451593c..f5fc315c0 100644 --- a/backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java +++ b/backend/src/test/java/com/cruru/email/facade/EmailFacadeTest.java @@ -1,14 +1,20 @@ package com.cruru.email.facade; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.cruru.applicant.domain.Applicant; import com.cruru.applicant.domain.repository.ApplicantRepository; -import com.cruru.email.controller.dto.EmailRequest; +import com.cruru.email.controller.request.EmailRequest; +import com.cruru.email.controller.request.VerifyCodeRequest; import com.cruru.email.domain.Email; +import com.cruru.email.exception.badrequest.VerificationCodeMismatchException; +import com.cruru.email.exception.badrequest.VerificationCodeNotFoundException; +import com.cruru.email.service.EmailRedisClient; import com.cruru.email.service.EmailService; import com.cruru.util.ServiceTest; import com.cruru.util.fixture.ApplicantFixture; @@ -20,6 +26,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; @DisplayName("발송 내역 파사드 테스트") @@ -27,11 +34,16 @@ class EmailFacadeTest extends ServiceTest { @SpyBean EmailService emailService; + @Autowired private ApplicantRepository applicantRepository; + @Autowired private EmailFacade emailFacade; + @MockBean + private EmailRedisClient emailRedisClient; + @DisplayName("이메일을 비동기로 발송하고, 발송 내역을 저장한다.") @Test void sendAndSave() { @@ -60,4 +72,54 @@ void sendAndSave() { verify(emailService, times(1)).save(any(Email.class)); }); } + + @DisplayName("이메일 인증 성공 시, 인증 코드가 검증된다.") + @Test + void verifyCode() { + // given + String email = "test@example.com"; + String verificationCode = "123456"; + VerifyCodeRequest request = new VerifyCodeRequest(email, verificationCode); + + when(emailRedisClient.getVerificationCode(email)).thenReturn(verificationCode); + + // when + emailFacade.verifyCode(request); + + // then + verify(emailRedisClient, times(1)).getVerificationCode(email); + } + + @DisplayName("저장된 인증 코드가 없으면 예외가 발생한다.") + @Test + void verifyCode_verificationCodeNotFoundException() { + // given + String email = "test@example.com"; + String verificationCode = "123456"; + VerifyCodeRequest request = new VerifyCodeRequest(email, verificationCode); + + when(emailRedisClient.getVerificationCode(email)).thenReturn(null); + + // when&then + assertThatThrownBy(() -> emailFacade.verifyCode(request)) + .isInstanceOf(VerificationCodeNotFoundException.class) + .hasMessage("인증 코드가 존재하지 않거나 만료되었습니다."); + } + + @DisplayName("저장된 인증 코드와 일치하지 않으면 예외가 발생한다.") + @Test + void verifyCode_verificationCodeMismatchException() { + // given + String email = "test@example.com"; + String correctCode = "123456"; + String wrongCode = "654321"; + VerifyCodeRequest request = new VerifyCodeRequest(email, wrongCode); + + when(emailRedisClient.getVerificationCode(email)).thenReturn(correctCode); + + // when&then + assertThatThrownBy(() -> emailFacade.verifyCode(request)) + .isInstanceOf(VerificationCodeMismatchException.class) + .hasMessage("인증 코드가 일치하지 않습니다."); + } }