Merge branch 'main' into check_length

This commit is contained in:
Ludy 2025-05-24 14:22:48 +02:00 committed by GitHub
commit c155f8ba1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 458 additions and 187 deletions

View File

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

View File

@ -3,6 +3,7 @@ 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;
@ -53,6 +54,11 @@ 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,5 +1,6 @@
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;
@ -52,6 +53,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("%.1f %s", bytes / Math.pow(1024, exp), pre); return String.format(Locale.US, "%.1f %s", bytes / Math.pow(1024, exp), pre);
} }
} }

View File

@ -364,9 +364,9 @@ home.compressPdfs.title=Komprimieren
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren
compressPdfs.tags=komprimieren,verkleinern,minimieren compressPdfs.tags=komprimieren,verkleinern,minimieren
home.unlockPDFForms.title=Unlock PDF Forms home.unlockPDFForms.title=Schreibgeschützte PDF-Formfelder entfernen
home.unlockPDFForms.desc=Remove read-only property of form fields in a PDF document. home.unlockPDFForms.desc=Entfernen Sie die schreibgeschützte Eigenschaft von Formularfeldern in einem PDF-Dokument.
unlockPDFForms.tags=remove,delete,form,field,readonly unlockPDFForms.tags=entfernen,löschen,form,feld,schreibgeschützt
home.changeMetadata.title=Metadaten ändern home.changeMetadata.title=Metadaten ändern
home.changeMetadata.desc=Ändern/Entfernen/Hinzufügen von Metadaten aus einem PDF-Dokument home.changeMetadata.desc=Ändern/Entfernen/Hinzufügen von Metadaten aus einem PDF-Dokument
@ -1197,9 +1197,9 @@ changeMetadata.selectText.5=Benutzerdefinierten Metadateneintrag hinzufügen
changeMetadata.submit=Ändern changeMetadata.submit=Ändern
#unlockPDFForms #unlockPDFForms
unlockPDFForms.title=Remove Read-Only from Form Fields unlockPDFForms.title=Entfernen Sie schreibgeschützte Formfelder
unlockPDFForms.header=Unlock PDF Forms unlockPDFForms.header=Schreibgeschützte PDF-Formfelder entfernen
unlockPDFForms.submit=Remove unlockPDFForms.submit=Entfernen
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF zu PDF/A pdfToPDFA.title=PDF zu PDF/A

View File

@ -10,9 +10,9 @@ multiPdfPrompt=Выберите PDF-файлы (2+)
multiPdfDropPrompt=Выберите (или перетащите) все необходимые PDF-файлы multiPdfDropPrompt=Выберите (или перетащите) все необходимые PDF-файлы
imgPrompt=Выберите изображение(я) imgPrompt=Выберите изображение(я)
genericSubmit=Отправить genericSubmit=Отправить
uploadLimit=Maximum file size: uploadLimit=Максимальный размер файла:
uploadLimitExceededSingular=is too large. Maximum allowed size is uploadLimitExceededSingular=слишком велик. Максимально допустимый размер -
uploadLimitExceededPlural=are too large. Maximum allowed size is uploadLimitExceededPlural=слишком велики. Максимально допустимый размер -
processTimeWarning=Внимание: Данный процесс может занять до минуты в зависимости от размера файла processTimeWarning=Внимание: Данный процесс может занять до минуты в зависимости от размера файла
pageOrderPrompt=Пользовательский порядок страниц (Введите список номеров страниц через запятую или функции типа 2n+1): pageOrderPrompt=Пользовательский порядок страниц (Введите список номеров страниц через запятую или функции типа 2n+1):
pageSelectionPrompt=Выбор страниц (Введите список номеров страниц через запятую 1,5,6 или функции типа 2n+1): pageSelectionPrompt=Выбор страниц (Введите список номеров страниц через запятую 1,5,6 или функции типа 2n+1):
@ -86,14 +86,14 @@ loading=Загрузка...
addToDoc=Добавить в документ addToDoc=Добавить в документ
reset=Сбросить reset=Сбросить
apply=Применить apply=Применить
noFileSelected=No file selected. Please upload one. noFileSelected=Файл не выбран. Пожалуйста, загрузите его.
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 Preferences legal.showCookieBanner=Настройки файлов cookie
############### ###############
# Pipeline # # Pipeline #
@ -237,7 +237,7 @@ adminUserSettings.activeUsers=Активные пользователи:
adminUserSettings.disabledUsers=Отключенные пользователи: adminUserSettings.disabledUsers=Отключенные пользователи:
adminUserSettings.totalUsers=Всего пользователей: adminUserSettings.totalUsers=Всего пользователей:
adminUserSettings.lastRequest=Последний запрос adminUserSettings.lastRequest=Последний запрос
adminUserSettings.usage=View Usage adminUserSettings.usage=Просмотр использования
endpointStatistics.title=Статистика конечных точек endpointStatistics.title=Статистика конечных точек
endpointStatistics.header=Статистика конечных точек endpointStatistics.header=Статистика конечных точек
@ -292,18 +292,18 @@ home.desc=Ваше локальное решение для всех потре
home.searchBar=Поиск функций... home.searchBar=Поиск функций...
home.viewPdf.title=View/Edit PDF home.viewPdf.title=Просмотр/Редактирование PDF
home.viewPdf.desc=Просмотр, аннотирование, добавление текста или изображений home.viewPdf.desc=Просмотр, аннотирование, добавление текста или изображений
viewPdf.tags=просмотр,чтение,аннотации,текст,изображение viewPdf.tags=просмотр,чтение,аннотации,текст,изображение
home.setFavorites=Set Favourites home.setFavorites=Добавить в избранное
home.hideFavorites=Hide Favourites home.hideFavorites=Скрыть избранное
home.showFavorites=Show Favourites home.showFavorites=Показать избранное
home.legacyHomepage=Old homepage home.legacyHomepage=Старая главная страница
home.newHomePage=Try our new homepage! home.newHomePage=Попробуйте нашу новую главную страницу!
home.alphabetical=Alphabetical home.alphabetical=По алфавиту
home.globalPopularity=Global Popularity home.globalPopularity=Глобальная популярность
home.sortBy=Sort by: home.sortBy=Сортировать по:
home.multiTool.title=Мультиинструмент PDF home.multiTool.title=Мультиинструмент PDF
home.multiTool.desc=Объединение, поворот, переупорядочивание и удаление страниц home.multiTool.desc=Объединение, поворот, переупорядочивание и удаление страниц
@ -364,9 +364,9 @@ home.compressPdfs.title=Сжать
home.compressPdfs.desc=Сжимайте PDF-файлы для уменьшения их размера. home.compressPdfs.desc=Сжимайте PDF-файлы для уменьшения их размера.
compressPdfs.tags=сжатие,маленький,крошечный compressPdfs.tags=сжатие,маленький,крошечный
home.unlockPDFForms.title=Unlock PDF Forms home.unlockPDFForms.title=Разблокировать формы PDF
home.unlockPDFForms.desc=Remove read-only property of form fields in a PDF document. home.unlockPDFForms.desc=Удалить свойство только для чтения из полей формы в PDF-документе.
unlockPDFForms.tags=remove,delete,form,field,readonly unlockPDFForms.tags=удалить,удаление,форма,поле,только для чтения
home.changeMetadata.title=Изменить метаданные home.changeMetadata.title=Изменить метаданные
home.changeMetadata.desc=Изменить/удалить/добавить метаданные из PDF-документа home.changeMetadata.desc=Изменить/удалить/добавить метаданные из PDF-документа
@ -494,9 +494,9 @@ home.MarkdownToPDF.title=Markdown в PDF
home.MarkdownToPDF.desc=Преобразует любой файл Markdown в PDF home.MarkdownToPDF.desc=Преобразует любой файл Markdown в PDF
MarkdownToPDF.tags=разметка,веб-контент,преобразование,конвертация MarkdownToPDF.tags=разметка,веб-контент,преобразование,конвертация
home.PDFToMarkdown.title=PDF to Markdown home.PDFToMarkdown.title=PDF в Markdown
home.PDFToMarkdown.desc=Converts any PDF to Markdown home.PDFToMarkdown.desc=Конвертирует любой PDF в Markdown
PDFToMarkdown.tags=markup,web-content,transformation,convert,md PDFToMarkdown.tags=разметка,веб-контент,преобразование,конвертировать,md
home.getPdfInfo.title=Получить ВСЮ информацию о PDF home.getPdfInfo.title=Получить ВСЮ информацию о PDF
home.getPdfInfo.desc=Собирает всю возможную информацию о PDF home.getPdfInfo.desc=Собирает всю возможную информацию о PDF
@ -609,7 +609,7 @@ login.userIsDisabled=Пользователь деактивирован, вхо
login.alreadyLoggedIn=Вы уже вошли в login.alreadyLoggedIn=Вы уже вошли в
login.alreadyLoggedIn2=устройств(а). Пожалуйста, выйдите из этих устройств и попробуйте снова. login.alreadyLoggedIn2=устройств(а). Пожалуйста, выйдите из этих устройств и попробуйте снова.
login.toManySessions=У вас слишком много активных сессий login.toManySessions=У вас слишком много активных сессий
login.logoutMessage=You have been logged out. login.logoutMessage=Вы вышли из системы.
#auto-redact #auto-redact
autoRedact.title=Автоматическое редактирование autoRedact.title=Автоматическое редактирование
@ -648,7 +648,7 @@ redact.showAttatchments=Показать вложения
redact.showLayers=Показать слои (двойной щелчок для сброса всех слоев к состоянию по умолчанию) redact.showLayers=Показать слои (двойной щелчок для сброса всех слоев к состоянию по умолчанию)
redact.colourPicker=Выбор цвета redact.colourPicker=Выбор цвета
redact.findCurrentOutlineItem=Найти текущий элемент структуры redact.findCurrentOutlineItem=Найти текущий элемент структуры
redact.applyChanges=Apply Changes redact.applyChanges=Применить изменения
#showJS #showJS
showJS.title=Показать Javascript showJS.title=Показать Javascript
@ -686,9 +686,9 @@ MarkdownToPDF.credit=Использует WeasyPrint
#pdf-to-markdown #pdf-to-markdown
PDFToMarkdown.title=PDF To Markdown PDFToMarkdown.title=PDF в Markdown
PDFToMarkdown.header=PDF To Markdown PDFToMarkdown.header=PDF в Markdown
PDFToMarkdown.submit=Convert PDFToMarkdown.submit=Конвертировать
#url-to-pdf #url-to-pdf
@ -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=Remove XMP metadata sanitizePDF.selectText.3=Удалить метаданные XMP
sanitizePDF.selectText.4=Удалить ссылки sanitizePDF.selectText.4=Удалить ссылки
sanitizePDF.selectText.5=Удалить шрифты sanitizePDF.selectText.5=Удалить шрифты
sanitizePDF.selectText.6=Remove Document Info Metadata sanitizePDF.selectText.6=Удалить метаданные информации о документе
sanitizePDF.submit=Очистить PDF sanitizePDF.submit=Очистить PDF
@ -894,8 +894,8 @@ sign.last=Последняя страница
sign.next=Следующая страница sign.next=Следующая страница
sign.previous=Предыдущая страница sign.previous=Предыдущая страница
sign.maintainRatio=Переключить сохранение пропорций sign.maintainRatio=Переключить сохранение пропорций
sign.undo=Undo sign.undo=Отменить
sign.redo=Redo sign.redo=Повторить
#repair #repair
repair.title=Восстановление repair.title=Восстановление
@ -966,8 +966,8 @@ compress.title=Сжать
compress.header=Сжать PDF compress.header=Сжать PDF
compress.credit=Этот сервис использует qpdf для сжатия/оптимизации PDF. compress.credit=Этот сервис использует qpdf для сжатия/оптимизации PDF.
compress.grayscale.label=Применить шкалу серого для сжатия compress.grayscale.label=Применить шкалу серого для сжатия
compress.selectText.1=Compression Settings compress.selectText.1=Настройки сжатия
compress.selectText.1.1=1-3 PDF compression,</br> 4-6 lite image compression,</br> 7-9 intense image compression Will dramatically reduce image quality compress.selectText.1.1=1-3 Сжатие PDF,</br> 4-6 легкое сжатие изображений,</br> 7-9 интенсивное сжатие изображений Существенно снижает качество изображений
compress.selectText.2=Уровень оптимизации: compress.selectText.2=Уровень оптимизации:
compress.selectText.4=Автоматический режим - автоматически настраивает качество для получения точного размера PDF compress.selectText.4=Автоматический режим - автоматически настраивает качество для получения точного размера PDF
compress.selectText.5=Ожидаемый размер PDF (например, 25MB, 10.8MB, 25KB) compress.selectText.5=Ожидаемый размер PDF (например, 25MB, 10.8MB, 25KB)
@ -1006,7 +1006,7 @@ pdfOrganiser.mode.7=Удалить первую
pdfOrganiser.mode.8=Удалить последнюю pdfOrganiser.mode.8=Удалить последнюю
pdfOrganiser.mode.9=Удалить первую и последнюю pdfOrganiser.mode.9=Удалить первую и последнюю
pdfOrganiser.mode.10=Объединение четных-нечетных pdfOrganiser.mode.10=Объединение четных-нечетных
pdfOrganiser.mode.11=Duplicate all pages pdfOrganiser.mode.11=Дублировать все страницы
pdfOrganiser.placeholder=(например, 1,3,2 или 4-8,2,10-12 или 2n-1) pdfOrganiser.placeholder=(например, 1,3,2 или 4-8,2,10-12 или 2n-1)
@ -1049,7 +1049,7 @@ decrypt.success=Файл успешно расшифрован.
multiTool-advert.message=Эта функция также доступна на нашей <a href="{0}">странице мультиинструмента</a>. Попробуйте её для улучшенного постраничного интерфейса и дополнительных возможностей! multiTool-advert.message=Эта функция также доступна на нашей <a href="{0}">странице мультиинструмента</a>. Попробуйте её для улучшенного постраничного интерфейса и дополнительных возможностей!
#view pdf #view pdf
viewPdf.title=View/Edit PDF viewPdf.title=Просмотр/Редактирование PDF
viewPdf.header=Просмотр PDF viewPdf.header=Просмотр PDF
#pageRemover #pageRemover
@ -1191,15 +1191,15 @@ changeMetadata.keywords=Ключевые слова:
changeMetadata.modDate=Дата изменения (yyyy/MM/dd HH:mm:ss): changeMetadata.modDate=Дата изменения (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Производитель: changeMetadata.producer=Производитель:
changeMetadata.subject=Тема: changeMetadata.subject=Тема:
changeMetadata.trapped=Trapped: changeMetadata.trapped=Захвачено:
changeMetadata.selectText.4=Другие метаданные: changeMetadata.selectText.4=Другие метаданные:
changeMetadata.selectText.5=Добавить пользовательскую запись метаданных changeMetadata.selectText.5=Добавить пользовательскую запись метаданных
changeMetadata.submit=Изменить changeMetadata.submit=Изменить
#unlockPDFForms #unlockPDFForms
unlockPDFForms.title=Remove Read-Only from Form Fields unlockPDFForms.title=Удалить только для чтения из полей формы
unlockPDFForms.header=Unlock PDF Forms unlockPDFForms.header=Разблокировать формы PDF
unlockPDFForms.submit=Remove unlockPDFForms.submit=Удалить
#pdfToPDFA #pdfToPDFA
pdfToPDFA.title=PDF в PDF/A pdfToPDFA.title=PDF в PDF/A
@ -1319,15 +1319,15 @@ survey.please=Пожалуйста, примите участие в нашем
survey.disabled=(Всплывающее окно опроса будет отключено в следующих обновлениях, но будет доступно в нижней части страницы) survey.disabled=(Всплывающее окно опроса будет отключено в следующих обновлениях, но будет доступно в нижней части страницы)
survey.button=Пройти опрос survey.button=Пройти опрос
survey.dontShowAgain=Больше не показывать survey.dontShowAgain=Больше не показывать
survey.meeting.1=If you're using Stirling PDF at work, we'd love to speak to you. We're offering technical support sessions in exchange for a 15 minute user discovery session. survey.meeting.1=Если вы используете Stirling PDF на работе, мы будем рады поговорить с вами. Мы предлагаем сеансы технической поддержки в обмен на 15-минутную сессию по изучению пользователей.
survey.meeting.2=This is a chance to: survey.meeting.2=Это возможность:
survey.meeting.3=Get help with deployment, integrations, or troubleshooting survey.meeting.3=Получить помощь с развертыванием, интеграцией или устранением неполадок
survey.meeting.4=Provide direct feedback on performance, edge cases, and feature gaps survey.meeting.4=Предоставить прямую обратную связь о производительности, крайних случаях и пробелах в функциях
survey.meeting.5=Help us refine Stirling PDF for real-world enterprise use survey.meeting.5=Помочь нам улучшить Stirling PDF для реального использования в корпоративной среде
survey.meeting.6=If you're interested, you can book time with our team directly. (English speaking only) survey.meeting.6=Если вы заинтересованы, вы можете записаться на встречу с нашей командой напрямую. (Только на английском языке)
survey.meeting.7=Looking forward to digging into your use cases and making Stirling PDF even better! survey.meeting.7=С нетерпением ждем возможности изучить ваши случаи использования и сделать Stirling PDF еще лучше!
survey.meeting.notInterested=Not a business and/or interested in a meeting? survey.meeting.notInterested=Не являетесь бизнесом и/или не заинтересованы во встрече?
survey.meeting.button=Book meeting survey.meeting.button=Записаться на встречу
#error #error
error.sorry=Извините за неполадки! error.sorry=Извините за неполадки!
@ -1415,25 +1415,25 @@ validateSignature.cert.bits=бит
#################### ####################
# Cookie banner # # Cookie banner #
#################### ####################
cookieBanner.popUp.title=How we use Cookies cookieBanner.popUp.title=Как мы используем файлы cookie
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.1=Мы используем файлы cookie и другие технологии, чтобы Stirling PDF работал лучше для вас — помогая нам улучшать наши инструменты и добавлять функции, которые вам понравятся.
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.description.2=Если вы не хотите, нажав «Нет, спасибо», вы включите только основные файлы cookie, необходимые для бесперебойной работы.
cookieBanner.popUp.acceptAllBtn=Okay cookieBanner.popUp.acceptAllBtn=Хорошо
cookieBanner.popUp.acceptNecessaryBtn=No Thanks cookieBanner.popUp.acceptNecessaryBtn=Нет, спасибо
cookieBanner.popUp.showPreferencesBtn=Manage preferences cookieBanner.popUp.showPreferencesBtn=Управление предпочтениями
cookieBanner.preferencesModal.title=Consent Preferences Center cookieBanner.preferencesModal.title=Центр управления предпочтениями
cookieBanner.preferencesModal.acceptAllBtn=Accept all cookieBanner.preferencesModal.acceptAllBtn=Принять все
cookieBanner.preferencesModal.acceptNecessaryBtn=Reject all cookieBanner.preferencesModal.acceptNecessaryBtn=Отклонить все
cookieBanner.preferencesModal.savePreferencesBtn=Save preferences cookieBanner.preferencesModal.savePreferencesBtn=Сохранить предпочтения
cookieBanner.preferencesModal.closeIconLabel=Close modal cookieBanner.preferencesModal.closeIconLabel=Закрыть окно
cookieBanner.preferencesModal.serviceCounterLabel=Service|Services cookieBanner.preferencesModal.serviceCounterLabel=Сервис|Сервисы
cookieBanner.preferencesModal.subtitle=Cookie Usage cookieBanner.preferencesModal.subtitle=Использование файлов 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.1=Stirling PDF использует файлы cookie и аналогичные технологии, чтобы улучшить ваш опыт и понять, как используются наши инструменты. Это помогает нам улучшать производительность, разрабатывать функции, которые важны для нашего сообщества, и предоставлять постоянную поддержку нашим пользователям.
cookieBanner.preferencesModal.description.2=Stirling PDF cannot—and will never—track or access the content of the documents you use. cookieBanner.preferencesModal.description.2=Stirling PDF не может — и никогда не будет — отслеживать или получать доступ к содержимому документов, которые вы используете.
cookieBanner.preferencesModal.description.3=Your privacy and trust are at the core of what we do. cookieBanner.preferencesModal.description.3=Ваша конфиденциальность и доверие — в основе того, что мы делаем.
cookieBanner.preferencesModal.necessary.title.1=Strictly Necessary Cookies cookieBanner.preferencesModal.necessary.title.1=Строго необходимые файлы cookie
cookieBanner.preferencesModal.necessary.title.2=Always Enabled cookieBanner.preferencesModal.necessary.title.2=Всегда включены
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.necessary.description=Эти файлы cookie необходимы для правильной работы веб-сайта. Они включают основные функции, такие как установка ваших предпочтений конфиденциальности, вход в систему и заполнение форм — поэтому их нельзя отключить.
cookieBanner.preferencesModal.analytics.title=Analytics cookieBanner.preferencesModal.analytics.title=Аналитика
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. cookieBanner.preferencesModal.analytics.description=Эти файлы cookie помогают нам понять, как используются наши инструменты, чтобы мы могли сосредоточиться на создании функций, которые ценит наше сообщество. Будьте уверены — Stirling PDF не может и никогда не будет отслеживать содержимое документов, с которыми вы работаете.

View File

@ -10,9 +10,9 @@ multiPdfPrompt=Оберіть PDFи (2+)
multiPdfDropPrompt=Оберіть (або перетягніть) всі необхідні PDFи multiPdfDropPrompt=Оберіть (або перетягніть) всі необхідні PDFи
imgPrompt=Оберіть зображення(я) imgPrompt=Оберіть зображення(я)
genericSubmit=Надіслати genericSubmit=Надіслати
uploadLimit=Maximum file size: uploadLimit=Максимальний розмір файлу:
uploadLimitExceededSingular=is too large. Maximum allowed size is uploadLimitExceededSingular=занадто великий. Максимально дозволений розмір -
uploadLimitExceededPlural=are too large. Maximum allowed size is uploadLimitExceededPlural=занадто великі. Максимально дозволений розмір -
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=No file selected. Please upload one. noFileSelected=Файл не вибрано. Будь ласка, завантажте один.
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 Preferences legal.showCookieBanner=Налаштування файлів cookie
############### ###############
# Pipeline # # Pipeline #
@ -237,7 +237,7 @@ adminUserSettings.activeUsers=Активні користувачі:
adminUserSettings.disabledUsers=Заблоковані користувачі: adminUserSettings.disabledUsers=Заблоковані користувачі:
adminUserSettings.totalUsers=Всього користувачів: adminUserSettings.totalUsers=Всього користувачів:
adminUserSettings.lastRequest=Останній запит adminUserSettings.lastRequest=Останній запит
adminUserSettings.usage=View Usage adminUserSettings.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=Unlock PDF Forms home.unlockPDFForms.title=Розблокувати PDF форми
home.unlockPDFForms.desc=Remove read-only property of form fields in a PDF document. home.unlockPDFForms.desc=Видалити властивість "тільки для читання" з полів форми у PDF-документі.
unlockPDFForms.tags=remove,delete,form,field,readonly unlockPDFForms.tags=видалити,розблокувати,форма,поле,тільки для читання
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=You have been logged out. login.logoutMessage=Ви вийшли з системи.
#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=Remove XMP metadata sanitizePDF.selectText.3=Видалити XMP метадані
sanitizePDF.selectText.4=Видалити посилання sanitizePDF.selectText.4=Видалити посилання
sanitizePDF.selectText.5=Видалити шрифти sanitizePDF.selectText.5=Видалити шрифти
sanitizePDF.selectText.6=Remove Document Info Metadata sanitizePDF.selectText.6=Видалити метадані інформації про документ
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=How we use Cookies cookieBanner.popUp.title=Як ми використовуємо файли cookie
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.1=Ми використовуємо файли cookie та інші технології, щоб Stirling PDF працював краще для вас — допомагаючи нам покращувати наші інструменти та створювати функції, які вам сподобаються.
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.description.2=Якщо ви не хочете, натискання «Ні, дякую» увімкне лише необхідні файли cookie, потрібні для безперебійної роботи.
cookieBanner.popUp.acceptAllBtn=Okay cookieBanner.popUp.acceptAllBtn=Добре
cookieBanner.popUp.acceptNecessaryBtn=No Thanks cookieBanner.popUp.acceptNecessaryBtn=Ні, дякую
cookieBanner.popUp.showPreferencesBtn=Manage preferences cookieBanner.popUp.showPreferencesBtn=Керувати налаштуваннями
cookieBanner.preferencesModal.title=Consent Preferences Center cookieBanner.preferencesModal.title=Центр налаштувань згоди
cookieBanner.preferencesModal.acceptAllBtn=Accept all cookieBanner.preferencesModal.acceptAllBtn=Прийняти всі
cookieBanner.preferencesModal.acceptNecessaryBtn=Reject all cookieBanner.preferencesModal.acceptNecessaryBtn=Відхилити всі
cookieBanner.preferencesModal.savePreferencesBtn=Save preferences cookieBanner.preferencesModal.savePreferencesBtn=Зберегти налаштування
cookieBanner.preferencesModal.closeIconLabel=Close modal cookieBanner.preferencesModal.closeIconLabel=Закрити модальне вікно
cookieBanner.preferencesModal.serviceCounterLabel=Service|Services cookieBanner.preferencesModal.serviceCounterLabel=Сервіс|Сервіси
cookieBanner.preferencesModal.subtitle=Cookie Usage cookieBanner.preferencesModal.subtitle=Використання файлів 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.1=Stirling PDF використовує файли cookie та подібні технології, щоб покращити ваш досвід і зрозуміти, як використовуються наші інструменти. Це допомагає нам покращувати продуктивність, розробляти функції, які вас цікавлять, і надавати постійну підтримку нашим користувачам.
cookieBanner.preferencesModal.description.2=Stirling PDF cannot—and will never—track or access the content of the documents you use. cookieBanner.preferencesModal.description.2=Stirling PDF не може — і ніколи не буде — відстежувати або отримувати доступ до вмісту документів, які ви використовуєте.
cookieBanner.preferencesModal.description.3=Your privacy and trust are at the core of what we do. cookieBanner.preferencesModal.description.3=Ваша конфіденційність і довіра є основою того, що ми робимо.
cookieBanner.preferencesModal.necessary.title.1=Strictly Necessary Cookies cookieBanner.preferencesModal.necessary.title.1=Суворо необхідні файли cookie
cookieBanner.preferencesModal.necessary.title.2=Always Enabled cookieBanner.preferencesModal.necessary.title.2=Завжди увімкнені
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.necessary.description=Ці файли cookie є необхідними для правильного функціонування вебсайту. Вони забезпечують основні функції, такі як налаштування ваших уподобань конфіденційності, вхід у систему та заповнення форм — тому їх не можна вимкнути.
cookieBanner.preferencesModal.analytics.title=Analytics cookieBanner.preferencesModal.analytics.title=Аналітика
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. cookieBanner.preferencesModal.analytics.description=Ці файли cookie допомагають нам зрозуміти, як використовуються наші інструменти, щоб ми могли зосередитися на створенні функцій, які найбільше цінує наша спільнота. Будьте впевнені — Stirling PDF не може і ніколи не буде відстежувати вміст документів, з якими ви працюєте.

View File

@ -1075,9 +1075,9 @@ split.desc.2=如选择1,3,7-9将把一个 10 页的文件分割成6个独立的P
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 页和第 7 页 split.desc.5=文档 #3第 4 页、第 5 页、第 6 页和第 7 页
split.desc.6=文档 #47 split.desc.6=文档 #48
split.desc.7=文档 #58 split.desc.7=文档 #59
split.desc.8=文档 #69 页和第 10 页 split.desc.8=文档 #610 页
split.splitPages=输入要分割的页面: split.splitPages=输入要分割的页面:
split.submit=拆分 split.submit=拆分

View File

@ -138,7 +138,7 @@
<p><span>🔍</span><span th:text="#{survey.meeting.5}">Help us refine Stirling PDF for real-world enterprise use</span></p> <p><span>🔍</span><span th:text="#{survey.meeting.5}">Help us refine Stirling PDF for real-world enterprise use</span></p>
<p th:text="#{survey.meeting.6}">If you're interested, you can book time with our team directly.</p> <p th:text="#{survey.meeting.6}">If you're interested, you can book time with our team directly.</p>
<p th:text="#{survey.meeting.7}">Looking forward to digging into your use cases and making Stirling PDF even better!</p> <p th:text="#{survey.meeting.7}">Looking forward to digging into your use cases and making Stirling PDF even better!</p>
<a href="https://calendly.com/d/cm4p-zz5-yy8/stirling-pdf-15-minute-group-discussion" target="_blank" class="btn btn-primary" id="takeSurvey2" th:text="#{survey.meeting.button}">Book meeting</a> <a href="https://calendly.com/d/crsr-tz6-487" target="_blank" class="btn btn-primary" id="takeSurvey2" th:text="#{survey.meeting.button}">Book meeting</a>
</br> </br>
</br> </br>
<p th:text="#{survey.meeting.notInterested}">Not a business and/or interested in a meeting?</p> <p th:text="#{survey.meeting.notInterested}">Not a business and/or interested in a meeting?</p>
@ -232,4 +232,4 @@
</body> </body>
</html> </html>

View File

@ -1,5 +1,7 @@
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;
@ -57,4 +59,111 @@ 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

@ -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")));
}
}

View File

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

View File

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