Compare commits

..

No commits in common. "35304a1491bb6a615282c8ebc0328d9920228db3" and "8bfdb2abb5609e2fe30226e2915017cd9a949b00" have entirely different histories.

8 changed files with 108 additions and 379 deletions

View File

@ -37,21 +37,8 @@ public class EmailService {
*/ */
@Async @Async
public void sendEmailWithAttachment(Email email) throws MessagingException { public void sendEmailWithAttachment(Email email) throws MessagingException {
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(); ApplicationProperties.Mail mailProperties = applicationProperties.getMail();
MultipartFile file = email.getFileInput();
// Creates a MimeMessage to represent the email // Creates a MimeMessage to represent the email
MimeMessage message = mailSender.createMimeMessage(); MimeMessage message = mailSender.createMimeMessage();

View File

@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mail.MailSendException;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -54,11 +53,6 @@ public class EmailController {
// Calls the service to send the email with attachment // Calls the service to send the email with attachment
emailService.sendEmailWithAttachment(email); emailService.sendEmailWithAttachment(email);
return ResponseEntity.ok("Email sent successfully"); 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) { } catch (MessagingException e) {
// Catches any messaging exception (e.g., invalid email address, SMTP server issues) // Catches any messaging exception (e.g., invalid email address, SMTP server issues)
String errorMsg = "Failed to send email: " + e.getMessage(); String errorMsg = "Failed to send email: " + e.getMessage();

View File

@ -1,6 +1,5 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import java.util.Locale;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -53,6 +52,6 @@ public class UploadLimitService {
if (bytes < 1024) return bytes + " B"; if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024)); int exp = (int) (Math.log(bytes) / Math.log(1024));
String pre = "KMGTPE".charAt(exp - 1) + "B"; String pre = "KMGTPE".charAt(exp - 1) + "B";
return String.format(Locale.US, "%.1f %s", bytes / Math.pow(1024, exp), pre); return String.format("%.1f %s", bytes / Math.pow(1024, exp), pre);
} }
} }

View File

@ -10,9 +10,9 @@ multiPdfPrompt=Оберіть PDFи (2+)
multiPdfDropPrompt=Оберіть (або перетягніть) всі необхідні PDFи multiPdfDropPrompt=Оберіть (або перетягніть) всі необхідні PDFи
imgPrompt=Оберіть зображення(я) imgPrompt=Оберіть зображення(я)
genericSubmit=Надіслати genericSubmit=Надіслати
uploadLimit=Максимальний розмір файлу: uploadLimit=Maximum file size:
uploadLimitExceededSingular=занадто великий. Максимально дозволений розмір - uploadLimitExceededSingular=is too large. Maximum allowed size is
uploadLimitExceededPlural=занадто великі. Максимально дозволений розмір - uploadLimitExceededPlural=are too large. Maximum allowed size is
processTimeWarning=Увага: Цей процес може тривати до хвилини в залежності від розміру файлу. processTimeWarning=Увага: Цей процес може тривати до хвилини в залежності від розміру файлу.
pageOrderPrompt=Порядок сторінок (введіть список номерів сторінок через кому): pageOrderPrompt=Порядок сторінок (введіть список номерів сторінок через кому):
pageSelectionPrompt=Користувацький вибір сторінки (введіть список номерів сторінок через кому 1,5,6 або функції типу 2n+1) : pageSelectionPrompt=Користувацький вибір сторінки (введіть список номерів сторінок через кому 1,5,6 або функції типу 2n+1) :
@ -86,14 +86,14 @@ loading=Завантаження...
addToDoc=Додати до документу addToDoc=Додати до документу
reset=Скинути reset=Скинути
apply=Застосувати apply=Застосувати
noFileSelected=Файл не вибрано. Будь ласка, завантажте один. noFileSelected=No file selected. Please upload one.
legal.privacy=Політика конфіденційності legal.privacy=Політика конфіденційності
legal.terms=Правила та умови legal.terms=Правила та умови
legal.accessibility=Доступність legal.accessibility=Доступність
legal.cookie=Політика використання файлів cookie legal.cookie=Політика використання файлів cookie
legal.impressum=Вихідні дані legal.impressum=Вихідні дані
legal.showCookieBanner=Налаштування файлів cookie legal.showCookieBanner=Cookie Preferences
############### ###############
# Pipeline # # Pipeline #
@ -237,7 +237,7 @@ adminUserSettings.activeUsers=Активні користувачі:
adminUserSettings.disabledUsers=Заблоковані користувачі: adminUserSettings.disabledUsers=Заблоковані користувачі:
adminUserSettings.totalUsers=Всього користувачів: adminUserSettings.totalUsers=Всього користувачів:
adminUserSettings.lastRequest=Останній запит adminUserSettings.lastRequest=Останній запит
adminUserSettings.usage=Переглянути використання adminUserSettings.usage=View Usage
endpointStatistics.title=Статистика кінцевих точок endpointStatistics.title=Статистика кінцевих точок
endpointStatistics.header=Статистика кінцевих точок endpointStatistics.header=Статистика кінцевих точок
@ -364,9 +364,9 @@ home.compressPdfs.title=Стиснути
home.compressPdfs.desc=Стискайте PDF-файли, щоб зменшити їх розмір. home.compressPdfs.desc=Стискайте PDF-файли, щоб зменшити їх розмір.
compressPdfs.tags=стиск,маленький,крихітний compressPdfs.tags=стиск,маленький,крихітний
home.unlockPDFForms.title=Розблокувати PDF форми home.unlockPDFForms.title=Unlock PDF Forms
home.unlockPDFForms.desc=Видалити властивість "тільки для читання" з полів форми у PDF-документі. home.unlockPDFForms.desc=Remove read-only property of form fields in a PDF document.
unlockPDFForms.tags=видалити,розблокувати,форма,поле,тільки для читання unlockPDFForms.tags=remove,delete,form,field,readonly
home.changeMetadata.title=Змінити метадані home.changeMetadata.title=Змінити метадані
home.changeMetadata.desc=Змінити/видалити/додати метадані з документа PDF home.changeMetadata.desc=Змінити/видалити/додати метадані з документа PDF
@ -609,7 +609,7 @@ login.userIsDisabled=Користувач деактивовано, вхід з
login.alreadyLoggedIn=Ви вже увійшли до login.alreadyLoggedIn=Ви вже увійшли до
login.alreadyLoggedIn2=пристроїв (а). Будь ласка, вийдіть із цих пристроїв і спробуйте знову. login.alreadyLoggedIn2=пристроїв (а). Будь ласка, вийдіть із цих пристроїв і спробуйте знову.
login.toManySessions=У вас дуже багато активних сесій login.toManySessions=У вас дуже багато активних сесій
login.logoutMessage=Ви вийшли з системи. login.logoutMessage=You have been logged out.
#auto-redact #auto-redact
autoRedact.title=Автоматичне редагування autoRedact.title=Автоматичне редагування
@ -742,10 +742,10 @@ sanitizePDF.title=Дезінфекція PDF
sanitizePDF.header=Дезінфекція PDF файлу sanitizePDF.header=Дезінфекція PDF файлу
sanitizePDF.selectText.1=Видалити JavaScript sanitizePDF.selectText.1=Видалити JavaScript
sanitizePDF.selectText.2=Видалити вбудовані файли sanitizePDF.selectText.2=Видалити вбудовані файли
sanitizePDF.selectText.3=Видалити XMP метадані sanitizePDF.selectText.3=Remove XMP metadata
sanitizePDF.selectText.4=Видалити посилання sanitizePDF.selectText.4=Видалити посилання
sanitizePDF.selectText.5=Видалити шрифти sanitizePDF.selectText.5=Видалити шрифти
sanitizePDF.selectText.6=Видалити метадані інформації про документ sanitizePDF.selectText.6=Remove Document Info Metadata
sanitizePDF.submit=Дезінфекція sanitizePDF.submit=Дезінфекція
@ -1071,7 +1071,7 @@ rotate.submit=Повернути
split.title=Розділити PDF split.title=Розділити PDF
split.header=Розділити PDF split.header=Розділити PDF
split.desc.1=Числа, які ви вибрали, це номери сторінок, на яких ви хочете зробити розділ. split.desc.1=Числа, які ви вибрали, це номери сторінок, на яких ви хочете зробити розділ.
split.desc.2=Таким чином, вибір 1,3,7-8 розділіть 10-сторінковий документ на 6 окремих PDF-файлів з: split.desc.2=Таким чином, вибір 1,3,7-8 розділить 10-сторінковий документ на 6 окремих PDF-файлів з:
split.desc.3=Документ #1: Сторінка 1 split.desc.3=Документ #1: Сторінка 1
split.desc.4=Документ #2: Сторінки 2 і 3 split.desc.4=Документ #2: Сторінки 2 і 3
split.desc.5=Документ #3: Сторінки 4, 5 і 6 split.desc.5=Документ #3: Сторінки 4, 5 і 6
@ -1372,68 +1372,68 @@ fileChooser.extractPDF=Видобування...
#release notes #release notes
releases.footer=Релізи releases.footer=Релізи
releases.title=Примітки до релізу releases.title=Примечания к релизу
releases.header=Примітки до релізу releases.header=Примечания к релизу
releases.current.version=Поточний реліз releases.current.version=Текущий релиз
releases.note=Примітки до релізу доступні лише англійською мовою releases.note=Примітка до релізу доступна тільки на англійській мові
#Validate Signature #Validate Signature
validateSignature.title=Перевірка підписів PDF validateSignature.title=Перевірка підписів PDF
validateSignature.header=Перевірка цифрових підписів validateSignature.header=Перевірка цифрових підписів
validateSignature.selectPDF=Виберіть підписаний PDF-файл validateSignature.selectPDF=Виберіть підписаний PDF-файл
validateSignature.submit=Перевірити підписи validateSignature.submit=Перевірити підписи
validateSignature.results=Результати перевірки validateSignature.results=Результаты проверки
validateSignature.status=Статус validateSignature.status=Статус
validateSignature.signer=Підписант validateSignature.signer=Підписант
validateSignature.date=Дата validateSignature.date=Дата
validateSignature.reason=Причина validateSignature.reason=Причина
validateSignature.location=Місцезнаходження validateSignature.location=Местоположение
validateSignature.noSignatures=У цьому документі не знайдено цифрових підписів validateSignature.noSignatures=В цьому документі не знайдено цифрових підписів
validateSignature.status.valid=Дійсний validateSignature.status.valid=Дійна
validateSignature.status.invalid=Недійсний validateSignature.status.invalid=Недійсна
validateSignature.chain.invalid=Перевірка ланцюга сертифікатів не вдалася - неможливо перевірити особу підписанта validateSignature.chain.invalid=Перевірка цепочки сертифікатів не удалась - неможливо перевірити особистість підписанта
validateSignature.trust.invalid=Сертифікат відсутній у довіреному сховищі - джерело не може бути перевірено validateSignature.trust.invalid=Сертифікат відсутній у довіреному сховищі - джерело не може бути перевірено
validateSignature.cert.expired=Термін дії сертифіката закінчився validateSignature.cert.expired=Срок дії сертифіката істеку
validateSignature.cert.revoked=Сертифікат було відкликано validateSignature.cert.revoked=Сертифікат був отозван
validateSignature.signature.info=Інформація про підпис validateSignature.signature.info=Інформація про підписи
validateSignature.signature=Підпис validateSignature.signature=Подпись
validateSignature.signature.mathValid=Підпис математично коректний, АЛЕ: validateSignature.signature.mathValid=Подпись математически корректна, НО:
validateSignature.selectCustomCert=Користувацький файл сертифіката X.509 (Необов'язково) validateSignature.selectCustomCert=Користувачський файл сертифіката X.509 (Необов'язково)
validateSignature.cert.info=Інформація про сертифікат validateSignature.cert.info=Сведения про сертифікати
validateSignature.cert.issuer=Видавець validateSignature.cert.issuer=Издатель
validateSignature.cert.subject=Суб'єкт validateSignature.cert.subject=суб'єкт
validateSignature.cert.serialNumber=Серійний номер validateSignature.cert.serialNumber=Серийний номер
validateSignature.cert.validFrom=Дійсний з validateSignature.cert.validFrom=Дійсний з
validateSignature.cert.validUntil=Дійсний до validateSignature.cert.validUntil=Дійсний до
validateSignature.cert.algorithm=Алгоритм validateSignature.cert.algorithm=Алгоритм
validateSignature.cert.keySize=Розмір ключа validateSignature.cert.keySize=Розмір ключа
validateSignature.cert.version=Версія validateSignature.cert.version=Версія
validateSignature.cert.keyUsage=Використання ключа validateSignature.cert.keyUsage=Використання ключа
validateSignature.cert.selfSigned=Самопідписаний validateSignature.cert.selfSigned=Самоподписанный
validateSignature.cert.bits=біт validateSignature.cert.bits=біт
#################### ####################
# Cookie banner # # Cookie banner #
#################### ####################
cookieBanner.popUp.title=Як ми використовуємо файли cookie cookieBanner.popUp.title=How we use Cookies
cookieBanner.popUp.description.1=Ми використовуємо файли cookie та інші технології, щоб Stirling PDF працював краще для вас — допомагаючи нам покращувати наші інструменти та створювати функції, які вам сподобаються. cookieBanner.popUp.description.1=We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love.
cookieBanner.popUp.description.2=Якщо ви не хочете, натискання «Ні, дякую» увімкне лише необхідні файли cookie, потрібні для безперебійної роботи. cookieBanner.popUp.description.2=If youd rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly.
cookieBanner.popUp.acceptAllBtn=Добре cookieBanner.popUp.acceptAllBtn=Okay
cookieBanner.popUp.acceptNecessaryBtn=Ні, дякую cookieBanner.popUp.acceptNecessaryBtn=No Thanks
cookieBanner.popUp.showPreferencesBtn=Керувати налаштуваннями cookieBanner.popUp.showPreferencesBtn=Manage preferences
cookieBanner.preferencesModal.title=Центр налаштувань згоди cookieBanner.preferencesModal.title=Consent Preferences Center
cookieBanner.preferencesModal.acceptAllBtn=Прийняти всі cookieBanner.preferencesModal.acceptAllBtn=Accept all
cookieBanner.preferencesModal.acceptNecessaryBtn=Відхилити всі cookieBanner.preferencesModal.acceptNecessaryBtn=Reject all
cookieBanner.preferencesModal.savePreferencesBtn=Зберегти налаштування cookieBanner.preferencesModal.savePreferencesBtn=Save preferences
cookieBanner.preferencesModal.closeIconLabel=Закрити модальне вікно cookieBanner.preferencesModal.closeIconLabel=Close modal
cookieBanner.preferencesModal.serviceCounterLabel=Сервіс|Сервіси cookieBanner.preferencesModal.serviceCounterLabel=Service|Services
cookieBanner.preferencesModal.subtitle=Використання файлів cookie cookieBanner.preferencesModal.subtitle=Cookie Usage
cookieBanner.preferencesModal.description.1=Stirling PDF використовує файли cookie та подібні технології, щоб покращити ваш досвід і зрозуміти, як використовуються наші інструменти. Це допомагає нам покращувати продуктивність, розробляти функції, які вас цікавлять, і надавати постійну підтримку нашим користувачам. cookieBanner.preferencesModal.description.1=Stirling PDF uses cookies and similar technologies to enhance your experience and understand how our tools are used. This helps us improve performance, develop the features you care about, and provide ongoing support to our users.
cookieBanner.preferencesModal.description.2=Stirling PDF не може — і ніколи не буде — відстежувати або отримувати доступ до вмісту документів, які ви використовуєте. cookieBanner.preferencesModal.description.2=Stirling PDF cannot—and will never—track or access the content of the documents you use.
cookieBanner.preferencesModal.description.3=Ваша конфіденційність і довіра є основою того, що ми робимо. cookieBanner.preferencesModal.description.3=Your privacy and trust are at the core of what we do.
cookieBanner.preferencesModal.necessary.title.1=Суворо необхідні файли cookie cookieBanner.preferencesModal.necessary.title.1=Strictly Necessary Cookies
cookieBanner.preferencesModal.necessary.title.2=Завжди увімкнені cookieBanner.preferencesModal.necessary.title.2=Always Enabled
cookieBanner.preferencesModal.necessary.description=Ці файли cookie є необхідними для правильного функціонування вебсайту. Вони забезпечують основні функції, такі як налаштування ваших уподобань конфіденційності, вхід у систему та заповнення форм — тому їх не можна вимкнути. cookieBanner.preferencesModal.necessary.description=These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they cant be turned off.
cookieBanner.preferencesModal.analytics.title=Аналітика cookieBanner.preferencesModal.analytics.title=Analytics
cookieBanner.preferencesModal.analytics.description=Ці файли cookie допомагають нам зрозуміти, як використовуються наші інструменти, щоб ми могли зосередитися на створенні функцій, які найбільше цінує наша спільнота. Будьте впевнені — Stirling PDF не може і ніколи не буде відстежувати вміст документів, з якими ви працюєте. cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.

View File

@ -1,7 +1,5 @@
package stirling.software.SPDF.config.security.mail; 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 static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -59,111 +57,4 @@ public class EmailServiceTest {
// Verify that the email was sent using mailSender // Verify that the email was sent using mailSender
verify(mailSender).send(mimeMessage); 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());
}
}
} }

View File

@ -1,54 +0,0 @@
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")));
}
}

View File

@ -1,25 +1,18 @@
package stirling.software.SPDF.controller.api; package stirling.software.SPDF.controller.api;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.doNothing; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.mockito.Mockito.doThrow; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
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.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mail.MailSendException;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.multipart.MultipartFile;
import jakarta.mail.MessagingException; import jakarta.mail.MessagingException;
@ -27,7 +20,7 @@ import stirling.software.SPDF.config.security.mail.EmailService;
import stirling.software.SPDF.model.api.Email; import stirling.software.SPDF.model.api.Email;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class EmailControllerTest { public class EmailControllerTest {
private MockMvc mockMvc; private MockMvc mockMvc;
@ -35,61 +28,59 @@ class EmailControllerTest {
@InjectMocks private EmailController emailController; @InjectMocks private EmailController emailController;
@Mock private MultipartFile fileInput;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
// Set up the MockMvc instance for testing
mockMvc = MockMvcBuilders.standaloneSetup(emailController).build(); mockMvc = MockMvcBuilders.standaloneSetup(emailController).build();
} }
@ParameterizedTest(name = "Case {index}: exception={0}, includeTo={1}") @Test
@MethodSource("emailParams") void testSendEmailWithAttachmentSuccess() throws Exception {
void shouldHandleEmailRequests( // Create a mock Email object
Exception serviceException, Email email = new Email();
boolean includeTo, email.setTo("test@example.com");
int expectedStatus, email.setSubject("Test Email");
String expectedContent) email.setBody("This is a test email.");
throws Exception { email.setFileInput(fileInput);
if (serviceException == null) {
doNothing().when(emailService).sendEmailWithAttachment(any(Email.class));
} else {
doThrow(serviceException).when(emailService).sendEmailWithAttachment(any(Email.class));
}
var request = // Mock the service to not throw any exception
multipart("/api/v1/general/send-email") doNothing().when(emailService).sendEmailWithAttachment(any(Email.class));
.file("fileInput", "dummy-content".getBytes())
.param("subject", "Test Email")
.param("body", "This is a test email.");
if (includeTo) { // Perform the request and verify the response
request = request.param("to", "test@example.com"); mockMvc.perform(
} multipart("/api/v1/general/send-email")
.file("fileInput", "dummy-content".getBytes())
mockMvc.perform(request) .param("to", email.getTo())
.andExpect(status().is(expectedStatus)) .param("subject", email.getSubject())
.andExpect(content().string(expectedContent)); .param("body", email.getBody()))
.andExpect(status().isOk())
.andExpect(content().string("Email sent successfully"));
} }
static Stream<Arguments> emailParams() { @Test
return Stream.of( void testSendEmailWithAttachmentFailure() throws Exception {
// success case // Create a mock Email object
Arguments.of(null, true, 200, "Email sent successfully"), Email email = new Email();
// generic messaging error email.setTo("test@example.com");
Arguments.of( email.setSubject("Test Email");
new MessagingException("Failed to send email"), email.setBody("This is a test email.");
true, email.setFileInput(fileInput);
500,
"Failed to send email: Failed to send email"), // Mock the service to throw a MessagingException
// missing 'to' results in MailSendException doThrow(new MessagingException("Failed to send email"))
Arguments.of( .when(emailService)
new MailSendException("Invalid Addresses"), .sendEmailWithAttachment(any(Email.class));
false,
500, // Perform the request and verify the response
"Invalid Addresses"), mockMvc.perform(
// invalid email address formatting multipart("/api/v1/general/send-email")
Arguments.of( .file("fileInput", "dummy-content".getBytes())
new MessagingException("Invalid Addresses"), .param("to", email.getTo())
true, .param("subject", email.getSubject())
500, .param("body", email.getBody()))
"Failed to send email: Invalid Addresses")); .andExpect(status().isInternalServerError())
.andExpect(content().string("Failed to send email: Failed to send email"));
} }
} }

View File

@ -1,79 +0,0 @@
package stirling.software.SPDF.controller.web;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import stirling.software.SPDF.model.ApplicationProperties;
class UploadLimitServiceTest {
private UploadLimitService uploadLimitService;
private ApplicationProperties applicationProperties;
private ApplicationProperties.System systemProps;
@BeforeEach
void setUp() {
applicationProperties = mock(ApplicationProperties.class);
systemProps = mock(ApplicationProperties.System.class);
when(applicationProperties.getSystem()).thenReturn(systemProps);
uploadLimitService = new UploadLimitService();
// inject mock
try {
var field = UploadLimitService.class.getDeclaredField("applicationProperties");
field.setAccessible(true);
field.set(uploadLimitService, applicationProperties);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@ParameterizedTest(name = "getUploadLimit case #{index}: input={0}, expected={1}")
@MethodSource("uploadLimitParams")
void shouldComputeUploadLimitCorrectly(String input, long expected) {
when(systemProps.getFileUploadLimit()).thenReturn(input);
long result = uploadLimitService.getUploadLimit();
assertEquals(expected, result);
}
static Stream<Arguments> uploadLimitParams() {
return Stream.of(
// empty or null input yields 0
Arguments.of(null, 0L),
Arguments.of("", 0L),
// invalid formats
Arguments.of("1234MB", 0L),
Arguments.of("5TB", 0L),
// valid formats
Arguments.of("10KB", 10 * 1024L),
Arguments.of("2MB", 2 * 1024 * 1024L),
Arguments.of("1GB", 1L * 1024 * 1024 * 1024),
Arguments.of("5mb", 5 * 1024 * 1024L),
Arguments.of("0MB", 0L));
}
@ParameterizedTest(name = "getReadableUploadLimit case #{index}: rawValue={0}, expected={1}")
@MethodSource("readableLimitParams")
void shouldReturnReadableFormat(String rawValue, String expected) {
when(systemProps.getFileUploadLimit()).thenReturn(rawValue);
String result = uploadLimitService.getReadableUploadLimit();
assertEquals(expected, result);
}
static Stream<Arguments> readableLimitParams() {
return Stream.of(
Arguments.of(null, "0 B"),
Arguments.of("", "0 B"),
Arguments.of("1KB", "1.0 KB"),
Arguments.of("2MB", "2.0 MB"));
}
}