Merge branch 'main' into org

This commit is contained in:
Anthony Stirling 2025-08-05 15:47:25 +01:00 committed by GitHub
commit 3da9ae46b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1304 additions and 1030 deletions

View File

@ -87,7 +87,7 @@ jobs:
- name: AI PR Title Analysis - name: AI PR Title Analysis
if: steps.actor.outputs.is_repo_dev == 'true' if: steps.actor.outputs.is_repo_dev == 'true'
id: ai-title-analysis id: ai-title-analysis
uses: actions/ai-inference@d645f067d89ee1d5d736a5990e327e504d1c5a4a # v1.1.0 uses: actions/ai-inference@9693b137b6566bb66055a713613bf4f0493701eb # v1.2.3
with: with:
model: openai/gpt-4o model: openai/gpt-4o
system-prompt-file: ".github/config/system-prompt.txt" system-prompt-file: ".github/config/system-prompt.txt"

View File

@ -16,7 +16,7 @@ on:
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of # This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened. # in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }} group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions: permissions:

View File

@ -15,7 +15,7 @@ on:
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of # This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened. # in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }} group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions: permissions:

View File

@ -2,8 +2,9 @@ name: Pre-commit
on: on:
workflow_dispatch: workflow_dispatch:
schedule: push:
- cron: "0 0 * * 1" branches:
- main
permissions: permissions:
contents: read contents: read
@ -46,6 +47,15 @@ jobs:
- run: pre-commit run --all-files -c .pre-commit-config.yaml - run: pre-commit run --all-files -c .pre-commit-config.yaml
continue-on-error: true continue-on-error: true
- name: Set up JDK
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
java-version: 17
distribution: "temurin"
- name: Build with Gradle
run: ./gradlew clean build
- name: git add - name: git add
run: | run: |
git add . git add .

View File

@ -74,6 +74,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -18,7 +18,7 @@ on:
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of # This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened. # in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }} group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions: permissions:

View File

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.0 rev: v0.12.7
hooks: hooks:
- id: ruff - id: ruff
args: args:
@ -22,7 +22,7 @@ repos:
files: \.(html|css|js|py|md)$ files: \.(html|css|js|py|md)$
exclude: (.vscode|.devcontainer|app/core/src/main/resources|app/proprietary/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js) exclude: (.vscode|.devcontainer|app/core/src/main/resources|app/proprietary/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.27.2 rev: v8.28.0
hooks: hooks:
- id: gitleaks - id: gitleaks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks

View File

@ -78,7 +78,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
# URW Base 35 fonts for better PDF rendering # URW Base 35 fonts for better PDF rendering
font-urw-base35 && \ font-urw-base35 && \
python3 -m venv /opt/venv && \ python3 -m venv /opt/venv && \
/opt/venv/bin/pip install --upgrade pip setuptools && \ /opt/venv/bin/pip install --no-cache-dir --upgrade pip setuptools && \
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \ /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
@ -89,7 +89,6 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
ln -s /usr/share/fontconfig/conf.avail/69-urw-*.conf /etc/fonts/conf.d/ && \ ln -s /usr/share/fontconfig/conf.avail/69-urw-*.conf /etc/fonts/conf.d/ && \
fc-cache -f -v && \ fc-cache -f -v && \
chmod +x /scripts/* && \ chmod +x /scripts/* && \
chmod +x /scripts/init.sh && \
# User permissions # User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \

View File

@ -36,7 +36,7 @@ ENV SETUPTOOLS_USE_DISTUTILS=local \
# Installation der benötigten Python-Pakete # Installation der benötigten Python-Pakete
RUN python3 -m venv --system-site-packages /opt/venv \ RUN python3 -m venv --system-site-packages /opt/venv \
&& . /opt/venv/bin/activate \ && . /opt/venv/bin/activate \
&& pip install --upgrade pip setuptools \ && pip install --no-cache-dir --upgrade pip setuptools \
&& pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit && pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit
# Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind # Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind
@ -54,8 +54,7 @@ RUN echo "devuser ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/devuser \
# Setze das Arbeitsverzeichnis (wird später per Bind-Mount überschrieben) # Setze das Arbeitsverzeichnis (wird später per Bind-Mount überschrieben)
WORKDIR /workspace WORKDIR /workspace
RUN chmod +x /workspace/.devcontainer/git-init.sh RUN chmod +x /workspace/.devcontainer/git-init.sh /workspace/.devcontainer/init-setup.sh
RUN sudo chmod +x /workspace/.devcontainer/init-setup.sh
# Wechsel zum NichtRoot Benutzer # Wechsel zum NichtRoot Benutzer
USER devuser USER devuser

View File

@ -91,7 +91,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
py3-pillow@testing \ py3-pillow@testing \
py3-pdf2image@testing && \ py3-pdf2image@testing && \
python3 -m venv /opt/venv && \ python3 -m venv /opt/venv && \
/opt/venv/bin/pip install --upgrade pip setuptools && \ /opt/venv/bin/pip install --no-cache-dir --upgrade pip setuptools && \
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \ /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
@ -102,7 +102,6 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
ln -s /usr/share/fontconfig/conf.avail/69-urw-*.conf /etc/fonts/conf.d/ && \ ln -s /usr/share/fontconfig/conf.avail/69-urw-*.conf /etc/fonts/conf.d/ && \
fc-cache -f -v && \ fc-cache -f -v && \
chmod +x /scripts/* && \ chmod +x /scripts/* && \
chmod +x /scripts/init.sh && \
# User permissions # User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \

View File

@ -152,7 +152,7 @@ Stirling-PDF currently supports 40 languages!
| Swedish (Svenska) (sv_SE) | ![67%](https://geps.dev/progress/67) | | Swedish (Svenska) (sv_SE) | ![67%](https://geps.dev/progress/67) |
| Thai (ไทย) (th_TH) | ![60%](https://geps.dev/progress/60) | | Thai (ไทย) (th_TH) | ![60%](https://geps.dev/progress/60) |
| Tibetan (བོད་ཡིག་) (bo_CN) | ![66%](https://geps.dev/progress/66) | | Tibetan (བོད་ཡིག་) (bo_CN) | ![66%](https://geps.dev/progress/66) |
| Traditional Chinese (繁體中文) (zh_TW) | ![77%](https://geps.dev/progress/77) | | Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![82%](https://geps.dev/progress/82) | | Turkish (Türkçe) (tr_TR) | ![82%](https://geps.dev/progress/82) |
| Ukrainian (Українська) (uk_UA) | ![72%](https://geps.dev/progress/72) | | Ukrainian (Українська) (uk_UA) | ![72%](https://geps.dev/progress/72) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![58%](https://geps.dev/progress/58) | | Vietnamese (Tiếng Việt) (vi_VN) | ![58%](https://geps.dev/progress/58) |

View File

@ -4,7 +4,7 @@ bootRun {
} }
spotless { spotless {
java { java {
target sourceSets.main.allJava target 'src/**/java/**/*.java'
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false) googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling") importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
@ -13,6 +13,18 @@ spotless {
leadingTabsToSpaces() leadingTabsToSpaces()
endWithNewline() endWithNewline()
} }
yaml {
target '**/*.yml', '**/*.yaml'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
format 'gradle', {
target '**/gradle/*.gradle', '**/*.gradle'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
} }
dependencies { dependencies {
api 'org.springframework.boot:spring-boot-starter-web' api 'org.springframework.boot:spring-boot-starter-web'

View File

@ -7,24 +7,19 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import java.util.Arrays;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
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.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -45,55 +40,37 @@ class AutoJobPostMappingIntegrationTest {
private AutoJobAspect autoJobAspect; private AutoJobAspect autoJobAspect;
@Mock @Mock private JobExecutorService jobExecutorService;
private JobExecutorService jobExecutorService;
@Mock @Mock private HttpServletRequest request;
private HttpServletRequest request;
@Mock @Mock private FileOrUploadService fileOrUploadService;
private FileOrUploadService fileOrUploadService;
@Mock @Mock private FileStorage fileStorage;
private FileStorage fileStorage;
@Mock private ResourceMonitor resourceMonitor;
@Mock @Mock private JobQueue jobQueue;
private ResourceMonitor resourceMonitor;
@Mock
private JobQueue jobQueue;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
autoJobAspect = new AutoJobAspect( autoJobAspect =
jobExecutorService, new AutoJobAspect(jobExecutorService, request, fileOrUploadService, fileStorage);
request,
fileOrUploadService,
fileStorage
);
} }
@Mock @Mock private ProceedingJoinPoint joinPoint;
private ProceedingJoinPoint joinPoint;
@Mock @Mock private AutoJobPostMapping autoJobPostMapping;
private AutoJobPostMapping autoJobPostMapping;
@Captor @Captor private ArgumentCaptor<Supplier<Object>> workCaptor;
private ArgumentCaptor<Supplier<Object>> workCaptor;
@Captor @Captor private ArgumentCaptor<Boolean> asyncCaptor;
private ArgumentCaptor<Boolean> asyncCaptor;
@Captor @Captor private ArgumentCaptor<Long> timeoutCaptor;
private ArgumentCaptor<Long> timeoutCaptor;
@Captor @Captor private ArgumentCaptor<Boolean> queueableCaptor;
private ArgumentCaptor<Boolean> queueableCaptor;
@Captor @Captor private ArgumentCaptor<Integer> resourceWeightCaptor;
private ArgumentCaptor<Integer> resourceWeightCaptor;
@Test @Test
void shouldExecuteWithCustomParameters() throws Throwable { void shouldExecuteWithCustomParameters() throws Throwable {
@ -113,7 +90,6 @@ class AutoJobPostMappingIntegrationTest {
MultipartFile mockFile = mock(MultipartFile.class); MultipartFile mockFile = mock(MultipartFile.class);
when(fileStorage.retrieveFile("test-file-id")).thenReturn(mockFile); when(fileStorage.retrieveFile("test-file-id")).thenReturn(mockFile);
when(jobExecutorService.runJobGeneric( when(jobExecutorService.runJobGeneric(
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt())) anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
.thenReturn(ResponseEntity.ok("success")); .thenReturn(ResponseEntity.ok("success"));
@ -124,7 +100,8 @@ class AutoJobPostMappingIntegrationTest {
// Then // Then
assertEquals(ResponseEntity.ok("success"), result); assertEquals(ResponseEntity.ok("success"), result);
verify(jobExecutorService).runJobGeneric( verify(jobExecutorService)
.runJobGeneric(
asyncCaptor.capture(), asyncCaptor.capture(),
workCaptor.capture(), workCaptor.capture(),
timeoutCaptor.capture(), timeoutCaptor.capture(),
@ -159,7 +136,8 @@ class AutoJobPostMappingIntegrationTest {
// Mock jobExecutorService to execute the work immediately // Mock jobExecutorService to execute the work immediately
when(jobExecutorService.runJobGeneric( when(jobExecutorService.runJobGeneric(
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt())) anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
.thenAnswer(invocation -> { .thenAnswer(
invocation -> {
Supplier<Object> work = invocation.getArgument(1); Supplier<Object> work = invocation.getArgument(1);
return work.get(); return work.get();
}); });
@ -197,7 +175,9 @@ class AutoJobPostMappingIntegrationTest {
autoJobAspect.wrapWithJobExecution(joinPoint, autoJobPostMapping); autoJobAspect.wrapWithJobExecution(joinPoint, autoJobPostMapping);
// Then // Then
assertEquals("stored-file-id", pdfFile.getFileId(), assertEquals(
"stored-file-id",
pdfFile.getFileId(),
"FileId should be set to the stored file id"); "FileId should be set to the stored file id");
assertNotNull(pdfFile.getFileInput(), "FileInput should be replaced with persistent file"); assertNotNull(pdfFile.getFileInput(), "FileInput should be replaced with persistent file");

View File

@ -1,10 +1,9 @@
package stirling.software.common.service; package stirling.software.common.service;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.AdditionalAnswers.*; import static org.mockito.AdditionalAnswers.*;
import static org.mockito.Mockito.*;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -21,14 +20,11 @@ import org.springframework.web.multipart.MultipartFile;
class FileStorageTest { class FileStorageTest {
@TempDir @TempDir Path tempDir;
Path tempDir;
@Mock @Mock private FileOrUploadService fileOrUploadService;
private FileOrUploadService fileOrUploadService;
@InjectMocks @InjectMocks private FileStorage fileStorage;
private FileStorage fileStorage;
private MultipartFile mockFile; private MultipartFile mockFile;
@ -50,11 +46,14 @@ class FileStorageTest {
when(mockFile.getBytes()).thenReturn(fileContent); when(mockFile.getBytes()).thenReturn(fileContent);
// Set up mock to handle transferTo by writing the file // Set up mock to handle transferTo by writing the file
doAnswer(invocation -> { doAnswer(
invocation -> {
java.io.File file = invocation.getArgument(0); java.io.File file = invocation.getArgument(0);
Files.write(file.toPath(), fileContent); Files.write(file.toPath(), fileContent);
return null; return null;
}).when(mockFile).transferTo(any(java.io.File.class)); })
.when(mockFile)
.transferTo(any(java.io.File.class));
// Act // Act
String fileId = fileStorage.storeFile(mockFile); String fileId = fileStorage.storeFile(mockFile);

View File

@ -4,14 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -30,11 +25,9 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import stirling.software.common.model.job.JobProgress;
import stirling.software.common.model.job.JobResponse; import stirling.software.common.model.job.JobResponse;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@ -42,28 +35,23 @@ class JobExecutorServiceTest {
private JobExecutorService jobExecutorService; private JobExecutorService jobExecutorService;
@Mock @Mock private TaskManager taskManager;
private TaskManager taskManager;
@Mock @Mock private FileStorage fileStorage;
private FileStorage fileStorage;
@Mock @Mock private HttpServletRequest request;
private HttpServletRequest request;
@Mock @Mock private ResourceMonitor resourceMonitor;
private ResourceMonitor resourceMonitor;
@Mock @Mock private JobQueue jobQueue;
private JobQueue jobQueue;
@Captor @Captor private ArgumentCaptor<String> jobIdCaptor;
private ArgumentCaptor<String> jobIdCaptor;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
// Initialize the service manually with all its dependencies // Initialize the service manually with all its dependencies
jobExecutorService = new JobExecutorService( jobExecutorService =
new JobExecutorService(
taskManager, taskManager,
fileStorage, fileStorage,
request, request,
@ -109,11 +97,11 @@ class JobExecutorServiceTest {
verify(taskManager).createTask(jobIdCaptor.capture()); verify(taskManager).createTask(jobIdCaptor.capture());
} }
@Test @Test
void shouldHandleSyncJobError() { void shouldHandleSyncJobError() {
// Given // Given
Supplier<Object> work = () -> { Supplier<Object> work =
() -> {
throw new RuntimeException("Test error"); throw new RuntimeException("Test error");
}; };
@ -141,8 +129,7 @@ class JobExecutorServiceTest {
when(jobQueue.queueJob(anyString(), eq(80), any(), anyLong())).thenReturn(future); when(jobQueue.queueJob(anyString(), eq(80), any(), anyLong())).thenReturn(future);
// When // When
ResponseEntity<?> response = jobExecutorService.runJobGeneric( ResponseEntity<?> response = jobExecutorService.runJobGeneric(true, work, 5000, true, 80);
true, work, 5000, true, 80);
// Then // Then
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
@ -160,8 +147,9 @@ class JobExecutorServiceTest {
long customTimeout = 60000L; long customTimeout = 60000L;
// Use reflection to access the private executeWithTimeout method // Use reflection to access the private executeWithTimeout method
java.lang.reflect.Method executeMethod = JobExecutorService.class java.lang.reflect.Method executeMethod =
.getDeclaredMethod("executeWithTimeout", Supplier.class, long.class); JobExecutorService.class.getDeclaredMethod(
"executeWithTimeout", Supplier.class, long.class);
executeMethod.setAccessible(true); executeMethod.setAccessible(true);
// Create a spy on the JobExecutorService to verify method calls // Create a spy on the JobExecutorService to verify method calls
@ -177,7 +165,8 @@ class JobExecutorServiceTest {
@Test @Test
void shouldHandleTimeout() throws Exception { void shouldHandleTimeout() throws Exception {
// Given // Given
Supplier<Object> work = () -> { Supplier<Object> work =
() -> {
try { try {
Thread.sleep(100); // Simulate long-running job Thread.sleep(100); // Simulate long-running job
return "test-result"; return "test-result";
@ -188,8 +177,9 @@ class JobExecutorServiceTest {
}; };
// Use reflection to access the private executeWithTimeout method // Use reflection to access the private executeWithTimeout method
java.lang.reflect.Method executeMethod = JobExecutorService.class java.lang.reflect.Method executeMethod =
.getDeclaredMethod("executeWithTimeout", Supplier.class, long.class); JobExecutorService.class.getDeclaredMethod(
"executeWithTimeout", Supplier.class, long.class);
executeMethod.setAccessible(true); executeMethod.setAccessible(true);
// When/Then // When/Then

View File

@ -1,10 +1,8 @@
package stirling.software.common.service; package stirling.software.common.service;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.util.Map; import java.util.Map;
@ -17,7 +15,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.common.model.job.JobProgress;
import stirling.software.common.service.ResourceMonitor.ResourceStatus; import stirling.software.common.service.ResourceMonitor.ResourceStatus;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@ -25,16 +22,17 @@ class JobQueueTest {
private JobQueue jobQueue; private JobQueue jobQueue;
@Mock @Mock private ResourceMonitor resourceMonitor;
private ResourceMonitor resourceMonitor;
private final AtomicReference<ResourceStatus> statusRef =
private final AtomicReference<ResourceStatus> statusRef = new AtomicReference<>(ResourceStatus.OK); new AtomicReference<>(ResourceStatus.OK);
@BeforeEach @BeforeEach
void setUp() { void setUp() {
// Mark stubbing as lenient to avoid UnnecessaryStubbingException // Mark stubbing as lenient to avoid UnnecessaryStubbingException
lenient().when(resourceMonitor.calculateDynamicQueueCapacity(anyInt(), anyInt())).thenReturn(10); lenient()
.when(resourceMonitor.calculateDynamicQueueCapacity(anyInt(), anyInt()))
.thenReturn(10);
lenient().when(resourceMonitor.getCurrentStatus()).thenReturn(statusRef); lenient().when(resourceMonitor.getCurrentStatus()).thenReturn(statusRef);
// Initialize JobQueue with mocked ResourceMonitor // Initialize JobQueue with mocked ResourceMonitor
@ -50,7 +48,6 @@ class JobQueueTest {
jobQueue.queueJob(jobId, resourceWeight, work, timeoutMs); jobQueue.queueJob(jobId, resourceWeight, work, timeoutMs);
assertTrue(jobQueue.isJobQueued(jobId)); assertTrue(jobQueue.isJobQueued(jobId));
assertEquals(1, jobQueue.getTotalQueuedJobs()); assertEquals(1, jobQueue.getTotalQueuedJobs());
} }

View File

@ -1,14 +1,10 @@
package stirling.software.common.service; package stirling.software.common.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.management.MemoryMXBean; import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean; import java.lang.management.OperatingSystemMXBean;
import java.time.Instant; import java.time.Instant;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -30,20 +26,19 @@ import stirling.software.common.service.ResourceMonitor.ResourceStatus;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ResourceMonitorTest { class ResourceMonitorTest {
@InjectMocks @InjectMocks private ResourceMonitor resourceMonitor;
private ResourceMonitor resourceMonitor;
@Mock @Mock private OperatingSystemMXBean osMXBean;
private OperatingSystemMXBean osMXBean;
@Mock @Mock private MemoryMXBean memoryMXBean;
private MemoryMXBean memoryMXBean;
@Spy @Spy
private AtomicReference<ResourceStatus> currentStatus = new AtomicReference<>(ResourceStatus.OK); private AtomicReference<ResourceStatus> currentStatus =
new AtomicReference<>(ResourceStatus.OK);
@Spy @Spy
private AtomicReference<ResourceMetrics> latestMetrics = new AtomicReference<>(new ResourceMetrics()); private AtomicReference<ResourceMetrics> latestMetrics =
new AtomicReference<>(new ResourceMetrics());
@BeforeEach @BeforeEach
void setUp() { void setUp() {
@ -92,7 +87,9 @@ class ResourceMonitorTest {
assertEquals(3, capacity, "With CRITICAL status, capacity should be reduced to 30%"); assertEquals(3, capacity, "With CRITICAL status, capacity should be reduced to 30%");
// Test minimum capacity enforcement // Test minimum capacity enforcement
assertEquals(minCapacity, resourceMonitor.calculateDynamicQueueCapacity(1, minCapacity), assertEquals(
minCapacity,
resourceMonitor.calculateDynamicQueueCapacity(1, minCapacity),
"Should never go below minimum capacity"); "Should never go below minimum capacity");
} }
@ -108,7 +105,8 @@ class ResourceMonitorTest {
"80, WARNING, true", // Heavy job, WARNING status "80, WARNING, true", // Heavy job, WARNING status
"80, CRITICAL, true" // Heavy job, CRITICAL status "80, CRITICAL, true" // Heavy job, CRITICAL status
}) })
void shouldQueueJobBasedOnWeightAndStatus(int weight, ResourceStatus status, boolean shouldQueue) { void shouldQueueJobBasedOnWeightAndStatus(
int weight, ResourceStatus status, boolean shouldQueue) {
// Given // Given
currentStatus.set(status); currentStatus.set(status);
@ -116,8 +114,11 @@ class ResourceMonitorTest {
boolean result = resourceMonitor.shouldQueueJob(weight); boolean result = resourceMonitor.shouldQueueJob(weight);
// Then // Then
assertEquals(shouldQueue, result, assertEquals(
String.format("For weight %d and status %s, shouldQueue should be %s", shouldQueue,
result,
String.format(
"For weight %d and status %s, shouldQueue should be %s",
weight, status, shouldQueue)); weight, status, shouldQueue));
} }
@ -131,7 +132,9 @@ class ResourceMonitorTest {
ResourceMetrics freshMetrics = new ResourceMetrics(0.5, 0.5, 1024, 2048, 4096, now); ResourceMetrics freshMetrics = new ResourceMetrics(0.5, 0.5, 1024, 2048, 4096, now);
// When/Then // When/Then
assertTrue(staleMetrics.isStale(5000), "Metrics from 6 seconds ago should be stale with 5s threshold"); assertTrue(
staleMetrics.isStale(5000),
"Metrics from 6 seconds ago should be stale with 5s threshold");
assertFalse(freshMetrics.isStale(5000), "Fresh metrics should not be stale"); assertFalse(freshMetrics.isStale(5000), "Fresh metrics should not be stale");
} }
} }

View File

@ -6,7 +6,6 @@ import static org.mockito.Mockito.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -22,11 +21,9 @@ import stirling.software.common.model.job.ResultFile;
class TaskManagerTest { class TaskManagerTest {
@Mock @Mock private FileStorage fileStorage;
private FileStorage fileStorage;
@InjectMocks @InjectMocks private TaskManager taskManager;
private TaskManager taskManager;
private AutoCloseable closeable; private AutoCloseable closeable;
@ -234,7 +231,8 @@ class TaskManagerTest {
ReflectionTestUtils.setField(oldJob, "complete", true); ReflectionTestUtils.setField(oldJob, "complete", true);
// Create a ResultFile and set it using the new approach // Create a ResultFile and set it using the new approach
ResultFile resultFile = ResultFile.builder() ResultFile resultFile =
ResultFile.builder()
.fileId("file-id") .fileId("file-id")
.fileName("test.pdf") .fileName("test.pdf")
.contentType("application/pdf") .contentType("application/pdf")
@ -245,7 +243,8 @@ class TaskManagerTest {
when(fileStorage.deleteFile("file-id")).thenReturn(true); when(fileStorage.deleteFile("file-id")).thenReturn(true);
// Obtain access to the private jobResults map // Obtain access to the private jobResults map
Map<String, JobResult> jobResultsMap = (Map<String, JobResult>) ReflectionTestUtils.getField(taskManager, "jobResults"); Map<String, JobResult> jobResultsMap =
(Map<String, JobResult>) ReflectionTestUtils.getField(taskManager, "jobResults");
// 3. Create an active job // 3. Create an active job
String activeJobId = "active-job"; String activeJobId = "active-job";

View File

@ -12,7 +12,6 @@ import java.nio.file.Path;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -30,31 +29,22 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.TempFileManager; import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.TempFileRegistry; import stirling.software.common.util.TempFileRegistry;
/** /** Tests for the TempFileCleanupService, focusing on its pattern-matching and cleanup logic. */
* Tests for the TempFileCleanupService, focusing on its pattern-matching and cleanup logic.
*/
public class TempFileCleanupServiceTest { public class TempFileCleanupServiceTest {
@TempDir @TempDir Path tempDir;
Path tempDir;
@Mock @Mock private TempFileRegistry registry;
private TempFileRegistry registry;
@Mock @Mock private TempFileManager tempFileManager;
private TempFileManager tempFileManager;
@Mock @Mock private ApplicationProperties applicationProperties;
private ApplicationProperties applicationProperties;
@Mock @Mock private ApplicationProperties.System system;
private ApplicationProperties.System system;
@Mock @Mock private ApplicationProperties.TempFileManagement tempFileManagement;
private ApplicationProperties.TempFileManagement tempFileManagement;
@InjectMocks @InjectMocks private TempFileCleanupService cleanupService;
private TempFileCleanupService cleanupService;
private Path systemTempDir; private Path systemTempDir;
private Path customTempDir; private Path customTempDir;
@ -124,7 +114,8 @@ public class TempFileCleanupServiceTest {
// Files that should be preserved // Files that should be preserved
Path jettyFile1 = Files.createFile(systemTempDir.resolve("jetty-123.tmp")); Path jettyFile1 = Files.createFile(systemTempDir.resolve("jetty-123.tmp"));
Path jettyFile2 = Files.createFile(systemTempDir.resolve("something-with-jetty-inside.tmp")); Path jettyFile2 =
Files.createFile(systemTempDir.resolve("something-with-jetty-inside.tmp"));
Path regularFile = Files.createFile(systemTempDir.resolve("important.txt")); Path regularFile = Files.createFile(systemTempDir.resolve("important.txt"));
// Create a nested directory with temp files // Create a nested directory with temp files
@ -143,19 +134,29 @@ public class TempFileCleanupServiceTest {
// Use MockedStatic to mock Files operations // Use MockedStatic to mock Files operations
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) { try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
// Mock Files.list for each directory we'll process // Mock Files.list for each directory we'll process
mockedFiles.when(() -> Files.list(eq(systemTempDir))) mockedFiles
.thenReturn(Stream.of( .when(() -> Files.list(eq(systemTempDir)))
ourTempFile1, ourTempFile2, oldTempFile, sysTempFile1, .thenReturn(
jettyFile1, jettyFile2, regularFile, emptyFile, nestedDir)); Stream.of(
ourTempFile1,
ourTempFile2,
oldTempFile,
sysTempFile1,
jettyFile1,
jettyFile2,
regularFile,
emptyFile,
nestedDir));
mockedFiles.when(() -> Files.list(eq(customTempDir))) mockedFiles
.when(() -> Files.list(eq(customTempDir)))
.thenReturn(Stream.of(ourTempFile3, ourTempFile4, sysTempFile2, sysTempFile3)); .thenReturn(Stream.of(ourTempFile3, ourTempFile4, sysTempFile2, sysTempFile3));
mockedFiles.when(() -> Files.list(eq(libreOfficeTempDir))) mockedFiles
.when(() -> Files.list(eq(libreOfficeTempDir)))
.thenReturn(Stream.of(ourTempFile5)); .thenReturn(Stream.of(ourTempFile5));
mockedFiles.when(() -> Files.list(eq(nestedDir))) mockedFiles.when(() -> Files.list(eq(nestedDir))).thenReturn(Stream.of(nestedTempFile));
.thenReturn(Stream.of(nestedTempFile));
// Configure Files.isDirectory for each path // Configure Files.isDirectory for each path
mockedFiles.when(() -> Files.isDirectory(eq(nestedDir))).thenReturn(true); mockedFiles.when(() -> Files.isDirectory(eq(nestedDir))).thenReturn(true);
@ -165,28 +166,37 @@ public class TempFileCleanupServiceTest {
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true); mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
// Configure Files.getLastModifiedTime to return different times based on file names // Configure Files.getLastModifiedTime to return different times based on file names
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class))) mockedFiles
.thenAnswer(invocation -> { .when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0); Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString(); String fileName = path.getFileName().toString();
// For files with "old" in the name, return a timestamp older than maxAgeMillis // For files with "old" in the name, return a timestamp older than
// maxAgeMillis
if (fileName.contains("old")) { if (fileName.contains("old")) {
return FileTime.fromMillis(System.currentTimeMillis() - 5000000); return FileTime.fromMillis(
System.currentTimeMillis() - 5000000);
} }
// For empty.tmp file, return a timestamp older than 5 minutes (for empty file test) // For empty.tmp file, return a timestamp older than 5 minutes (for
// empty file test)
else if (fileName.equals("empty.tmp")) { else if (fileName.equals("empty.tmp")) {
return FileTime.fromMillis(System.currentTimeMillis() - 6 * 60 * 1000); return FileTime.fromMillis(
System.currentTimeMillis() - 6 * 60 * 1000);
} }
// For all other files, return a recent timestamp // For all other files, return a recent timestamp
else { else {
return FileTime.fromMillis(System.currentTimeMillis() - 60000); // 1 minute ago return FileTime.fromMillis(
System.currentTimeMillis() - 60000); // 1 minute ago
} }
}); });
// Configure Files.size to return different sizes based on file names // Configure Files.size to return different sizes based on file names
mockedFiles.when(() -> Files.size(any(Path.class))) mockedFiles
.thenAnswer(invocation -> { .when(() -> Files.size(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0); Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString(); String fileName = path.getFileName().toString();
@ -201,8 +211,10 @@ public class TempFileCleanupServiceTest {
}); });
// For deleteIfExists, track which files would be deleted // For deleteIfExists, track which files would be deleted
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class))) mockedFiles
.thenAnswer(invocation -> { .when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0); Path path = invocation.getArgument(0);
deletedFiles.add(path); deletedFiles.add(path);
return true; return true;
@ -218,20 +230,33 @@ public class TempFileCleanupServiceTest {
assertTrue(deletedFiles.contains(emptyFile), "Empty file should be deleted"); assertTrue(deletedFiles.contains(emptyFile), "Empty file should be deleted");
// Regular temp files should not be deleted because they're too new // Regular temp files should not be deleted because they're too new
assertFalse(deletedFiles.contains(ourTempFile1), "Recent temp file should be preserved"); assertFalse(
assertFalse(deletedFiles.contains(ourTempFile2), "Recent temp file should be preserved"); deletedFiles.contains(ourTempFile1), "Recent temp file should be preserved");
assertFalse(deletedFiles.contains(ourTempFile3), "Recent temp file should be preserved"); assertFalse(
assertFalse(deletedFiles.contains(ourTempFile4), "Recent temp file should be preserved"); deletedFiles.contains(ourTempFile2), "Recent temp file should be preserved");
assertFalse(deletedFiles.contains(ourTempFile5), "Recent temp file should be preserved"); assertFalse(
deletedFiles.contains(ourTempFile3), "Recent temp file should be preserved");
assertFalse(
deletedFiles.contains(ourTempFile4), "Recent temp file should be preserved");
assertFalse(
deletedFiles.contains(ourTempFile5), "Recent temp file should be preserved");
// System temp files should not be deleted in non-container mode // System temp files should not be deleted in non-container mode
assertFalse(deletedFiles.contains(sysTempFile1), "System temp file should be preserved in non-container mode"); assertFalse(
assertFalse(deletedFiles.contains(sysTempFile2), "System temp file should be preserved in non-container mode"); deletedFiles.contains(sysTempFile1),
assertFalse(deletedFiles.contains(sysTempFile3), "System temp file should be preserved in non-container mode"); "System temp file should be preserved in non-container mode");
assertFalse(
deletedFiles.contains(sysTempFile2),
"System temp file should be preserved in non-container mode");
assertFalse(
deletedFiles.contains(sysTempFile3),
"System temp file should be preserved in non-container mode");
// Jetty files and regular files should never be deleted // Jetty files and regular files should never be deleted
assertFalse(deletedFiles.contains(jettyFile1), "Jetty file should be preserved"); assertFalse(deletedFiles.contains(jettyFile1), "Jetty file should be preserved");
assertFalse(deletedFiles.contains(jettyFile2), "File with jetty in name should be preserved"); assertFalse(
deletedFiles.contains(jettyFile2),
"File with jetty in name should be preserved");
assertFalse(deletedFiles.contains(regularFile), "Regular file should be preserved"); assertFalse(deletedFiles.contains(regularFile), "Regular file should be preserved");
} }
} }
@ -252,7 +277,8 @@ public class TempFileCleanupServiceTest {
// Use MockedStatic to mock Files operations // Use MockedStatic to mock Files operations
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) { try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
// Mock Files.list for systemTempDir // Mock Files.list for systemTempDir
mockedFiles.when(() -> Files.list(eq(systemTempDir))) mockedFiles
.when(() -> Files.list(eq(systemTempDir)))
.thenReturn(Stream.of(ourTempFile, sysTempFile, regularFile)); .thenReturn(Stream.of(ourTempFile, sysTempFile, regularFile));
// Configure Files.isDirectory // Configure Files.isDirectory
@ -262,16 +288,20 @@ public class TempFileCleanupServiceTest {
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true); mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
// Configure Files.getLastModifiedTime to return recent timestamps // Configure Files.getLastModifiedTime to return recent timestamps
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class))) mockedFiles
.thenReturn(FileTime.fromMillis(System.currentTimeMillis() - 60000)); // 1 minute ago .when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenReturn(
FileTime.fromMillis(
System.currentTimeMillis() - 60000)); // 1 minute ago
// Configure Files.size to return normal size // Configure Files.size to return normal size
mockedFiles.when(() -> Files.size(any(Path.class))) mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(1024L); // 1 KB
.thenReturn(1024L); // 1 KB
// For deleteIfExists, track which files would be deleted // For deleteIfExists, track which files would be deleted
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class))) mockedFiles
.thenAnswer(invocation -> { .when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0); Path path = invocation.getArgument(0);
deletedFiles.add(path); deletedFiles.add(path);
return true; return true;
@ -280,10 +310,15 @@ public class TempFileCleanupServiceTest {
// Act - set containerMode to true and maxAgeMillis to 0 for container startup cleanup // Act - set containerMode to true and maxAgeMillis to 0 for container startup cleanup
invokeCleanupDirectoryStreaming(systemTempDir, true, 0, 0); invokeCleanupDirectoryStreaming(systemTempDir, true, 0, 0);
// Assert - In container mode, both our temp files and system temp files should be deleted // Assert - In container mode, both our temp files and system temp files should be
// deleted
// regardless of age (when maxAgeMillis is 0) // regardless of age (when maxAgeMillis is 0)
assertTrue(deletedFiles.contains(ourTempFile), "Our temp file should be deleted in container mode"); assertTrue(
assertTrue(deletedFiles.contains(sysTempFile), "System temp file should be deleted in container mode"); deletedFiles.contains(ourTempFile),
"Our temp file should be deleted in container mode");
assertTrue(
deletedFiles.contains(sysTempFile),
"System temp file should be deleted in container mode");
assertFalse(deletedFiles.contains(regularFile), "Regular file should be preserved"); assertFalse(deletedFiles.contains(regularFile), "Regular file should be preserved");
} }
} }
@ -303,7 +338,8 @@ public class TempFileCleanupServiceTest {
// Use MockedStatic to mock Files operations // Use MockedStatic to mock Files operations
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) { try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
// Mock Files.list for systemTempDir // Mock Files.list for systemTempDir
mockedFiles.when(() -> Files.list(eq(systemTempDir))) mockedFiles
.when(() -> Files.list(eq(systemTempDir)))
.thenReturn(Stream.of(emptyFile, recentEmptyFile)); .thenReturn(Stream.of(emptyFile, recentEmptyFile));
// Configure Files.isDirectory // Configure Files.isDirectory
@ -313,27 +349,32 @@ public class TempFileCleanupServiceTest {
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true); mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
// Configure Files.getLastModifiedTime to return different times based on file names // Configure Files.getLastModifiedTime to return different times based on file names
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class))) mockedFiles
.thenAnswer(invocation -> { .when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0); Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString(); String fileName = path.getFileName().toString();
if (fileName.equals("empty.tmp")) { if (fileName.equals("empty.tmp")) {
// More than 5 minutes old // More than 5 minutes old
return FileTime.fromMillis(System.currentTimeMillis() - 6 * 60 * 1000); return FileTime.fromMillis(
System.currentTimeMillis() - 6 * 60 * 1000);
} else { } else {
// Less than 5 minutes old // Less than 5 minutes old
return FileTime.fromMillis(System.currentTimeMillis() - 2 * 60 * 1000); return FileTime.fromMillis(
System.currentTimeMillis() - 2 * 60 * 1000);
} }
}); });
// Configure Files.size to return 0 for empty files // Configure Files.size to return 0 for empty files
mockedFiles.when(() -> Files.size(any(Path.class))) mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(0L);
.thenReturn(0L);
// For deleteIfExists, track which files would be deleted // For deleteIfExists, track which files would be deleted
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class))) mockedFiles
.thenAnswer(invocation -> { .when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0); Path path = invocation.getArgument(0);
deletedFiles.add(path); deletedFiles.add(path);
return true; return true;
@ -343,9 +384,11 @@ public class TempFileCleanupServiceTest {
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000); invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
// Assert // Assert
assertTrue(deletedFiles.contains(emptyFile), assertTrue(
deletedFiles.contains(emptyFile),
"Empty file older than 5 minutes should be deleted"); "Empty file older than 5 minutes should be deleted");
assertFalse(deletedFiles.contains(recentEmptyFile), assertFalse(
deletedFiles.contains(recentEmptyFile),
"Empty file newer than 5 minutes should not be deleted"); "Empty file newer than 5 minutes should not be deleted");
} }
} }
@ -370,17 +413,13 @@ public class TempFileCleanupServiceTest {
// Use MockedStatic to mock Files operations // Use MockedStatic to mock Files operations
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) { try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
// Mock Files.list for each directory // Mock Files.list for each directory
mockedFiles.when(() -> Files.list(eq(systemTempDir))) mockedFiles.when(() -> Files.list(eq(systemTempDir))).thenReturn(Stream.of(dir1));
.thenReturn(Stream.of(dir1));
mockedFiles.when(() -> Files.list(eq(dir1))) mockedFiles.when(() -> Files.list(eq(dir1))).thenReturn(Stream.of(tempFile1, dir2));
.thenReturn(Stream.of(tempFile1, dir2));
mockedFiles.when(() -> Files.list(eq(dir2))) mockedFiles.when(() -> Files.list(eq(dir2))).thenReturn(Stream.of(tempFile2, dir3));
.thenReturn(Stream.of(tempFile2, dir3));
mockedFiles.when(() -> Files.list(eq(dir3))) mockedFiles.when(() -> Files.list(eq(dir3))).thenReturn(Stream.of(tempFile3));
.thenReturn(Stream.of(tempFile3));
// Configure Files.isDirectory for each path // Configure Files.isDirectory for each path
mockedFiles.when(() -> Files.isDirectory(eq(dir1))).thenReturn(true); mockedFiles.when(() -> Files.isDirectory(eq(dir1))).thenReturn(true);
@ -394,14 +433,17 @@ public class TempFileCleanupServiceTest {
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true); mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
// Configure Files.getLastModifiedTime to return different times based on file names // Configure Files.getLastModifiedTime to return different times based on file names
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class))) mockedFiles
.thenAnswer(invocation -> { .when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0); Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString(); String fileName = path.getFileName().toString();
if (fileName.contains("old")) { if (fileName.contains("old")) {
// Old file // Old file
return FileTime.fromMillis(System.currentTimeMillis() - 5000000); return FileTime.fromMillis(
System.currentTimeMillis() - 5000000);
} else { } else {
// Recent file // Recent file
return FileTime.fromMillis(System.currentTimeMillis() - 60000); return FileTime.fromMillis(System.currentTimeMillis() - 60000);
@ -409,12 +451,13 @@ public class TempFileCleanupServiceTest {
}); });
// Configure Files.size to return normal size // Configure Files.size to return normal size
mockedFiles.when(() -> Files.size(any(Path.class))) mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(1024L);
.thenReturn(1024L);
// For deleteIfExists, track which files would be deleted // For deleteIfExists, track which files would be deleted
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class))) mockedFiles
.thenAnswer(invocation -> { .when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0); Path path = invocation.getArgument(0);
deletedFiles.add(path); deletedFiles.add(path);
return true; return true;
@ -430,14 +473,15 @@ public class TempFileCleanupServiceTest {
// Assert // Assert
assertFalse(deletedFiles.contains(tempFile1), "Recent temp file should be preserved"); assertFalse(deletedFiles.contains(tempFile1), "Recent temp file should be preserved");
assertFalse(deletedFiles.contains(tempFile2), "Recent temp file should be preserved"); assertFalse(deletedFiles.contains(tempFile2), "Recent temp file should be preserved");
assertTrue(deletedFiles.contains(tempFile3), "Old temp file in nested directory should be deleted"); assertTrue(
deletedFiles.contains(tempFile3),
"Old temp file in nested directory should be deleted");
} }
} }
/** /** Helper method to invoke the private cleanupDirectoryStreaming method using reflection */
* Helper method to invoke the private cleanupDirectoryStreaming method using reflection private void invokeCleanupDirectoryStreaming(
*/ Path directory, boolean containerMode, int depth, long maxAgeMillis)
private void invokeCleanupDirectoryStreaming(Path directory, boolean containerMode, int depth, long maxAgeMillis)
throws IOException { throws IOException {
try { try {
// Create a consumer that tracks deleted files // Create a consumer that tracks deleted files
@ -445,13 +489,26 @@ public class TempFileCleanupServiceTest {
Consumer<Path> deleteCallback = path -> deleteCount.incrementAndGet(); Consumer<Path> deleteCallback = path -> deleteCount.incrementAndGet();
// Get the method with updated signature // Get the method with updated signature
var method = TempFileCleanupService.class.getDeclaredMethod( var method =
TempFileCleanupService.class.getDeclaredMethod(
"cleanupDirectoryStreaming", "cleanupDirectoryStreaming",
Path.class, boolean.class, int.class, long.class, boolean.class, Consumer.class); Path.class,
boolean.class,
int.class,
long.class,
boolean.class,
Consumer.class);
method.setAccessible(true); method.setAccessible(true);
// Invoke the method with appropriate parameters // Invoke the method with appropriate parameters
method.invoke(cleanupService, directory, containerMode, depth, maxAgeMillis, false, deleteCallback); method.invoke(
cleanupService,
directory,
containerMode,
depth,
maxAgeMillis,
false,
deleteCallback);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Error invoking cleanupDirectoryStreaming", e); throw new RuntimeException("Error invoking cleanupDirectoryStreaming", e);
} }

View File

@ -1,14 +1,5 @@
package stirling.software.common.util; package stirling.software.common.util;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
@ -19,6 +10,18 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
class CheckProgramInstallTest { class CheckProgramInstallTest {
private MockedStatic<ProcessExecutor> mockProcessExecutor; private MockedStatic<ProcessExecutor> mockProcessExecutor;

View File

@ -23,15 +23,19 @@ class CustomHtmlSanitizerTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class); SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class);
stirling.software.common.model.ApplicationProperties mockApplicationProperties = mock(stirling.software.common.model.ApplicationProperties.class); stirling.software.common.model.ApplicationProperties mockApplicationProperties =
stirling.software.common.model.ApplicationProperties.System mockSystem = mock(stirling.software.common.model.ApplicationProperties.System.class); mock(stirling.software.common.model.ApplicationProperties.class);
stirling.software.common.model.ApplicationProperties.System mockSystem =
mock(stirling.software.common.model.ApplicationProperties.System.class);
// Allow all URLs by default for basic tests // Allow all URLs by default for basic tests
when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString())).thenReturn(true); when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString()))
.thenReturn(true);
when(mockApplicationProperties.getSystem()).thenReturn(mockSystem); when(mockApplicationProperties.getSystem()).thenReturn(mockSystem);
when(mockSystem.getDisableSanitize()).thenReturn(false); // Enable sanitization for tests when(mockSystem.getDisableSanitize()).thenReturn(false); // Enable sanitization for tests
customHtmlSanitizer = new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties); customHtmlSanitizer =
new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties);
} }
@ParameterizedTest @ParameterizedTest

View File

@ -1,5 +1,18 @@
package stirling.software.common.util; package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -7,12 +20,7 @@ import java.util.Base64;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -20,22 +28,13 @@ 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.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.BeforeEach;
import stirling.software.common.model.api.converters.EmlToPdfRequest; import stirling.software.common.model.api.converters.EmlToPdfRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.service.SsrfProtectionService; import stirling.software.common.service.SsrfProtectionService;
import stirling.software.common.util.CustomHtmlSanitizer;
@DisplayName("EML to PDF Conversion tests") @DisplayName("EML to PDF Conversion tests")
class EmlToPdfTest { class EmlToPdfTest {
@ -45,21 +44,29 @@ class EmlToPdfTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class); SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class);
stirling.software.common.model.ApplicationProperties mockApplicationProperties = mock(stirling.software.common.model.ApplicationProperties.class); stirling.software.common.model.ApplicationProperties mockApplicationProperties =
stirling.software.common.model.ApplicationProperties.System mockSystem = mock(stirling.software.common.model.ApplicationProperties.System.class); mock(stirling.software.common.model.ApplicationProperties.class);
stirling.software.common.model.ApplicationProperties.System mockSystem =
mock(stirling.software.common.model.ApplicationProperties.System.class);
when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString())).thenReturn(true); when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString()))
.thenReturn(true);
when(mockApplicationProperties.getSystem()).thenReturn(mockSystem); when(mockApplicationProperties.getSystem()).thenReturn(mockSystem);
when(mockSystem.getDisableSanitize()).thenReturn(false); when(mockSystem.getDisableSanitize()).thenReturn(false);
customHtmlSanitizer = new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties); customHtmlSanitizer =
new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties);
} }
// Focus on testing EML to HTML conversion functionality since the PDF conversion relies on WeasyPrint // Focus on testing EML to HTML conversion functionality since the PDF conversion relies on
// WeasyPrint
// But HTML to PDF conversion is also briefly tested at PdfConversionTests class. // But HTML to PDF conversion is also briefly tested at PdfConversionTests class.
private void testEmailConversion(String emlContent, String[] expectedContent, boolean includeAttachments) throws IOException { private void testEmailConversion(
String emlContent, String[] expectedContent, boolean includeAttachments)
throws IOException {
byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8); byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8);
EmlToPdfRequest request = includeAttachments ? createRequestWithAttachments() : createBasicRequest(); EmlToPdfRequest request =
includeAttachments ? createRequestWithAttachments() : createBasicRequest();
String htmlResult = EmlToPdf.convertEmlToHtml(emlBytes, request); String htmlResult = EmlToPdf.convertEmlToHtml(emlBytes, request);
@ -75,19 +82,23 @@ class EmlToPdfTest {
@Test @Test
@DisplayName("Should parse simple text email correctly") @DisplayName("Should parse simple text email correctly")
void parseSimpleTextEmail() throws IOException { void parseSimpleTextEmail() throws IOException {
String emlContent = createSimpleTextEmail( String emlContent =
createSimpleTextEmail(
"sender@example.com", "sender@example.com",
"recipient@example.com", "recipient@example.com",
"Simple Test Subject", "Simple Test Subject",
"This is a simple plain text email body."); "This is a simple plain text email body.");
testEmailConversion(emlContent, new String[] { testEmailConversion(
emlContent,
new String[] {
"Simple Test Subject", "Simple Test Subject",
"sender@example.com", "sender@example.com",
"recipient@example.com", "recipient@example.com",
"This is a simple plain text email body", "This is a simple plain text email body",
"<!DOCTYPE html>" "<!DOCTYPE html>"
}, false); },
false);
} }
@Test @Test
@ -103,35 +114,39 @@ class EmlToPdfTest {
assertNotNull(htmlResult); assertNotNull(htmlResult);
assertTrue(htmlResult.contains("sender@example.com")); assertTrue(htmlResult.contains("sender@example.com"));
assertTrue(htmlResult.contains("This is an email body")); assertTrue(htmlResult.contains("This is an email body"));
assertTrue(htmlResult.contains("<title></title>") || assertTrue(
htmlResult.contains("<title>No Subject</title>")); htmlResult.contains("<title></title>")
|| htmlResult.contains("<title>No Subject</title>"));
} }
@Test @Test
@DisplayName("Should parse HTML email with styling") @DisplayName("Should parse HTML email with styling")
void parseHtmlEmailWithStyling() throws IOException { void parseHtmlEmailWithStyling() throws IOException {
String htmlBody = "<html><head><style>.header{color:blue;font-weight:bold;}" + String htmlBody =
".content{margin:10px;}.footer{font-size:12px;}</style></head>" + "<html><head><style>.header{color:blue;font-weight:bold;}"
"<body><div class=\"header\">Important Notice</div>" + + ".content{margin:10px;}.footer{font-size:12px;}</style></head>"
"<div class=\"content\">This is <strong>HTML content</strong> with styling.</div>" + + "<body><div class=\"header\">Important Notice</div>"
"<div class=\"footer\">Best regards</div></body></html>"; + "<div class=\"content\">This is <strong>HTML content</strong> with styling.</div>"
+ "<div class=\"footer\">Best regards</div></body></html>";
String emlContent = createHtmlEmail( String emlContent =
createHtmlEmail(
"html@example.com", "user@example.com", "HTML Email Test", htmlBody); "html@example.com", "user@example.com", "HTML Email Test", htmlBody);
testEmailConversion(emlContent, new String[] { testEmailConversion(
"HTML Email Test", emlContent,
"Important Notice", new String[] {
"HTML content", "HTML Email Test", "Important Notice", "HTML content", "font-weight: bold"
"font-weight: bold" },
}, false); false);
} }
@Test @Test
@DisplayName("Should parse multipart email with attachments") @DisplayName("Should parse multipart email with attachments")
void parseMultipartEmailWithAttachments() throws IOException { void parseMultipartEmailWithAttachments() throws IOException {
String boundary = "----=_Part_" + getTimestamp(); String boundary = "----=_Part_" + getTimestamp();
String emlContent = createMultipartEmailWithAttachment( String emlContent =
createMultipartEmailWithAttachment(
"multipart@example.com", "multipart@example.com",
"user@example.com", "user@example.com",
"Multipart Email Test", "Multipart Email Test",
@ -140,10 +155,10 @@ class EmlToPdfTest {
"document.txt", "document.txt",
"Sample attachment content"); "Sample attachment content");
testEmailConversion(emlContent, new String[] { testEmailConversion(
"Multipart Email Test", emlContent,
"This email has both text content" new String[] {"Multipart Email Test", "This email has both text content"},
}, true); true);
} }
} }
@ -155,39 +170,41 @@ class EmlToPdfTest {
@DisplayName("Should handle international characters and UTF-8") @DisplayName("Should handle international characters and UTF-8")
void handleInternationalCharacters() throws IOException { void handleInternationalCharacters() throws IOException {
String bodyWithIntlChars = "Hello! 你好 Привет مرحبا Hëllö Thañks! Önë Mörë"; String bodyWithIntlChars = "Hello! 你好 Привет مرحبا Hëllö Thañks! Önë Mörë";
String emlContent = createSimpleTextEmail( String emlContent =
createSimpleTextEmail(
"intl@example.com", "intl@example.com",
"user@example.com", "user@example.com",
"International Characters Test", "International Characters Test",
bodyWithIntlChars); bodyWithIntlChars);
testEmailConversion(emlContent, new String[] { testEmailConversion(
"你好", "Привет", "مرحبا", "Hëllö", "Önë", "Mörë" emlContent,
}, false); new String[] {"你好", "Привет", "مرحبا", "Hëllö", "Önë", "Mörë"},
false);
} }
@Test @Test
@DisplayName("Should decode quoted-printable content correctly") @DisplayName("Should decode quoted-printable content correctly")
void decodeQuotedPrintableContent() throws IOException { void decodeQuotedPrintableContent() throws IOException {
String content = createQuotedPrintableEmail( String content = createQuotedPrintableEmail();
);
testEmailConversion(content, new String[] { testEmailConversion(
content,
new String[] {
"Quoted-Printable Test", "Quoted-Printable Test",
"This is quoted printable content with special chars: éàè." "This is quoted printable content with special chars: éàè."
}, false); },
false);
} }
@Test @Test
@DisplayName("Should decode Base64 content") @DisplayName("Should decode Base64 content")
void decodeBase64Content() throws IOException { void decodeBase64Content() throws IOException {
String originalText = "This is Base64 encoded content: éàü ñ"; String originalText = "This is Base64 encoded content: éàü ñ";
String content = createBase64Email( String content = createBase64Email(originalText);
originalText);
testEmailConversion(content, new String[] { testEmailConversion(
"Base64 Test", "Base64 encoded content" content, new String[] {"Base64 Test", "Base64 encoded content"}, false);
}, false);
} }
@Test @Test
@ -195,8 +212,12 @@ class EmlToPdfTest {
void handleInlineImages() throws IOException { void handleInlineImages() throws IOException {
String boundary = "----=_Part_CID_1234567890"; String boundary = "----=_Part_CID_1234567890";
String cid = "image123@example.com"; String cid = "image123@example.com";
String htmlBody = "<html><body><p>Here is an image:</p><img src=\"cid:" + cid + "\"></body></html>"; String htmlBody =
String imageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="; "<html><body><p>Here is an image:</p><img src=\"cid:"
+ cid
+ "\"></body></html>";
String imageBase64 =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
String emlContent = createEmailWithInlineImage(htmlBody, boundary, cid, imageBase64); String emlContent = createEmailWithInlineImage(htmlBody, boundary, cid, imageBase64);
@ -218,15 +239,17 @@ class EmlToPdfTest {
@Test @Test
@DisplayName("Should generate valid HTML structure") @DisplayName("Should generate valid HTML structure")
void generateValidHtmlStructure() throws IOException { void generateValidHtmlStructure() throws IOException {
String emlContent = createSimpleTextEmail( String emlContent =
createSimpleTextEmail(
"structure@test.com", "structure@test.com",
"user@test.com", "user@test.com",
"HTML Structure Test", "HTML Structure Test",
"Testing HTML structure output"); "Testing HTML structure output");
testEmailConversion(emlContent, new String[] { testEmailConversion(
"<!DOCTYPE html>", "<html", "</html>", "HTML Structure Test" emlContent,
}, false); new String[] {"<!DOCTYPE html>", "<html", "</html>", "HTML Structure Test"},
false);
byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8); byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8);
String htmlResult = EmlToPdf.convertEmlToHtml(emlBytes, createBasicRequest()); String htmlResult = EmlToPdf.convertEmlToHtml(emlBytes, createBasicRequest());
@ -236,17 +259,19 @@ class EmlToPdfTest {
@Test @Test
@DisplayName("Should preserve safe CSS and remove problematic styles") @DisplayName("Should preserve safe CSS and remove problematic styles")
void handleCssStylesCorrectly() throws IOException { void handleCssStylesCorrectly() throws IOException {
String styledHtml = "<html><head><style>" + String styledHtml =
".safe { color: blue; font-size: 14px; }" + "<html><head><style>"
".problematic { position: fixed; word-break: break-all; }" + + ".safe { color: blue; font-size: 14px; }"
".good { margin: 10px; padding: 5px; }" + + ".problematic { position: fixed; word-break: break-all; }"
"</style></head><body>" + + ".good { margin: 10px; padding: 5px; }"
"<div class=\"safe\">Safe styling</div>" + + "</style></head><body>"
"<div class=\"problematic\">Problematic styling</div>" + + "<div class=\"safe\">Safe styling</div>"
"<div class=\"good\">Good styling</div>" + + "<div class=\"problematic\">Problematic styling</div>"
"</body></html>"; + "<div class=\"good\">Good styling</div>"
+ "</body></html>";
String emlContent = createHtmlEmail("css@test.com", "user@test.com", "CSS Test", styledHtml); String emlContent =
createHtmlEmail("css@test.com", "user@test.com", "CSS Test", styledHtml);
byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8); byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8);
EmlToPdfRequest request = createBasicRequest(); EmlToPdfRequest request = createBasicRequest();
@ -264,19 +289,22 @@ class EmlToPdfTest {
@Test @Test
@DisplayName("Should handle complex nested HTML structures") @DisplayName("Should handle complex nested HTML structures")
void handleComplexNestedHtml() throws IOException { void handleComplexNestedHtml() throws IOException {
String complexHtml = "<html><head><title>Complex Email</title></head><body>" + String complexHtml =
"<div class=\"container\"><header><h1>Email Header</h1></header><main><section>" + "<html><head><title>Complex Email</title></head><body>"
"<p>Paragraph with <a href=\"https://example.com\">link</a></p><ul>" + + "<div class=\"container\"><header><h1>Email Header</h1></header><main><section>"
"<li>List item 1</li><li>List item 2 with <em>emphasis</em></li></ul><table>" + + "<p>Paragraph with <a href=\"https://example.com\">link</a></p><ul>"
"<tr><td>Cell 1</td><td>Cell 2</td></tr><tr><td>Cell 3</td><td>Cell 4</td></tr>" + + "<li>List item 1</li><li>List item 2 with <em>emphasis</em></li></ul><table>"
"</table></section></main></div></body></html>"; + "<tr><td>Cell 1</td><td>Cell 2</td></tr><tr><td>Cell 3</td><td>Cell 4</td></tr>"
+ "</table></section></main></div></body></html>";
String emlContent = createHtmlEmail( String emlContent =
createHtmlEmail(
"complex@test.com", "user@test.com", "Complex HTML Test", complexHtml); "complex@test.com", "user@test.com", "Complex HTML Test", complexHtml);
testEmailConversion(emlContent, new String[] { testEmailConversion(
"Email Header", "List item 2", "Cell 3", "example.com" emlContent,
}, false); new String[] {"Email Header", "List item 2", "Cell 3", "example.com"},
false);
byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8); byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8);
String htmlResult = EmlToPdf.convertEmlToHtml(emlBytes, createBasicRequest()); String htmlResult = EmlToPdf.convertEmlToHtml(emlBytes, createBasicRequest());
@ -293,7 +321,8 @@ class EmlToPdfTest {
void rejectNullInput() { void rejectNullInput() {
EmlToPdfRequest request = createBasicRequest(); EmlToPdfRequest request = createBasicRequest();
Exception exception = assertThrows( Exception exception =
assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> EmlToPdf.convertEmlToHtml(null, request)); () -> EmlToPdf.convertEmlToHtml(null, request));
assertTrue(exception.getMessage().contains("EML file is empty or null")); assertTrue(exception.getMessage().contains("EML file is empty or null"));
@ -304,7 +333,8 @@ class EmlToPdfTest {
void rejectEmptyInput() { void rejectEmptyInput() {
EmlToPdfRequest request = createBasicRequest(); EmlToPdfRequest request = createBasicRequest();
Exception exception = assertThrows( Exception exception =
assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> EmlToPdf.convertEmlToHtml(new byte[0], request)); () -> EmlToPdf.convertEmlToHtml(new byte[0], request));
assertTrue(exception.getMessage().contains("EML file is empty or null")); assertTrue(exception.getMessage().contains("EML file is empty or null"));
@ -313,7 +343,8 @@ class EmlToPdfTest {
@Test @Test
@DisplayName("Should handle malformed EML gracefully") @DisplayName("Should handle malformed EML gracefully")
void handleMalformedEmlGracefully() { void handleMalformedEmlGracefully() {
String malformedEml = """ String malformedEml =
"""
From: sender@test.com From: sender@test.com
Subject: Malformed EML Subject: Malformed EML
This line breaks header format This line breaks header format
@ -337,10 +368,12 @@ class EmlToPdfTest {
@Test @Test
@DisplayName("Should reject invalid EML format") @DisplayName("Should reject invalid EML format")
void rejectInvalidEmlFormat() { void rejectInvalidEmlFormat() {
byte[] invalidEml = "This is definitely not an EML file".getBytes(StandardCharsets.UTF_8); byte[] invalidEml =
"This is definitely not an EML file".getBytes(StandardCharsets.UTF_8);
EmlToPdfRequest request = createBasicRequest(); EmlToPdfRequest request = createBasicRequest();
Exception exception = assertThrows( Exception exception =
assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> EmlToPdf.convertEmlToHtml(invalidEml, request)); () -> EmlToPdf.convertEmlToHtml(invalidEml, request));
assertTrue(exception.getMessage().contains("Invalid EML file format")); assertTrue(exception.getMessage().contains("Invalid EML file format"));
@ -354,8 +387,10 @@ class EmlToPdfTest {
@Test @Test
@DisplayName("Should successfully parse email using advanced parser") @DisplayName("Should successfully parse email using advanced parser")
void initializeDependencyMailSession() { void initializeDependencyMailSession() {
assertDoesNotThrow(() -> { assertDoesNotThrow(
String emlContent = createSimpleTextEmail( () -> {
String emlContent =
createSimpleTextEmail(
"Dependency@test.com", "Dependency@test.com",
"user@test.com", "user@test.com",
"Dependency Mail Test", "Dependency Mail Test",
@ -397,7 +432,8 @@ class EmlToPdfTest {
@DisplayName("Should handle email with only an attachment and no body") @DisplayName("Should handle email with only an attachment and no body")
void handleAttachmentOnlyEmail() throws IOException { void handleAttachmentOnlyEmail() throws IOException {
String boundary = "----=_Part_AttachmentOnly_1234567890"; String boundary = "----=_Part_AttachmentOnly_1234567890";
String emlContent = createMultipartEmailWithAttachment( String emlContent =
createMultipartEmailWithAttachment(
"sender@example.com", "sender@example.com",
"recipient@example.com", "recipient@example.com",
"Attachment Only Test", "Attachment Only Test",
@ -406,9 +442,10 @@ class EmlToPdfTest {
"data.bin", "data.bin",
"binary data"); "binary data");
testEmailConversion(emlContent, new String[] { testEmailConversion(
"Attachment Only Test", "data.bin", "No content available" emlContent,
}, true); new String[] {"Attachment Only Test", "data.bin", "No content available"},
true);
} }
@Test @Test
@ -417,10 +454,12 @@ class EmlToPdfTest {
String boundary = "----=_Part_MixedAttachments_1234567890"; String boundary = "----=_Part_MixedAttachments_1234567890";
String cid = "inline_image@example.com"; String cid = "inline_image@example.com";
String htmlBody = "<html><body><img src=\"cid:" + cid + "\"></body></html>"; String htmlBody = "<html><body><img src=\"cid:" + cid + "\"></body></html>";
String imageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR42mNkAAIAAAoAAb6A/yoAAAAASUVORK5CYII="; String imageBase64 =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR42mNkAAIAAAoAAb6A/yoAAAAASUVORK5CYII=";
String attachmentText = "This is a text attachment."; String attachmentText = "This is a text attachment.";
String emlContent = createEmailWithMixedAttachments( String emlContent =
createEmailWithMixedAttachments(
htmlBody, boundary, cid, imageBase64, attachmentText); htmlBody, boundary, cid, imageBase64, attachmentText);
byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8); byte[] emlBytes = emlContent.getBytes(StandardCharsets.UTF_8);
@ -439,7 +478,8 @@ class EmlToPdfTest {
String subject = "Subject with special characters: ñ é ü"; String subject = "Subject with special characters: ñ é ü";
String body = "Body with special characters: ñ é ü"; String body = "Body with special characters: ñ é ü";
String emlContent = createSimpleTextEmailWithCharset( String emlContent =
createSimpleTextEmailWithCharset(
"sender@example.com", "sender@example.com",
"recipient@example.com", "recipient@example.com",
subject, subject,
@ -464,36 +504,40 @@ class EmlToPdfTest {
longLine.append("word").append(i).append(" "); longLine.append("word").append(i).append(" ");
} }
String emlContent = createSimpleTextEmail( String emlContent =
createSimpleTextEmail(
"sender@example.com", "sender@example.com",
"recipient@example.com", "recipient@example.com",
"Long Line Test", "Long Line Test",
longLine.toString()); longLine.toString());
testEmailConversion(emlContent, new String[] { testEmailConversion(
"Long Line Test", "This is a very long line", "word999" emlContent,
}, false); new String[] {"Long Line Test", "This is a very long line", "word999"},
false);
} }
@Test @Test
@DisplayName("Should handle .eml files as attachments") @DisplayName("Should handle .eml files as attachments")
void handleEmlAttachment() throws IOException { void handleEmlAttachment() throws IOException {
String boundary = "----=_Part_EmlAttachment_1234567890"; String boundary = "----=_Part_EmlAttachment_1234567890";
String innerEmlContent = createSimpleTextEmail( String innerEmlContent =
createSimpleTextEmail(
"inner@example.com", "inner@example.com",
"inner_recipient@example.com", "inner_recipient@example.com",
"Inner Email Subject", "Inner Email Subject",
"This is the body of the attached email."); "This is the body of the attached email.");
String emlContent = createEmailWithEmlAttachment( String emlContent = createEmailWithEmlAttachment(boundary, innerEmlContent);
boundary,
innerEmlContent);
testEmailConversion(emlContent, new String[] { testEmailConversion(
emlContent,
new String[] {
"Fwd: Inner Email Subject", "Fwd: Inner Email Subject",
"Please see the attached email.", "Please see the attached email.",
"attached_email.eml" "attached_email.eml"
}, true); },
true);
} }
@ParameterizedTest @ParameterizedTest
@ -503,12 +547,9 @@ class EmlToPdfTest {
String subject = "Encoding Test"; String subject = "Encoding Test";
String body = "Testing " + charset + " encoding"; String body = "Testing " + charset + " encoding";
String emlContent = createSimpleTextEmailWithCharset( String emlContent =
"sender@example.com", createSimpleTextEmailWithCharset(
"recipient@example.com", "sender@example.com", "recipient@example.com", subject, body, charset);
subject,
body,
charset);
testEmailConversion(emlContent, new String[] {subject, body}, false); testEmailConversion(emlContent, new String[] {subject, body}, false);
} }
@ -544,7 +585,8 @@ class EmlToPdfTest {
when(mockPdfDocumentFactory.load(any(byte[].class))).thenReturn(mockPdDocument); when(mockPdfDocumentFactory.load(any(byte[].class))).thenReturn(mockPdDocument);
when(mockPdDocument.getNumberOfPages()).thenReturn(1); when(mockPdDocument.getNumberOfPages()).thenReturn(1);
try (MockedStatic<FileToPdf> fileToPdf = mockStatic(FileToPdf.class, org.mockito.Mockito.withSettings().lenient())) { try (MockedStatic<FileToPdf> fileToPdf =
mockStatic(FileToPdf.class, org.mockito.Mockito.withSettings().lenient())) {
fileToPdf fileToPdf
.when( .when(
() -> () ->
@ -592,7 +634,8 @@ class EmlToPdfTest {
@DisplayName("Should convert EML to PDF with attachments when requested") @DisplayName("Should convert EML to PDF with attachments when requested")
void convertEmlToPdfWithAttachments() throws Exception { void convertEmlToPdfWithAttachments() throws Exception {
String boundary = "----=_Part_1234567890"; String boundary = "----=_Part_1234567890";
String emlContent = createMultipartEmailWithAttachment( String emlContent =
createMultipartEmailWithAttachment(
"multipart@example.com", "multipart@example.com",
"user@example.com", "user@example.com",
"Multipart Email Test", "Multipart Email Test",
@ -613,7 +656,8 @@ class EmlToPdfTest {
when(mockPdfDocumentFactory.load(any(byte[].class))).thenReturn(mockPdDocument); when(mockPdfDocumentFactory.load(any(byte[].class))).thenReturn(mockPdDocument);
when(mockPdDocument.getNumberOfPages()).thenReturn(1); when(mockPdDocument.getNumberOfPages()).thenReturn(1);
try (MockedStatic<FileToPdf> fileToPdf = mockStatic(FileToPdf.class, org.mockito.Mockito.withSettings().lenient())) { try (MockedStatic<FileToPdf> fileToPdf =
mockStatic(FileToPdf.class, org.mockito.Mockito.withSettings().lenient())) {
fileToPdf fileToPdf
.when( .when(
() -> () ->
@ -679,7 +723,8 @@ class EmlToPdfTest {
EmlToPdfRequest request = createBasicRequest(); EmlToPdfRequest request = createBasicRequest();
String errorMessage = "Conversion failed"; String errorMessage = "Conversion failed";
try (MockedStatic<FileToPdf> fileToPdf = mockStatic(FileToPdf.class, org.mockito.Mockito.withSettings().lenient())) { try (MockedStatic<FileToPdf> fileToPdf =
mockStatic(FileToPdf.class, org.mockito.Mockito.withSettings().lenient())) {
fileToPdf fileToPdf
.when( .when(
() -> () ->
@ -692,9 +737,11 @@ class EmlToPdfTest {
any(CustomHtmlSanitizer.class))) any(CustomHtmlSanitizer.class)))
.thenThrow(new IOException(errorMessage)); .thenThrow(new IOException(errorMessage));
IOException exception = assertThrows( IOException exception =
assertThrows(
IOException.class, IOException.class,
() -> EmlToPdf.convertEmlToPdf( () ->
EmlToPdf.convertEmlToPdf(
"weasyprint", "weasyprint",
request, request,
emlBytes, emlBytes,
@ -710,14 +757,17 @@ class EmlToPdfTest {
// Helper methods // Helper methods
private String getTimestamp() { private String getTimestamp() {
java.time.ZonedDateTime fixedDateTime = java.time.ZonedDateTime.of(2023, 1, 1, 12, 0, 0, 0, java.time.ZoneId.of("GMT")); java.time.ZonedDateTime fixedDateTime =
java.time.ZonedDateTime.of(2023, 1, 1, 12, 0, 0, 0, java.time.ZoneId.of("GMT"));
return java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME.format(fixedDateTime); return java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME.format(fixedDateTime);
} }
private String createSimpleTextEmail(String from, String to, String subject, String body) { private String createSimpleTextEmail(String from, String to, String subject, String body) {
return createSimpleTextEmailWithCharset(from, to, subject, body, "UTF-8"); return createSimpleTextEmailWithCharset(from, to, subject, body, "UTF-8");
} }
private String createSimpleTextEmailWithCharset(String from, String to, String subject, String body, String charset) { private String createSimpleTextEmailWithCharset(
String from, String to, String subject, String body, String charset) {
return String.format( return String.format(
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nContent-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n%s", "From: %s\nTo: %s\nSubject: %s\nDate: %s\nContent-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n%s",
from, to, subject, getTimestamp(), charset, body); from, to, subject, getTimestamp(), charset, body);
@ -735,9 +785,17 @@ class EmlToPdfTest {
from, to, subject, getTimestamp(), htmlBody); from, to, subject, getTimestamp(), htmlBody);
} }
private String createMultipartEmailWithAttachment(String from, String to, String subject, String body, private String createMultipartEmailWithAttachment(
String boundary, String filename, String attachmentContent) { String from,
String encodedContent = Base64.getEncoder().encodeToString(attachmentContent.getBytes(StandardCharsets.UTF_8)); String to,
String subject,
String body,
String boundary,
String filename,
String attachmentContent) {
String encodedContent =
Base64.getEncoder()
.encodeToString(attachmentContent.getBytes(StandardCharsets.UTF_8));
return String.format( return String.format(
""" """
From: %s From: %s
@ -760,11 +818,23 @@ class EmlToPdfTest {
%s %s
--%s--""", --%s--""",
from, to, subject, getTimestamp(), boundary, boundary, body, boundary, filename, encodedContent, boundary); from,
to,
subject,
getTimestamp(),
boundary,
boundary,
body,
boundary,
filename,
encodedContent,
boundary);
} }
private String createEmailWithEmlAttachment(String boundary, String attachmentEmlContent) { private String createEmailWithEmlAttachment(String boundary, String attachmentEmlContent) {
String encodedContent = Base64.getEncoder().encodeToString(attachmentEmlContent.getBytes(StandardCharsets.UTF_8)); String encodedContent =
Base64.getEncoder()
.encodeToString(attachmentEmlContent.getBytes(StandardCharsets.UTF_8));
return String.format( return String.format(
""" """
From: %s From: %s
@ -787,10 +857,22 @@ class EmlToPdfTest {
%s %s
--%s--""", --%s--""",
"outer@example.com", "outer_recipient@example.com", "Fwd: Inner Email Subject", getTimestamp(), boundary, boundary, "Please see the attached email.", boundary, "attached_email.eml", "attached_email.eml", encodedContent, boundary); "outer@example.com",
"outer_recipient@example.com",
"Fwd: Inner Email Subject",
getTimestamp(),
boundary,
boundary,
"Please see the attached email.",
boundary,
"attached_email.eml",
"attached_email.eml",
encodedContent,
boundary);
} }
private String createMultipartAlternativeEmail(String textBody, String htmlBody, String boundary) { private String createMultipartAlternativeEmail(
String textBody, String htmlBody, String boundary) {
return String.format( return String.format(
""" """
From: %s From: %s
@ -813,24 +895,42 @@ class EmlToPdfTest {
%s %s
--%s--""", --%s--""",
"sender@example.com", "receiver@example.com", "Multipart/Alternative Test", getTimestamp(), "sender@example.com",
boundary, boundary, textBody, boundary, htmlBody, boundary); "receiver@example.com",
"Multipart/Alternative Test",
getTimestamp(),
boundary,
boundary,
textBody,
boundary,
htmlBody,
boundary);
} }
private String createQuotedPrintableEmail() { private String createQuotedPrintableEmail() {
return String.format( return String.format(
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n%s", "From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable\n\n%s",
"sender@example.com", "recipient@example.com", "Quoted-Printable Test", getTimestamp(), "This is quoted=20printable content with special chars: =C3=A9=C3=A0=C3=A8."); "sender@example.com",
"recipient@example.com",
"Quoted-Printable Test",
getTimestamp(),
"This is quoted=20printable content with special chars: =C3=A9=C3=A0=C3=A8.");
} }
private String createBase64Email(String body) { private String createBase64Email(String body) {
String encodedBody = Base64.getEncoder().encodeToString(body.getBytes(StandardCharsets.UTF_8)); String encodedBody =
Base64.getEncoder().encodeToString(body.getBytes(StandardCharsets.UTF_8));
return String.format( return String.format(
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: base64\n\n%s", "From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: base64\n\n%s",
"sender@example.com", "recipient@example.com", "Base64 Test", getTimestamp(), encodedBody); "sender@example.com",
"recipient@example.com",
"Base64 Test",
getTimestamp(),
encodedBody);
} }
private String createEmailWithInlineImage(String htmlBody, String boundary, String contentId, String base64Image) { private String createEmailWithInlineImage(
String htmlBody, String boundary, String contentId, String base64Image) {
return String.format( return String.format(
""" """
From: %s From: %s
@ -854,13 +954,27 @@ class EmlToPdfTest {
%s %s
--%s--""", --%s--""",
"sender@example.com", "receiver@example.com", "Inline Image Test", getTimestamp(), "sender@example.com",
boundary, boundary, htmlBody, boundary, contentId, base64Image, boundary); "receiver@example.com",
"Inline Image Test",
getTimestamp(),
boundary,
boundary,
htmlBody,
boundary,
contentId,
base64Image,
boundary);
} }
private String createEmailWithMixedAttachments(String htmlBody, String boundary, String contentId, private String createEmailWithMixedAttachments(
String base64Image, String attachmentBody) { String htmlBody,
String encodedAttachment = Base64.getEncoder().encodeToString(attachmentBody.getBytes(StandardCharsets.UTF_8)); String boundary,
String contentId,
String base64Image,
String attachmentBody) {
String encodedAttachment =
Base64.getEncoder().encodeToString(attachmentBody.getBytes(StandardCharsets.UTF_8));
return String.format( return String.format(
""" """
From: %s From: %s
@ -896,10 +1010,25 @@ class EmlToPdfTest {
%s %s
--%s--""", --%s--""",
"sender@example.com", "receiver@example.com", "Mixed Attachments Test", getTimestamp(), "sender@example.com",
boundary, boundary, boundary, boundary, htmlBody, boundary, contentId, base64Image, boundary, "receiver@example.com",
boundary, "text.txt", encodedAttachment, boundary); "Mixed Attachments Test",
getTimestamp(),
boundary,
boundary,
boundary,
boundary,
htmlBody,
boundary,
contentId,
base64Image,
boundary,
boundary,
"text.txt",
encodedAttachment,
boundary);
} }
// Creates a basic EmlToPdfRequest with default settings // Creates a basic EmlToPdfRequest with default settings
private EmlToPdfRequest createBasicRequest() { private EmlToPdfRequest createBasicRequest() {
EmlToPdfRequest request = new EmlToPdfRequest(); EmlToPdfRequest request = new EmlToPdfRequest();

View File

@ -19,6 +19,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.configuration.RuntimePathConfig;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)

View File

@ -1,15 +1,14 @@
package stirling.software.common.util; package stirling.software.common.util;
import java.nio.file.Files;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.anyString;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -24,14 +23,18 @@ public class FileToPdfTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class); SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class);
stirling.software.common.model.ApplicationProperties mockApplicationProperties = mock(stirling.software.common.model.ApplicationProperties.class); stirling.software.common.model.ApplicationProperties mockApplicationProperties =
stirling.software.common.model.ApplicationProperties.System mockSystem = mock(stirling.software.common.model.ApplicationProperties.System.class); mock(stirling.software.common.model.ApplicationProperties.class);
stirling.software.common.model.ApplicationProperties.System mockSystem =
mock(stirling.software.common.model.ApplicationProperties.System.class);
when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString())).thenReturn(true); when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString()))
.thenReturn(true);
when(mockApplicationProperties.getSystem()).thenReturn(mockSystem); when(mockApplicationProperties.getSystem()).thenReturn(mockSystem);
when(mockSystem.getDisableSanitize()).thenReturn(false); when(mockSystem.getDisableSanitize()).thenReturn(false);
customHtmlSanitizer = new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties); customHtmlSanitizer =
new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties);
} }
/** /**
@ -60,7 +63,12 @@ public class FileToPdfTest {
Exception.class, Exception.class,
() -> () ->
FileToPdf.convertHtmlToPdf( FileToPdf.convertHtmlToPdf(
"/path/", request, fileBytes, fileName, tempFileManager, customHtmlSanitizer)); "/path/",
request,
fileBytes,
fileName,
tempFileManager,
customHtmlSanitizer));
assertNotNull(thrown); assertNotNull(thrown);
} }

View File

@ -1,21 +1,23 @@
package stirling.software.common.util; package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; 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.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
import stirling.software.common.model.oauth2.GitHubProvider; import stirling.software.common.model.oauth2.GitHubProvider;
import stirling.software.common.model.oauth2.GoogleProvider; import stirling.software.common.model.oauth2.GoogleProvider;
import stirling.software.common.model.oauth2.Provider; import stirling.software.common.model.oauth2.Provider;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ProviderUtilsTest { class ProviderUtilsTest {

View File

@ -42,7 +42,6 @@ class SpringContextHolderTest {
verify(mockApplicationContext).getBean(TestBean.class); verify(mockApplicationContext).getBean(TestBean.class);
} }
@Test @Test
void testGetBean_ApplicationContextNotSet() { void testGetBean_ApplicationContextNotSet() {
// Don't set application context // Don't set application context
@ -58,7 +57,8 @@ class SpringContextHolderTest {
void testGetBean_BeanNotFound() { void testGetBean_BeanNotFound() {
// Arrange // Arrange
contextHolder.setApplicationContext(mockApplicationContext); contextHolder.setApplicationContext(mockApplicationContext);
when(mockApplicationContext.getBean(TestBean.class)).thenThrow(new org.springframework.beans.BeansException("Bean not found") {}); when(mockApplicationContext.getBean(TestBean.class))
.thenThrow(new org.springframework.beans.BeansException("Bean not found") {});
// Act // Act
TestBean result = SpringContextHolder.getBean(TestBean.class); TestBean result = SpringContextHolder.getBean(TestBean.class);
@ -68,6 +68,5 @@ class SpringContextHolderTest {
} }
// Simple test class // Simple test class
private static class TestBean { private static class TestBean {}
}
} }

View File

@ -1,11 +1,13 @@
package stirling.software.common.util.misc; package stirling.software.common.util.misc;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.misc.HighContrastColorCombination;
import stirling.software.common.model.api.misc.ReplaceAndInvert;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.misc.HighContrastColorCombination;
import stirling.software.common.model.api.misc.ReplaceAndInvert;
class HighContrastColorReplaceDeciderTest { class HighContrastColorReplaceDeciderTest {
@Test @Test

View File

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.api.misc.ReplaceAndInvert; import stirling.software.common.model.api.misc.ReplaceAndInvert;
class InvertFullColorStrategyTest { class InvertFullColorStrategyTest {

View File

@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.api.misc.ReplaceAndInvert; import stirling.software.common.model.api.misc.ReplaceAndInvert;
class ReplaceAndInvertColorStrategyTest { class ReplaceAndInvertColorStrategyTest {

View File

@ -1,14 +1,17 @@
package stirling.software.common.util.propertyeditor; package stirling.software.common.util.propertyeditor;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.security.RedactionArea;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.security.RedactionArea;
class StringToArrayListPropertyEditorTest { class StringToArrayListPropertyEditorTest {
private StringToArrayListPropertyEditor editor; private StringToArrayListPropertyEditor editor;

View File

@ -14,7 +14,7 @@ configurations {
spotless { spotless {
java { java {
target sourceSets.main.allJava target 'src/**/java/**/*.java'
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false) googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling") importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
@ -23,6 +23,18 @@ spotless {
leadingTabsToSpaces() leadingTabsToSpaces()
endWithNewline() endWithNewline()
} }
yaml {
target '**/*.yml', '**/*.yaml'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
format 'gradle', {
target '**/gradle/*.gradle', '**/*.gradle'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
} }
dependencies { dependencies {
@ -62,7 +74,7 @@ dependencies {
exclude group: 'com.google.code.gson', module: 'gson' exclude group: 'com.google.code.gson', module: 'gson'
} }
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4' implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
implementation 'com.opencsv:opencsv:5.11.2' // https://mvnrepository.com/artifact/com.opencsv/opencsv implementation 'com.opencsv:opencsv:5.12.0' // https://mvnrepository.com/artifact/com.opencsv/opencsv
// Batik // Batik
implementation 'org.apache.xmlgraphics:batik-all:1.19' implementation 'org.apache.xmlgraphics:batik-all:1.19'

View File

@ -70,10 +70,18 @@ public class StampController {
String stampType = request.getStampType(); String stampType = request.getStampType();
String stampText = request.getStampText(); String stampText = request.getStampText();
MultipartFile stampImage = request.getStampImage(); MultipartFile stampImage = request.getStampImage();
if ("image".equalsIgnoreCase(stampType)) {
if (stampImage == null) {
throw new IllegalArgumentException(
"Stamp image file must be provided when stamp type is 'image'");
}
String stampImageName = stampImage.getOriginalFilename(); String stampImageName = stampImage.getOriginalFilename();
if (stampImageName.contains("..") || stampImageName.startsWith("/")) { if (stampImageName == null
|| stampImageName.contains("..")
|| stampImageName.startsWith("/")) {
throw new IllegalArgumentException("Invalid stamp image file path"); throw new IllegalArgumentException("Invalid stamp image file path");
} }
}
String alphabet = request.getAlphabet(); String alphabet = request.getAlphabet();
float fontSize = request.getFontSize(); float fontSize = request.getFontSize();
float rotation = request.getRotation(); float rotation = request.getRotation();

View File

@ -108,9 +108,13 @@ public class PipelineProcessor {
if (inputFileTypes == null) { if (inputFileTypes == null) {
inputFileTypes = new ArrayList<String>(Arrays.asList("ALL")); inputFileTypes = new ArrayList<String>(Arrays.asList("ALL"));
} }
if (!operation.matches("^[a-zA-Z0-9_-]+$")) {
throw new IllegalArgumentException("Invalid operation value received."); if (!apiDocService.isValidOperation(operation, parameters)) {
log.error("Invalid operation or parameters: o:{} p:{}", operation, parameters);
throw new IllegalArgumentException(
"Invalid operation: " + operation + " with parameters: " + parameters);
} }
String url = getBaseUrl() + operation; String url = getBaseUrl() + operation;
List<Resource> newOutputFiles = new ArrayList<>(); List<Resource> newOutputFiles = new ArrayList<>();
if (!isMultiInputOperation) { if (!isMultiInputOperation) {
@ -136,7 +140,7 @@ public class PipelineProcessor {
// skip // skip
// this // this
// file // file
if (operation.startsWith("filter-") if (operation.startsWith("/api/v1/filter/filter-")
&& (response.getBody() == null && (response.getBody() == null
|| response.getBody().length == 0)) { || response.getBody().length == 0)) {
filtersApplied = true; filtersApplied = true;

View File

@ -1282,7 +1282,7 @@ merge.header=Több PDF egyesítése (2+)
merge.sortByName=Rendezés név szerint merge.sortByName=Rendezés név szerint
merge.sortByDate=Rendezés dátum szerint merge.sortByDate=Rendezés dátum szerint
merge.removeCertSign=Digitális aláírás eltávolítása az egyesített fájlban? merge.removeCertSign=Digitális aláírás eltávolítása az egyesített fájlban?
merge.generateToc=Generate table of contents in the merged file? merge.generateToc=Tartalomjegyzék létrehozása az egyesített fájlban?
merge.submit=Egyesítés merge.submit=Egyesítés

View File

@ -260,7 +260,7 @@ disabledCurrentUserMessage=無法停用目前使用者
downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,將不會顯示目前使用者。 downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,將不會顯示目前使用者。
userAlreadyExistsOAuthMessage=使用者已經以 OAuth2 使用者身份存在。 userAlreadyExistsOAuthMessage=使用者已經以 OAuth2 使用者身份存在。
userAlreadyExistsWebMessage=使用者已經以網頁使用者身份存在。 userAlreadyExistsWebMessage=使用者已經以網頁使用者身份存在。
invalidRoleMessage=Invalid role. invalidRoleMessage=無效的角色。
error=錯誤 error=錯誤
oops=哎呀! oops=哎呀!
help=說明 help=說明
@ -273,7 +273,7 @@ color=顏色
sponsor=贊助 sponsor=贊助
info=資訊 info=資訊
pro=專業版 pro=專業版
proFeatures=Pro Features proFeatures=專業版功能
page=頁面 page=頁面
pages=頁面 pages=頁面
loading=載入中... loading=載入中...

View File

@ -45,77 +45,77 @@
{ {
"moduleName": "com.fasterxml.jackson.core:jackson-annotations", "moduleName": "com.fasterxml.jackson.core:jackson-annotations",
"moduleUrl": "https://github.com/FasterXML/jackson", "moduleUrl": "https://github.com/FasterXML/jackson",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.core:jackson-core", "moduleName": "com.fasterxml.jackson.core:jackson-core",
"moduleUrl": "https://github.com/FasterXML/jackson-core", "moduleUrl": "https://github.com/FasterXML/jackson-core",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.core:jackson-databind", "moduleName": "com.fasterxml.jackson.core:jackson-databind",
"moduleUrl": "https://github.com/FasterXML/jackson", "moduleUrl": "https://github.com/FasterXML/jackson",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", "moduleName": "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml",
"moduleUrl": "https://github.com/FasterXML/jackson-dataformats-text", "moduleUrl": "https://github.com/FasterXML/jackson-dataformats-text",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8", "moduleName": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8", "moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", "moduleName": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310", "moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-base", "moduleName": "com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-base",
"moduleUrl": "https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-base", "moduleUrl": "https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-base",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider", "moduleName": "com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider",
"moduleUrl": "https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-json-provider", "moduleUrl": "https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-json-provider",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations", "moduleName": "com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-base", "moduleUrl": "https://github.com/FasterXML/jackson-modules-base",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson.module:jackson-module-parameter-names", "moduleName": "com.fasterxml.jackson.module:jackson-module-parameter-names",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names", "moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "com.fasterxml.jackson:jackson-bom", "moduleName": "com.fasterxml.jackson:jackson-bom",
"moduleUrl": "https://github.com/FasterXML/jackson-bom", "moduleUrl": "https://github.com/FasterXML/jackson-bom",
"moduleVersion": "2.19.1", "moduleVersion": "2.19.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@ -199,13 +199,6 @@
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{
"moduleName": "com.google.protobuf:protobuf-java",
"moduleUrl": "https://developers.google.com/protocol-buffers/",
"moduleVersion": "4.31.0",
"moduleLicense": "BSD-3-Clause",
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-3-Clause"
},
{ {
"moduleName": "com.google.zxing:core", "moduleName": "com.google.zxing:core",
"moduleUrl": "https://github.com/zxing/zxing/core", "moduleUrl": "https://github.com/zxing/zxing/core",
@ -277,7 +270,7 @@
{ {
"moduleName": "com.opencsv:opencsv", "moduleName": "com.opencsv:opencsv",
"moduleUrl": "http://opencsv.sf.net", "moduleUrl": "http://opencsv.sf.net",
"moduleVersion": "5.11.2", "moduleVersion": "5.12.0",
"moduleLicense": "Apache 2", "moduleLicense": "Apache 2",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@ -498,7 +491,7 @@
{ {
"moduleName": "com.zaxxer:HikariCP", "moduleName": "com.zaxxer:HikariCP",
"moduleUrl": "https://github.com/brettwooldridge/HikariCP", "moduleUrl": "https://github.com/brettwooldridge/HikariCP",
"moduleVersion": "6.3.0", "moduleVersion": "6.3.1",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@ -553,77 +546,71 @@
{ {
"moduleName": "io.micrometer:micrometer-commons", "moduleName": "io.micrometer:micrometer-commons",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.1", "moduleVersion": "1.15.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.micrometer:micrometer-core", "moduleName": "io.micrometer:micrometer-core",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.1", "moduleVersion": "1.15.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.micrometer:micrometer-jakarta9", "moduleName": "io.micrometer:micrometer-jakarta9",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.1", "moduleVersion": "1.15.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.micrometer:micrometer-observation", "moduleName": "io.micrometer:micrometer-observation",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.1", "moduleVersion": "1.15.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.micrometer:micrometer-registry-prometheus", "moduleName": "io.micrometer:micrometer-registry-prometheus",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer", "moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.1", "moduleVersion": "1.15.2",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.prometheus:prometheus-metrics-config", "moduleName": "io.prometheus:prometheus-metrics-config",
"moduleVersion": "1.3.8", "moduleVersion": "1.3.10",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.prometheus:prometheus-metrics-core", "moduleName": "io.prometheus:prometheus-metrics-core",
"moduleVersion": "1.3.8", "moduleVersion": "1.3.10",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.prometheus:prometheus-metrics-exposition-formats", "moduleName": "io.prometheus:prometheus-metrics-exposition-formats",
"moduleVersion": "1.3.8", "moduleVersion": "1.3.10",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "io.prometheus:prometheus-metrics-exposition-formats-no-protobuf",
"moduleVersion": "1.3.8",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.prometheus:prometheus-metrics-exposition-textformats", "moduleName": "io.prometheus:prometheus-metrics-exposition-textformats",
"moduleVersion": "1.3.8", "moduleVersion": "1.3.10",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.prometheus:prometheus-metrics-model", "moduleName": "io.prometheus:prometheus-metrics-model",
"moduleVersion": "1.3.8", "moduleVersion": "1.3.10",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{ {
"moduleName": "io.prometheus:prometheus-metrics-tracer-common", "moduleName": "io.prometheus:prometheus-metrics-tracer-common",
"moduleVersion": "1.3.8", "moduleVersion": "1.3.10",
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@ -925,7 +912,7 @@
{ {
"moduleName": "org.apache.tomcat.embed:tomcat-embed-el", "moduleName": "org.apache.tomcat.embed:tomcat-embed-el",
"moduleUrl": "https://tomcat.apache.org/", "moduleUrl": "https://tomcat.apache.org/",
"moduleVersion": "10.1.42", "moduleVersion": "10.1.43",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
@ -1034,182 +1021,182 @@
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet", "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp", "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common", "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-alpn-client", "moduleName": "org.eclipse.jetty:jetty-alpn-client",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-client", "moduleName": "org.eclipse.jetty:jetty-client",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-ee", "moduleName": "org.eclipse.jetty:jetty-ee",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-http", "moduleName": "org.eclipse.jetty:jetty-http",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-io", "moduleName": "org.eclipse.jetty:jetty-io",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-plus", "moduleName": "org.eclipse.jetty:jetty-plus",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-security", "moduleName": "org.eclipse.jetty:jetty-security",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-server", "moduleName": "org.eclipse.jetty:jetty-server",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-session", "moduleName": "org.eclipse.jetty:jetty-session",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-util", "moduleName": "org.eclipse.jetty:jetty-util",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
{ {
"moduleName": "org.eclipse.jetty:jetty-xml", "moduleName": "org.eclipse.jetty:jetty-xml",
"moduleUrl": "https://jetty.org/", "moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.22", "moduleVersion": "12.0.23",
"moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
}, },
@ -1251,7 +1238,7 @@
{ {
"moduleName": "org.hibernate.orm:hibernate-core", "moduleName": "org.hibernate.orm:hibernate-core",
"moduleUrl": "https://www.hibernate.org/orm/6.6", "moduleUrl": "https://www.hibernate.org/orm/6.6",
"moduleVersion": "6.6.18.Final", "moduleVersion": "6.6.22.Final",
"moduleLicense": "GNU Library General Public License v2.1 or later", "moduleLicense": "GNU Library General Public License v2.1 or later",
"moduleLicenseUrl": "https://www.opensource.org/licenses/LGPL-2.1" "moduleLicenseUrl": "https://www.opensource.org/licenses/LGPL-2.1"
}, },
@ -1468,196 +1455,196 @@
{ {
"moduleName": "org.springframework.boot:spring-boot", "moduleName": "org.springframework.boot:spring-boot",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-actuator", "moduleName": "org.springframework.boot:spring-boot-actuator",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-actuator-autoconfigure", "moduleName": "org.springframework.boot:spring-boot-actuator-autoconfigure",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-autoconfigure", "moduleName": "org.springframework.boot:spring-boot-autoconfigure",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter", "moduleName": "org.springframework.boot:spring-boot-starter",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-actuator", "moduleName": "org.springframework.boot:spring-boot-starter-actuator",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-aop", "moduleName": "org.springframework.boot:spring-boot-starter-aop",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-data-jpa", "moduleName": "org.springframework.boot:spring-boot-starter-data-jpa",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-jdbc", "moduleName": "org.springframework.boot:spring-boot-starter-jdbc",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-jetty", "moduleName": "org.springframework.boot:spring-boot-starter-jetty",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-json", "moduleName": "org.springframework.boot:spring-boot-starter-json",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-logging", "moduleName": "org.springframework.boot:spring-boot-starter-logging",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-mail", "moduleName": "org.springframework.boot:spring-boot-starter-mail",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client", "moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-security", "moduleName": "org.springframework.boot:spring-boot-starter-security",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-thymeleaf", "moduleName": "org.springframework.boot:spring-boot-starter-thymeleaf",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-validation", "moduleName": "org.springframework.boot:spring-boot-starter-validation",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.boot:spring-boot-starter-web", "moduleName": "org.springframework.boot:spring-boot-starter-web",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.3", "moduleVersion": "3.5.4",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.data:spring-data-commons", "moduleName": "org.springframework.data:spring-data-commons",
"moduleUrl": "https://spring.io/projects/spring-data", "moduleUrl": "https://spring.io/projects/spring-data",
"moduleVersion": "3.5.1", "moduleVersion": "3.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.data:spring-data-jpa", "moduleName": "org.springframework.data:spring-data-jpa",
"moduleUrl": "https://projects.spring.io/spring-data-jpa", "moduleUrl": "https://projects.spring.io/spring-data-jpa",
"moduleVersion": "3.5.1", "moduleVersion": "3.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-config", "moduleName": "org.springframework.security:spring-security-config",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.1", "moduleVersion": "6.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-core", "moduleName": "org.springframework.security:spring-security-core",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.1", "moduleVersion": "6.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-crypto", "moduleName": "org.springframework.security:spring-security-crypto",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.1", "moduleVersion": "6.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-oauth2-client", "moduleName": "org.springframework.security:spring-security-oauth2-client",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.1", "moduleVersion": "6.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-oauth2-core", "moduleName": "org.springframework.security:spring-security-oauth2-core",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.1", "moduleVersion": "6.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-oauth2-jose", "moduleName": "org.springframework.security:spring-security-oauth2-jose",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.1", "moduleVersion": "6.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-saml2-service-provider", "moduleName": "org.springframework.security:spring-security-saml2-service-provider",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.1", "moduleVersion": "6.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework.security:spring-security-web", "moduleName": "org.springframework.security:spring-security-web",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.1", "moduleVersion": "6.5.2",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
@ -1671,91 +1658,91 @@
{ {
"moduleName": "org.springframework:spring-aop", "moduleName": "org.springframework:spring-aop",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-aspects", "moduleName": "org.springframework:spring-aspects",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-beans", "moduleName": "org.springframework:spring-beans",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-context", "moduleName": "org.springframework:spring-context",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-context-support", "moduleName": "org.springframework:spring-context-support",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-core", "moduleName": "org.springframework:spring-core",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-expression", "moduleName": "org.springframework:spring-expression",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-jcl", "moduleName": "org.springframework:spring-jcl",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-jdbc", "moduleName": "org.springframework:spring-jdbc",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-orm", "moduleName": "org.springframework:spring-orm",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-tx", "moduleName": "org.springframework:spring-tx",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-web", "moduleName": "org.springframework:spring-web",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },
{ {
"moduleName": "org.springframework:spring-webmvc", "moduleName": "org.springframework:spring-webmvc",
"moduleUrl": "https://github.com/spring-projects/spring-framework", "moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.8", "moduleVersion": "6.2.9",
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
}, },

View File

@ -1,6 +1,6 @@
/* Main bookmark container styles */ /* Main bookmark container styles */
.bookmark-editor { .bookmark-editor {
margin-top: 20px; margin-bottom: 20px;
padding: 20px; padding: 20px;
border: 1px solid var(--border-color, #ced4da); border: 1px solid var(--border-color, #ced4da);
border-radius: 0.25rem; border-radius: 0.25rem;

View File

@ -36,14 +36,11 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class EditTableOfContentsControllerTest { class EditTableOfContentsControllerTest {
@Mock @Mock private CustomPDFDocumentFactory pdfDocumentFactory;
private CustomPDFDocumentFactory pdfDocumentFactory;
@Mock @Mock private ObjectMapper objectMapper;
private ObjectMapper objectMapper;
@InjectMocks @InjectMocks private EditTableOfContentsController editTableOfContentsController;
private EditTableOfContentsController editTableOfContentsController;
private MockMultipartFile mockFile; private MockMultipartFile mockFile;
private PDDocument mockDocument; private PDDocument mockDocument;
@ -56,7 +53,9 @@ class EditTableOfContentsControllerTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
mockFile = new MockMultipartFile("file", "test.pdf", "application/pdf", "PDF content".getBytes()); mockFile =
new MockMultipartFile(
"file", "test.pdf", "application/pdf", "PDF content".getBytes());
mockDocument = mock(PDDocument.class); mockDocument = mock(PDDocument.class);
mockCatalog = mock(PDDocumentCatalog.class); mockCatalog = mock(PDDocumentCatalog.class);
mockPages = mock(PDPageTree.class); mockPages = mock(PDPageTree.class);
@ -149,7 +148,8 @@ class EditTableOfContentsControllerTest {
assertEquals(1, parentBookmark.get("pageNumber")); assertEquals(1, parentBookmark.get("pageNumber"));
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Map<String, Object>> children = (List<Map<String, Object>>) parentBookmark.get("children"); List<Map<String, Object>> children =
(List<Map<String, Object>>) parentBookmark.get("children");
assertEquals(1, children.size()); assertEquals(1, children.size());
Map<String, Object> childBookmark = children.get(0); Map<String, Object> childBookmark = children.get(0);
@ -202,17 +202,21 @@ class EditTableOfContentsControllerTest {
bookmarks.add(bookmark); bookmarks.add(bookmark);
when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDocument); when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDocument);
when(objectMapper.readValue(eq(request.getBookmarkData()), any(TypeReference.class))).thenReturn(bookmarks); when(objectMapper.readValue(eq(request.getBookmarkData()), any(TypeReference.class)))
.thenReturn(bookmarks);
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
when(mockDocument.getNumberOfPages()).thenReturn(5); when(mockDocument.getNumberOfPages()).thenReturn(5);
when(mockDocument.getPage(0)).thenReturn(mockPage1); when(mockDocument.getPage(0)).thenReturn(mockPage1);
// Mock saving behavior // Mock saving behavior
doAnswer(invocation -> { doAnswer(
invocation -> {
ByteArrayOutputStream baos = invocation.getArgument(0); ByteArrayOutputStream baos = invocation.getArgument(0);
baos.write("mocked pdf content".getBytes()); baos.write("mocked pdf content".getBytes());
return null; return null;
}).when(mockDocument).save(any(ByteArrayOutputStream.class)); })
.when(mockDocument)
.save(any(ByteArrayOutputStream.class));
// When // When
ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request); ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request);
@ -221,7 +225,8 @@ class EditTableOfContentsControllerTest {
assertNotNull(result); assertNotNull(result);
assertNotNull(result.getBody()); assertNotNull(result.getBody());
ArgumentCaptor<PDDocumentOutline> outlineCaptor = ArgumentCaptor.forClass(PDDocumentOutline.class); ArgumentCaptor<PDDocumentOutline> outlineCaptor =
ArgumentCaptor.forClass(PDDocumentOutline.class);
verify(mockCatalog).setDocumentOutline(outlineCaptor.capture()); verify(mockCatalog).setDocumentOutline(outlineCaptor.capture());
PDDocumentOutline capturedOutline = outlineCaptor.getValue(); PDDocumentOutline capturedOutline = outlineCaptor.getValue();
@ -236,7 +241,8 @@ class EditTableOfContentsControllerTest {
EditTableOfContentsRequest request = new EditTableOfContentsRequest(); EditTableOfContentsRequest request = new EditTableOfContentsRequest();
request.setFileInput(mockFile); request.setFileInput(mockFile);
String bookmarkJson = "[{\"title\":\"Chapter 1\",\"pageNumber\":1,\"children\":[{\"title\":\"Section 1.1\",\"pageNumber\":2,\"children\":[]}]}]"; String bookmarkJson =
"[{\"title\":\"Chapter 1\",\"pageNumber\":1,\"children\":[{\"title\":\"Section 1.1\",\"pageNumber\":2,\"children\":[]}]}]";
request.setBookmarkData(bookmarkJson); request.setBookmarkData(bookmarkJson);
List<BookmarkItem> bookmarks = new ArrayList<>(); List<BookmarkItem> bookmarks = new ArrayList<>();
@ -255,17 +261,21 @@ class EditTableOfContentsControllerTest {
bookmarks.add(parentBookmark); bookmarks.add(parentBookmark);
when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDocument); when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDocument);
when(objectMapper.readValue(eq(bookmarkJson), any(TypeReference.class))).thenReturn(bookmarks); when(objectMapper.readValue(eq(bookmarkJson), any(TypeReference.class)))
.thenReturn(bookmarks);
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
when(mockDocument.getNumberOfPages()).thenReturn(5); when(mockDocument.getNumberOfPages()).thenReturn(5);
when(mockDocument.getPage(0)).thenReturn(mockPage1); when(mockDocument.getPage(0)).thenReturn(mockPage1);
when(mockDocument.getPage(1)).thenReturn(mockPage2); when(mockDocument.getPage(1)).thenReturn(mockPage2);
doAnswer(invocation -> { doAnswer(
invocation -> {
ByteArrayOutputStream baos = invocation.getArgument(0); ByteArrayOutputStream baos = invocation.getArgument(0);
baos.write("mocked pdf content".getBytes()); baos.write("mocked pdf content".getBytes());
return null; return null;
}).when(mockDocument).save(any(ByteArrayOutputStream.class)); })
.when(mockDocument)
.save(any(ByteArrayOutputStream.class));
// When // When
ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request); ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request);
@ -281,7 +291,8 @@ class EditTableOfContentsControllerTest {
// Given // Given
EditTableOfContentsRequest request = new EditTableOfContentsRequest(); EditTableOfContentsRequest request = new EditTableOfContentsRequest();
request.setFileInput(mockFile); request.setFileInput(mockFile);
request.setBookmarkData("[{\"title\":\"Chapter 1\",\"pageNumber\":-5,\"children\":[]},{\"title\":\"Chapter 2\",\"pageNumber\":100,\"children\":[]}]"); request.setBookmarkData(
"[{\"title\":\"Chapter 1\",\"pageNumber\":-5,\"children\":[]},{\"title\":\"Chapter 2\",\"pageNumber\":100,\"children\":[]}]");
List<BookmarkItem> bookmarks = new ArrayList<>(); List<BookmarkItem> bookmarks = new ArrayList<>();
@ -299,17 +310,21 @@ class EditTableOfContentsControllerTest {
bookmarks.add(bookmark2); bookmarks.add(bookmark2);
when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDocument); when(pdfDocumentFactory.load(mockFile)).thenReturn(mockDocument);
when(objectMapper.readValue(eq(request.getBookmarkData()), any(TypeReference.class))).thenReturn(bookmarks); when(objectMapper.readValue(eq(request.getBookmarkData()), any(TypeReference.class)))
.thenReturn(bookmarks);
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
when(mockDocument.getNumberOfPages()).thenReturn(5); when(mockDocument.getNumberOfPages()).thenReturn(5);
when(mockDocument.getPage(0)).thenReturn(mockPage1); // For negative page number when(mockDocument.getPage(0)).thenReturn(mockPage1); // For negative page number
when(mockDocument.getPage(4)).thenReturn(mockPage2); // For page number exceeding bounds when(mockDocument.getPage(4)).thenReturn(mockPage2); // For page number exceeding bounds
doAnswer(invocation -> { doAnswer(
invocation -> {
ByteArrayOutputStream baos = invocation.getArgument(0); ByteArrayOutputStream baos = invocation.getArgument(0);
baos.write("mocked pdf content".getBytes()); baos.write("mocked pdf content".getBytes());
return null; return null;
}).when(mockDocument).save(any(ByteArrayOutputStream.class)); })
.when(mockDocument)
.save(any(ByteArrayOutputStream.class));
// When // When
ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request); ResponseEntity<byte[]> result = editTableOfContentsController.editTableOfContents(request);
@ -332,16 +347,20 @@ class EditTableOfContentsControllerTest {
when(mockDocument.getPage(2)).thenReturn(mockPage1); // 0-indexed when(mockDocument.getPage(2)).thenReturn(mockPage1); // 0-indexed
// When // When
Method createOutlineItemMethod = EditTableOfContentsController.class.getDeclaredMethod("createOutlineItem", PDDocument.class, BookmarkItem.class); Method createOutlineItemMethod =
EditTableOfContentsController.class.getDeclaredMethod(
"createOutlineItem", PDDocument.class, BookmarkItem.class);
createOutlineItemMethod.setAccessible(true); createOutlineItemMethod.setAccessible(true);
PDOutlineItem result = (PDOutlineItem) createOutlineItemMethod.invoke(editTableOfContentsController, mockDocument, bookmark); PDOutlineItem result =
(PDOutlineItem)
createOutlineItemMethod.invoke(
editTableOfContentsController, mockDocument, bookmark);
// Then // Then
assertNotNull(result); assertNotNull(result);
verify(mockDocument).getPage(2); verify(mockDocument).getPage(2);
} }
@Test @Test
void testBookmarkItem_GettersAndSetters() { void testBookmarkItem_GettersAndSetters() {
// Given // Given
@ -365,18 +384,24 @@ class EditTableOfContentsControllerTest {
EditTableOfContentsRequest request = new EditTableOfContentsRequest(); EditTableOfContentsRequest request = new EditTableOfContentsRequest();
request.setFileInput(mockFile); request.setFileInput(mockFile);
when(pdfDocumentFactory.load(mockFile)).thenThrow(new RuntimeException("Failed to load PDF")); when(pdfDocumentFactory.load(mockFile))
.thenThrow(new RuntimeException("Failed to load PDF"));
// When & Then // When & Then
assertThrows(RuntimeException.class, () -> editTableOfContentsController.editTableOfContents(request)); assertThrows(
RuntimeException.class,
() -> editTableOfContentsController.editTableOfContents(request));
} }
@Test @Test
void testExtractBookmarks_IOExceptionDuringLoad_ThrowsException() throws Exception { void testExtractBookmarks_IOExceptionDuringLoad_ThrowsException() throws Exception {
// Given // Given
when(pdfDocumentFactory.load(mockFile)).thenThrow(new RuntimeException("Failed to load PDF")); when(pdfDocumentFactory.load(mockFile))
.thenThrow(new RuntimeException("Failed to load PDF"));
// When & Then // When & Then
assertThrows(RuntimeException.class, () -> editTableOfContentsController.extractBookmarks(mockFile)); assertThrows(
RuntimeException.class,
() -> editTableOfContentsController.extractBookmarks(mockFile));
} }
} }

View File

@ -29,11 +29,9 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class MergeControllerTest { class MergeControllerTest {
@Mock @Mock private CustomPDFDocumentFactory pdfDocumentFactory;
private CustomPDFDocumentFactory pdfDocumentFactory;
@InjectMocks @InjectMocks private MergeController mergeController;
private MergeController mergeController;
private MockMultipartFile mockFile1; private MockMultipartFile mockFile1;
private MockMultipartFile mockFile2; private MockMultipartFile mockFile2;
@ -47,9 +45,15 @@ class MergeControllerTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
mockFile1 = new MockMultipartFile("file1", "document1.pdf", "application/pdf", "PDF content 1".getBytes()); mockFile1 =
mockFile2 = new MockMultipartFile("file2", "document2.pdf", "application/pdf", "PDF content 2".getBytes()); new MockMultipartFile(
mockFile3 = new MockMultipartFile("file3", "chapter3.pdf", "application/pdf", "PDF content 3".getBytes()); "file1", "document1.pdf", "application/pdf", "PDF content 1".getBytes());
mockFile2 =
new MockMultipartFile(
"file2", "document2.pdf", "application/pdf", "PDF content 2".getBytes());
mockFile3 =
new MockMultipartFile(
"file3", "chapter3.pdf", "application/pdf", "PDF content 3".getBytes());
mockDocument = mock(PDDocument.class); mockDocument = mock(PDDocument.class);
mockMergedDocument = mock(PDDocument.class); mockMergedDocument = mock(PDDocument.class);
@ -85,12 +89,15 @@ class MergeControllerTest {
when(doc3.getNumberOfPages()).thenReturn(2); when(doc3.getNumberOfPages()).thenReturn(2);
// When // When
Method addTableOfContentsMethod = MergeController.class.getDeclaredMethod("addTableOfContents", PDDocument.class, MultipartFile[].class); Method addTableOfContentsMethod =
MergeController.class.getDeclaredMethod(
"addTableOfContents", PDDocument.class, MultipartFile[].class);
addTableOfContentsMethod.setAccessible(true); addTableOfContentsMethod.setAccessible(true);
addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files); addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files);
// Then // Then
ArgumentCaptor<PDDocumentOutline> outlineCaptor = ArgumentCaptor.forClass(PDDocumentOutline.class); ArgumentCaptor<PDDocumentOutline> outlineCaptor =
ArgumentCaptor.forClass(PDDocumentOutline.class);
verify(mockCatalog).setDocumentOutline(outlineCaptor.capture()); verify(mockCatalog).setDocumentOutline(outlineCaptor.capture());
PDDocumentOutline capturedOutline = outlineCaptor.getValue(); PDDocumentOutline capturedOutline = outlineCaptor.getValue();
@ -121,7 +128,9 @@ class MergeControllerTest {
when(doc1.getNumberOfPages()).thenReturn(3); when(doc1.getNumberOfPages()).thenReturn(3);
// When // When
Method addTableOfContentsMethod = MergeController.class.getDeclaredMethod("addTableOfContents", PDDocument.class, MultipartFile[].class); Method addTableOfContentsMethod =
MergeController.class.getDeclaredMethod(
"addTableOfContents", PDDocument.class, MultipartFile[].class);
addTableOfContentsMethod.setAccessible(true); addTableOfContentsMethod.setAccessible(true);
addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files); addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files);
@ -138,7 +147,9 @@ class MergeControllerTest {
when(mockMergedDocument.getDocumentCatalog()).thenReturn(mockCatalog); when(mockMergedDocument.getDocumentCatalog()).thenReturn(mockCatalog);
// When // When
Method addTableOfContentsMethod = MergeController.class.getDeclaredMethod("addTableOfContents", PDDocument.class, MultipartFile[].class); Method addTableOfContentsMethod =
MergeController.class.getDeclaredMethod(
"addTableOfContents", PDDocument.class, MultipartFile[].class);
addTableOfContentsMethod.setAccessible(true); addTableOfContentsMethod.setAccessible(true);
addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files); addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files);
@ -155,7 +166,8 @@ class MergeControllerTest {
when(mockMergedDocument.getDocumentCatalog()).thenReturn(mockCatalog); when(mockMergedDocument.getDocumentCatalog()).thenReturn(mockCatalog);
when(mockMergedDocument.getNumberOfPages()).thenReturn(4); when(mockMergedDocument.getNumberOfPages()).thenReturn(4);
when(mockMergedDocument.getPage(anyInt())).thenReturn(mockPage1); // Use anyInt() to avoid stubbing conflicts when(mockMergedDocument.getPage(anyInt()))
.thenReturn(mockPage1); // Use anyInt() to avoid stubbing conflicts
// First document loads successfully // First document loads successfully
PDDocument doc1 = mock(PDDocument.class); PDDocument doc1 = mock(PDDocument.class);
@ -163,16 +175,18 @@ class MergeControllerTest {
when(doc1.getNumberOfPages()).thenReturn(2); when(doc1.getNumberOfPages()).thenReturn(2);
// Second document throws IOException // Second document throws IOException
when(pdfDocumentFactory.load(mockFile2)).thenThrow(new IOException("Failed to load document")); when(pdfDocumentFactory.load(mockFile2))
.thenThrow(new IOException("Failed to load document"));
// When // When
Method addTableOfContentsMethod = MergeController.class.getDeclaredMethod("addTableOfContents", PDDocument.class, MultipartFile[].class); Method addTableOfContentsMethod =
MergeController.class.getDeclaredMethod(
"addTableOfContents", PDDocument.class, MultipartFile[].class);
addTableOfContentsMethod.setAccessible(true); addTableOfContentsMethod.setAccessible(true);
// Should not throw exception // Should not throw exception
assertDoesNotThrow(() -> assertDoesNotThrow(
addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files) () -> addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files));
);
// Then // Then
verify(mockCatalog).setDocumentOutline(any(PDDocumentOutline.class)); verify(mockCatalog).setDocumentOutline(any(PDDocumentOutline.class));
@ -184,7 +198,9 @@ class MergeControllerTest {
@Test @Test
void testAddTableOfContents_FilenameWithoutExtension_UsesFullName() throws Exception { void testAddTableOfContents_FilenameWithoutExtension_UsesFullName() throws Exception {
// Given // Given
MockMultipartFile fileWithoutExtension = new MockMultipartFile("file", "document_no_ext", "application/pdf", "PDF content".getBytes()); MockMultipartFile fileWithoutExtension =
new MockMultipartFile(
"file", "document_no_ext", "application/pdf", "PDF content".getBytes());
MultipartFile[] files = {fileWithoutExtension}; MultipartFile[] files = {fileWithoutExtension};
when(mockMergedDocument.getDocumentCatalog()).thenReturn(mockCatalog); when(mockMergedDocument.getDocumentCatalog()).thenReturn(mockCatalog);
@ -196,7 +212,9 @@ class MergeControllerTest {
when(doc.getNumberOfPages()).thenReturn(1); when(doc.getNumberOfPages()).thenReturn(1);
// When // When
Method addTableOfContentsMethod = MergeController.class.getDeclaredMethod("addTableOfContents", PDDocument.class, MultipartFile[].class); Method addTableOfContentsMethod =
MergeController.class.getDeclaredMethod(
"addTableOfContents", PDDocument.class, MultipartFile[].class);
addTableOfContentsMethod.setAccessible(true); addTableOfContentsMethod.setAccessible(true);
addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files); addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files);
@ -218,13 +236,14 @@ class MergeControllerTest {
when(doc1.getNumberOfPages()).thenReturn(3); when(doc1.getNumberOfPages()).thenReturn(3);
// When // When
Method addTableOfContentsMethod = MergeController.class.getDeclaredMethod("addTableOfContents", PDDocument.class, MultipartFile[].class); Method addTableOfContentsMethod =
MergeController.class.getDeclaredMethod(
"addTableOfContents", PDDocument.class, MultipartFile[].class);
addTableOfContentsMethod.setAccessible(true); addTableOfContentsMethod.setAccessible(true);
// Should not throw exception // Should not throw exception
assertDoesNotThrow(() -> assertDoesNotThrow(
addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files) () -> addTableOfContentsMethod.invoke(mergeController, mockMergedDocument, files));
);
// Then // Then
verify(mockCatalog).setDocumentOutline(any(PDDocumentOutline.class)); verify(mockCatalog).setDocumentOutline(any(PDDocumentOutline.class));
@ -275,5 +294,4 @@ class MergeControllerTest {
assertEquals(mockMergedDocument, result); assertEquals(mockMergedDocument, result);
verify(mockMergedDocument, never()).addPage(any(PDPage.class)); verify(mockMergedDocument, never()).addPage(any(PDPage.class));
} }
} }

View File

@ -4,8 +4,6 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*; import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import org.mockito.MockedStatic;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -15,6 +13,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -29,14 +28,11 @@ import stirling.software.common.util.WebResponseUtils;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class AttachmentControllerTest { class AttachmentControllerTest {
@Mock @Mock private CustomPDFDocumentFactory pdfDocumentFactory;
private CustomPDFDocumentFactory pdfDocumentFactory;
@Mock @Mock private AttachmentServiceInterface pdfAttachmentService;
private AttachmentServiceInterface pdfAttachmentService;
@InjectMocks @InjectMocks private AttachmentController attachmentController;
private AttachmentController attachmentController;
private MockMultipartFile pdfFile; private MockMultipartFile pdfFile;
private MockMultipartFile attachment1; private MockMultipartFile attachment1;
@ -47,9 +43,15 @@ class AttachmentControllerTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
pdfFile = new MockMultipartFile("fileInput", "test.pdf", "application/pdf", "PDF content".getBytes()); pdfFile =
attachment1 = new MockMultipartFile("attachment1", "file1.txt", "text/plain", "File 1 content".getBytes()); new MockMultipartFile(
attachment2 = new MockMultipartFile("attachment2", "file2.jpg", "image/jpeg", "Image content".getBytes()); "fileInput", "test.pdf", "application/pdf", "PDF content".getBytes());
attachment1 =
new MockMultipartFile(
"attachment1", "file1.txt", "text/plain", "File 1 content".getBytes());
attachment2 =
new MockMultipartFile(
"attachment2", "file2.jpg", "image/jpeg", "Image content".getBytes());
request = new AddAttachmentRequest(); request = new AddAttachmentRequest();
mockDocument = mock(PDDocument.class); mockDocument = mock(PDDocument.class);
modifiedMockDocument = mock(PDDocument.class); modifiedMockDocument = mock(PDDocument.class);
@ -60,13 +62,21 @@ class AttachmentControllerTest {
List<MultipartFile> attachments = List.of(attachment1, attachment2); List<MultipartFile> attachments = List.of(attachment1, attachment2);
request.setAttachments(attachments); request.setAttachments(attachments);
request.setFileInput(pdfFile); request.setFileInput(pdfFile);
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("modified PDF content".getBytes()); ResponseEntity<byte[]> expectedResponse =
ResponseEntity.ok("modified PDF content".getBytes());
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument); when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
when(pdfAttachmentService.addAttachment(mockDocument, attachments)).thenReturn(modifiedMockDocument); when(pdfAttachmentService.addAttachment(mockDocument, attachments))
.thenReturn(modifiedMockDocument);
try (MockedStatic<WebResponseUtils> mockedWebResponseUtils = mockStatic(WebResponseUtils.class)) { try (MockedStatic<WebResponseUtils> mockedWebResponseUtils =
mockedWebResponseUtils.when(() -> WebResponseUtils.pdfDocToWebResponse(eq(modifiedMockDocument), eq("test_with_attachments.pdf"))) mockStatic(WebResponseUtils.class)) {
mockedWebResponseUtils
.when(
() ->
WebResponseUtils.pdfDocToWebResponse(
eq(modifiedMockDocument),
eq("test_with_attachments.pdf")))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<byte[]> response = attachmentController.addAttachments(request); ResponseEntity<byte[]> response = attachmentController.addAttachments(request);
@ -84,13 +94,21 @@ class AttachmentControllerTest {
List<MultipartFile> attachments = List.of(attachment1); List<MultipartFile> attachments = List.of(attachment1);
request.setAttachments(attachments); request.setAttachments(attachments);
request.setFileInput(pdfFile); request.setFileInput(pdfFile);
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("modified PDF content".getBytes()); ResponseEntity<byte[]> expectedResponse =
ResponseEntity.ok("modified PDF content".getBytes());
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument); when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
when(pdfAttachmentService.addAttachment(mockDocument, attachments)).thenReturn(modifiedMockDocument); when(pdfAttachmentService.addAttachment(mockDocument, attachments))
.thenReturn(modifiedMockDocument);
try (MockedStatic<WebResponseUtils> mockedWebResponseUtils = mockStatic(WebResponseUtils.class)) { try (MockedStatic<WebResponseUtils> mockedWebResponseUtils =
mockedWebResponseUtils.when(() -> WebResponseUtils.pdfDocToWebResponse(eq(modifiedMockDocument), eq("test_with_attachments.pdf"))) mockStatic(WebResponseUtils.class)) {
mockedWebResponseUtils
.when(
() ->
WebResponseUtils.pdfDocToWebResponse(
eq(modifiedMockDocument),
eq("test_with_attachments.pdf")))
.thenReturn(expectedResponse); .thenReturn(expectedResponse);
ResponseEntity<byte[]> response = attachmentController.addAttachments(request); ResponseEntity<byte[]> response = attachmentController.addAttachments(request);

View File

@ -20,11 +20,11 @@ import org.springframework.http.ResponseEntity;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import stirling.software.common.service.UserServiceInterface;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation; import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.model.PipelineResult; import stirling.software.SPDF.model.PipelineResult;
import stirling.software.SPDF.service.ApiDocService; import stirling.software.SPDF.service.ApiDocService;
import stirling.software.common.service.UserServiceInterface;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class PipelineProcessorTest { class PipelineProcessorTest {
@ -45,12 +45,13 @@ class PipelineProcessorTest {
@Test @Test
void runPipelineWithFilterSetsFlag() throws Exception { void runPipelineWithFilterSetsFlag() throws Exception {
PipelineOperation op = new PipelineOperation(); PipelineOperation op = new PipelineOperation();
op.setOperation("filter-page-count"); op.setOperation("/api/v1/filter/filter-page-count");
op.setParameters(Map.of()); op.setParameters(Map.of());
PipelineConfig config = new PipelineConfig(); PipelineConfig config = new PipelineConfig();
config.setOperations(List.of(op)); config.setOperations(List.of(op));
Resource file = new ByteArrayResource("data".getBytes()) { Resource file =
new ByteArrayResource("data".getBytes()) {
@Override @Override
public String getFilename() { public String getFilename() {
return "test.pdf"; return "test.pdf";
@ -59,9 +60,11 @@ class PipelineProcessorTest {
List<Resource> files = List.of(file); List<Resource> files = List.of(file);
when(apiDocService.isMultiInput("filter-page-count")).thenReturn(false); when(apiDocService.isMultiInput("/api/v1/filter/filter-page-count")).thenReturn(false);
when(apiDocService.getExtensionTypes(false, "filter-page-count")) when(apiDocService.getExtensionTypes(false, "/api/v1/filter/filter-page-count"))
.thenReturn(List.of("pdf")); .thenReturn(List.of("pdf"));
when(apiDocService.isValidOperation(eq("/api/v1/filter/filter-page-count"), anyMap()))
.thenReturn(true);
doReturn(new ResponseEntity<>(new byte[0], HttpStatus.OK)) doReturn(new ResponseEntity<>(new byte[0], HttpStatus.OK))
.when(pipelineProcessor) .when(pipelineProcessor)

View File

@ -1,15 +1,17 @@
package stirling.software.SPDF.service; package stirling.software.SPDF.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class AttachmentServiceTest { class AttachmentServiceTest {
@ -27,8 +29,8 @@ class AttachmentServiceTest {
var attachments = List.of(mock(MultipartFile.class)); var attachments = List.of(mock(MultipartFile.class));
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt"); when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
when(attachments.get(0).getInputStream()).thenReturn( when(attachments.get(0).getInputStream())
new ByteArrayInputStream("Test content".getBytes())); .thenReturn(new ByteArrayInputStream("Test content".getBytes()));
when(attachments.get(0).getSize()).thenReturn(12L); when(attachments.get(0).getSize()).thenReturn(12L);
when(attachments.get(0).getContentType()).thenReturn("text/plain"); when(attachments.get(0).getContentType()).thenReturn("text/plain");
@ -49,14 +51,14 @@ class AttachmentServiceTest {
var attachments = List.of(attachment1, attachment2); var attachments = List.of(attachment1, attachment2);
when(attachment1.getOriginalFilename()).thenReturn("document.pdf"); when(attachment1.getOriginalFilename()).thenReturn("document.pdf");
when(attachment1.getInputStream()).thenReturn( when(attachment1.getInputStream())
new ByteArrayInputStream("PDF content".getBytes())); .thenReturn(new ByteArrayInputStream("PDF content".getBytes()));
when(attachment1.getSize()).thenReturn(15L); when(attachment1.getSize()).thenReturn(15L);
when(attachment1.getContentType()).thenReturn("application/pdf"); when(attachment1.getContentType()).thenReturn("application/pdf");
when(attachment2.getOriginalFilename()).thenReturn("image.jpg"); when(attachment2.getOriginalFilename()).thenReturn("image.jpg");
when(attachment2.getInputStream()).thenReturn( when(attachment2.getInputStream())
new ByteArrayInputStream("Image content".getBytes())); .thenReturn(new ByteArrayInputStream("Image content".getBytes()));
when(attachment2.getSize()).thenReturn(20L); when(attachment2.getSize()).thenReturn(20L);
when(attachment2.getContentType()).thenReturn("image/jpeg"); when(attachment2.getContentType()).thenReturn("image/jpeg");
@ -74,8 +76,8 @@ class AttachmentServiceTest {
var attachments = List.of(mock(MultipartFile.class)); var attachments = List.of(mock(MultipartFile.class));
when(attachments.get(0).getOriginalFilename()).thenReturn("image.jpg"); when(attachments.get(0).getOriginalFilename()).thenReturn("image.jpg");
when(attachments.get(0).getInputStream()).thenReturn( when(attachments.get(0).getInputStream())
new ByteArrayInputStream("Image content".getBytes())); .thenReturn(new ByteArrayInputStream("Image content".getBytes()));
when(attachments.get(0).getSize()).thenReturn(25L); when(attachments.get(0).getSize()).thenReturn(25L);
when(attachments.get(0).getContentType()).thenReturn(""); when(attachments.get(0).getContentType()).thenReturn("");

View File

@ -17,8 +17,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.SPDF.model.SignatureFile; import stirling.software.SPDF.model.SignatureFile;
import stirling.software.common.configuration.InstallationPathConfig;
class SignatureServiceTest { class SignatureServiceTest {

View File

@ -12,36 +12,28 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockHttpSession;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import stirling.software.common.model.job.JobResult; import stirling.software.common.model.job.JobResult;
import stirling.software.common.model.job.JobStats;
import stirling.software.common.model.job.ResultFile;
import stirling.software.common.service.FileStorage; import stirling.software.common.service.FileStorage;
import stirling.software.common.service.JobQueue; import stirling.software.common.service.JobQueue;
import stirling.software.common.service.TaskManager; import stirling.software.common.service.TaskManager;
class JobControllerTest { class JobControllerTest {
@Mock @Mock private TaskManager taskManager;
private TaskManager taskManager;
@Mock @Mock private FileStorage fileStorage;
private FileStorage fileStorage;
@Mock @Mock private JobQueue jobQueue;
private JobQueue jobQueue;
@Mock @Mock private HttpServletRequest request;
private HttpServletRequest request;
private MockHttpSession session; private MockHttpSession session;
@InjectMocks @InjectMocks private JobController controller;
private JobController controller;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
@ -139,7 +131,8 @@ class JobControllerTest {
JobResult mockResult = new JobResult(); JobResult mockResult = new JobResult();
mockResult.setJobId(jobId); mockResult.setJobId(jobId);
mockResult.completeWithSingleFile(fileId, originalFileName, contentType, fileContent.length); mockResult.completeWithSingleFile(
fileId, originalFileName, contentType, fileContent.length);
when(taskManager.getJobResult(jobId)).thenReturn(mockResult); when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
when(fileStorage.retrieveBytes(fileId)).thenReturn(fileContent); when(fileStorage.retrieveBytes(fileId)).thenReturn(fileContent);
@ -150,7 +143,8 @@ class JobControllerTest {
// Assert // Assert
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(contentType, response.getHeaders().getFirst("Content-Type")); assertEquals(contentType, response.getHeaders().getFirst("Content-Type"));
assertTrue(response.getHeaders().getFirst("Content-Disposition").contains(originalFileName)); assertTrue(
response.getHeaders().getFirst("Content-Disposition").contains(originalFileName));
assertEquals(fileContent, response.getBody()); assertEquals(fileContent, response.getBody());
} }

View File

@ -6,7 +6,7 @@ bootRun {
} }
spotless { spotless {
java { java {
target sourceSets.main.allJava target 'src/**/java/**/*.java'
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false) googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling") importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
@ -15,6 +15,18 @@ spotless {
leadingTabsToSpaces() leadingTabsToSpaces()
endWithNewline() endWithNewline()
} }
yaml {
target '**/*.yml', '**/*.yaml'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
format 'gradle', {
target '**/gradle/*.gradle', '**/*.gradle'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
} }
dependencies { dependencies {
implementation project(':common') implementation project(':common')

View File

@ -1,17 +1,20 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import jakarta.servlet.http.HttpServletRequest; import static org.mockito.Mockito.*;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class CustomLogoutSuccessHandlerTest { class CustomLogoutSuccessHandlerTest {

View File

@ -1,6 +1,10 @@
package stirling.software.proprietary.security.configuration; package stirling.software.proprietary.security.configuration;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -8,10 +12,9 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class DatabaseConfigTest { class DatabaseConfigTest {

View File

@ -2,8 +2,10 @@ package stirling.software.proprietary.security.service;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import jakarta.mail.MessagingException; import static org.mockito.Mockito.mock;
import jakarta.mail.internet.MimeMessage; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@ -17,9 +19,6 @@ import jakarta.mail.internet.MimeMessage;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.proprietary.security.model.api.Email; import stirling.software.proprietary.security.model.api.Email;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class EmailServiceTest { public class EmailServiceTest {

View File

@ -1,6 +1,11 @@
package stirling.software.proprietary.security.service; package stirling.software.proprietary.security.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.util.Optional; import java.util.Optional;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@ -18,8 +23,7 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class TeamServiceTest { class TeamServiceTest {
@Mock @Mock private TeamRepository teamRepository;
private TeamRepository teamRepository;
@Mock @Mock
private OrganizationService organizationService; private OrganizationService organizationService;

View File

@ -1,8 +1,13 @@
package stirling.software.proprietary.security.service; package stirling.software.proprietary.security.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -11,10 +16,9 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.model.Team; import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.database.repository.AuthorityRepository; import stirling.software.proprietary.security.database.repository.AuthorityRepository;
import stirling.software.proprietary.security.database.repository.UserRepository; import stirling.software.proprietary.security.database.repository.UserRepository;
@ -22,42 +26,27 @@ import stirling.software.proprietary.security.model.AuthenticationType;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.repository.TeamRepository; import stirling.software.proprietary.security.repository.TeamRepository;
import stirling.software.proprietary.security.session.SessionPersistentRegistry; import stirling.software.proprietary.security.session.SessionPersistentRegistry;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class UserServiceTest { class UserServiceTest {
@Mock @Mock private UserRepository userRepository;
private UserRepository userRepository;
@Mock @Mock private TeamRepository teamRepository;
private TeamRepository teamRepository;
@Mock @Mock private AuthorityRepository authorityRepository;
private AuthorityRepository authorityRepository;
@Mock @Mock private PasswordEncoder passwordEncoder;
private PasswordEncoder passwordEncoder;
@Mock @Mock private MessageSource messageSource;
private MessageSource messageSource;
@Mock @Mock private SessionPersistentRegistry sessionPersistentRegistry;
private SessionPersistentRegistry sessionPersistentRegistry;
@Mock @Mock private DatabaseServiceInterface databaseService;
private DatabaseServiceInterface databaseService;
@Mock @Mock private ApplicationProperties.Security.OAUTH2 oauth2Properties;
private ApplicationProperties.Security.OAUTH2 oauth2Properties;
@InjectMocks @InjectMocks private UserService userService;
private UserService userService;
private Team mockTeam; private Team mockTeam;
private User mockUser; private User mockUser;
@ -146,10 +135,10 @@ class UserServiceTest {
AuthenticationType authType = AuthenticationType.WEB; AuthenticationType authType = AuthenticationType.WEB;
// When & Then // When & Then
IllegalArgumentException exception = assertThrows( IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> userService.saveUser(invalidUsername, authType) () -> userService.saveUser(invalidUsername, authType));
);
verify(userRepository, never()).save(any(User.class)); verify(userRepository, never()).save(any(User.class));
verify(databaseService, never()).exportDatabase(); verify(databaseService, never()).exportDatabase();
@ -221,10 +210,10 @@ class UserServiceTest {
AuthenticationType authType = AuthenticationType.WEB; AuthenticationType authType = AuthenticationType.WEB;
// When & Then // When & Then
IllegalArgumentException exception = assertThrows( IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> userService.saveUser(reservedUsername, authType) () -> userService.saveUser(reservedUsername, authType));
);
verify(userRepository, never()).save(any(User.class)); verify(userRepository, never()).save(any(User.class));
verify(databaseService, never()).exportDatabase(); verify(databaseService, never()).exportDatabase();
@ -237,10 +226,10 @@ class UserServiceTest {
AuthenticationType authType = AuthenticationType.WEB; AuthenticationType authType = AuthenticationType.WEB;
// When & Then // When & Then
IllegalArgumentException exception = assertThrows( IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> userService.saveUser(anonymousUsername, authType) () -> userService.saveUser(anonymousUsername, authType));
);
verify(userRepository, never()).save(any(User.class)); verify(userRepository, never()).save(any(User.class));
verify(databaseService, never()).exportDatabase(); verify(databaseService, never()).exportDatabase();
@ -313,5 +302,4 @@ class UserServiceTest {
verify(userRepository).save(any(User.class)); verify(userRepository).save(any(User.class));
verify(databaseService).exportDatabase(); verify(databaseService).exportDatabase();
} }
} }

View File

@ -2,10 +2,10 @@ plugins {
id "java" id "java"
id "jacoco" id "jacoco"
id "io.spring.dependency-management" version "1.1.7" id "io.spring.dependency-management" version "1.1.7"
id "org.springframework.boot" version "3.5.3" id "org.springframework.boot" version "3.5.4"
id "org.springdoc.openapi-gradle-plugin" version "1.9.0" id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
id "io.swagger.swaggerhub" version "1.3.2" id "io.swagger.swaggerhub" version "1.3.2"
id "edu.sc.seis.launch4j" version "3.0.6" id "edu.sc.seis.launch4j" version "3.0.7"
id "com.diffplug.spotless" version "7.2.1" id "com.diffplug.spotless" version "7.2.1"
id "com.github.jk1.dependency-license-report" version "2.9" id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3" //id "nebula.lint" version "19.0.3"
@ -21,7 +21,7 @@ import java.nio.file.Files
import java.time.Year import java.time.Year
ext { ext {
springBootVersion = "3.5.3" springBootVersion = "3.5.4"
pdfboxVersion = "3.0.5" pdfboxVersion = "3.0.5"
imageioVersion = "3.12.0" imageioVersion = "3.12.0"
lombokVersion = "1.18.38" lombokVersion = "1.18.38"
@ -29,7 +29,7 @@ ext {
springSecuritySamlVersion = "6.5.2" springSecuritySamlVersion = "6.5.2"
openSamlVersion = "4.3.2" openSamlVersion = "4.3.2"
commonmarkVersion = "0.25.0" commonmarkVersion = "0.25.0"
googleJavaFormatVersion = "1.27.0" googleJavaFormatVersion = "1.28.0"
tempJrePath = null tempJrePath = null
} }
@ -528,16 +528,14 @@ launch4j {
} }
spotless { spotless {
java { yaml {
target sourceSets.main.allJava target '*.yml', '*.yaml'
target project(':common').sourceSets.main.allJava trimTrailingWhitespace()
target project(':proprietary').sourceSets.main.allJava leadingTabsToSpaces()
target project(':stirling-pdf').sourceSets.main.allJava endWithNewline()
}
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false) format 'gradle', {
target 'build.gradle', 'settings.gradle', 'gradle/*.gradle', 'gradle/**/*.gradle'
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
toggleOffOn()
trimTrailingWhitespace() trimTrailingWhitespace()
leadingTabsToSpaces() leadingTabsToSpaces()
endWithNewline() endWithNewline()

View File

@ -1026,8 +1026,7 @@ ignore = [
[zh_TW] [zh_TW]
ignore = [ ignore = [
'lang.dzo',
'lang.iku',
'lang.que',
'language.direction', 'language.direction',
'poweredBy',
'showJS.tags',
] ]