EmailService.java

1
package com.edtech.service;
2
3
import java.io.IOException;
4
import java.net.URI;
5
import java.net.http.HttpClient;
6
import java.net.http.HttpRequest;
7
import java.net.http.HttpResponse;
8
import java.nio.charset.StandardCharsets;
9
import org.slf4j.Logger;
10
import org.slf4j.LoggerFactory;
11
import org.springframework.beans.factory.annotation.Autowired;
12
import org.springframework.beans.factory.annotation.Value;
13
import org.springframework.mail.MailException;
14
import org.springframework.mail.SimpleMailMessage;
15
import org.springframework.mail.javamail.JavaMailSender;
16
import org.springframework.scheduling.annotation.Async;
17
import org.springframework.stereotype.Service;
18
19
/** Sends account emails through the Resend HTTPS API in production and SMTP locally. */
20
@Service
21
public class EmailService {
22
23
  private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
24
  private static final URI RESEND_EMAILS_URI = URI.create("https://api.resend.com/emails");
25
26
  @Autowired private JavaMailSender mailSender;
27
28
  @Value("${SMTP_FROM:noreply@edtechacademic.com.br}")
29
  private String fromAddress;
30
31
  @Value("${SMTP_PASSWORD:}")
32
  private String resendApiKey;
33
34
  /** Sends the password recovery code. */
35
  @Async
36
  public void sendRecoveryEmail(String toEmail, String otpCode) {
37 1 1. sendRecoveryEmail : removed call to com/edtech/service/EmailService::sendEmail → KILLED
    sendEmail(
38
        toEmail,
39
        "Código de Recuperação de Senha - EdTech",
40
        "Olá,\n\n"
41
            + "Você solicitou a recuperação da sua senha na EdTech.\n"
42
            + "Seu código de segurança (OTP) de 6 dígitos é: "
43
            + otpCode
44
            + "\n\n"
45
            + "Este código é válido por 15 minutos.\n"
46
            + "Se você não solicitou isso, ignore este e-mail.\n\n"
47
            + "Equipe EdTech");
48
  }
49
50
  /** Sends the registration verification code. */
51
  @Async
52
  public void sendVerificationEmail(String toEmail, String otpCode) {
53 1 1. sendVerificationEmail : removed call to com/edtech/service/EmailService::sendEmail → KILLED
    sendEmail(
54
        toEmail,
55
        "Código de Verificação de Conta - EdTech",
56
        "Olá,\n\n"
57
            + "Bem-vindo à EdTech!\n"
58
            + "Seu código de verificação (OTP) de 6 dígitos é: "
59
            + otpCode
60
            + "\n\n"
61
            + "Este código é válido por 15 minutos.\n"
62
            + "Se você não solicitou isso, ignore este e-mail.\n\n"
63
            + "Equipe EdTech");
64
  }
65
66
  private void sendEmail(String toEmail, String subject, String text) {
67 1 1. sendEmail : negated conditional → KILLED
    String apiKey = resendApiKey == null ? "" : resendApiKey.trim();
68 1 1. sendEmail : negated conditional → KILLED
    if (apiKey.startsWith("re_")) {
69 1 1. sendEmail : removed call to com/edtech/service/EmailService::sendWithResendApi → NO_COVERAGE
      sendWithResendApi(apiKey, toEmail, subject, text);
70
      return;
71
    }
72
73
    SimpleMailMessage message = new SimpleMailMessage();
74 1 1. sendEmail : removed call to org/springframework/mail/SimpleMailMessage::setFrom → KILLED
    message.setFrom(resolveFromAddress());
75 1 1. sendEmail : removed call to org/springframework/mail/SimpleMailMessage::setTo → KILLED
    message.setTo(toEmail);
76 1 1. sendEmail : removed call to org/springframework/mail/SimpleMailMessage::setSubject → KILLED
    message.setSubject(subject);
77 1 1. sendEmail : removed call to org/springframework/mail/SimpleMailMessage::setText → KILLED
    message.setText(text);
78
    try {
79 1 1. sendEmail : removed call to org/springframework/mail/javamail/JavaMailSender::send → KILLED
      mailSender.send(message);
80
      logger.info("Email dispatched through SMTP to {}.", toEmail);
81
    } catch (MailException ex) {
82
      logger.warn("SMTP email delivery failed for {}.", toEmail, ex);
83
    }
84
  }
85
86
  private void sendWithResendApi(String apiKey, String toEmail, String subject, String text) {
87
    String payload =
88
        "{\"from\":\""
89
            + escapeJson(resolveFromAddress())
90
            + "\",\"to\":[\""
91
            + escapeJson(toEmail)
92
            + "\"],\"subject\":\""
93
            + escapeJson(subject)
94
            + "\",\"text\":\""
95
            + escapeJson(text)
96
            + "\"}";
97
    HttpRequest request =
98
        HttpRequest.newBuilder(RESEND_EMAILS_URI)
99
            .header("Authorization", "Bearer " + apiKey)
100
            .header("Content-Type", "application/json")
101
            .POST(HttpRequest.BodyPublishers.ofString(payload, StandardCharsets.UTF_8))
102
            .build();
103
    try {
104
      HttpResponse<String> response =
105
          HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
106 4 1. sendWithResendApi : changed conditional boundary → NO_COVERAGE
2. sendWithResendApi : negated conditional → NO_COVERAGE
3. sendWithResendApi : negated conditional → NO_COVERAGE
4. sendWithResendApi : changed conditional boundary → NO_COVERAGE
      if (response.statusCode() >= 200 && response.statusCode() < 300) {
107
        logger.info("Email dispatched through Resend API to {}.", toEmail);
108
      } else {
109
        logger.warn(
110
            "Resend API rejected email for {} with HTTP status {}.",
111
            toEmail,
112
            response.statusCode());
113
      }
114
    } catch (IOException ex) {
115
      logger.warn("Resend API delivery failed for {}.", toEmail, ex);
116
    } catch (InterruptedException ex) {
117 1 1. sendWithResendApi : removed call to java/lang/Thread::interrupt → NO_COVERAGE
      Thread.currentThread().interrupt();
118
      logger.warn("Resend API delivery interrupted for {}.", toEmail, ex);
119
    }
120
  }
121
122
  private String escapeJson(String value) {
123 1 1. escapeJson : replaced return value with "" for com/edtech/service/EmailService::escapeJson → NO_COVERAGE
    return value
124
        .replace("\\", "\\\\")
125
        .replace("\"", "\\\"")
126
        .replace("\n", "\\n")
127
        .replace("\r", "\\r");
128
  }
129
130
  private String resolveFromAddress() {
131 3 1. resolveFromAddress : replaced return value with "" for com/edtech/service/EmailService::resolveFromAddress → KILLED
2. resolveFromAddress : negated conditional → KILLED
3. resolveFromAddress : negated conditional → KILLED
    return fromAddress == null || fromAddress.isBlank()
132
        ? "noreply@edtechacademic.com.br"
133
        : fromAddress;
134
  }
135
}

Mutations

37

1.1
Location : sendRecoveryEmail
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
removed call to com/edtech/service/EmailService::sendEmail → KILLED

53

1.1
Location : sendVerificationEmail
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:registerRejectsDuplicatedEmail()]
removed call to com/edtech/service/EmailService::sendEmail → KILLED

67

1.1
Location : sendEmail
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
negated conditional → KILLED

68

1.1
Location : sendEmail
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
negated conditional → KILLED

69

1.1
Location : sendEmail
Killed by : none
removed call to com/edtech/service/EmailService::sendWithResendApi → NO_COVERAGE

74

1.1
Location : sendEmail
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
removed call to org/springframework/mail/SimpleMailMessage::setFrom → KILLED

75

1.1
Location : sendEmail
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
removed call to org/springframework/mail/SimpleMailMessage::setTo → KILLED

76

1.1
Location : sendEmail
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
removed call to org/springframework/mail/SimpleMailMessage::setSubject → KILLED

77

1.1
Location : sendEmail
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
removed call to org/springframework/mail/SimpleMailMessage::setText → KILLED

79

1.1
Location : sendEmail
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
removed call to org/springframework/mail/javamail/JavaMailSender::send → KILLED

106

1.1
Location : sendWithResendApi
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : sendWithResendApi
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : sendWithResendApi
Killed by : none
negated conditional → NO_COVERAGE

4.4
Location : sendWithResendApi
Killed by : none
changed conditional boundary → NO_COVERAGE

117

1.1
Location : sendWithResendApi
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

123

1.1
Location : escapeJson
Killed by : none
replaced return value with "" for com/edtech/service/EmailService::escapeJson → NO_COVERAGE

131

1.1
Location : resolveFromAddress
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
replaced return value with "" for com/edtech/service/EmailService::resolveFromAddress → KILLED

2.2
Location : resolveFromAddress
Killed by : com.edtech.controller.AuthControllerTest.[engine:junit-jupiter]/[class:com.edtech.controller.AuthControllerTest]/[method:registerRejectsDuplicatedEmail()]
negated conditional → KILLED

3.3
Location : resolveFromAddress
Killed by : com.edtech.service.EmailServiceTest.[engine:junit-jupiter]/[class:com.edtech.service.EmailServiceTest]/[method:testSendRecoveryEmail()]
negated conditional → KILLED

Active mutators

Tests examined


Report generated by PIT 1.25.9 support