- 
                Notifications
    
You must be signed in to change notification settings  - Fork 0
 
[refactor] 이메일 발송 비동기식으로 전환 #91
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
          
     Merged
      
      
    
  
     Merged
                    Changes from 1 commit
      Commits
    
    
            Show all changes
          
          
            5 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      974bd32
              
                refactor: 이메일 전송 비동기 처리 (DASOMBE-13)
              
              
                hodoon 75608bf
              
                refactor: 이메일 전송 오류시 로그 저장 DB생성, 이메일 템플릿 분기처리 (DASOMBE-13)
              
              
                hodoon 937e5f5
              
                refactor: 이메일 로그 builder 패턴으로 인스턴스 초기화 완료 (DASOMBE-13)
              
              
                hodoon 51fd724
              
                refactor: 성공여부도 저장 가능하게 수정 (DASOMBE-13)
              
              
                hodoon 5562c21
              
                refactor: 빌더 패턴 EmailLog엔티티 안으로 이동, 에러메시지 저장 로직 try-catch문 밖으로 이동 (DA…
              
              
                hodoon File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -6,9 +6,12 @@ | |
| import jakarta.mail.MessagingException; | ||
| import jakarta.mail.internet.MimeMessage; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.mail.javamail.JavaMailSender; | ||
| import org.springframework.mail.javamail.MimeMessageHelper; | ||
| import org.springframework.scheduling.annotation.Async; | ||
| import org.springframework.stereotype.Service; | ||
| import org.thymeleaf.TemplateEngine; | ||
| import org.thymeleaf.context.Context; | ||
| 
        
          
        
         | 
    @@ -19,62 +22,92 @@ public class EmailService { | |
| 
     | 
||
| private final TemplateEngine templateEngine; | ||
| private final JavaMailSender javaMailSender; | ||
| private static final Logger log = LoggerFactory.getLogger(EmailService.class); | ||
| 
     | 
||
| @Value("${spring.mail.username}") | ||
| private String from; | ||
| 
     | 
||
| public void sendEmail(String to, String name, MailType mailType) throws MessagingException { | ||
| if (mailType == null){ | ||
| throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID); | ||
| @Async | ||
| public void sendEmail(String to, String name, MailType mailType) { | ||
| try { | ||
| if (mailType == null) { | ||
| throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID); | ||
| } | ||
| 
     | 
||
| // 메일 제목 및 템플릿 설정 | ||
| String subject = getSubject(mailType); | ||
| String emailContent = getEmailContent(mailType); | ||
| String buttonText = getButtonText(mailType); | ||
| String buttonUrl = "https://dmu-dasom.or.kr/recruit/result"; | ||
| 
     | 
||
| // HTML 템플릿에 전달할 데이터 설정 | ||
| Context context = new Context(); | ||
| context.setVariable("name", name); // 지원자 이름 전달 | ||
| context.setVariable("emailContent", emailContent); // 이메일 내용 전달 | ||
| context.setVariable("buttonUrl", buttonUrl); // 버튼 링크 전달 | ||
| context.setVariable("buttonText", buttonText); | ||
| 
     | 
||
| // HTML 템플릿 처리 | ||
| String htmlBody = templateEngine.process("email-template", context); | ||
| 
     | 
||
| // 이메일 생성 및 전송 | ||
| MimeMessage message = javaMailSender.createMimeMessage(); | ||
| MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); | ||
| 
     | 
||
| helper.setTo(to); | ||
| helper.setSubject(subject); | ||
| helper.setText(htmlBody, true); | ||
| helper.setFrom((from != null && !from.isEmpty()) ? from : "[email protected]"); | ||
| 
     | 
||
| // Content-Type을 명시적으로 설정 | ||
| message.setContent(htmlBody, "text/html; charset=utf-8"); | ||
| 
     | 
||
| javaMailSender.send(message); | ||
| log.info("Email sent successfull {}", to); | ||
| } catch (MessagingException e) { | ||
| log.error("Failed to send email to {}: {}", to, e.getMessage()); | ||
| } catch (CustomException e) { | ||
| log.error("Email sending error for {}: {}", to, e.getMessage()); | ||
                
      
                  hodoon marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||
| } | ||
| } | ||
| 
     | 
||
| // 메일 제목 및 템플릿 설정 | ||
| String subject; | ||
| String emailContent; | ||
| String buttonUrl = "https://dmu-dasom.or.kr/recruit/result"; | ||
| String buttonText; | ||
| // 메일 유형에 맞는 제목 반환 | ||
| private String getSubject(MailType mailType){ | ||
                
      
                  hodoon marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| switch (mailType){ | ||
| case DOCUMENT_RESULT: | ||
| return "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 서류 결과 안내"; | ||
| case FINAL_RESULT: | ||
| return "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 최종 면접 결과 안내"; | ||
| default: | ||
| throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID); | ||
| } | ||
| } | ||
| 
     | 
||
| // 메일 유형에 맞는 본문 반환 | ||
| private String getEmailContent(MailType mailType){ | ||
| switch (mailType) { | ||
| case DOCUMENT_RESULT -> { | ||
| subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 서류 결과 안내"; | ||
| emailContent = "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" + | ||
| case DOCUMENT_RESULT: | ||
| return "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" + | ||
                
      
                  hodoon marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| "내부 서류 평가 결과 및 추후 일정에 관해 안내해드리고자 이메일을 발송하게 되었습니다.<br>" + | ||
| "서류 전형 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다."; | ||
| buttonText = "서류 결과 확인하기"; | ||
| } | ||
| case FINAL_RESULT -> { | ||
| subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 최종 면접 결과 안내"; | ||
| emailContent = "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" + | ||
| case FINAL_RESULT: | ||
| return "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" + | ||
| "최종 면접 결과 및 추후 일정에 관해 안내해드리고자 이메일을 발송하게 되었습니다.<br>" + | ||
| "최종 면접 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다."; | ||
| buttonText = "최종 결과 확인하기"; | ||
| } | ||
| default -> throw new IllegalStateException("Unexpected value: " + mailType); | ||
| default: | ||
| throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID); | ||
| } | ||
| 
     | 
||
| // HTML 템플릿에 전달할 데이터 설정 | ||
| Context context = new Context(); | ||
| context.setVariable("name", name); // 지원자 이름 전달 | ||
| context.setVariable("emailContent", emailContent); // 이메일 내용 전달 | ||
| context.setVariable("buttonUrl", buttonUrl); // 버튼 링크 전달 | ||
| context.setVariable("buttonText", buttonText); | ||
| 
     | 
||
| // HTML 템플릿 처리 | ||
| String htmlBody = templateEngine.process("email-template", context); | ||
| 
     | 
||
| // 이메일 생성 및 전송 | ||
| MimeMessage message = javaMailSender.createMimeMessage(); | ||
| MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); | ||
| 
     | 
||
| helper.setTo(to); | ||
| helper.setSubject(subject); | ||
| helper.setText(htmlBody, true); | ||
| helper.setFrom((from != null && !from.isEmpty()) ? from : "[email protected]"); | ||
| 
     | 
||
| // Content-Type을 명시적으로 설정 | ||
| message.setContent(htmlBody, "text/html; charset=utf-8"); | ||
| 
     | 
||
| javaMailSender.send(message); | ||
| } | ||
| 
     | 
||
| // 메일 유형에 맞는 버튼 텍스트 반환 | ||
| private String getButtonText(MailType mailType) { | ||
| switch (mailType) { | ||
| case DOCUMENT_RESULT: | ||
| return "서류 결과 확인하기"; | ||
| case FINAL_RESULT: | ||
| return "최종 결과 확인하기"; | ||
| default: | ||
| throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID); | ||
| } | ||
| } | ||
| } | ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| package dmu.dasom.api.domain.email; | ||
| 
     | 
||
| import dmu.dasom.api.domain.common.exception.CustomException; | ||
| import dmu.dasom.api.domain.common.exception.ErrorCode; | ||
| 
     | 
||
| import dmu.dasom.api.domain.google.enums.MailType; | ||
| import dmu.dasom.api.domain.google.service.EmailService; | ||
| import jakarta.mail.MessagingException; | ||
| 
        
          
        
         | 
    @@ -18,92 +17,96 @@ | |
| import org.thymeleaf.TemplateEngine; | ||
| import org.thymeleaf.context.Context; | ||
| 
     | 
||
| import static org.junit.jupiter.api.Assertions.*; | ||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
| import static org.mockito.ArgumentMatchers.eq; | ||
| import static org.mockito.Mockito.*; | ||
| 
     | 
||
| class EmailServiceTest { | ||
| 
     | 
||
| @Mock | ||
| private JavaMailSender javaMailSender; | ||
| 
     | 
||
| @Mock | ||
| private TemplateEngine templateEngine; | ||
| 
     | 
||
| @InjectMocks | ||
| private EmailService emailService; | ||
| 
     | 
||
| private MimeMessage mimeMessage; | ||
| 
     | 
||
| @BeforeEach | ||
| void setUp() { | ||
| void setUp(){ | ||
| MockitoAnnotations.openMocks(this); | ||
| mimeMessage = mock(MimeMessage.class); | ||
| when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage); | ||
| 
     | 
||
| // 테스트 환경에서 from 값을 설정 | ||
| when(javaMailSender.createMimeMessage()).thenReturn(mock(MimeMessage.class)); | ||
| ReflectionTestUtils.setField(emailService, "from", "[email protected]"); | ||
| } | ||
| 
     | 
||
| @Test | ||
| @DisplayName("서류 합격 메일 발송 테스트") | ||
| void sendEmail_documentResult() throws MessagingException { | ||
| @DisplayName("성공 - 서류 결과 메일 발송 테스트") | ||
| void sendDocumentResultMessage_Success() throws MessagingException { | ||
| // given | ||
| String to = "[email protected]"; | ||
| String name = "지원자"; | ||
| MailType mailType = MailType.DOCUMENT_RESULT; | ||
| 
     | 
||
| String expectedTemplate = "email-template"; | ||
| String expectedHtmlBody = "<html><body>Document Pass</body></html>"; | ||
| when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody); | ||
| String expectedHtmlBody = "<html><body>Test HTML</body></html>"; | ||
| when(templateEngine.process(eq("email-template"), any(Context.class))).thenReturn(expectedHtmlBody); | ||
| 
     | 
||
| // when | ||
| emailService.sendEmail(to, name, mailType); | ||
| 
     | 
||
| // then | ||
| ArgumentCaptor<MimeMessage> messageCaptor = ArgumentCaptor.forClass(MimeMessage.class); | ||
| verify(javaMailSender).send(messageCaptor.capture()); | ||
| 
     | 
||
| MimeMessage sentMessage = messageCaptor.getValue(); | ||
| assertNotNull(sentMessage); | ||
| verify(templateEngine).process(eq(expectedTemplate), any(Context.class)); | ||
| //then | ||
| // 비동기 처리 확인 | ||
| verify(javaMailSender, timeout(1000)).send(any(MimeMessage.class)); | ||
| // 메일 전송 확인 | ||
| ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class); | ||
| verify(templateEngine).process(eq("email-template"), contextCaptor.capture()); | ||
| Context capturedContext = contextCaptor.getValue(); | ||
| 
     | 
||
| // context 변수 확인 | ||
| assertEquals(name, capturedContext.getVariable("name")); | ||
| assertEquals("서류 결과 확인하기", capturedContext.getVariable("buttonText")); | ||
| assertNotNull(capturedContext.getVariable("emailContent")); | ||
| } | ||
| 
     | 
||
| @Test | ||
| @DisplayName("최종 합격 메일 발송 테스트") | ||
| void sendEmail_finalResult() throws MessagingException { | ||
| @DisplayName("성공 - 최종 결과 메일 발송 테스트") | ||
| void sendFinalResultMessage_Success() throws MessagingException { | ||
| // given | ||
| String to = "[email protected]"; | ||
| String name = "지원자"; | ||
| MailType mailType = MailType.FINAL_RESULT; | ||
| 
     | 
||
| String expectedTemplate = "email-template"; | ||
| String expectedHtmlBody = "<html><body>Final Pass</body></html>"; | ||
| when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody); | ||
| String expectedHtmlBody = "<html><body>Test HTML</body></html>"; | ||
| when(templateEngine.process(eq("email-template"), any(Context.class))).thenReturn(expectedHtmlBody); | ||
| 
     | 
||
| // when | ||
| emailService.sendEmail(to, name, mailType); | ||
| 
     | 
||
| // then | ||
| ArgumentCaptor<MimeMessage> messageCaptor = ArgumentCaptor.forClass(MimeMessage.class); | ||
| verify(javaMailSender).send(messageCaptor.capture()); | ||
| 
     | 
||
| MimeMessage sentMessage = messageCaptor.getValue(); | ||
| assertNotNull(sentMessage); | ||
| verify(templateEngine).process(eq(expectedTemplate), any(Context.class)); | ||
| //then | ||
| // 비동기 처리 확인 | ||
| verify(javaMailSender, timeout(1000)).send(any(MimeMessage.class)); | ||
| // 메일 전송 확인 | ||
| ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class); | ||
| verify(templateEngine).process(eq("email-template"), contextCaptor.capture()); | ||
| Context capturedContext = contextCaptor.getValue(); | ||
| 
     | 
||
| // context 변수 확인 | ||
| assertEquals(name, capturedContext.getVariable("name")); | ||
| assertEquals("최종 결과 확인하기", capturedContext.getVariable("buttonText")); | ||
| assertNotNull(capturedContext.getVariable("emailContent")); | ||
| } | ||
| 
     | 
||
| @Test | ||
| @DisplayName("잘못된 MailType 처리 테스트") | ||
| void sendEmail_invalidMailType() { | ||
| // given | ||
| @DisplayName("실패 - MailType이 null일 경우, 예외 발생 테스트") | ||
| void sendEmail_nullMailType_shouldNotSend() { | ||
| //given | ||
| String to = "[email protected]"; | ||
| String name = "지원자"; | ||
| 
     | 
||
| // when & then | ||
| CustomException exception = assertThrows(CustomException.class, () -> { | ||
| emailService.sendEmail(to, name, null); | ||
| }); | ||
| // when | ||
| emailService.sendEmail(to, name, null); | ||
| 
     | 
||
| assertEquals(ErrorCode.MAIL_TYPE_NOT_VALID, exception.getErrorCode()); | ||
| // then | ||
| verify(javaMailSender, never()).send(any(MimeMessage.class)); | ||
| } | ||
| 
     | 
||
| 
     | 
||
| } | ||
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.