Skip to content

Commit

Permalink
Merge pull request #134 from Soongsil-CoffeeChat/ref/#133
Browse files Browse the repository at this point in the history
[Ref] 메일인증 비동기처리 개선 및 안정성 테스트코드 작성
  • Loading branch information
KimKyoHwee authored Aug 27, 2024
2 parents e694aa8 + a159161 commit 76b35ba
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 13 deletions.
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ dependencies {
//Cool SMS
implementation 'net.nurigo:sdk:4.2.7'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"

//SQS
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1")
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs'

}

// Querydsl 빌드 옵션 설정
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ public SecurityConfig(CustomOAuth2UserService customOAuth2UserService,
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_MENTEE" + "ROLE_ADMIN > ROLE_MENTOR\n" +
"ROLE_MENTEE > ROLE_USER" + "ROLE_MENTOR > ROLE_USER");
hierarchy.setHierarchy("""
ROLE_ADMIN > ROLE_MENTEE
ROLE_ADMIN > ROLE_MENTOR
ROLE_MENTEE > ROLE_USER
ROLE_MENTOR > ROLE_USER
""");
return hierarchy;
}


@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.soongsil.CoffeeChat.config;
package com.soongsil.CoffeeChat.config.aws;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/com/soongsil/CoffeeChat/config/aws/SqsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.soongsil.CoffeeChat.config.aws;

import io.awspring.cloud.sqs.config.SqsMessageListenerContainerFactory;
import io.awspring.cloud.sqs.operations.SqsTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsAsyncClient;

public class SqsConfig {
@Value("${spring.cloud.aws.region.static}")
private String region;

@Value("${spring.cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${spring.cloud.aws.credentials.secret-key}")
private String secretKey;
// 클라이언트 설정
@Bean
public SqsAsyncClient sqsAsyncClient() {
return SqsAsyncClient.builder()
.credentialsProvider(() -> new AwsCredentials() {
@Override
public String accessKeyId() {
return accessKey;
}
@Override
public String secretAccessKey() {
return secretKey;
}
})
.region(Region.of(region))
.build();
}
// Listener Factory 설정 (Listener쪽에서만 설정하면 됨)
@Bean
public SqsMessageListenerContainerFactory<Object> defaultSqsListenerContainerFactory() {
return SqsMessageListenerContainerFactory
.builder()
.sqsAsyncClient(sqsAsyncClient())
.build();
}

// 메세지 발송을 위한 SQS 템플릿 설정 (Sender쪽에서만 설정하면 됨)
@Bean
public SqsTemplate sqsTemplate() {
return SqsTemplate.newTemplate(sqsAsyncClient());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public ResponseEntity<ApiResponseGenerator<Map<String, String>>> sendAuthenticat
@RequestParam("email") String receiver) throws
MessagingException,
InterruptedException {
System.out.println("receiver = " + receiver);
return ResponseEntity.ok().body(
ApiResponseGenerator.onSuccessOK(
Map.of(
Expand Down
26 changes: 18 additions & 8 deletions src/main/java/com/soongsil/CoffeeChat/util/email/EmailUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.concurrent.CompletableFuture;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
Expand All @@ -17,7 +20,9 @@
public class EmailUtil {

private final JavaMailSender javaMailSender;
private final ApplicationContext applicationContext;

// 비동기 메일 발송 메서드
@Async("mailExecutor")
public void sendMail(String receiver, String subject, String content) throws MessagingException {
MimeMessage message = javaMailSender.createMimeMessage();
Expand All @@ -27,16 +32,21 @@ public void sendMail(String receiver, String subject, String content) throws Mes
javaMailSender.send(message);
}

@Async("mailExecutor")
public String sendAuthenticationEmail(String receiver) throws
MessagingException,
InterruptedException {
String code = String.valueOf((int)((Math.random() * 900000) + 100000));
// 인증 이메일 발송 메서드
public String sendAuthenticationEmail(String receiver) throws MessagingException, InterruptedException {
String code = String.valueOf((int) ((Math.random() * 900000) + 100000));

sendMail(receiver, "[COGO] 이메일 인증번호입니다.",
createMessageTemplate("[COGO] 이메일 인증 안내", "이메일 인증을 완료하려면 아래의 인증 번호를 사용하여 계속 진행하세요:", code));
// 비동기 메일 발송 메서드를 프록시를 통해 호출
EmailUtil proxy = applicationContext.getBean(EmailUtil.class);
try {
proxy.sendMail(receiver, "[COGO] 이메일 인증번호입니다.",
createMessageTemplate("[COGO] 이메일 인증 안내", "이메일 인증을 완료하려면 아래의 인증 번호를 사용하여 계속 진행하세요:", code));
} catch (RuntimeException e) {
// 예외가 발생했을 때 로그를 남기고, 기본 코드 반환 등을 처리
System.out.println("메일 전송 실패: " + e.getMessage());
}

return code.toString();
return code;
}

public void sendApplicationMatchedEmail(String receiver, String mentorName, String menteeName, LocalDate date,
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ cloud:
auto: false
stack:
auto: false
sqs:
queue-name: COGO_Email_SQS

logging.level:
org.hibernate.SQL: debug
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.soongsil.CoffeeChat.Concurrency;

import com.soongsil.CoffeeChat.dto.ApplicationCreateRequest;
import com.soongsil.CoffeeChat.dto.ApplicationCreateResponse;
import com.soongsil.CoffeeChat.entity.Application;
import com.soongsil.CoffeeChat.entity.Mentee;
import com.soongsil.CoffeeChat.entity.Mentor;
Expand Down
66 changes: 66 additions & 0 deletions src/test/java/com/soongsil/CoffeeChat/Mail/EmailServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.soongsil.CoffeeChat.Mail;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import com.soongsil.CoffeeChat.util.email.EmailUtil;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext;
import org.springframework.mail.javamail.JavaMailSender;

@ExtendWith(MockitoExtension.class)
public class EmailServiceTest {

@Mock
private JavaMailSender javaMailSender;

@InjectMocks
private EmailUtil emailUtil;

@Mock
private MimeMessage mimeMessage;

@Mock
private ApplicationContext applicationContext;

@BeforeEach
void setUp() {
// JavaMailSender의 createMimeMessage 메서드가 mock MimeMessage 객체를 반환하도록 설정
when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage);

// EmailUtil 프록시를 반환하도록 ApplicationContext 설정
when(applicationContext.getBean(EmailUtil.class)).thenReturn(emailUtil);
}

@Test
void testSendAuthenticationEmailReturnsCodeAndHandlesException() throws MessagingException, InterruptedException {
// given
String receiver = "[email protected]";
// sendMail이 호출될 때 예외를 던지도록 설정(런타임)
doThrow(new RuntimeException("Failed to send email"))
.when(javaMailSender).send(any(MimeMessage.class));
// when
String resultCode = null;
try {
resultCode = emailUtil.sendAuthenticationEmail(receiver);
} catch (RuntimeException e) {
// 예외가 발생해도 메서드가 종료되지 않고 정상적으로 처리되었는지 확인
System.out.println("예외 발생: " + e.getMessage());
}
// then
//assertNotNull(resultCode); // 반환된 코드가 null이 아닌지 확인
assertEquals(6, resultCode.length()); // 반환된 코드의 길이가 6자리인지 확인
assertTrue(resultCode.matches("\\d{6}")); // 반환된 코드가 6자리 숫자인지 확인
System.out.println("resultCode = " + resultCode);
verify(javaMailSender, times(1)).send(any(MimeMessage.class)); // sendMail이 한 번만 호출되었는지 확인
}
}

0 comments on commit 76b35ba

Please sign in to comment.