Add Email Sending Service with Attachment Support (#3455)

# Description of Changes

Please provide a summary of the changes, including:

- **What was changed**  
- Introduced a new `EmailService` for asynchronous email delivery with
attachment support.
- Added `MailConfig` to configure a `JavaMailSender` bean using SMTP
settings from `ApplicationProperties`.
- Created `EmailController` endpoint (`/api/v1/general/send-email`) to
accept multipart/form-data requests for sending emails.
- Defined an `Email` API model to encapsulate recipient, subject, body,
and file input.
- Extended `ApplicationProperties` to include a nested `Mail` class for
SMTP host, port, username/password, and sender address.
- Updated `settings.yml.template` to include SMTP configuration
placeholders.
- Enhanced `.github/labeler-config.yml` to cover all new security- and
API-related source files for automated labeling.

- **Why the change was made**  
- To enable Stirling-PDF to notify users via email—particularly useful
for sending generated PDFs or alerts—directly from the application.
- To centralize mail server configuration in application properties and
streamline onboarding for new environments.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Ludy
2025-05-07 10:20:21 +02:00
committed by GitHub
parent 2ac606608a
commit 5b0eaec436
10 changed files with 406 additions and 12 deletions

View File

@@ -0,0 +1,64 @@
package stirling.software.SPDF.config.security.mail;
import static org.mockito.Mockito.*;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.Email;
@ExtendWith(MockitoExtension.class)
public class EmailServiceTest {
@Mock
private JavaMailSender mailSender;
@Mock
private ApplicationProperties applicationProperties;
@Mock
private ApplicationProperties.Mail mailProperties;
@Mock
private MultipartFile fileInput;
@InjectMocks
private EmailService emailService;
@Test
void testSendEmailWithAttachment() throws MessagingException {
// Mock the values returned by ApplicationProperties
when(applicationProperties.getMail()).thenReturn(mailProperties);
when(mailProperties.getFrom()).thenReturn("no-reply@stirling-software.com");
// 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 MultipartFile behavior
when(fileInput.getOriginalFilename()).thenReturn("testFile.txt");
// Mock MimeMessage
MimeMessage mimeMessage = mock(MimeMessage.class);
// Configure mailSender to return the mocked MimeMessage
when(mailSender.createMimeMessage()).thenReturn(mimeMessage);
// Call the service method
emailService.sendEmailWithAttachment(email);
// Verify that the email was sent using mailSender
verify(mailSender).send(mimeMessage);
}
}

View File

@@ -0,0 +1,84 @@
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 jakarta.mail.MessagingException;
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.junit.jupiter.MockitoExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.config.security.mail.EmailService;
import stirling.software.SPDF.model.api.Email;
@ExtendWith(MockitoExtension.class)
public class EmailControllerTest {
private MockMvc mockMvc;
@Mock private EmailService emailService;
@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);
// Mock the service to not throw any exception
doNothing().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().isOk())
.andExpect(content().string("Email sent successfully"));
}
@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"));
}
}