diff --git a/src/main/java/stirling/software/SPDF/config/security/mail/EmailService.java b/src/main/java/stirling/software/SPDF/config/security/mail/EmailService.java index 8939fbab6..507e51599 100644 --- a/src/main/java/stirling/software/SPDF/config/security/mail/EmailService.java +++ b/src/main/java/stirling/software/SPDF/config/security/mail/EmailService.java @@ -37,8 +37,21 @@ public class EmailService { */ @Async public void sendEmailWithAttachment(Email email) throws MessagingException { - ApplicationProperties.Mail mailProperties = applicationProperties.getMail(); MultipartFile file = email.getFileInput(); + // 1) Validate recipient email address + if (email.getTo() == null || email.getTo().trim().isEmpty()) { + throw new MessagingException("Invalid Addresses"); + } + + // 2) Validate attachment + if (file == null + || file.isEmpty() + || file.getOriginalFilename() == null + || file.getOriginalFilename().isEmpty()) { + throw new MessagingException("An attachment is required to send the email."); + } + + ApplicationProperties.Mail mailProperties = applicationProperties.getMail(); // Creates a MimeMessage to represent the email MimeMessage message = mailSender.createMimeMessage(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/EmailController.java b/src/main/java/stirling/software/SPDF/controller/api/EmailController.java index 6f7dd3867..dc1c9dff4 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/EmailController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/EmailController.java @@ -3,6 +3,7 @@ package stirling.software.SPDF.controller.api; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.mail.MailSendException; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -53,6 +54,11 @@ public class EmailController { // Calls the service to send the email with attachment emailService.sendEmailWithAttachment(email); return ResponseEntity.ok("Email sent successfully"); + } catch (MailSendException ex) { + // handles your "Invalid Addresses" case + String errorMsg = ex.getMessage(); + log.error("MailSendException: {}", errorMsg, ex); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMsg); } catch (MessagingException e) { // Catches any messaging exception (e.g., invalid email address, SMTP server issues) String errorMsg = "Failed to send email: " + e.getMessage(); diff --git a/src/test/java/stirling/software/SPDF/config/security/mail/EmailServiceTest.java b/src/test/java/stirling/software/SPDF/config/security/mail/EmailServiceTest.java index 777b2b658..1781eb1bb 100644 --- a/src/test/java/stirling/software/SPDF/config/security/mail/EmailServiceTest.java +++ b/src/test/java/stirling/software/SPDF/config/security/mail/EmailServiceTest.java @@ -1,5 +1,7 @@ package stirling.software.SPDF.config.security.mail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; @@ -57,4 +59,111 @@ public class EmailServiceTest { // Verify that the email was sent using mailSender verify(mailSender).send(mimeMessage); } + + @Test + void testSendEmailWithAttachmentThrowsExceptionForMissingFilename() throws MessagingException { + Email email = new Email(); + email.setTo("test@example.com"); + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(fileInput); + + when(fileInput.isEmpty()).thenReturn(false); + when(fileInput.getOriginalFilename()).thenReturn(""); + + try { + emailService.sendEmailWithAttachment(email); + fail("Expected MessagingException to be thrown"); + } catch (MessagingException e) { + assertEquals("An attachment is required to send the email.", e.getMessage()); + } + } + + @Test + void testSendEmailWithAttachmentThrowsExceptionForMissingFilenameNull() + throws MessagingException { + Email email = new Email(); + email.setTo("test@example.com"); + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(fileInput); + + when(fileInput.isEmpty()).thenReturn(false); + when(fileInput.getOriginalFilename()).thenReturn(null); + + try { + emailService.sendEmailWithAttachment(email); + fail("Expected MessagingException to be thrown"); + } catch (MessagingException e) { + assertEquals("An attachment is required to send the email.", e.getMessage()); + } + } + + @Test + void testSendEmailWithAttachmentThrowsExceptionForMissingFile() throws MessagingException { + Email email = new Email(); + email.setTo("test@example.com"); + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(fileInput); + + when(fileInput.isEmpty()).thenReturn(true); + + try { + emailService.sendEmailWithAttachment(email); + fail("Expected MessagingException to be thrown"); + } catch (MessagingException e) { + assertEquals("An attachment is required to send the email.", e.getMessage()); + } + } + + @Test + void testSendEmailWithAttachmentThrowsExceptionForMissingFileNull() throws MessagingException { + Email email = new Email(); + email.setTo("test@example.com"); + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(null); // Missing file + + try { + emailService.sendEmailWithAttachment(email); + fail("Expected MessagingException to be thrown"); + } catch (MessagingException e) { + assertEquals("An attachment is required to send the email.", e.getMessage()); + } + } + + @Test + void testSendEmailWithAttachmentThrowsExceptionForInvalidAddressNull() + throws MessagingException { + Email email = new Email(); + email.setTo(null); // Invalid address + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(fileInput); + + try { + emailService.sendEmailWithAttachment(email); + fail("Expected MailSendException to be thrown"); + } catch (MessagingException e) { + assertEquals("Invalid Addresses", e.getMessage()); + } + } + + @Test + void testSendEmailWithAttachmentThrowsExceptionForInvalidAddressEmpty() + throws MessagingException { + Email email = new Email(); + email.setTo(""); // Invalid address + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(fileInput); + + try { + emailService.sendEmailWithAttachment(email); + fail("Expected MailSendException to be thrown"); + } catch (MessagingException e) { + assertEquals("Invalid Addresses", e.getMessage()); + } + } } diff --git a/src/test/java/stirling/software/SPDF/config/security/mail/MailConfigTest.java b/src/test/java/stirling/software/SPDF/config/security/mail/MailConfigTest.java new file mode 100644 index 000000000..2e47f14e3 --- /dev/null +++ b/src/test/java/stirling/software/SPDF/config/security/mail/MailConfigTest.java @@ -0,0 +1,54 @@ +package stirling.software.SPDF.config.security.mail; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Properties; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import stirling.software.SPDF.model.ApplicationProperties; + +class MailConfigTest { + + private ApplicationProperties.Mail mailProps; + + @BeforeEach + void initMailProperties() { + mailProps = mock(ApplicationProperties.Mail.class); + when(mailProps.getHost()).thenReturn("smtp.example.com"); + when(mailProps.getPort()).thenReturn(587); + when(mailProps.getUsername()).thenReturn("user@example.com"); + when(mailProps.getPassword()).thenReturn("password"); + } + + @Test + void shouldConfigureJavaMailSenderWithCorrectProperties() { + ApplicationProperties appProps = mock(ApplicationProperties.class); + when(appProps.getMail()).thenReturn(mailProps); + + MailConfig config = new MailConfig(appProps); + JavaMailSender sender = config.javaMailSender(); + + assertInstanceOf(JavaMailSenderImpl.class, sender); + JavaMailSenderImpl impl = (JavaMailSenderImpl) sender; + + Properties props = impl.getJavaMailProperties(); + + assertAll( + "SMTP configuration", + () -> assertEquals("smtp.example.com", impl.getHost()), + () -> assertEquals(587, impl.getPort()), + () -> assertEquals("user@example.com", impl.getUsername()), + () -> assertEquals("password", impl.getPassword()), + () -> assertEquals("UTF-8", impl.getDefaultEncoding()), + () -> assertEquals("true", props.getProperty("mail.smtp.auth")), + () -> assertEquals("true", props.getProperty("mail.smtp.starttls.enable"))); + } +} diff --git a/src/test/java/stirling/software/SPDF/controller/api/EmailControllerTest.java b/src/test/java/stirling/software/SPDF/controller/api/EmailControllerTest.java index d33f3c947..dfd68e069 100644 --- a/src/test/java/stirling/software/SPDF/controller/api/EmailControllerTest.java +++ b/src/test/java/stirling/software/SPDF/controller/api/EmailControllerTest.java @@ -1,18 +1,25 @@ package stirling.software.SPDF.controller.api; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mail.MailSendException; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.multipart.MultipartFile; import jakarta.mail.MessagingException; @@ -20,7 +27,7 @@ import stirling.software.SPDF.config.security.mail.EmailService; import stirling.software.SPDF.model.api.Email; @ExtendWith(MockitoExtension.class) -public class EmailControllerTest { +class EmailControllerTest { private MockMvc mockMvc; @@ -28,59 +35,61 @@ public class EmailControllerTest { @InjectMocks private EmailController emailController; - @Mock private MultipartFile fileInput; - @BeforeEach void setUp() { - // Set up the MockMvc instance for testing mockMvc = MockMvcBuilders.standaloneSetup(emailController).build(); } - @Test - void testSendEmailWithAttachmentSuccess() throws Exception { - // Create a mock Email object - Email email = new Email(); - email.setTo("test@example.com"); - email.setSubject("Test Email"); - email.setBody("This is a test email."); - email.setFileInput(fileInput); + @ParameterizedTest(name = "Case {index}: exception={0}, includeTo={1}") + @MethodSource("emailParams") + void shouldHandleEmailRequests( + Exception serviceException, + boolean includeTo, + int expectedStatus, + String expectedContent) + throws Exception { + if (serviceException == null) { + doNothing().when(emailService).sendEmailWithAttachment(any(Email.class)); + } else { + doThrow(serviceException).when(emailService).sendEmailWithAttachment(any(Email.class)); + } - // Mock the service to not throw any exception - doNothing().when(emailService).sendEmailWithAttachment(any(Email.class)); + var request = + multipart("/api/v1/general/send-email") + .file("fileInput", "dummy-content".getBytes()) + .param("subject", "Test Email") + .param("body", "This is a test email."); - // Perform the request and verify the response - mockMvc.perform( - multipart("/api/v1/general/send-email") - .file("fileInput", "dummy-content".getBytes()) - .param("to", email.getTo()) - .param("subject", email.getSubject()) - .param("body", email.getBody())) - .andExpect(status().isOk()) - .andExpect(content().string("Email sent successfully")); + if (includeTo) { + request = request.param("to", "test@example.com"); + } + + mockMvc.perform(request) + .andExpect(status().is(expectedStatus)) + .andExpect(content().string(expectedContent)); } - @Test - void testSendEmailWithAttachmentFailure() throws Exception { - // Create a mock Email object - Email email = new Email(); - email.setTo("test@example.com"); - email.setSubject("Test Email"); - email.setBody("This is a test email."); - email.setFileInput(fileInput); - - // Mock the service to throw a MessagingException - doThrow(new MessagingException("Failed to send email")) - .when(emailService) - .sendEmailWithAttachment(any(Email.class)); - - // Perform the request and verify the response - mockMvc.perform( - multipart("/api/v1/general/send-email") - .file("fileInput", "dummy-content".getBytes()) - .param("to", email.getTo()) - .param("subject", email.getSubject()) - .param("body", email.getBody())) - .andExpect(status().isInternalServerError()) - .andExpect(content().string("Failed to send email: Failed to send email")); + static Stream emailParams() { + return Stream.of( + // success case + Arguments.of(null, true, 200, "Email sent successfully"), + // generic messaging error + Arguments.of( + new MessagingException("Failed to send email"), + true, + 500, + "Failed to send email: Failed to send email"), + // missing 'to' results in MailSendException + Arguments.of( + new MailSendException("Invalid Addresses"), + false, + 500, + "Invalid Addresses"), + // invalid email address formatting + Arguments.of( + new MessagingException("Invalid Addresses"), + true, + 500, + "Failed to send email: Invalid Addresses")); } }