mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
V1 merge (#5193)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Signed-off-by: dependabot[bot] <[email protected]> Signed-off-by: Balázs Szücs <[email protected]> Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com> Co-authored-by: ConnorYoh <[email protected]> Co-authored-by: Connor Yoh <[email protected]> Co-authored-by: OUNZAR Aymane <[email protected]> Co-authored-by: YAOU Reda <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com> Co-authored-by: Balázs Szücs <[email protected]> Co-authored-by: Ludy <[email protected]> Co-authored-by: tkymmm <[email protected]> Co-authored-by: Peter Dave Hello <[email protected]> Co-authored-by: albanobattistella <[email protected]> Co-authored-by: PingLin8888 <[email protected]> Co-authored-by: FdaSilvaYY <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: OteJlo <[email protected]> Co-authored-by: Angel <[email protected]> Co-authored-by: Ricardo Catarino <[email protected]> Co-authored-by: Luis Antonio Argüelles González <[email protected]> Co-authored-by: Dawid Urbański <[email protected]> Co-authored-by: Stephan Paternotte <[email protected]> Co-authored-by: Leonardo Santos Paulucio <[email protected]> Co-authored-by: hamza khalem <[email protected]> Co-authored-by: IT Creativity + Art Team <[email protected]> Co-authored-by: Reece Browne <[email protected]> Co-authored-by: James Brunton <[email protected]> Co-authored-by: Victor Villarreal <[email protected]>
This commit is contained in:
co-authored by
ConnorYoh
Connor Yoh
OUNZAR Aymane
YAOU Reda
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
Balázs Szücs
Ludy
tkymmm
Peter Dave Hello
albanobattistella
PingLin8888
FdaSilvaYY
Copilot
OteJlo
Angel
Ricardo Catarino
Luis Antonio Argüelles González
Dawid Urbański
Stephan Paternotte
Leonardo Santos Paulucio
hamza khalem
IT Creativity + Art Team
Reece Browne
James Brunton
Victor Villarreal
parent
a5dcdd5bd9
commit
68ed54e398
+3
-11
@@ -1,8 +1,6 @@
|
||||
package stirling.software.common.annotations;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
@@ -31,8 +29,6 @@ import stirling.software.common.aop.AutoJobAspect;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.FileStorage;
|
||||
import stirling.software.common.service.JobExecutorService;
|
||||
import stirling.software.common.service.JobQueue;
|
||||
import stirling.software.common.service.ResourceMonitor;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AutoJobPostMappingIntegrationTest {
|
||||
@@ -45,10 +41,6 @@ class AutoJobPostMappingIntegrationTest {
|
||||
|
||||
@Mock private FileStorage fileStorage;
|
||||
|
||||
@Mock private ResourceMonitor resourceMonitor;
|
||||
|
||||
@Mock private JobQueue jobQueue;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
autoJobAspect = new AutoJobAspect(jobExecutorService, request, fileStorage);
|
||||
@@ -73,7 +65,7 @@ class AutoJobPostMappingIntegrationTest {
|
||||
// Given
|
||||
PDFFile pdfFile = new PDFFile();
|
||||
pdfFile.setFileId("test-file-id");
|
||||
Object[] args = new Object[] {pdfFile};
|
||||
Object[] args = {pdfFile};
|
||||
|
||||
when(joinPoint.getArgs()).thenReturn(args);
|
||||
when(request.getParameter("async")).thenReturn("true");
|
||||
@@ -153,7 +145,7 @@ class AutoJobPostMappingIntegrationTest {
|
||||
// Given
|
||||
PDFFile pdfFile = new PDFFile();
|
||||
pdfFile.setFileInput(mock(MultipartFile.class));
|
||||
Object[] args = new Object[] {pdfFile};
|
||||
Object[] args = {pdfFile};
|
||||
|
||||
when(joinPoint.getArgs()).thenReturn(args);
|
||||
when(request.getParameter("async")).thenReturn("true");
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package stirling.software.common.configuration.interfaces;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ShowAdminInterfaceTest {
|
||||
|
||||
// Create a simple implementation for testing
|
||||
static class TestImpl implements ShowAdminInterface {}
|
||||
|
||||
@Test
|
||||
void getShowUpdateOnlyAdmins_returnsTrueByDefault() {
|
||||
ShowAdminInterface instance = new TestImpl();
|
||||
assertTrue(instance.getShowUpdateOnlyAdmins(), "Default should return true");
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -18,17 +18,17 @@ class ApplicationPropertiesDynamicYamlPropertySourceTest {
|
||||
|
||||
@Test
|
||||
void loads_yaml_into_environment() throws Exception {
|
||||
// YAML-Config in Temp-Datei schreiben
|
||||
String yaml =
|
||||
""
|
||||
+ "ui:\n"
|
||||
+ " appName: \"My App\"\n"
|
||||
+ "system:\n"
|
||||
+ " enableAnalytics: true\n";
|
||||
"""
|
||||
\
|
||||
ui:
|
||||
appName: "My App"
|
||||
system:
|
||||
enableAnalytics: true
|
||||
""";
|
||||
Path tmp = Files.createTempFile("spdf-settings-", ".yml");
|
||||
Files.writeString(tmp, yaml);
|
||||
|
||||
// Pfad per statischem Mock liefern
|
||||
try (MockedStatic<InstallationPathConfig> mocked =
|
||||
Mockito.mockStatic(InstallationPathConfig.class)) {
|
||||
mocked.when(InstallationPathConfig::getSettingsPath).thenReturn(tmp.toString());
|
||||
@@ -36,7 +36,7 @@ class ApplicationPropertiesDynamicYamlPropertySourceTest {
|
||||
ConfigurableEnvironment env = new StandardEnvironment();
|
||||
ApplicationProperties props = new ApplicationProperties();
|
||||
|
||||
props.dynamicYamlPropertySource(env); // fügt PropertySource an erster Stelle ein
|
||||
props.dynamicYamlPropertySource(env);
|
||||
|
||||
assertEquals("My App", env.getProperty("ui.appName"));
|
||||
assertEquals("true", env.getProperty("system.enableAnalytics"));
|
||||
@@ -44,7 +44,7 @@ class ApplicationPropertiesDynamicYamlPropertySourceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void throws_when_settings_file_missing() throws Exception {
|
||||
void throws_when_settings_file_missing() {
|
||||
String missing = "/path/does/not/exist/spdf.yml";
|
||||
try (MockedStatic<InstallationPathConfig> mocked =
|
||||
Mockito.mockStatic(InstallationPathConfig.class)) {
|
||||
|
||||
+2
-2
@@ -61,7 +61,7 @@ class ApplicationPropertiesSaml2HttpTest {
|
||||
Resource r = s.getSpCert();
|
||||
|
||||
assertNotNull(r);
|
||||
assertTrue(r instanceof FileSystemResource, "Expected FileSystemResource for FS path");
|
||||
assertInstanceOf(FileSystemResource.class, r, "Expected FileSystemResource for FS path");
|
||||
assertTrue(r.exists(), "Temp file should exist");
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ class ApplicationPropertiesSaml2HttpTest {
|
||||
Resource r = s.getIdpCert();
|
||||
|
||||
assertNotNull(r);
|
||||
assertTrue(r instanceof FileSystemResource, "Expected FileSystemResource for FS path");
|
||||
assertInstanceOf(FileSystemResource.class, r, "Expected FileSystemResource for FS path");
|
||||
assertFalse(r.exists(), "Resource should not exist for missing file");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package stirling.software.common.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
public class FileInfoTest {
|
||||
|
||||
private static final LocalDateTime FIXED_NOW = LocalDateTime.of(2025, 11, 1, 12, 0, 0);
|
||||
|
||||
@ParameterizedTest(name = "{index}: fileSize={0}")
|
||||
@CsvSource({
|
||||
"0, '0 Bytes'",
|
||||
@@ -25,87 +29,146 @@ public class FileInfoTest {
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
File.separator
|
||||
+ "path"
|
||||
+ File.separator
|
||||
+ "to"
|
||||
+ File.separator
|
||||
+ "example.txt",
|
||||
LocalDateTime.now(),
|
||||
"/path/to/example.txt",
|
||||
FIXED_NOW,
|
||||
fileSize,
|
||||
LocalDateTime.now().minusDays(1));
|
||||
FIXED_NOW.minusDays(1));
|
||||
|
||||
assertEquals(expectedFormattedSize, fileInfo.getFormattedFileSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFilePathAsPath() {
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"test.pdf",
|
||||
File.separator + "tmp" + File.separator + "test.pdf",
|
||||
LocalDateTime.now(),
|
||||
1234,
|
||||
LocalDateTime.now().minusDays(2));
|
||||
assertEquals(
|
||||
File.separator + "tmp" + File.separator + "test.pdf",
|
||||
fileInfo.getFilePathAsPath().toString());
|
||||
@Nested
|
||||
@DisplayName("getFilePathAsPath")
|
||||
class GetFilePathAsPathTests {
|
||||
@Test
|
||||
@DisplayName("Should convert filePath string into a Path instance")
|
||||
void shouldConvertStringToPath() {
|
||||
FileInfo fi =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
"/path/to/example.txt",
|
||||
FIXED_NOW,
|
||||
123,
|
||||
FIXED_NOW.minusDays(1));
|
||||
|
||||
Path path = fi.getFilePathAsPath();
|
||||
|
||||
// Basic sanity checks
|
||||
assertNotNull(path, "Path should not be null");
|
||||
assertEquals(
|
||||
Path.of("/path/to/example.txt"),
|
||||
path,
|
||||
"Converted Path should match input string");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFormattedModificationDate() {
|
||||
LocalDateTime modDate = LocalDateTime.of(2024, 6, 1, 15, 30, 45);
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"file.txt",
|
||||
File.separator + "file.txt",
|
||||
modDate,
|
||||
100,
|
||||
LocalDateTime.of(2024, 5, 31, 10, 0, 0));
|
||||
assertEquals("2024-06-01 15:30:45", fileInfo.getFormattedModificationDate());
|
||||
@Nested
|
||||
@DisplayName("Date formatting")
|
||||
class DateFormattingTests {
|
||||
@Test
|
||||
@DisplayName("Should format modificationDate as 'yyyy-MM-dd HH:mm:ss'")
|
||||
void shouldFormatModificationDate() {
|
||||
LocalDateTime mod = LocalDateTime.of(2025, 8, 10, 15, 30, 45);
|
||||
FileInfo fi =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
"/path/to/example.txt",
|
||||
mod,
|
||||
1,
|
||||
LocalDateTime.of(2024, 1, 1, 0, 0, 0));
|
||||
|
||||
assertEquals("2025-08-10 15:30:45", fi.getFormattedModificationDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should format creationDate as 'yyyy-MM-dd HH:mm:ss'")
|
||||
void shouldFormatCreationDate() {
|
||||
LocalDateTime created = LocalDateTime.of(2024, 12, 31, 23, 59, 59);
|
||||
FileInfo fi =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
"/path/to/example.txt",
|
||||
LocalDateTime.of(2025, 1, 1, 0, 0, 0),
|
||||
1,
|
||||
created);
|
||||
|
||||
assertEquals("2024-12-31 23:59:59", fi.getFormattedCreationDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw NPE when modificationDate is null (current behavior)")
|
||||
void shouldThrowWhenModificationDateNull() {
|
||||
// Assumption: Current implementation does not guard null -> NPE is expected.
|
||||
FileInfo fi =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
"/path/to/example.txt",
|
||||
null, // modificationDate null
|
||||
1,
|
||||
FIXED_NOW);
|
||||
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
fi::getFormattedModificationDate,
|
||||
"Formatting a null modificationDate should throw NPE with current"
|
||||
+ " implementation");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw NPE when creationDate is null (current behavior)")
|
||||
void shouldThrowWhenCreationDateNull() {
|
||||
// Assumption: Current implementation does not guard null -> NPE is expected.
|
||||
FileInfo fi =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
"/path/to/example.txt",
|
||||
FIXED_NOW,
|
||||
1,
|
||||
null); // creationDate null
|
||||
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
fi::getFormattedCreationDate,
|
||||
"Formatting a null creationDate should throw NPE with current implementation");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFormattedCreationDate() {
|
||||
LocalDateTime creationDate = LocalDateTime.of(2023, 12, 25, 8, 15, 0);
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"holiday.txt",
|
||||
File.separator + "holiday.txt",
|
||||
LocalDateTime.of(2024, 1, 1, 0, 0, 0),
|
||||
500,
|
||||
creationDate);
|
||||
assertEquals("2023-12-25 08:15:00", fileInfo.getFormattedCreationDate());
|
||||
}
|
||||
@Nested
|
||||
@DisplayName("Additional size formatting cases")
|
||||
class AdditionalSizeFormattingTests {
|
||||
|
||||
@Test
|
||||
void testGettersAndSetters() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"doc.pdf",
|
||||
File.separator + "docs" + File.separator + "doc.pdf",
|
||||
now,
|
||||
2048,
|
||||
now.minusDays(1));
|
||||
// Test getters
|
||||
assertEquals("doc.pdf", fileInfo.getFileName());
|
||||
assertEquals(File.separator + "docs" + File.separator + "doc.pdf", fileInfo.getFilePath());
|
||||
assertEquals(now, fileInfo.getModificationDate());
|
||||
assertEquals(2048, fileInfo.getFileSize());
|
||||
assertEquals(now.minusDays(1), fileInfo.getCreationDate());
|
||||
@Test
|
||||
@DisplayName("Should round to two decimals for KB (e.g., 1536 B -> 1.50 KB)")
|
||||
void shouldRoundKbToTwoDecimals() {
|
||||
FileInfo fi =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
"/path/to/example.txt",
|
||||
FIXED_NOW,
|
||||
1536, // 1.5 KB
|
||||
FIXED_NOW.minusDays(1));
|
||||
|
||||
// Test setters
|
||||
fileInfo.setFileName("new.pdf");
|
||||
fileInfo.setFilePath(File.separator + "new" + File.separator + "new.pdf");
|
||||
fileInfo.setModificationDate(now.plusDays(1));
|
||||
fileInfo.setFileSize(4096);
|
||||
fileInfo.setCreationDate(now.minusDays(2));
|
||||
assertEquals("1.50 KB", fi.getFormattedFileSize());
|
||||
}
|
||||
|
||||
assertEquals("new.pdf", fileInfo.getFileName());
|
||||
assertEquals(File.separator + "new" + File.separator + "new.pdf", fileInfo.getFilePath());
|
||||
assertEquals(now.plusDays(1), fileInfo.getModificationDate());
|
||||
assertEquals(4096, fileInfo.getFileSize());
|
||||
assertEquals(now.minusDays(2), fileInfo.getCreationDate());
|
||||
@Test
|
||||
@DisplayName("Values above 1 TB are still represented in GB (design choice)")
|
||||
void shouldRepresentTerabytesInGb() {
|
||||
// 2 TB = 2 * 1024 GB -> 2 * 1024 * 1024^3 bytes
|
||||
long twoTB = 2L * 1024 * 1024 * 1024 * 1024; // 2 * 2^40
|
||||
FileInfo fi =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
"/path/to/example.txt",
|
||||
FIXED_NOW,
|
||||
twoTB,
|
||||
FIXED_NOW.minusDays(1));
|
||||
|
||||
// 2 TB equals 2048.00 GB with current implementation
|
||||
assertEquals(
|
||||
"2048.00 GB",
|
||||
fi.getFormattedFileSize(),
|
||||
"Current implementation caps at GB and shows TB in GB units");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package stirling.software.common.model.exception;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@DisplayName("Tests for UnsupportedClaimException")
|
||||
class UnsupportedClaimExceptionTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("should store message passed to constructor")
|
||||
void shouldStoreMessageFromConstructor() {
|
||||
String expectedMessage = "This claim is not supported";
|
||||
UnsupportedClaimException exception = new UnsupportedClaimException(expectedMessage);
|
||||
|
||||
// Verify the stored message
|
||||
assertEquals(
|
||||
expectedMessage,
|
||||
exception.getMessage(),
|
||||
"Constructor should correctly store the provided message");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should allow null message without throwing exception")
|
||||
void shouldAllowNullMessage() {
|
||||
UnsupportedClaimException exception = new UnsupportedClaimException(null);
|
||||
|
||||
// Null message should be stored as null
|
||||
assertNull(
|
||||
exception.getMessage(),
|
||||
"Constructor should accept null message and store it as null");
|
||||
}
|
||||
}
|
||||
+52
-59
@@ -1,7 +1,6 @@
|
||||
package stirling.software.common.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static stirling.software.common.service.SpyPDFDocumentFactory.*;
|
||||
|
||||
@@ -22,7 +21,6 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.SpyPDFDocumentFactory.StrategyType;
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@@ -43,58 +41,7 @@ class CustomPDFDocumentFactoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_FileInput(int sizeMB, StrategyType expected) throws IOException {
|
||||
File file = writeTempFile(inflatePdf(basePdfBytes, sizeMB));
|
||||
try (PDDocument doc = factory.load(file)) {
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_ByteArray(int sizeMB, StrategyType expected) throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, sizeMB);
|
||||
try (PDDocument doc = factory.load(inflated)) {
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_InputStream(int sizeMB, StrategyType expected) throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, sizeMB);
|
||||
try (PDDocument doc = factory.load(new ByteArrayInputStream(inflated))) {
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_MultipartFile(int sizeMB, StrategyType expected) throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, sizeMB);
|
||||
MockMultipartFile multipart =
|
||||
new MockMultipartFile("file", "doc.pdf", MediaType.APPLICATION_PDF_VALUE, inflated);
|
||||
try (PDDocument doc = factory.load(multipart)) {
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_PDFFile(int sizeMB, StrategyType expected) throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, sizeMB);
|
||||
MockMultipartFile multipart =
|
||||
new MockMultipartFile("file", "doc.pdf", MediaType.APPLICATION_PDF_VALUE, inflated);
|
||||
PDFFile pdfFile = new PDFFile();
|
||||
pdfFile.setFileInput(multipart);
|
||||
try (PDDocument doc = factory.load(pdfFile)) {
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] inflatePdf(byte[] input, int sizeInMB) throws IOException {
|
||||
private static byte[] inflatePdf(byte[] input, int sizeInMB) throws IOException {
|
||||
try (PDDocument doc = Loader.loadPDF(input)) {
|
||||
byte[] largeData = new byte[sizeInMB * 1024 * 1024];
|
||||
Arrays.fill(largeData, (byte) 'A');
|
||||
@@ -113,6 +60,46 @@ class CustomPDFDocumentFactoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static File writeTempFile(byte[] content) throws IOException {
|
||||
File file = Files.createTempFile("pdf-test-", ".pdf").toFile();
|
||||
Files.write(file.toPath(), content);
|
||||
return file;
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_FileInput(int sizeMB, StrategyType expected) throws IOException {
|
||||
File file = writeTempFile(inflatePdf(basePdfBytes, sizeMB));
|
||||
factory.load(file);
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_ByteArray(int sizeMB, StrategyType expected) throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, sizeMB);
|
||||
factory.load(inflated);
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_InputStream(int sizeMB, StrategyType expected) throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, sizeMB);
|
||||
factory.load(new ByteArrayInputStream(inflated));
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_MultipartFile(int sizeMB, StrategyType expected) throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, sizeMB);
|
||||
MockMultipartFile multipart =
|
||||
new MockMultipartFile("file", "doc.pdf", MediaType.APPLICATION_PDF_VALUE, inflated);
|
||||
factory.load(multipart);
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadFromPath() throws IOException {
|
||||
File file = writeTempFile(inflatePdf(basePdfBytes, 5));
|
||||
@@ -130,7 +117,7 @@ class CustomPDFDocumentFactoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
// neeed to add password pdf
|
||||
// need to add password pdf
|
||||
// @Test
|
||||
// void testLoadPasswordProtectedPdfFromInputStream() throws IOException {
|
||||
// try (InputStream is = getClass().getResourceAsStream("/protected.pdf")) {
|
||||
@@ -212,10 +199,16 @@ class CustomPDFDocumentFactoryTest {
|
||||
assertTrue(newBytes.length > 0);
|
||||
}
|
||||
|
||||
private File writeTempFile(byte[] content) throws IOException {
|
||||
File file = Files.createTempFile("pdf-test-", ".pdf").toFile();
|
||||
Files.write(file.toPath(), content);
|
||||
return file;
|
||||
@ParameterizedTest
|
||||
@CsvSource({"5,MEMORY_ONLY", "20,MIXED", "60,TEMP_FILE"})
|
||||
void testStrategy_PDFFile(int sizeMB, StrategyType expected) throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, sizeMB);
|
||||
MockMultipartFile multipart =
|
||||
new MockMultipartFile("file", "doc.pdf", MediaType.APPLICATION_PDF_VALUE, inflated);
|
||||
PDFFile pdfFile = new PDFFile();
|
||||
pdfFile.setFileInput(multipart);
|
||||
factory.load(pdfFile);
|
||||
Assertions.assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package stirling.software.common.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.AdditionalAnswers.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -86,7 +82,7 @@ class FileStorageTest {
|
||||
void testRetrieveFile() throws IOException {
|
||||
// Arrange
|
||||
byte[] fileContent = "Test PDF content".getBytes();
|
||||
String fileId = UUID.randomUUID().toString();
|
||||
String fileId = "test-file-1";
|
||||
Path filePath = tempDir.resolve(fileId);
|
||||
Files.write(filePath, fileContent);
|
||||
|
||||
@@ -106,7 +102,7 @@ class FileStorageTest {
|
||||
void testRetrieveBytes() throws IOException {
|
||||
// Arrange
|
||||
byte[] fileContent = "Test PDF content".getBytes();
|
||||
String fileId = UUID.randomUUID().toString();
|
||||
String fileId = "test-file-2";
|
||||
Path filePath = tempDir.resolve(fileId);
|
||||
Files.write(filePath, fileContent);
|
||||
|
||||
@@ -139,7 +135,7 @@ class FileStorageTest {
|
||||
void testDeleteFile() throws IOException {
|
||||
// Arrange
|
||||
byte[] fileContent = "Test PDF content".getBytes();
|
||||
String fileId = UUID.randomUUID().toString();
|
||||
String fileId = "test-file-3";
|
||||
Path filePath = tempDir.resolve(fileId);
|
||||
Files.write(filePath, fileContent);
|
||||
|
||||
@@ -167,7 +163,7 @@ class FileStorageTest {
|
||||
void testFileExists() throws IOException {
|
||||
// Arrange
|
||||
byte[] fileContent = "Test PDF content".getBytes();
|
||||
String fileId = UUID.randomUUID().toString();
|
||||
String fileId = "test-file-4";
|
||||
Path filePath = tempDir.resolve(fileId);
|
||||
Files.write(filePath, fileContent);
|
||||
|
||||
|
||||
+11
-10
@@ -1,6 +1,7 @@
|
||||
package stirling.software.common.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -105,7 +106,7 @@ class JobExecutorServiceTest {
|
||||
|
||||
// Then
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertTrue(response.getBody() instanceof JobResponse);
|
||||
assertInstanceOf(JobResponse.class, response.getBody());
|
||||
JobResponse<?> jobResponse = (JobResponse<?>) response.getBody();
|
||||
assertTrue(jobResponse.isAsync());
|
||||
assertNotNull(jobResponse.getJobId());
|
||||
@@ -134,7 +135,7 @@ class JobExecutorServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldQueueJobWhenResourcesLimited() {
|
||||
void shouldQueueJobWhenResourcesLimited() throws Exception {
|
||||
// Given
|
||||
Supplier<Object> work = () -> "test-result";
|
||||
CompletableFuture<ResponseEntity<?>> future = new CompletableFuture<>();
|
||||
@@ -150,7 +151,7 @@ class JobExecutorServiceTest {
|
||||
|
||||
// Then
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertTrue(response.getBody() instanceof JobResponse);
|
||||
assertInstanceOf(JobResponse.class, response.getBody());
|
||||
|
||||
// Verify job was queued
|
||||
verify(jobQueue).queueJob(anyString(), eq(80), any(), eq(5000L));
|
||||
@@ -184,13 +185,13 @@ class JobExecutorServiceTest {
|
||||
// Given
|
||||
Supplier<Object> work =
|
||||
() -> {
|
||||
try {
|
||||
Thread.sleep(100); // Simulate long-running job
|
||||
return "test-result";
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
// Simulate long-running job without actual sleep
|
||||
// Use a loop to consume time instead of Thread.sleep
|
||||
long startTime = System.nanoTime();
|
||||
while (System.nanoTime() - startTime < 100_000_000) { // 100ms in nanoseconds
|
||||
// Busy wait to simulate work without Thread.sleep
|
||||
}
|
||||
return "test-result";
|
||||
};
|
||||
|
||||
// Use reflection to access the private executeWithTimeout method
|
||||
@@ -203,7 +204,7 @@ class JobExecutorServiceTest {
|
||||
try {
|
||||
executeMethod.invoke(jobExecutorService, work, 1L); // Very short timeout
|
||||
} catch (Exception e) {
|
||||
assertTrue(e.getCause() instanceof TimeoutException);
|
||||
assertInstanceOf(TimeoutException.class, e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package stirling.software.common.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.time.Instant;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -33,11 +32,11 @@ class ResourceMonitorTest {
|
||||
@Mock private MemoryMXBean memoryMXBean;
|
||||
|
||||
@Spy
|
||||
private AtomicReference<ResourceStatus> currentStatus =
|
||||
private final AtomicReference<ResourceStatus> currentStatus =
|
||||
new AtomicReference<>(ResourceStatus.OK);
|
||||
|
||||
@Spy
|
||||
private AtomicReference<ResourceMetrics> latestMetrics =
|
||||
private final AtomicReference<ResourceMetrics> latestMetrics =
|
||||
new AtomicReference<>(new ResourceMetrics());
|
||||
|
||||
@BeforeEach
|
||||
@@ -118,18 +117,24 @@ class ResourceMonitorTest {
|
||||
shouldQueue,
|
||||
result,
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"For weight %d and status %s, shouldQueue should be %s",
|
||||
weight, status, shouldQueue));
|
||||
weight,
|
||||
status,
|
||||
shouldQueue));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourceMetricsShouldDetectStaleState() {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final Instant testTime = Instant.now();
|
||||
|
||||
// Given
|
||||
Instant now = Instant.now();
|
||||
Instant pastInstant = now.minusMillis(6000);
|
||||
Instant pastInstant =
|
||||
testTime.minusMillis(6000); // 6 seconds ago (relative to test start time)
|
||||
|
||||
ResourceMetrics staleMetrics = new ResourceMetrics(0.5, 0.5, 1024, 2048, 4096, pastInstant);
|
||||
ResourceMetrics freshMetrics = new ResourceMetrics(0.5, 0.5, 1024, 2048, 4096, now);
|
||||
ResourceMetrics freshMetrics = new ResourceMetrics(0.5, 0.5, 1024, 2048, 4096, testTime);
|
||||
|
||||
// When/Then
|
||||
assertTrue(
|
||||
|
||||
@@ -5,7 +5,6 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -42,7 +41,7 @@ class TaskManagerTest {
|
||||
@Test
|
||||
void testCreateTask() {
|
||||
// Act
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
String jobId = "test-job-1";
|
||||
taskManager.createTask(jobId);
|
||||
|
||||
// Assert
|
||||
@@ -56,7 +55,7 @@ class TaskManagerTest {
|
||||
@Test
|
||||
void testSetResult() {
|
||||
// Arrange
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
String jobId = "test-job-2";
|
||||
taskManager.createTask(jobId);
|
||||
Object resultObject = "Test result";
|
||||
|
||||
@@ -74,7 +73,7 @@ class TaskManagerTest {
|
||||
@Test
|
||||
void testSetFileResult() throws Exception {
|
||||
// Arrange
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
String jobId = "test-job-3";
|
||||
taskManager.createTask(jobId);
|
||||
String fileId = "file-id";
|
||||
String originalFileName = "test.pdf";
|
||||
@@ -108,7 +107,7 @@ class TaskManagerTest {
|
||||
@Test
|
||||
void testSetError() {
|
||||
// Arrange
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
String jobId = "test-job-4";
|
||||
taskManager.createTask(jobId);
|
||||
String errorMessage = "Test error";
|
||||
|
||||
@@ -126,7 +125,7 @@ class TaskManagerTest {
|
||||
@Test
|
||||
void testSetComplete_WithExistingResult() {
|
||||
// Arrange
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
String jobId = "test-job-5";
|
||||
taskManager.createTask(jobId);
|
||||
Object resultObject = "Test result";
|
||||
taskManager.setResult(jobId, resultObject);
|
||||
@@ -144,7 +143,7 @@ class TaskManagerTest {
|
||||
@Test
|
||||
void testSetComplete_WithoutExistingResult() {
|
||||
// Arrange
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
String jobId = "test-job-6";
|
||||
taskManager.createTask(jobId);
|
||||
|
||||
// Act
|
||||
@@ -160,7 +159,7 @@ class TaskManagerTest {
|
||||
@Test
|
||||
void testIsComplete() {
|
||||
// Arrange
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
String jobId = "test-job-7";
|
||||
taskManager.createTask(jobId);
|
||||
|
||||
// Assert - not complete initially
|
||||
@@ -215,7 +214,9 @@ class TaskManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCleanupOldJobs() throws Exception {
|
||||
void testCleanupOldJobs() {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final LocalDateTime testTime = LocalDateTime.now();
|
||||
// Arrange
|
||||
// 1. Create a recent completed job
|
||||
String recentJobId = "recent-job";
|
||||
@@ -227,8 +228,9 @@ class TaskManagerTest {
|
||||
taskManager.createTask(oldJobId);
|
||||
JobResult oldJob = taskManager.getJobResult(oldJobId);
|
||||
|
||||
// Manually set the completion time to be older than the expiry
|
||||
LocalDateTime oldTime = LocalDateTime.now().minusHours(1);
|
||||
// Manually set the completion time to be older than the expiry (relative to test start
|
||||
// time)
|
||||
LocalDateTime oldTime = testTime.minusHours(1);
|
||||
ReflectionTestUtils.setField(oldJob, "completedAt", oldTime);
|
||||
ReflectionTestUtils.setField(oldJob, "complete", true);
|
||||
|
||||
@@ -253,6 +255,7 @@ class TaskManagerTest {
|
||||
taskManager.createTask(activeJobId);
|
||||
|
||||
// Verify all jobs are in the map
|
||||
assertNotNull(jobResultsMap);
|
||||
assertTrue(jobResultsMap.containsKey(recentJobId));
|
||||
assertTrue(jobResultsMap.containsKey(oldJobId));
|
||||
assertTrue(jobResultsMap.containsKey(activeJobId));
|
||||
@@ -268,7 +271,7 @@ class TaskManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShutdown() throws Exception {
|
||||
void testShutdown() {
|
||||
// This mainly tests that the shutdown method doesn't throw exceptions
|
||||
taskManager.shutdown();
|
||||
|
||||
@@ -279,7 +282,7 @@ class TaskManagerTest {
|
||||
@Test
|
||||
void testAddNote() {
|
||||
// Arrange
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
String jobId = "test-job-8";
|
||||
taskManager.createTask(jobId);
|
||||
String note = "Test note";
|
||||
|
||||
|
||||
+49
-41
@@ -1,8 +1,6 @@
|
||||
package stirling.software.common.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.File;
|
||||
@@ -133,6 +131,9 @@ public class TempFileCleanupServiceTest {
|
||||
|
||||
// Use MockedStatic to mock Files operations
|
||||
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final long testTime = System.currentTimeMillis();
|
||||
|
||||
// Mock Files.list for each directory we'll process
|
||||
mockedFiles
|
||||
.when(() -> Files.list(eq(systemTempDir)))
|
||||
@@ -177,18 +178,17 @@ public class TempFileCleanupServiceTest {
|
||||
// maxAgeMillis
|
||||
if (fileName.contains("old")) {
|
||||
return FileTime.fromMillis(
|
||||
System.currentTimeMillis() - 5000000);
|
||||
testTime - 5000000); // ~1.4 hours ago
|
||||
}
|
||||
// For empty.tmp file, return a timestamp older than 5 minutes (for
|
||||
// empty file test)
|
||||
else if (fileName.equals("empty.tmp")) {
|
||||
else if ("empty.tmp".equals(fileName)) {
|
||||
return FileTime.fromMillis(
|
||||
System.currentTimeMillis() - 6 * 60 * 1000);
|
||||
testTime - 6 * 60 * 1000); // 6 minutes ago
|
||||
}
|
||||
// For all other files, return a recent timestamp
|
||||
else {
|
||||
return FileTime.fromMillis(
|
||||
System.currentTimeMillis() - 60000); // 1 minute ago
|
||||
return FileTime.fromMillis(testTime - 60000); // 1 minute ago
|
||||
}
|
||||
});
|
||||
|
||||
@@ -201,7 +201,7 @@ public class TempFileCleanupServiceTest {
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
// Return 0 bytes for the empty file
|
||||
if (fileName.equals("empty.tmp")) {
|
||||
if ("empty.tmp".equals(fileName)) {
|
||||
return 0L;
|
||||
}
|
||||
// Return normal size for all other files
|
||||
@@ -221,9 +221,9 @@ public class TempFileCleanupServiceTest {
|
||||
});
|
||||
|
||||
// Act - set containerMode to false for this test
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
|
||||
invokeCleanupDirectoryStreaming(customTempDir, false, 0, 3600000);
|
||||
invokeCleanupDirectoryStreaming(libreOfficeTempDir, false, 0, 3600000);
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 3600000);
|
||||
invokeCleanupDirectoryStreaming(customTempDir, false, 3600000);
|
||||
invokeCleanupDirectoryStreaming(libreOfficeTempDir, false, 3600000);
|
||||
|
||||
// Assert - Only old temp files and empty files should be deleted
|
||||
assertTrue(deletedFiles.contains(oldTempFile), "Old temp file should be deleted");
|
||||
@@ -276,6 +276,9 @@ public class TempFileCleanupServiceTest {
|
||||
|
||||
// Use MockedStatic to mock Files operations
|
||||
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final long testTime = System.currentTimeMillis();
|
||||
|
||||
// Mock Files.list for systemTempDir
|
||||
mockedFiles
|
||||
.when(() -> Files.list(eq(systemTempDir)))
|
||||
@@ -290,9 +293,7 @@ public class TempFileCleanupServiceTest {
|
||||
// Configure Files.getLastModifiedTime to return recent timestamps
|
||||
mockedFiles
|
||||
.when(() -> Files.getLastModifiedTime(any(Path.class)))
|
||||
.thenReturn(
|
||||
FileTime.fromMillis(
|
||||
System.currentTimeMillis() - 60000)); // 1 minute ago
|
||||
.thenReturn(FileTime.fromMillis(testTime - 60000)); // 1 minute ago
|
||||
|
||||
// Configure Files.size to return normal size
|
||||
mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(1024L); // 1 KB
|
||||
@@ -308,7 +309,7 @@ public class TempFileCleanupServiceTest {
|
||||
});
|
||||
|
||||
// Act - set containerMode to true and maxAgeMillis to 0 for container startup cleanup
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, true, 0, 0);
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, true, 0);
|
||||
|
||||
// Assert - In container mode, both our temp files and system temp files should be
|
||||
// deleted
|
||||
@@ -337,6 +338,9 @@ public class TempFileCleanupServiceTest {
|
||||
|
||||
// Use MockedStatic to mock Files operations
|
||||
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final long testTime = System.currentTimeMillis();
|
||||
|
||||
// Mock Files.list for systemTempDir
|
||||
mockedFiles
|
||||
.when(() -> Files.list(eq(systemTempDir)))
|
||||
@@ -356,14 +360,14 @@ public class TempFileCleanupServiceTest {
|
||||
Path path = invocation.getArgument(0);
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
if (fileName.equals("empty.tmp")) {
|
||||
if ("empty.tmp".equals(fileName)) {
|
||||
// More than 5 minutes old
|
||||
return FileTime.fromMillis(
|
||||
System.currentTimeMillis() - 6 * 60 * 1000);
|
||||
testTime - 6 * 60 * 1000); // 6 minutes ago
|
||||
} else {
|
||||
// Less than 5 minutes old
|
||||
return FileTime.fromMillis(
|
||||
System.currentTimeMillis() - 2 * 60 * 1000);
|
||||
testTime - 2 * 60 * 1000); // 2 minutes ago
|
||||
}
|
||||
});
|
||||
|
||||
@@ -381,7 +385,7 @@ public class TempFileCleanupServiceTest {
|
||||
});
|
||||
|
||||
// Act
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 3600000);
|
||||
|
||||
// Assert
|
||||
assertTrue(
|
||||
@@ -412,14 +416,25 @@ public class TempFileCleanupServiceTest {
|
||||
|
||||
// Use MockedStatic to mock Files operations
|
||||
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final long testTime = System.currentTimeMillis();
|
||||
|
||||
// Mock Files.list for each directory
|
||||
mockedFiles.when(() -> Files.list(eq(systemTempDir))).thenReturn(Stream.of(dir1));
|
||||
mockedFiles
|
||||
.when(() -> Files.list(eq(systemTempDir)))
|
||||
.thenAnswer(invocation -> Stream.of(dir1));
|
||||
|
||||
mockedFiles.when(() -> Files.list(eq(dir1))).thenReturn(Stream.of(tempFile1, dir2));
|
||||
mockedFiles
|
||||
.when(() -> Files.list(eq(dir1)))
|
||||
.thenAnswer(invocation -> Stream.of(tempFile1, dir2));
|
||||
|
||||
mockedFiles.when(() -> Files.list(eq(dir2))).thenReturn(Stream.of(tempFile2, dir3));
|
||||
mockedFiles
|
||||
.when(() -> Files.list(eq(dir2)))
|
||||
.thenAnswer(invocation -> Stream.of(tempFile2, dir3));
|
||||
|
||||
mockedFiles.when(() -> Files.list(eq(dir3))).thenReturn(Stream.of(tempFile3));
|
||||
mockedFiles
|
||||
.when(() -> Files.list(eq(dir3)))
|
||||
.thenAnswer(invocation -> Stream.of(tempFile3));
|
||||
|
||||
// Configure Files.isDirectory for each path
|
||||
mockedFiles.when(() -> Files.isDirectory(eq(dir1))).thenReturn(true);
|
||||
@@ -432,6 +447,9 @@ public class TempFileCleanupServiceTest {
|
||||
// Configure Files.exists to return true for all paths
|
||||
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
|
||||
|
||||
// Configure Files.size to return 0 for all files (ensure they're not empty)
|
||||
mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(1024L);
|
||||
|
||||
// Configure Files.getLastModifiedTime to return different times based on file names
|
||||
mockedFiles
|
||||
.when(() -> Files.getLastModifiedTime(any(Path.class)))
|
||||
@@ -441,19 +459,14 @@ public class TempFileCleanupServiceTest {
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
if (fileName.contains("old")) {
|
||||
// Old file
|
||||
return FileTime.fromMillis(
|
||||
System.currentTimeMillis() - 5000000);
|
||||
// Old file - very old timestamp (older than 1 hour)
|
||||
return FileTime.fromMillis(testTime - 7200000); // 2 hours ago
|
||||
} else {
|
||||
// Recent file
|
||||
return FileTime.fromMillis(System.currentTimeMillis() - 60000);
|
||||
// Recent file - very recent timestamp (less than 1 hour)
|
||||
return FileTime.fromMillis(testTime - 60000); // 1 minute ago
|
||||
}
|
||||
});
|
||||
|
||||
// Configure Files.size to return normal size
|
||||
mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(1024L);
|
||||
|
||||
// For deleteIfExists, track which files would be deleted
|
||||
mockedFiles
|
||||
.when(() -> Files.deleteIfExists(any(Path.class)))
|
||||
.thenAnswer(
|
||||
@@ -463,12 +476,8 @@ public class TempFileCleanupServiceTest {
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
|
||||
|
||||
// Debug - print what was deleted
|
||||
System.out.println("Deleted files: " + deletedFiles);
|
||||
System.out.println("Looking for: " + tempFile3);
|
||||
// Act - pass maxAgeMillis = 3600000 (1 hour)
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 3600000);
|
||||
|
||||
// Assert
|
||||
assertFalse(deletedFiles.contains(tempFile1), "Recent temp file should be preserved");
|
||||
@@ -481,8 +490,7 @@ public class TempFileCleanupServiceTest {
|
||||
|
||||
/** Helper method to invoke the private cleanupDirectoryStreaming method using reflection */
|
||||
private void invokeCleanupDirectoryStreaming(
|
||||
Path directory, boolean containerMode, int depth, long maxAgeMillis)
|
||||
throws IOException {
|
||||
Path directory, boolean containerMode, long maxAgeMillis) {
|
||||
try {
|
||||
// Create a consumer that tracks deleted files
|
||||
AtomicInteger deleteCount = new AtomicInteger(0);
|
||||
@@ -505,7 +513,7 @@ public class TempFileCleanupServiceTest {
|
||||
cleanupService,
|
||||
directory,
|
||||
containerMode,
|
||||
depth,
|
||||
0,
|
||||
maxAgeMillis,
|
||||
false,
|
||||
deleteCallback);
|
||||
|
||||
+58
-7
@@ -1,9 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -26,6 +23,7 @@ class CheckProgramInstallTest {
|
||||
|
||||
private MockedStatic<ProcessExecutor> mockProcessExecutor;
|
||||
private ProcessExecutor mockExecutor;
|
||||
private ProcessExecutor mockFfmpegExecutor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
@@ -34,10 +32,14 @@ class CheckProgramInstallTest {
|
||||
|
||||
// Set up mock for ProcessExecutor
|
||||
mockExecutor = Mockito.mock(ProcessExecutor.class);
|
||||
mockFfmpegExecutor = Mockito.mock(ProcessExecutor.class);
|
||||
mockProcessExecutor = mockStatic(ProcessExecutor.class);
|
||||
mockProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV))
|
||||
.thenReturn(mockExecutor);
|
||||
mockProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.FFMPEG))
|
||||
.thenReturn(mockFfmpegExecutor);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -49,7 +51,7 @@ class CheckProgramInstallTest {
|
||||
}
|
||||
|
||||
/** Reset static fields in the CheckProgramInstall class using reflection */
|
||||
private void resetStaticFields() throws Exception {
|
||||
private static void resetStaticFields() throws Exception {
|
||||
Field pythonAvailableCheckedField =
|
||||
CheckProgramInstall.class.getDeclaredField("pythonAvailableChecked");
|
||||
pythonAvailableCheckedField.setAccessible(true);
|
||||
@@ -59,6 +61,15 @@ class CheckProgramInstallTest {
|
||||
CheckProgramInstall.class.getDeclaredField("availablePythonCommand");
|
||||
availablePythonCommandField.setAccessible(true);
|
||||
availablePythonCommandField.set(null, null);
|
||||
|
||||
Field ffmpegAvailableCheckedField =
|
||||
CheckProgramInstall.class.getDeclaredField("ffmpegAvailableChecked");
|
||||
ffmpegAvailableCheckedField.setAccessible(true);
|
||||
ffmpegAvailableCheckedField.set(null, false);
|
||||
|
||||
Field ffmpegAvailableField = CheckProgramInstall.class.getDeclaredField("ffmpegAvailable");
|
||||
ffmpegAvailableField.setAccessible(true);
|
||||
ffmpegAvailableField.set(null, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,8 +119,7 @@ class CheckProgramInstallTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailablePythonCommand_WhenPythonReturnsNonZeroExitCode()
|
||||
throws IOException, InterruptedException, Exception {
|
||||
void testGetAvailablePythonCommand_WhenPythonReturnsNonZeroExitCode() throws Exception {
|
||||
// Arrange
|
||||
// Reset the static fields again to ensure clean state
|
||||
resetStaticFields();
|
||||
@@ -205,4 +215,45 @@ class CheckProgramInstallTest {
|
||||
// Verify getAvailablePythonCommand was called internally
|
||||
verify(mockExecutor).runCommandWithOutputHandling(Arrays.asList("python3", "--version"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFfmpegAvailable_WhenInstalled() throws Exception {
|
||||
resetStaticFields();
|
||||
ProcessExecutorResult result = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(mockFfmpegExecutor.runCommandWithOutputHandling(Arrays.asList("ffmpeg", "-version")))
|
||||
.thenReturn(result);
|
||||
|
||||
assertTrue(CheckProgramInstall.isFfmpegAvailable());
|
||||
verify(mockFfmpegExecutor)
|
||||
.runCommandWithOutputHandling(Arrays.asList("ffmpeg", "-version"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFfmpegAvailable_WhenNotInstalled() throws Exception {
|
||||
resetStaticFields();
|
||||
when(mockFfmpegExecutor.runCommandWithOutputHandling(Arrays.asList("ffmpeg", "-version")))
|
||||
.thenThrow(new IOException("Command not found"));
|
||||
|
||||
assertFalse(CheckProgramInstall.isFfmpegAvailable());
|
||||
verify(mockFfmpegExecutor)
|
||||
.runCommandWithOutputHandling(Arrays.asList("ffmpeg", "-version"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFfmpegAvailable_CachesResult() throws Exception {
|
||||
resetStaticFields();
|
||||
ProcessExecutorResult result = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(mockFfmpegExecutor.runCommandWithOutputHandling(Arrays.asList("ffmpeg", "-version")))
|
||||
.thenReturn(result);
|
||||
|
||||
assertTrue(CheckProgramInstall.isFfmpegAvailable());
|
||||
|
||||
when(mockFfmpegExecutor.runCommandWithOutputHandling(Arrays.asList("ffmpeg", "-version")))
|
||||
.thenThrow(new IOException("Command not found"));
|
||||
|
||||
assertTrue(CheckProgramInstall.isFfmpegAvailable());
|
||||
|
||||
verify(mockFfmpegExecutor, times(1))
|
||||
.runCommandWithOutputHandling(Arrays.asList("ffmpeg", "-version"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class ChecksumUtilsTest {
|
||||
@Test
|
||||
void crc32_unsignedFormatting_highBitSet() throws Exception {
|
||||
// CRC32 of single zero byte (0x00) is 0xD202EF8D (>= 0x8000_0000)
|
||||
byte[] data = new byte[] {0x00};
|
||||
byte[] data = {0x00};
|
||||
|
||||
// Hex (unsigned, 8 chars, lowercase)
|
||||
try (InputStream is = new ByteArrayInputStream(data)) {
|
||||
|
||||
+163
-38
@@ -1,41 +1,44 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.service.SsrfProtectionService;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CustomHtmlSanitizerTest {
|
||||
|
||||
@Mock private SsrfProtectionService ssrfProtectionService;
|
||||
@Mock private ApplicationProperties applicationProperties;
|
||||
@Mock private ApplicationProperties.System systemProperties;
|
||||
|
||||
private CustomHtmlSanitizer customHtmlSanitizer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class);
|
||||
stirling.software.common.model.ApplicationProperties mockApplicationProperties =
|
||||
mock(stirling.software.common.model.ApplicationProperties.class);
|
||||
stirling.software.common.model.ApplicationProperties.System mockSystem =
|
||||
mock(stirling.software.common.model.ApplicationProperties.System.class);
|
||||
// Default behavior: allow all URLs and enable sanitization. Lenient stubs avoid
|
||||
// strict-stubbing failures when individual tests bypass certain branches.
|
||||
lenient().when(ssrfProtectionService.isUrlAllowed(anyString())).thenReturn(true);
|
||||
lenient().when(applicationProperties.getSystem()).thenReturn(systemProperties);
|
||||
lenient().when(systemProperties.isDisableSanitize()).thenReturn(false);
|
||||
|
||||
// Allow all URLs by default for basic tests
|
||||
when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString()))
|
||||
.thenReturn(true);
|
||||
when(mockApplicationProperties.getSystem()).thenReturn(mockSystem);
|
||||
when(mockSystem.getDisableSanitize()).thenReturn(false); // Enable sanitization for tests
|
||||
|
||||
customHtmlSanitizer =
|
||||
new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties);
|
||||
customHtmlSanitizer = new CustomHtmlSanitizer(ssrfProtectionService, applicationProperties);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@@ -56,10 +59,11 @@ class CustomHtmlSanitizerTest {
|
||||
"<p>This is <strong>valid</strong> HTML with <em>formatting</em>.</p>",
|
||||
new String[] {"<p>", "<strong>", "<em>"}),
|
||||
Arguments.of(
|
||||
"<p>Text with <b>bold</b>, <i>italic</i>, <u>underline</u>, "
|
||||
+ "<em>emphasis</em>, <strong>strong</strong>, <strike>strikethrough</strike>, "
|
||||
+ "<s>strike</s>, <sub>subscript</sub>, <sup>superscript</sup>, "
|
||||
+ "<tt>teletype</tt>, <code>code</code>, <big>big</big>, <small>small</small>.</p>",
|
||||
"<p>Text with <b>bold</b>, <i>italic</i>, <u>underline</u>,"
|
||||
+ " <em>emphasis</em>, <strong>strong</strong>,"
|
||||
+ " <strike>strikethrough</strike>, <s>strike</s>,"
|
||||
+ " <sub>subscript</sub>, <sup>superscript</sup>, <tt>teletype</tt>,"
|
||||
+ " <code>code</code>, <big>big</big>, <small>small</small>.</p>",
|
||||
new String[] {
|
||||
"<b>bold</b>",
|
||||
"<i>italic</i>",
|
||||
@@ -163,7 +167,7 @@ class CustomHtmlSanitizerTest {
|
||||
|
||||
@Test
|
||||
void testSanitizeAllowsImages() {
|
||||
// Arrange - Testing Sanitizers.IMAGES
|
||||
// Arrange - Testing custom images policy with SSRF check
|
||||
String htmlWithImage =
|
||||
"<img src=\"image.jpg\" alt=\"An image\" width=\"100\" height=\"100\">";
|
||||
|
||||
@@ -182,7 +186,16 @@ class CustomHtmlSanitizerTest {
|
||||
void testSanitizeDisallowsDataUrlImages() {
|
||||
// Arrange
|
||||
String htmlWithDataUrlImage =
|
||||
"<img src=\"data:image/svg+xml;base64,PHN2ZyBvbmxvYWQ9ImFsZXJ0KDEpIj48L3N2Zz4=\" alt=\"SVG with XSS\">";
|
||||
"<img src=\"data:image/svg+xml;base64,PHN2ZyBvbmxvYWQ9ImFsZXJ0KDEpIj48L3N2Zz4=\""
|
||||
+ " alt=\"SVG with XSS\">";
|
||||
|
||||
// Changed: Explicitly tell SSRF service to reject data: URLs so the custom AttributePolicy
|
||||
// drops the src attribute. Without this, a permissive SSRF mock might allow data: URLs.
|
||||
lenient()
|
||||
.when(
|
||||
ssrfProtectionService.isUrlAllowed(
|
||||
argThat(v -> v != null && v.startsWith("data:"))))
|
||||
.thenReturn(false);
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithDataUrlImage);
|
||||
@@ -257,9 +270,9 @@ class CustomHtmlSanitizerTest {
|
||||
void testSanitizeRemovesObjectAndEmbed() {
|
||||
// Arrange
|
||||
String htmlWithObjects =
|
||||
"<p>Safe content</p>"
|
||||
+ "<object data=\"data.swf\" type=\"application/x-shockwave-flash\"></object>"
|
||||
+ "<embed src=\"embed.swf\" type=\"application/x-shockwave-flash\">";
|
||||
"<p>Safe content</p><object data=\"data.swf\""
|
||||
+ " type=\"application/x-shockwave-flash\"></object><embed src=\"embed.swf\""
|
||||
+ " type=\"application/x-shockwave-flash\">";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithObjects);
|
||||
@@ -295,17 +308,12 @@ class CustomHtmlSanitizerTest {
|
||||
void testSanitizeHandlesComplexHtml() {
|
||||
// Arrange
|
||||
String complexHtml =
|
||||
"<div class=\"container\">"
|
||||
+ " <h1 style=\"color: blue;\">Welcome</h1>"
|
||||
+ " <p>This is a <strong>test</strong> with <a href=\"https://example.com\">link</a>.</p>"
|
||||
+ " <table>"
|
||||
+ " <tr><th>Name</th><th>Value</th></tr>"
|
||||
+ " <tr><td>Item 1</td><td>100</td></tr>"
|
||||
+ " </table>"
|
||||
+ " <img src=\"image.jpg\" alt=\"Test image\">"
|
||||
+ " <script>alert('XSS');</script>"
|
||||
+ " <iframe src=\"https://evil.com\"></iframe>"
|
||||
+ "</div>";
|
||||
"<div class=\"container\"> <h1 style=\"color: blue;\">Welcome</h1> <p>This is a"
|
||||
+ " <strong>test</strong> with <a href=\"https://example.com\">link</a>.</p> "
|
||||
+ " <table> <tr><th>Name</th><th>Value</th></tr> <tr><td>Item"
|
||||
+ " 1</td><td>100</td></tr> </table> <img src=\"image.jpg\" alt=\"Test"
|
||||
+ " image\"> <script>alert('XSS');</script> <iframe"
|
||||
+ " src=\"https://evil.com\"></iframe></div>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = customHtmlSanitizer.sanitize(complexHtml);
|
||||
@@ -353,4 +361,121 @@ class CustomHtmlSanitizerTest {
|
||||
// Assert
|
||||
assertEquals("", sanitizedHtml, "Null input should result in empty string");
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// Additional coverage
|
||||
// -----------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return input unchanged when sanitize is disabled via properties")
|
||||
void shouldBypassSanitizationWhenDisabled() {
|
||||
// Arrange
|
||||
String malicious =
|
||||
"<p>ok</p><script>alert('XSS')</script><img src=\"http://blocked.local/a.png\">";
|
||||
|
||||
// For this test, disable sanitize
|
||||
when(systemProperties.isDisableSanitize()).thenReturn(true);
|
||||
|
||||
// Also ensure SSRF would block it if sanitization were enabled (to prove bypass)
|
||||
lenient().when(ssrfProtectionService.isUrlAllowed(anyString())).thenReturn(false);
|
||||
|
||||
// Act
|
||||
String result = customHtmlSanitizer.sanitize(malicious);
|
||||
|
||||
// Assert
|
||||
// When disabled, sanitizer must return original string as-is.
|
||||
assertEquals(malicious, result, "Sanitization disabled should return input as-is");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should remove img src when SSRF service rejects the URL")
|
||||
void shouldRemoveImageSrcWhenSsrfRejects() {
|
||||
// Arrange
|
||||
String html = "<img src=\"http://internal/admin\" alt=\"x\">";
|
||||
|
||||
// Reject this URL
|
||||
lenient()
|
||||
.when(ssrfProtectionService.isUrlAllowed("http://internal/admin"))
|
||||
.thenReturn(false);
|
||||
|
||||
// Act
|
||||
String sanitized = customHtmlSanitizer.sanitize(html);
|
||||
|
||||
// Assert
|
||||
assertTrue(sanitized.contains("<img"), "Image element should remain");
|
||||
assertFalse(
|
||||
sanitized.contains("src=\"http://internal/admin\""),
|
||||
"Rejected URL should be removed from src attribute");
|
||||
assertTrue(sanitized.contains("alt=\"x\""), "Other allowed attributes should remain");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should trim and preserve allowed img src values")
|
||||
void shouldTrimAndPreserveAllowedImgSrc() {
|
||||
// Arrange
|
||||
String html = "<img src=\" https://example.com/image.jpg \" alt=\"pic\" title=\"t\">";
|
||||
|
||||
// Explicit allow (clarity)
|
||||
lenient()
|
||||
.when(ssrfProtectionService.isUrlAllowed("https://example.com/image.jpg"))
|
||||
.thenReturn(true);
|
||||
|
||||
// Act
|
||||
String sanitized = customHtmlSanitizer.sanitize(html);
|
||||
|
||||
// Assert
|
||||
assertTrue(sanitized.contains("<img"), "Image element should remain");
|
||||
assertFalse(
|
||||
sanitized.contains(" https://example.com/image.jpg "),
|
||||
"Untrimmed src value should not appear in output");
|
||||
assertTrue(
|
||||
sanitized.contains("alt=\"pic\"") || sanitized.contains("alt='pic'"),
|
||||
"Alt attribute should remain");
|
||||
assertTrue(
|
||||
sanitized.contains("title=\"t\"") || sanitized.contains("title='t'"),
|
||||
"Title attribute should remain");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should drop empty or whitespace-only img src values")
|
||||
void shouldDropEmptyImgSrc() {
|
||||
// Arrange
|
||||
String html = "<img src=\" \" alt=\"no src\">";
|
||||
|
||||
// Act
|
||||
String sanitized = customHtmlSanitizer.sanitize(html);
|
||||
|
||||
// Assert
|
||||
assertTrue(sanitized.contains("<img"), "Image element should remain");
|
||||
assertFalse(sanitized.contains("src="), "Empty src should be removed entirely");
|
||||
assertTrue(sanitized.contains("alt=\"no src\""), "Other attributes should remain");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Anchor tag protocol handling (sanitizer-level)")
|
||||
class AnchorProtocolTests {
|
||||
@Test
|
||||
@DisplayName(
|
||||
"mailto: links should keep anchor and text; protocol may be stripped depending on"
|
||||
+ " sanitizer version")
|
||||
void mailtoAllowed() {
|
||||
String html = "<a href=\"mailto:[email protected]\">mail me</a>";
|
||||
String sanitized = customHtmlSanitizer.sanitize(html);
|
||||
|
||||
// Anchor and text should remain
|
||||
assertTrue(sanitized.contains("<a"), "Anchor element should be preserved");
|
||||
assertTrue(sanitized.contains("mail me"), "Link text should remain");
|
||||
|
||||
// Some sanitizer versions allow mailto:, others strip it. Accept both.
|
||||
boolean keepsMailto = sanitized.contains("mailto:[email protected]");
|
||||
boolean strippedHref =
|
||||
sanitized.contains("href="); // still has an href, possibly sanitized
|
||||
// We accept either: mailto kept OR href present in sanitized form OR (in strict setups)
|
||||
// href removed but anchor kept.
|
||||
assertTrue(
|
||||
keepsMailto || strippedHref || sanitized.contains("</a>"),
|
||||
"Sanitized output should keep anchor; mailto: may or may not be present"
|
||||
+ " depending on sanitizer configuration");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
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.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -17,6 +12,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
@@ -52,7 +48,7 @@ class EmlToPdfTest {
|
||||
when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString()))
|
||||
.thenReturn(true);
|
||||
when(mockApplicationProperties.getSystem()).thenReturn(mockSystem);
|
||||
when(mockSystem.getDisableSanitize()).thenReturn(false);
|
||||
when(mockSystem.isDisableSanitize()).thenReturn(false);
|
||||
|
||||
customHtmlSanitizer =
|
||||
new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties);
|
||||
@@ -765,7 +761,7 @@ class EmlToPdfTest {
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
private String getTimestamp() {
|
||||
private static String getTimestamp() {
|
||||
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);
|
||||
@@ -778,20 +774,33 @@ class EmlToPdfTest {
|
||||
private String createSimpleTextEmailWithCharset(
|
||||
String from, String to, String subject, String body, String charset) {
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"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);
|
||||
}
|
||||
|
||||
private String createEmailWithCustomHeaders() {
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"From: [email protected]\nDate: %s\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n%s",
|
||||
getTimestamp(), "This is an email body with some headers missing.");
|
||||
getTimestamp(),
|
||||
"This is an email body with some headers missing.");
|
||||
}
|
||||
|
||||
private String createHtmlEmail(String from, String to, String subject, String htmlBody) {
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"From: %s\nTo: %s\nSubject: %s\nDate: %s\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n%s",
|
||||
from, to, subject, getTimestamp(), htmlBody);
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
getTimestamp(),
|
||||
htmlBody);
|
||||
}
|
||||
|
||||
private String createMultipartEmailWithAttachment(
|
||||
@@ -806,6 +815,7 @@ class EmlToPdfTest {
|
||||
Base64.getEncoder()
|
||||
.encodeToString(attachmentContent.getBytes(StandardCharsets.UTF_8));
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"""
|
||||
From: %s
|
||||
To: %s
|
||||
@@ -845,6 +855,7 @@ class EmlToPdfTest {
|
||||
Base64.getEncoder()
|
||||
.encodeToString(attachmentEmlContent.getBytes(StandardCharsets.UTF_8));
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"""
|
||||
From: %s
|
||||
To: %s
|
||||
@@ -883,6 +894,7 @@ class EmlToPdfTest {
|
||||
private String createMultipartAlternativeEmail(
|
||||
String textBody, String htmlBody, String boundary) {
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"""
|
||||
From: %s
|
||||
To: %s
|
||||
@@ -918,6 +930,7 @@ class EmlToPdfTest {
|
||||
|
||||
private String createQuotedPrintableEmail() {
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"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",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
@@ -930,6 +943,7 @@ class EmlToPdfTest {
|
||||
String encodedBody =
|
||||
Base64.getEncoder().encodeToString(body.getBytes(StandardCharsets.UTF_8));
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"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",
|
||||
"[email protected]",
|
||||
"[email protected]",
|
||||
@@ -941,6 +955,7 @@ class EmlToPdfTest {
|
||||
private String createEmailWithInlineImage(
|
||||
String htmlBody, String boundary, String contentId, String base64Image) {
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"""
|
||||
From: %s
|
||||
To: %s
|
||||
@@ -985,6 +1000,7 @@ class EmlToPdfTest {
|
||||
String encodedAttachment =
|
||||
Base64.getEncoder().encodeToString(attachmentBody.getBytes(StandardCharsets.UTF_8));
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"""
|
||||
From: %s
|
||||
To: %s
|
||||
@@ -1039,13 +1055,13 @@ class EmlToPdfTest {
|
||||
}
|
||||
|
||||
// Creates a basic EmlToPdfRequest with default settings
|
||||
private EmlToPdfRequest createBasicRequest() {
|
||||
private static EmlToPdfRequest createBasicRequest() {
|
||||
EmlToPdfRequest request = new EmlToPdfRequest();
|
||||
request.setIncludeAttachments(false);
|
||||
return request;
|
||||
}
|
||||
|
||||
private EmlToPdfRequest createRequestWithAttachments() {
|
||||
private static EmlToPdfRequest createRequestWithAttachments() {
|
||||
EmlToPdfRequest request = new EmlToPdfRequest();
|
||||
request.setIncludeAttachments(true);
|
||||
request.setMaxAttachmentSizeMB(10);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ExceptionUtils}. Assumptions: - PdfErrorUtils.isCorruptedPdfError is a
|
||||
* static method that returns true for certain exception types. We will mock it in tests for
|
||||
* handlePdfException and logException.
|
||||
*/
|
||||
class ExceptionUtilsTest {
|
||||
|
||||
@Nested
|
||||
@DisplayName("PDF corruption exception creation")
|
||||
class PdfCorruptionTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("should create PdfCorruptedException without context")
|
||||
void testCreatePdfCorruptedExceptionWithoutContext() {
|
||||
Exception cause = new Exception("root");
|
||||
IOException ex = ExceptionUtils.createPdfCorruptedException(null, cause);
|
||||
|
||||
assertTrue(ex.getMessage().contains("PDF file appears to be corrupted"));
|
||||
assertSame(cause, ex.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should create PdfCorruptedException with context")
|
||||
void testCreatePdfCorruptedExceptionWithContext() {
|
||||
Exception cause = new Exception("root");
|
||||
IOException ex = ExceptionUtils.createPdfCorruptedException("during merge", cause);
|
||||
|
||||
assertTrue(
|
||||
ex.getMessage()
|
||||
.startsWith("Error during merge: PDF file appears to be corrupted"));
|
||||
assertSame(cause, ex.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should create MultiplePdfCorruptedException")
|
||||
void testCreateMultiplePdfCorruptedException() {
|
||||
Exception cause = new Exception("root");
|
||||
IOException ex = ExceptionUtils.createMultiplePdfCorruptedException(cause);
|
||||
|
||||
assertTrue(ex.getMessage().startsWith("One or more PDF files appear to be corrupted"));
|
||||
assertSame(cause, ex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("PDF encryption and password exception creation")
|
||||
class PdfSecurityTests {
|
||||
|
||||
@Test
|
||||
void testCreatePdfEncryptionException() {
|
||||
Exception cause = new Exception("root");
|
||||
IOException ex = ExceptionUtils.createPdfEncryptionException(cause);
|
||||
assertTrue(ex.getMessage().contains("corrupted encryption data"));
|
||||
assertSame(cause, ex.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreatePdfPasswordException() {
|
||||
Exception cause = new Exception("root");
|
||||
IOException ex = ExceptionUtils.createPdfPasswordException(cause);
|
||||
assertTrue(ex.getMessage().contains("passworded"));
|
||||
assertSame(cause, ex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("File processing exception creation")
|
||||
class FileProcessingTests {
|
||||
|
||||
@Test
|
||||
void testCreateFileProcessingException() {
|
||||
Exception cause = new Exception("boom");
|
||||
IOException ex = ExceptionUtils.createFileProcessingException("merge", cause);
|
||||
assertTrue(ex.getMessage().contains("while processing the file during merge"));
|
||||
assertSame(cause, ex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Generic exception creation")
|
||||
class GenericCreationTests {
|
||||
|
||||
@Test
|
||||
void testCreateIOException() {
|
||||
IOException ex =
|
||||
ExceptionUtils.createIOException(
|
||||
"key", "Default message: {0}", new Exception("cause"), "X");
|
||||
assertEquals("Default message: X", ex.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateRuntimeException() {
|
||||
RuntimeException ex =
|
||||
ExceptionUtils.createRuntimeException(
|
||||
"key", "Default message: {0}", new Exception("cause"), "Y");
|
||||
assertEquals("Default message: Y", ex.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateIllegalArgumentException() {
|
||||
IllegalArgumentException ex =
|
||||
ExceptionUtils.createIllegalArgumentException("key", "Format {0}", "Z");
|
||||
assertEquals("Format Z", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Predefined validation exceptions")
|
||||
class PredefinedValidationTests {
|
||||
|
||||
@Test
|
||||
void testCreateHtmlFileRequiredException() {
|
||||
IllegalArgumentException ex = ExceptionUtils.createHtmlFileRequiredException();
|
||||
assertTrue(ex.getMessage().contains("HTML or ZIP"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreatePdfFileRequiredException() {
|
||||
IllegalArgumentException ex = ExceptionUtils.createPdfFileRequiredException();
|
||||
assertTrue(ex.getMessage().contains("PDF"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateInvalidPageSizeException() {
|
||||
IllegalArgumentException ex = ExceptionUtils.createInvalidPageSizeException("A5");
|
||||
assertTrue(ex.getMessage().contains("page size"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("OCR and system requirement exceptions")
|
||||
class OcrAndSystemTests {
|
||||
|
||||
@Test
|
||||
void testCreateOcrLanguageRequiredException() {
|
||||
IOException ex = ExceptionUtils.createOcrLanguageRequiredException();
|
||||
assertTrue(ex.getMessage().contains("OCR language"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateOcrInvalidLanguagesException() {
|
||||
IOException ex = ExceptionUtils.createOcrInvalidLanguagesException();
|
||||
assertTrue(ex.getMessage().contains("none of the selected languages"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateOcrToolsUnavailableException() {
|
||||
IOException ex = ExceptionUtils.createOcrToolsUnavailableException();
|
||||
assertTrue(ex.getMessage().contains("OCR tools"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreatePythonRequiredForWebpException() {
|
||||
IOException ex = ExceptionUtils.createPythonRequiredForWebpException();
|
||||
assertTrue(ex.getMessage().contains("Python"));
|
||||
assertTrue(ex.getMessage().contains("WebP conversion"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("File operation and compression exceptions")
|
||||
class FileAndCompressionTests {
|
||||
|
||||
@Test
|
||||
void testCreatePdfaConversionFailedException() {
|
||||
RuntimeException ex = ExceptionUtils.createPdfaConversionFailedException();
|
||||
assertTrue(ex.getMessage().contains("PDF/A conversion failed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateMd5AlgorithmException() {
|
||||
RuntimeException ex = ExceptionUtils.createMd5AlgorithmException(new Exception("x"));
|
||||
assertTrue(ex.getMessage().contains("MD5"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateGhostscriptCompressionExceptionNoCause() {
|
||||
IOException ex = ExceptionUtils.createGhostscriptCompressionException();
|
||||
assertTrue(ex.getMessage().contains("Ghostscript"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateGhostscriptCompressionExceptionWithCause() {
|
||||
IOException ex =
|
||||
ExceptionUtils.createGhostscriptCompressionException(new Exception("cause"));
|
||||
assertTrue(ex.getMessage().contains("Ghostscript"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("PDF exception handling")
|
||||
class PdfExceptionHandlingTests {
|
||||
|
||||
@Test
|
||||
void testHandlePdfExceptionWhenCorrupted() {
|
||||
IOException original = new IOException("corrupted pdf");
|
||||
try (MockedStatic<PdfErrorUtils> mock = mockStatic(PdfErrorUtils.class)) {
|
||||
mock.when(() -> PdfErrorUtils.isCorruptedPdfError(original)).thenReturn(true);
|
||||
IOException result = ExceptionUtils.handlePdfException(original);
|
||||
assertNotSame(original, result);
|
||||
assertTrue(result.getMessage().contains("corrupted"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandlePdfExceptionWhenEncryptionError() {
|
||||
IOException original = new IOException("BadPaddingException");
|
||||
try (MockedStatic<PdfErrorUtils> mock = mockStatic(PdfErrorUtils.class)) {
|
||||
mock.when(() -> PdfErrorUtils.isCorruptedPdfError(original)).thenReturn(false);
|
||||
IOException result = ExceptionUtils.handlePdfException(original);
|
||||
assertTrue(result.getMessage().contains("corrupted encryption data"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandlePdfExceptionWhenPasswordError() {
|
||||
IOException original = new IOException("password is incorrect");
|
||||
try (MockedStatic<PdfErrorUtils> mock = mockStatic(PdfErrorUtils.class)) {
|
||||
mock.when(() -> PdfErrorUtils.isCorruptedPdfError(original)).thenReturn(false);
|
||||
IOException result = ExceptionUtils.handlePdfException(original);
|
||||
assertTrue(result.getMessage().contains("passworded"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandlePdfExceptionWhenNoSpecialError() {
|
||||
IOException original = new IOException("something else");
|
||||
try (MockedStatic<PdfErrorUtils> mock = mockStatic(PdfErrorUtils.class)) {
|
||||
mock.when(() -> PdfErrorUtils.isCorruptedPdfError(original)).thenReturn(false);
|
||||
IOException result = ExceptionUtils.handlePdfException(original);
|
||||
assertSame(original, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Encryption and password detection")
|
||||
class ErrorDetectionTests {
|
||||
|
||||
@Test
|
||||
void testIsEncryptionErrorTrue() {
|
||||
assertTrue(ExceptionUtils.isEncryptionError(new IOException("BadPaddingException")));
|
||||
assertTrue(
|
||||
ExceptionUtils.isEncryptionError(
|
||||
new IOException("Given final block not properly padded")));
|
||||
assertTrue(
|
||||
ExceptionUtils.isEncryptionError(
|
||||
new IOException("AES initialization vector not fully read")));
|
||||
assertTrue(ExceptionUtils.isEncryptionError(new IOException("Failed to decrypt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsEncryptionErrorFalse() {
|
||||
assertFalse(ExceptionUtils.isEncryptionError(new IOException("other message")));
|
||||
assertFalse(ExceptionUtils.isEncryptionError(new IOException((String) null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsPasswordErrorTrue() {
|
||||
assertTrue(ExceptionUtils.isPasswordError(new IOException("password is incorrect")));
|
||||
assertTrue(ExceptionUtils.isPasswordError(new IOException("Password is not provided")));
|
||||
assertTrue(
|
||||
ExceptionUtils.isPasswordError(
|
||||
new IOException("PDF contains an encryption dictionary")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsPasswordErrorFalse() {
|
||||
assertFalse(ExceptionUtils.isPasswordError(new IOException("something else")));
|
||||
assertFalse(ExceptionUtils.isPasswordError(new IOException((String) null)));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Logging behavior")
|
||||
class LoggingTests {
|
||||
|
||||
@Test
|
||||
void testLogExceptionWhenCorruptedPdf() {
|
||||
Exception e = new IOException("corrupted");
|
||||
try (MockedStatic<PdfErrorUtils> mock = mockStatic(PdfErrorUtils.class)) {
|
||||
mock.when(() -> PdfErrorUtils.isCorruptedPdfError(e)).thenReturn(true);
|
||||
// We can't assert log output here without a custom appender, but this ensures no
|
||||
// exception is thrown
|
||||
ExceptionUtils.logException("merge", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogExceptionWhenEncryptionError() {
|
||||
IOException e = new IOException("BadPaddingException");
|
||||
try (MockedStatic<PdfErrorUtils> mock = mockStatic(PdfErrorUtils.class)) {
|
||||
mock.when(() -> PdfErrorUtils.isCorruptedPdfError(e)).thenReturn(false);
|
||||
ExceptionUtils.logException("merge", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogExceptionWhenPasswordError() {
|
||||
IOException e = new IOException("password is incorrect");
|
||||
try (MockedStatic<PdfErrorUtils> mock = mockStatic(PdfErrorUtils.class)) {
|
||||
mock.when(() -> PdfErrorUtils.isCorruptedPdfError(e)).thenReturn(false);
|
||||
ExceptionUtils.logException("merge", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogExceptionUnexpectedError() {
|
||||
Exception e = new RuntimeException("unexpected");
|
||||
try (MockedStatic<PdfErrorUtils> mock = mockStatic(PdfErrorUtils.class)) {
|
||||
mock.when(() -> PdfErrorUtils.isCorruptedPdfError(e)).thenReturn(false);
|
||||
ExceptionUtils.logException("merge", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Invalid and null argument exceptions")
|
||||
class ArgumentValidationTests {
|
||||
|
||||
@Test
|
||||
void testCreateInvalidArgumentExceptionSingle() {
|
||||
IllegalArgumentException ex =
|
||||
ExceptionUtils.createInvalidArgumentException("arg", "invalidValue");
|
||||
assertTrue(ex.getMessage().contains("arg"));
|
||||
assertTrue(ex.getMessage().contains("invalidValue"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateInvalidArgumentExceptionWithValue() {
|
||||
IllegalArgumentException ex =
|
||||
ExceptionUtils.createInvalidArgumentException("arg", "val");
|
||||
assertTrue(ex.getMessage().contains("val"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateNullArgumentException() {
|
||||
IllegalArgumentException ex = ExceptionUtils.createNullArgumentException("arg");
|
||||
assertTrue(ex.getMessage().contains("arg"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -46,12 +45,15 @@ class FileMonitorTest {
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_OldFile() throws IOException {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final Instant testTime = Instant.now();
|
||||
|
||||
// Create a test file
|
||||
Path testFile = tempDir.resolve("test-file.txt");
|
||||
Files.write(testFile, "test content".getBytes());
|
||||
|
||||
// Set modified time to 10 seconds ago
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
// Set modified time to 10 seconds ago (relative to test start time)
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000)));
|
||||
|
||||
// File should be ready for processing as it was modified more than 5 seconds ago
|
||||
assertTrue(fileMonitor.isFileReadyForProcessing(testFile));
|
||||
@@ -59,12 +61,15 @@ class FileMonitorTest {
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_RecentFile() throws IOException {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final Instant testTime = Instant.now();
|
||||
|
||||
// Create a test file
|
||||
Path testFile = tempDir.resolve("recent-file.txt");
|
||||
Files.write(testFile, "test content".getBytes());
|
||||
|
||||
// Set modified time to just now
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(Instant.now()));
|
||||
// Set modified time to just now (relative to test start time)
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(testTime));
|
||||
|
||||
// File should not be ready for processing as it was just modified
|
||||
assertFalse(fileMonitor.isFileReadyForProcessing(testFile));
|
||||
@@ -81,12 +86,16 @@ class FileMonitorTest {
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_LockedFile() throws IOException {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final Instant testTime = Instant.now();
|
||||
|
||||
// Create a test file
|
||||
Path testFile = tempDir.resolve("locked-file.txt");
|
||||
Files.write(testFile, "test content".getBytes());
|
||||
|
||||
// Set modified time to 10 seconds ago to make sure it passes the time check
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
// Set modified time to 10 seconds ago (relative to test start time) to make sure it passes
|
||||
// the time check
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000)));
|
||||
|
||||
// Verify the file is considered ready when it meets the time criteria
|
||||
assertTrue(
|
||||
@@ -105,12 +114,12 @@ class FileMonitorTest {
|
||||
// Create a PDF file
|
||||
Path pdfFile = tempDir.resolve("test.pdf");
|
||||
Files.write(pdfFile, "pdf content".getBytes());
|
||||
Files.setLastModifiedTime(pdfFile, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
Files.setLastModifiedTime(pdfFile, FileTime.from(Instant.ofEpochMilli(1000000L)));
|
||||
|
||||
// Create a TXT file
|
||||
Path txtFile = tempDir.resolve("test.txt");
|
||||
Files.write(txtFile, "text content".getBytes());
|
||||
Files.setLastModifiedTime(txtFile, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
Files.setLastModifiedTime(txtFile, FileTime.from(Instant.ofEpochMilli(1000000L)));
|
||||
|
||||
// PDF file should be ready for processing
|
||||
assertTrue(pdfMonitor.isFileReadyForProcessing(pdfFile));
|
||||
@@ -126,12 +135,15 @@ class FileMonitorTest {
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_FileInUse() throws IOException {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final Instant testTime = Instant.now();
|
||||
|
||||
// Create a test file
|
||||
Path testFile = tempDir.resolve("in-use-file.txt");
|
||||
Files.write(testFile, "initial content".getBytes());
|
||||
|
||||
// Set modified time to 10 seconds ago
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
// Set modified time to 10 seconds ago (relative to test start time)
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000)));
|
||||
|
||||
// First check that the file is ready when meeting time criteria
|
||||
assertTrue(
|
||||
@@ -140,7 +152,7 @@ class FileMonitorTest {
|
||||
|
||||
// After modifying the file to simulate closing, it should still be ready
|
||||
Files.write(testFile, "updated content".getBytes());
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000)));
|
||||
|
||||
assertTrue(
|
||||
fileMonitor.isFileReadyForProcessing(testFile),
|
||||
@@ -149,12 +161,15 @@ class FileMonitorTest {
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_FileWithAbsolutePath() throws IOException {
|
||||
// Capture test time at the beginning for deterministic calculations
|
||||
final Instant testTime = Instant.now();
|
||||
|
||||
// Create a test file
|
||||
Path testFile = tempDir.resolve("absolute-path-file.txt");
|
||||
Files.write(testFile, "test content".getBytes());
|
||||
|
||||
// Set modified time to 10 seconds ago
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
// Set modified time to 10 seconds ago (relative to test start time)
|
||||
Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000)));
|
||||
|
||||
// File should be ready for processing as it was modified more than 5 seconds ago
|
||||
// Use the absolute path to make sure it's handled correctly
|
||||
@@ -168,7 +183,7 @@ class FileMonitorTest {
|
||||
Files.createDirectory(testDir);
|
||||
|
||||
// Set modified time to 10 seconds ago
|
||||
Files.setLastModifiedTime(testDir, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
Files.setLastModifiedTime(testDir, FileTime.from(Instant.ofEpochMilli(1000000L)));
|
||||
|
||||
// A directory should not be considered ready for processing
|
||||
boolean isReady = fileMonitor.isFileReadyForProcessing(testDir);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -31,7 +29,7 @@ public class FileToPdfTest {
|
||||
when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString()))
|
||||
.thenReturn(true);
|
||||
when(mockApplicationProperties.getSystem()).thenReturn(mockSystem);
|
||||
when(mockSystem.getDisableSanitize()).thenReturn(false);
|
||||
when(mockSystem.isDisableSanitize()).thenReturn(false);
|
||||
|
||||
customHtmlSanitizer =
|
||||
new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties);
|
||||
|
||||
+15
@@ -29,6 +29,21 @@ class GeneralUtilsAdditionalTest {
|
||||
assertTrue(GeneralUtils.isValidURL("https://example.com"));
|
||||
assertFalse(GeneralUtils.isValidURL("htp:/bad"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://localhost"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://0.0.0.0"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://192.168.1.1"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://169.254.0.1"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://172.16.0.1"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://192.0.2.1"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://192.0.0.0"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://192.168.0.0"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://198.18.0.1"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://198.51.100.0"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://203.0.113.0"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://10.0.0.0"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://100.64.0.1"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://224.0.0.0"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://[::ffff:127.0.0.1]/"));
|
||||
assertFalse(GeneralUtils.isURLReachable("http://[fd12:3456:789a::1]/"));
|
||||
assertFalse(GeneralUtils.isURLReachable("ftp://example.com"));
|
||||
|
||||
assertTrue(GeneralUtils.isValidUUID("123e4567-e89b-12d3-a456-426614174000"));
|
||||
|
||||
+13
-15
@@ -1,8 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
@@ -11,10 +9,18 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ImageProcessingUtilsTest {
|
||||
|
||||
private static void fillImageWithColor(BufferedImage image) {
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
image.setRGB(x, y, Color.RED.getRGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConvertColorTypeToGreyscale() {
|
||||
BufferedImage sourceImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
|
||||
fillImageWithColor(sourceImage, Color.RED);
|
||||
fillImageWithColor(sourceImage);
|
||||
|
||||
BufferedImage convertedImage =
|
||||
ImageProcessingUtils.convertColorType(sourceImage, "greyscale");
|
||||
@@ -33,7 +39,7 @@ public class ImageProcessingUtilsTest {
|
||||
@Test
|
||||
void testConvertColorTypeToBlackWhite() {
|
||||
BufferedImage sourceImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
|
||||
fillImageWithColor(sourceImage, Color.RED);
|
||||
fillImageWithColor(sourceImage);
|
||||
|
||||
BufferedImage convertedImage =
|
||||
ImageProcessingUtils.convertColorType(sourceImage, "blackwhite");
|
||||
@@ -51,7 +57,7 @@ public class ImageProcessingUtilsTest {
|
||||
@Test
|
||||
void testConvertColorTypeToFullColor() {
|
||||
BufferedImage sourceImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
|
||||
fillImageWithColor(sourceImage, Color.RED);
|
||||
fillImageWithColor(sourceImage);
|
||||
|
||||
BufferedImage convertedImage =
|
||||
ImageProcessingUtils.convertColorType(sourceImage, "fullcolor");
|
||||
@@ -63,7 +69,7 @@ public class ImageProcessingUtilsTest {
|
||||
@Test
|
||||
void testConvertColorTypeInvalid() {
|
||||
BufferedImage sourceImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
|
||||
fillImageWithColor(sourceImage, Color.RED);
|
||||
fillImageWithColor(sourceImage);
|
||||
|
||||
BufferedImage convertedImage =
|
||||
ImageProcessingUtils.convertColorType(sourceImage, "invalidtype");
|
||||
@@ -71,12 +77,4 @@ public class ImageProcessingUtilsTest {
|
||||
assertNotNull(convertedImage);
|
||||
assertEquals(sourceImage, convertedImage);
|
||||
}
|
||||
|
||||
private void fillImageWithColor(BufferedImage image, Color color) {
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
image.setRGB(x, y, color.getRGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -34,6 +32,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.ZipSecurity;
|
||||
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
|
||||
/**
|
||||
@@ -50,6 +49,7 @@ class PDFToFileTest {
|
||||
@Mock private ProcessExecutor mockProcessExecutor;
|
||||
@Mock private ProcessExecutorResult mockExecutorResult;
|
||||
@Mock private TempFileManager mockTempFileManager;
|
||||
@Mock private RuntimePathConfig mockRuntimePathConfig;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
@@ -63,7 +63,9 @@ class PDFToFileTest {
|
||||
.when(mockTempFileManager.createTempDirectory())
|
||||
.thenAnswer(invocation -> Files.createTempDirectory("test"));
|
||||
|
||||
pdfToFile = new PDFToFile(mockTempFileManager);
|
||||
lenient().when(mockRuntimePathConfig.getSOfficePath()).thenReturn("/usr/bin/soffice");
|
||||
|
||||
pdfToFile = new PDFToFile(mockTempFileManager, mockRuntimePathConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -329,12 +331,10 @@ class PDFToFileTest {
|
||||
boolean foundImage = false;
|
||||
|
||||
while ((entry = zipStream.getNextEntry()) != null) {
|
||||
if ("test.html".equals(entry.getName())) {
|
||||
foundMainHtml = true;
|
||||
} else if ("test_ind.html".equals(entry.getName())) {
|
||||
foundIndexHtml = true;
|
||||
} else if ("test_img.png".equals(entry.getName())) {
|
||||
foundImage = true;
|
||||
switch (entry.getName()) {
|
||||
case "test.html" -> foundMainHtml = true;
|
||||
case "test_ind.html" -> foundIndexHtml = true;
|
||||
case "test_img.png" -> foundImage = true;
|
||||
}
|
||||
zipStream.closeEntry();
|
||||
}
|
||||
@@ -367,7 +367,8 @@ class PDFToFileTest {
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(
|
||||
args ->
|
||||
args.contains("--convert-to")
|
||||
args != null
|
||||
&& args.contains("--convert-to")
|
||||
&& args.contains("docx"))))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
@@ -382,6 +383,7 @@ class PDFToFileTest {
|
||||
}
|
||||
|
||||
// Create output file
|
||||
assertNotNull(outDir);
|
||||
Files.write(
|
||||
Path.of(outDir, "document.docx"),
|
||||
"Fake DOCX content".getBytes());
|
||||
@@ -427,7 +429,11 @@ class PDFToFileTest {
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(args -> args.contains("--convert-to") && args.contains("odp"))))
|
||||
argThat(
|
||||
args ->
|
||||
args != null
|
||||
&& args.contains("--convert-to")
|
||||
&& args.contains("odp"))))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
// When command is executed, find the output directory argument
|
||||
@@ -442,6 +448,7 @@ class PDFToFileTest {
|
||||
|
||||
// Create multiple output files (simulating a presentation with
|
||||
// multiple files)
|
||||
assertNotNull(outDir);
|
||||
Files.write(
|
||||
Path.of(outDir, "document.odp"),
|
||||
"Fake ODP content".getBytes());
|
||||
@@ -515,7 +522,8 @@ class PDFToFileTest {
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(
|
||||
args ->
|
||||
args.contains("--convert-to")
|
||||
args != null
|
||||
&& args.contains("--convert-to")
|
||||
&& args.contains("txt:Text"))))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
@@ -530,6 +538,7 @@ class PDFToFileTest {
|
||||
}
|
||||
|
||||
// Create text output file
|
||||
assertNotNull(outDir);
|
||||
Files.write(
|
||||
Path.of(outDir, "document.txt"),
|
||||
"Extracted text content".getBytes());
|
||||
@@ -587,6 +596,7 @@ class PDFToFileTest {
|
||||
}
|
||||
|
||||
// Create output file - uses default name
|
||||
assertNotNull(outDir);
|
||||
Files.write(
|
||||
Path.of(outDir, "output.docx"),
|
||||
"Fake DOCX content".getBytes());
|
||||
@@ -611,4 +621,110 @@ class PDFToFileTest {
|
||||
.contains("output.docx"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToOfficeFormat_UsesUnoconvertWhenConfigured()
|
||||
throws IOException, InterruptedException {
|
||||
when(mockRuntimePathConfig.getUnoConvertPath()).thenReturn("/custom/unoconvert");
|
||||
PDFToFile pdfToFileWithUno = new PDFToFile(mockTempFileManager, mockRuntimePathConfig);
|
||||
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file",
|
||||
"document.pdf",
|
||||
MediaType.APPLICATION_PDF_VALUE,
|
||||
"Fake PDF content".getBytes());
|
||||
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(args -> args != null && args.contains("/custom/unoconvert"))))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
List<String> args = invocation.getArgument(0);
|
||||
String outputPath = args.get(args.size() - 1);
|
||||
Files.write(Path.of(outputPath), "Fake DOCX content".getBytes());
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
ResponseEntity<byte[]> response =
|
||||
pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import");
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("document.docx"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToOfficeFormat_FallsBackWhenUnoconvertFails()
|
||||
throws IOException, InterruptedException {
|
||||
when(mockRuntimePathConfig.getUnoConvertPath()).thenReturn("/custom/unoconvert");
|
||||
PDFToFile pdfToFileWithUno = new PDFToFile(mockTempFileManager, mockRuntimePathConfig);
|
||||
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file",
|
||||
"document.pdf",
|
||||
MediaType.APPLICATION_PDF_VALUE,
|
||||
"Fake PDF content".getBytes());
|
||||
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(args -> args != null && args.contains("/custom/unoconvert"))))
|
||||
.thenThrow(new IOException("Conversion failed"));
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(
|
||||
args ->
|
||||
args != null
|
||||
&& args.stream()
|
||||
.anyMatch(
|
||||
arg ->
|
||||
arg.contains(
|
||||
"soffice")))))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
List<String> args = invocation.getArgument(0);
|
||||
String outDir = null;
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
if ("--outdir".equals(args.get(i)) && i + 1 < args.size()) {
|
||||
outDir = args.get(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertNotNull(outDir);
|
||||
Files.write(
|
||||
Path.of(outDir, "document.docx"),
|
||||
"Fallback DOCX content".getBytes());
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
ResponseEntity<byte[]> response =
|
||||
pdfToFileWithUno.processPdfToOfficeFormat(pdfFile, "docx", "writer_pdf_import");
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("document.docx"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
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.when;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
||||
import org.apache.pdfbox.pdmodel.PDResources;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
@@ -31,32 +49,77 @@ public class PdfUtilsTest {
|
||||
|
||||
@Test
|
||||
void testTextToPageSize() {
|
||||
assertEquals(PDRectangle.A0, PdfUtils.textToPageSize("A0"));
|
||||
assertEquals(PDRectangle.A1, PdfUtils.textToPageSize("A1"));
|
||||
assertEquals(PDRectangle.A2, PdfUtils.textToPageSize("A2"));
|
||||
assertEquals(PDRectangle.A3, PdfUtils.textToPageSize("A3"));
|
||||
assertEquals(PDRectangle.A4, PdfUtils.textToPageSize("A4"));
|
||||
assertEquals(PDRectangle.A5, PdfUtils.textToPageSize("A5"));
|
||||
assertEquals(PDRectangle.A6, PdfUtils.textToPageSize("A6"));
|
||||
assertEquals(PDRectangle.LETTER, PdfUtils.textToPageSize("LETTER"));
|
||||
assertEquals(PDRectangle.LEGAL, PdfUtils.textToPageSize("LEGAL"));
|
||||
assertThrows(IllegalArgumentException.class, () -> PdfUtils.textToPageSize("INVALID"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasImagesOnPage() throws IOException {
|
||||
// Mock a PDPage and its resources
|
||||
PDPage page = Mockito.mock(PDPage.class);
|
||||
PDResources resources = Mockito.mock(PDResources.class);
|
||||
Mockito.when(page.getResources()).thenReturn(resources);
|
||||
void testGetAllImages() throws Exception {
|
||||
// Root resources
|
||||
PDResources root = mock(PDResources.class);
|
||||
|
||||
// Case 1: No images in resources
|
||||
Mockito.when(resources.getXObjectNames()).thenReturn(Collections.emptySet());
|
||||
assertFalse(PdfUtils.hasImagesOnPage(page));
|
||||
COSName im1 = COSName.getPDFName("Im1");
|
||||
COSName form1 = COSName.getPDFName("Form1");
|
||||
COSName other1 = COSName.getPDFName("Other1");
|
||||
when(root.getXObjectNames()).thenReturn(Arrays.asList(im1, form1, other1));
|
||||
|
||||
// Case 2: Resources with an image
|
||||
Set<COSName> xObjectNames = new HashSet<>();
|
||||
COSName cosName = Mockito.mock(COSName.class);
|
||||
xObjectNames.add(cosName);
|
||||
// Direct image at root
|
||||
PDImageXObject imgXObj1 = mock(PDImageXObject.class);
|
||||
BufferedImage img1 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB);
|
||||
when(imgXObj1.getImage()).thenReturn(img1);
|
||||
when(root.getXObject(im1)).thenReturn(imgXObj1);
|
||||
|
||||
PDImageXObject imageXObject = Mockito.mock(PDImageXObject.class);
|
||||
Mockito.when(resources.getXObjectNames()).thenReturn(xObjectNames);
|
||||
Mockito.when(resources.getXObject(cosName)).thenReturn(imageXObject);
|
||||
// "Other" XObject that should be ignored
|
||||
PDXObject otherXObj = mock(PDXObject.class);
|
||||
when(root.getXObject(other1)).thenReturn(otherXObj);
|
||||
|
||||
assertTrue(PdfUtils.hasImagesOnPage(page));
|
||||
// Form XObject with its own resources
|
||||
PDFormXObject formXObj = mock(PDFormXObject.class);
|
||||
PDResources formRes = mock(PDResources.class);
|
||||
when(formXObj.getResources()).thenReturn(formRes);
|
||||
when(root.getXObject(form1)).thenReturn(formXObj);
|
||||
|
||||
// Inside the form: one image and a nested form
|
||||
COSName im2 = COSName.getPDFName("Im2");
|
||||
COSName nestedForm = COSName.getPDFName("NestedForm");
|
||||
when(formRes.getXObjectNames()).thenReturn(Arrays.asList(im2, nestedForm));
|
||||
|
||||
PDImageXObject imgXObj2 = mock(PDImageXObject.class);
|
||||
BufferedImage img2 = new BufferedImage(3, 3, BufferedImage.TYPE_INT_RGB);
|
||||
when(imgXObj2.getImage()).thenReturn(img2);
|
||||
when(formRes.getXObject(im2)).thenReturn(imgXObj2);
|
||||
|
||||
PDFormXObject nestedFormXObj = mock(PDFormXObject.class);
|
||||
PDResources nestedRes = mock(PDResources.class);
|
||||
when(nestedFormXObj.getResources()).thenReturn(nestedRes);
|
||||
when(formRes.getXObject(nestedForm)).thenReturn(nestedFormXObj);
|
||||
|
||||
// Deep nest: another image
|
||||
COSName im3 = COSName.getPDFName("Im3");
|
||||
when(nestedRes.getXObjectNames()).thenReturn(List.of(im3));
|
||||
|
||||
PDImageXObject imgXObj3 = mock(PDImageXObject.class);
|
||||
BufferedImage img3 = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
when(imgXObj3.getImage()).thenReturn(img3);
|
||||
when(nestedRes.getXObject(im3)).thenReturn(imgXObj3);
|
||||
|
||||
// Act
|
||||
List<RenderedImage> result = PdfUtils.getAllImages(root);
|
||||
|
||||
// Assert
|
||||
assertEquals(
|
||||
3, result.size(), "It should find exactly 3 images (root + form + nested form).");
|
||||
assertTrue(
|
||||
result.containsAll(List.of(img1, img2, img3)),
|
||||
"All expected images must be present.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,7 +170,7 @@ public class PdfUtilsTest {
|
||||
g.fillRect(0, 0, 10, 10);
|
||||
g.dispose();
|
||||
ByteArrayOutputStream imgOut = new ByteArrayOutputStream();
|
||||
javax.imageio.ImageIO.write(image, "png", imgOut);
|
||||
ImageIO.write(image, "png", imgOut);
|
||||
|
||||
PdfMetadataService meta =
|
||||
new PdfMetadataService(new ApplicationProperties(), "label", false, null);
|
||||
@@ -120,4 +183,554 @@ public class PdfUtilsTest {
|
||||
assertEquals(1, resultDoc.getNumberOfPages());
|
||||
}
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// Additional tests (added without modifying existing ones)
|
||||
// ===============================================================
|
||||
|
||||
/* Helper: create a colored test image */
|
||||
private static BufferedImage createImage(int w, int h, Color color) {
|
||||
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = img.createGraphics();
|
||||
g.setColor(color);
|
||||
g.fillRect(0, 0, w, h);
|
||||
g.dispose();
|
||||
return img;
|
||||
}
|
||||
|
||||
/* Helper: create a factory like in existing tests */
|
||||
private static CustomPDFDocumentFactory factory() {
|
||||
PdfMetadataService meta =
|
||||
new PdfMetadataService(new ApplicationProperties(), "label", false, null);
|
||||
return new CustomPDFDocumentFactory(meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("convertPdfToPdfImage: creates image-PDF with same page count")
|
||||
void convertPdfToPdfImage_shouldCreateImagePdfWithSamePageCount() throws IOException {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p1 = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(p1);
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText("Hello PDF");
|
||||
cs.endText();
|
||||
}
|
||||
PDPage p2 = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(p2);
|
||||
|
||||
PDDocument out = PdfUtils.convertPdfToPdfImage(doc);
|
||||
assertNotNull(out);
|
||||
assertEquals(2, out.getNumberOfPages(), "Page count should be preserved");
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("imageToPdf: PNG -> single-page PDF (static ImageProcessingUtils mocked)")
|
||||
void imageToPdf_shouldCreatePdfFromPng() throws Exception {
|
||||
BufferedImage img = createImage(320, 200, Color.RED);
|
||||
ByteArrayOutputStream pngOut = new ByteArrayOutputStream();
|
||||
ImageIO.write(img, "png", pngOut);
|
||||
|
||||
MockMultipartFile file =
|
||||
new MockMultipartFile("files", "test.png", "image/png", pngOut.toByteArray());
|
||||
|
||||
try (MockedStatic<ImageProcessingUtils> mocked = mockStatic(ImageProcessingUtils.class)) {
|
||||
// Assume: loadImageWithExifOrientation/convertColorType exist – static mock
|
||||
mocked.when(() -> ImageProcessingUtils.loadImageWithExifOrientation(any()))
|
||||
.thenReturn(img);
|
||||
mocked.when(
|
||||
() ->
|
||||
ImageProcessingUtils.convertColorType(
|
||||
any(BufferedImage.class), anyString()))
|
||||
.thenAnswer(inv -> inv.getArgument(0, BufferedImage.class));
|
||||
|
||||
byte[] pdfBytes =
|
||||
PdfUtils.imageToPdf(
|
||||
new MockMultipartFile[] {file},
|
||||
"maintainAspectRatio",
|
||||
true,
|
||||
"RGB",
|
||||
factory());
|
||||
|
||||
try (PDDocument result = factory().load(pdfBytes)) {
|
||||
assertEquals(1, result.getNumberOfPages());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("imageToPdf: JPEG -> single-page PDF (JPEGFactory path)")
|
||||
void imageToPdf_shouldCreatePdfFromJpeg_UsingJpegFactory() throws Exception {
|
||||
BufferedImage img = createImage(640, 360, Color.BLUE);
|
||||
ByteArrayOutputStream jpgOut = new ByteArrayOutputStream();
|
||||
ImageIO.write(img, "jpg", jpgOut);
|
||||
|
||||
MockMultipartFile file =
|
||||
new MockMultipartFile("files", "photo.jpg", "image/jpeg", jpgOut.toByteArray());
|
||||
|
||||
try (MockedStatic<ImageProcessingUtils> mocked = mockStatic(ImageProcessingUtils.class)) {
|
||||
mocked.when(() -> ImageProcessingUtils.loadImageWithExifOrientation(any()))
|
||||
.thenReturn(img);
|
||||
mocked.when(
|
||||
() ->
|
||||
ImageProcessingUtils.convertColorType(
|
||||
any(BufferedImage.class), anyString()))
|
||||
.thenAnswer(inv -> inv.getArgument(0, BufferedImage.class));
|
||||
|
||||
byte[] pdfBytes =
|
||||
PdfUtils.imageToPdf(
|
||||
new MockMultipartFile[] {file}, "fillPage", false, "RGB", factory());
|
||||
|
||||
try (PDDocument result = factory().load(pdfBytes)) {
|
||||
assertEquals(1, result.getNumberOfPages());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("addImageToDocument: fitDocumentToImage -> page size = image size")
|
||||
void addImageToDocument_shouldUseImageSizeForPage_whenFitDocumentToImage() throws IOException {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
BufferedImage img = createImage(300, 500, Color.GREEN);
|
||||
PDImageXObject ximg = LosslessFactory.createFromImage(doc, img);
|
||||
|
||||
PdfUtils.addImageToDocument(doc, ximg, "fitDocumentToImage", false);
|
||||
|
||||
assertEquals(1, doc.getNumberOfPages());
|
||||
PDRectangle box = doc.getPage(0).getMediaBox();
|
||||
assertEquals(300, (int) box.getWidth());
|
||||
assertEquals(500, (int) box.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("addImageToDocument: autoRotate rotates A4 for landscape image")
|
||||
void addImageToDocument_shouldRotateA4_whenAutoRotateAndLandscape() throws IOException {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
BufferedImage img = createImage(800, 400, Color.ORANGE); // Landscape
|
||||
PDImageXObject ximg = LosslessFactory.createFromImage(doc, img);
|
||||
|
||||
PdfUtils.addImageToDocument(doc, ximg, "maintainAspectRatio", true);
|
||||
|
||||
assertEquals(1, doc.getNumberOfPages());
|
||||
PDRectangle box = doc.getPage(0).getMediaBox();
|
||||
assertTrue(
|
||||
box.getWidth() > box.getHeight(),
|
||||
"A4 should be landscape when auto-rotate + landscape");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("addImageToDocument: fillPage runs without errors")
|
||||
void addImageToDocument_fillPage_executes() throws IOException {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
BufferedImage img = createImage(200, 200, Color.MAGENTA);
|
||||
PDImageXObject ximg = LosslessFactory.createFromImage(doc, img);
|
||||
|
||||
PdfUtils.addImageToDocument(doc, ximg, "fillPage", false);
|
||||
|
||||
assertEquals(1, doc.getNumberOfPages());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("overlayImage: everyPage=true overlays all pages")
|
||||
void overlayImage_shouldOverlayAllPages_whenEveryPageTrue() throws IOException {
|
||||
CustomPDFDocumentFactory factory = factory();
|
||||
|
||||
// Create PDF with 2 pages
|
||||
byte[] basePdf;
|
||||
try (PDDocument doc = factory.createNewDocument()) {
|
||||
doc.addPage(new PDPage(PDRectangle.A4));
|
||||
doc.addPage(new PDPage(PDRectangle.A4));
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
doc.save(baos);
|
||||
basePdf = baos.toByteArray();
|
||||
}
|
||||
|
||||
// Create image bytes
|
||||
BufferedImage img = createImage(50, 50, Color.BLACK);
|
||||
ByteArrayOutputStream pngOut = new ByteArrayOutputStream();
|
||||
ImageIO.write(img, "png", pngOut);
|
||||
|
||||
byte[] result = PdfUtils.overlayImage(factory, basePdf, pngOut.toByteArray(), 10, 10, true);
|
||||
|
||||
try (PDDocument out = factory.load(result)) {
|
||||
assertEquals(2, out.getNumberOfPages(), "Page count remains identical");
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function: document with text on page1/page2 */
|
||||
private static PDDocument createDocWithText(String p1, String p2) throws IOException {
|
||||
PDDocument doc = new PDDocument();
|
||||
|
||||
PDPage page1 = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(page1);
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, page1, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText(p1);
|
||||
cs.endText();
|
||||
}
|
||||
|
||||
PDPage page2 = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(page2);
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, page2, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText(p2);
|
||||
cs.endText();
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("containsTextInFile: pagesToCheck='all' finds text")
|
||||
void containsTextInFile_allPages_true() throws IOException {
|
||||
try (PDDocument doc = createDocWithText("alpha", "beta")) {
|
||||
assertTrue(PdfUtils.containsTextInFile(doc, "beta", "all"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("containsTextInFile: single page '2' finds text")
|
||||
void containsTextInFile_singlePage_two_true() throws IOException {
|
||||
try (PDDocument doc = createDocWithText("alpha", "beta")) {
|
||||
assertTrue(PdfUtils.containsTextInFile(doc, "beta", "2"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("containsTextInFile: range '1-1' finds text on page 1")
|
||||
void containsTextInFile_range_oneToOne_true() throws IOException {
|
||||
try (PDDocument doc = createDocWithText("findme", "other")) {
|
||||
assertTrue(PdfUtils.containsTextInFile(doc, "findme", "1-1"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("containsTextInFile: list '1,2' finds text (whitespace robust)")
|
||||
void containsTextInFile_list_pages_true() throws IOException {
|
||||
try (PDDocument doc = createDocWithText("foo", "bar")) {
|
||||
assertTrue(PdfUtils.containsTextInFile(doc, "bar", " 1 , 2 "));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("containsTextInFile: text not present -> false")
|
||||
void containsTextInFile_textNotPresent_false() throws IOException {
|
||||
try (PDDocument doc = createDocWithText("xxx", "yyy")) {
|
||||
assertFalse(PdfUtils.containsTextInFile(doc, "zzz", "all"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("pageSize: different size returns false")
|
||||
void pageSize_shouldReturnFalse_whenSizeDoesNotMatch() throws IOException {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
doc.addPage(new PDPage(PDRectangle.A4));
|
||||
assertFalse(PdfUtils.pageSize(doc, "600x842"));
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== New: convertFromPdf – coverage =====================
|
||||
|
||||
@Test
|
||||
@DisplayName("convertFromPdf: singleImage=true creates combined PNG file (readable)")
|
||||
void convertFromPdf_singleImagePng_combinedReadable() throws Exception {
|
||||
// Create two-page PDF
|
||||
byte[] pdfBytes;
|
||||
PdfMetadataService meta =
|
||||
new PdfMetadataService(new ApplicationProperties(), "label", false, null);
|
||||
CustomPDFDocumentFactory factory = new CustomPDFDocumentFactory(meta);
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
doc.addPage(new PDPage(PDRectangle.A4));
|
||||
doc.addPage(new PDPage(PDRectangle.A4));
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
doc.save(baos);
|
||||
pdfBytes = baos.toByteArray();
|
||||
}
|
||||
|
||||
byte[] imageBytes =
|
||||
PdfUtils.convertFromPdf(
|
||||
factory, pdfBytes, "png", ImageType.RGB, true, 72, "test.pdf", false);
|
||||
|
||||
// Should be readable as a single combined PNG image
|
||||
BufferedImage img = ImageIO.read(new java.io.ByteArrayInputStream(imageBytes));
|
||||
assertNotNull(img, "PNG should be readable");
|
||||
assertTrue(img.getWidth() > 0 && img.getHeight() > 0, "Image dimensions > 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"convertFromPdf: singleImage=false returns ZIP with PNG entries (first image readable)")
|
||||
void convertFromPdf_multiImagePng_firstReadable() throws Exception {
|
||||
// Create two-page PDF
|
||||
byte[] pdfBytes;
|
||||
PdfMetadataService meta =
|
||||
new PdfMetadataService(new ApplicationProperties(), "label", false, null);
|
||||
CustomPDFDocumentFactory factory = new CustomPDFDocumentFactory(meta);
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
doc.addPage(new PDPage(PDRectangle.A4));
|
||||
doc.addPage(new PDPage(PDRectangle.A4));
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
doc.save(baos);
|
||||
pdfBytes = baos.toByteArray();
|
||||
}
|
||||
|
||||
// Act: singleImage=false -> ZIP with separate images
|
||||
byte[] zipBytes =
|
||||
PdfUtils.convertFromPdf(
|
||||
factory, pdfBytes, "png", ImageType.RGB, false, 72, "test.pdf", false);
|
||||
|
||||
// Assert: open ZIP, read first entry as PNG
|
||||
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
|
||||
ZipEntry entry = zis.getNextEntry();
|
||||
assertNotNull(entry, "ZIP should contain at least one entry");
|
||||
|
||||
ByteArrayOutputStream imgOut = new ByteArrayOutputStream();
|
||||
zis.transferTo(imgOut);
|
||||
BufferedImage first = ImageIO.read(new ByteArrayInputStream(imgOut.toByteArray()));
|
||||
|
||||
assertNotNull(first, "First PNG entry should be readable");
|
||||
assertTrue(first.getWidth() > 0 && first.getHeight() > 0, "Image dimensions > 0");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasText: detects phrase on selected pages ('1', '2', 'all')")
|
||||
void hasText_shouldDetectPhrase_onSelectedPages() throws Exception {
|
||||
// Arrange: PDF with 2 pages and text
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p1 = new PDPage(PDRectangle.A4);
|
||||
PDPage p2 = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(p1);
|
||||
doc.addPage(p2);
|
||||
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText("alpha on page 1");
|
||||
cs.endText();
|
||||
}
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p2, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText("beta on page 2");
|
||||
cs.endText();
|
||||
}
|
||||
|
||||
assertTrue(PdfUtils.hasText(doc, "1", "alpha"), "Page 1 should contain 'alpha'");
|
||||
}
|
||||
|
||||
// For further checks, create new doc with identical content
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p1 = new PDPage(PDRectangle.A4);
|
||||
PDPage p2 = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(p1);
|
||||
doc.addPage(p2);
|
||||
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText("alpha on page 1");
|
||||
cs.endText();
|
||||
}
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p2, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText("beta on page 2");
|
||||
cs.endText();
|
||||
}
|
||||
|
||||
assertTrue(PdfUtils.hasText(doc, "2", "beta"), "Page 2 should contain 'beta'");
|
||||
}
|
||||
|
||||
// Third doc for 'all'
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p1 = new PDPage(PDRectangle.A4);
|
||||
PDPage p2 = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(p1);
|
||||
doc.addPage(p2);
|
||||
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText("gamma");
|
||||
cs.endText();
|
||||
}
|
||||
assertTrue(PdfUtils.hasText(doc, "all", "gamma"), "'all' should find text on page 1");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasTextOnPage: true if page contains phrase, else false")
|
||||
void hasTextOnPage_shouldReturnTrueOnlyForPagesWithPhrase() throws Exception {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p1 = new PDPage(PDRectangle.A4);
|
||||
PDPage p2 = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(p1);
|
||||
doc.addPage(p2);
|
||||
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) {
|
||||
cs.beginText();
|
||||
cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||
cs.newLineAtOffset(50, 750);
|
||||
cs.showText("needle");
|
||||
cs.endText();
|
||||
}
|
||||
|
||||
assertTrue(PdfUtils.hasTextOnPage(p1, "needle"));
|
||||
assertTrue(!PdfUtils.hasTextOnPage(p2, "needle"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasImages: detects images on selected pages and 'all'")
|
||||
void hasImages_shouldDetectImages_onSelectedPages() throws Exception {
|
||||
// Case 1: Page 1 without image (but resources set) -> false
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p1 = new PDPage(PDRectangle.A4);
|
||||
PDPage p2 = new PDPage(PDRectangle.A4);
|
||||
p1.setResources(new PDResources());
|
||||
p2.setResources(new PDResources());
|
||||
doc.addPage(p1);
|
||||
doc.addPage(p2);
|
||||
|
||||
// Image only on page 2
|
||||
BufferedImage bi = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = bi.createGraphics();
|
||||
g.setColor(Color.GREEN);
|
||||
g.fillRect(0, 0, 20, 20);
|
||||
g.dispose();
|
||||
|
||||
PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi);
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p2, AppendMode.APPEND, true, true)) {
|
||||
cs.drawImage(ximg, 50, 700, 20, 20);
|
||||
}
|
||||
|
||||
assertTrue(!PdfUtils.hasImages(doc, "1"), "Page 1 should have no image");
|
||||
}
|
||||
|
||||
// Case 2: Page 2 with image -> true
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p1 = new PDPage(PDRectangle.A4);
|
||||
PDPage p2 = new PDPage(PDRectangle.A4);
|
||||
p1.setResources(new PDResources());
|
||||
p2.setResources(new PDResources());
|
||||
doc.addPage(p1);
|
||||
doc.addPage(p2);
|
||||
|
||||
BufferedImage bi = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = bi.createGraphics();
|
||||
g.setColor(Color.BLUE);
|
||||
g.fillRect(0, 0, 20, 20);
|
||||
g.dispose();
|
||||
|
||||
PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi);
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p2, AppendMode.APPEND, true, true)) {
|
||||
cs.drawImage(ximg, 50, 700, 20, 20);
|
||||
}
|
||||
|
||||
assertTrue(PdfUtils.hasImages(doc, "2"), "Page 2 should have an image");
|
||||
}
|
||||
|
||||
// Case 3: 'all' detects image
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p = new PDPage(PDRectangle.A4);
|
||||
p.setResources(new PDResources());
|
||||
doc.addPage(p);
|
||||
|
||||
BufferedImage bi = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB);
|
||||
PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi);
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p, AppendMode.APPEND, true, true)) {
|
||||
cs.drawImage(ximg, 20, 730, 10, 10);
|
||||
}
|
||||
|
||||
assertTrue(PdfUtils.hasImages(doc, "all"), "'all' should detect the image");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasImagesOnPage: true if page contains an image, else false")
|
||||
void hasImagesOnPage_shouldReturnTrueOnlyForPagesWithImage() throws Exception {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p1 = new PDPage(PDRectangle.A4);
|
||||
PDPage p2 = new PDPage(PDRectangle.A4);
|
||||
p1.setResources(new PDResources());
|
||||
p2.setResources(new PDResources());
|
||||
doc.addPage(p1);
|
||||
doc.addPage(p2);
|
||||
|
||||
BufferedImage bi = new BufferedImage(12, 12, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = bi.createGraphics();
|
||||
g.setColor(Color.RED);
|
||||
g.fillRect(0, 0, 12, 12);
|
||||
g.dispose();
|
||||
|
||||
PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi);
|
||||
try (PDPageContentStream cs =
|
||||
new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) {
|
||||
cs.drawImage(ximg, 40, 720, 12, 12);
|
||||
}
|
||||
|
||||
assertTrue(PdfUtils.hasImagesOnPage(p1));
|
||||
assertTrue(!PdfUtils.hasImagesOnPage(p2));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("convertFromPdf: singleImage=true with JPG -> no alpha, white background")
|
||||
void convertFromPdf_singleImageJpg_noAlphaWhiteBackground() throws Exception {
|
||||
// small 1-page PDF
|
||||
byte[] pdfBytes;
|
||||
PdfMetadataService meta =
|
||||
new PdfMetadataService(new ApplicationProperties(), "label", false, null);
|
||||
CustomPDFDocumentFactory factory = new CustomPDFDocumentFactory(meta);
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage p = new PDPage(PDRectangle.A4);
|
||||
doc.addPage(p);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
doc.save(baos);
|
||||
pdfBytes = baos.toByteArray();
|
||||
}
|
||||
|
||||
byte[] jpgBytes =
|
||||
PdfUtils.convertFromPdf(
|
||||
factory, pdfBytes, "jpg", ImageType.RGB, true, 72, "sample.pdf", false);
|
||||
|
||||
BufferedImage img = ImageIO.read(new ByteArrayInputStream(jpgBytes));
|
||||
assertNotNull(img, "JPG should be readable");
|
||||
|
||||
ColorModel cm = img.getColorModel();
|
||||
assertFalse(cm.hasAlpha(), "JPG output should have no alpha channel");
|
||||
|
||||
// JPG background should be white (approximate check)
|
||||
int rgb = img.getRGB(img.getWidth() / 2, img.getHeight() / 2) & 0x00FFFFFF;
|
||||
assertEquals(0xFFFFFF, rgb, "Background pixel should be white");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -48,9 +45,7 @@ public class ProcessExecutorTest {
|
||||
IOException thrown =
|
||||
assertThrows(
|
||||
IOException.class,
|
||||
() -> {
|
||||
processExecutor.runCommandWithOutputHandling(command);
|
||||
});
|
||||
() -> processExecutor.runCommandWithOutputHandling(command));
|
||||
|
||||
// Check the exception message to ensure it indicates the command was not found
|
||||
String errorMessage = thrown.getMessage();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -22,7 +23,7 @@ public class PropertyConfigsTest {
|
||||
boolean result = PropertyConfigs.getBooleanValue(keys, defaultValue);
|
||||
|
||||
// Verify the result
|
||||
assertEquals(true, result);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,7 +52,7 @@ public class PropertyConfigsTest {
|
||||
boolean result = PropertyConfigs.getBooleanValue(key, defaultValue);
|
||||
|
||||
// Verify the result
|
||||
assertEquals(true, result);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -62,17 +62,11 @@ public class RegexPatternUtilsTest {
|
||||
|
||||
@Test
|
||||
void testNullRegexHandling() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> {
|
||||
utils.getPattern(null);
|
||||
});
|
||||
assertThrows(IllegalArgumentException.class, () -> utils.getPattern(null));
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> {
|
||||
utils.getPattern(null, Pattern.CASE_INSENSITIVE);
|
||||
});
|
||||
() -> utils.getPattern(null, Pattern.CASE_INSENSITIVE));
|
||||
|
||||
assertFalse(utils.isCached(null));
|
||||
assertFalse(utils.removeFromCache(null));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
||||
@@ -57,8 +57,7 @@ class SpringContextHolderTest {
|
||||
void testGetBean_BeanNotFound() {
|
||||
// Arrange
|
||||
contextHolder.setApplicationContext(mockApplicationContext);
|
||||
when(mockApplicationContext.getBean(TestBean.class))
|
||||
.thenThrow(new org.springframework.beans.BeansException("Bean not found") {});
|
||||
when(mockApplicationContext.getBean(TestBean.class)).thenThrow(new MyBeansException());
|
||||
|
||||
// Act
|
||||
TestBean result = SpringContextHolder.getBean(TestBean.class);
|
||||
@@ -69,4 +68,10 @@ class SpringContextHolderTest {
|
||||
|
||||
// Simple test class
|
||||
private static class TestBean {}
|
||||
|
||||
private static class MyBeansException extends org.springframework.beans.BeansException {
|
||||
public MyBeansException() {
|
||||
super("Bean not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TempDirectory}. Assumption: TempFileManager has methods
|
||||
* createTempDirectory() and deleteTempDirectory(Path).
|
||||
*/
|
||||
class TempDirectoryTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("should create temp directory and return correct path info")
|
||||
void shouldReturnCorrectPathInfo() throws IOException {
|
||||
TempFileManager manager = mock(TempFileManager.class);
|
||||
Path tempPath = Files.createTempDirectory("testDir");
|
||||
when(manager.createTempDirectory()).thenReturn(tempPath);
|
||||
|
||||
try (TempDirectory tempDir = new TempDirectory(manager)) {
|
||||
assertEquals(
|
||||
tempPath,
|
||||
tempDir.getPath(),
|
||||
"getPath should return the created directory path");
|
||||
assertEquals(
|
||||
tempPath.toAbsolutePath().toString(),
|
||||
tempDir.getAbsolutePath(),
|
||||
"getAbsolutePath should return absolute path");
|
||||
assertTrue(tempDir.exists(), "exists should return true when directory exists");
|
||||
assertTrue(
|
||||
tempDir.toString().contains(tempPath.toAbsolutePath().toString()),
|
||||
"toString should include the absolute path");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should call deleteTempDirectory on close")
|
||||
void shouldDeleteTempDirectoryOnClose() throws IOException {
|
||||
TempFileManager manager = mock(TempFileManager.class);
|
||||
Path tempPath = Files.createTempDirectory("testDir");
|
||||
when(manager.createTempDirectory()).thenReturn(tempPath);
|
||||
|
||||
try (TempDirectory tempDir = new TempDirectory(manager)) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
ArgumentCaptor<Path> captor = ArgumentCaptor.forClass(Path.class);
|
||||
verify(manager, times(1)).deleteTempDirectory(captor.capture());
|
||||
assertEquals(
|
||||
tempPath,
|
||||
captor.getValue(),
|
||||
"deleteTempDirectory should be called with the created path");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should handle multiple close calls without exception")
|
||||
void shouldHandleMultipleCloseCalls() throws IOException {
|
||||
TempFileManager manager = mock(TempFileManager.class);
|
||||
Path tempPath = Files.createTempDirectory("testDir");
|
||||
when(manager.createTempDirectory()).thenReturn(tempPath);
|
||||
|
||||
TempDirectory tempDir = new TempDirectory(manager);
|
||||
tempDir.close();
|
||||
assertDoesNotThrow(tempDir::close, "Second close should not throw exception");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should return false for exists if directory does not exist")
|
||||
void shouldReturnFalseIfDirectoryDoesNotExist() throws IOException {
|
||||
TempFileManager manager = mock(TempFileManager.class);
|
||||
Path tempPath = Files.createTempDirectory("testDir");
|
||||
Files.delete(tempPath); // delete immediately
|
||||
when(manager.createTempDirectory()).thenReturn(tempPath);
|
||||
|
||||
try (TempDirectory tempDir = new TempDirectory(manager)) {
|
||||
assertFalse(tempDir.exists(), "exists should return false when directory is missing");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should throw IOException if createTempDirectory fails")
|
||||
void shouldThrowIfCreateTempDirectoryFails() throws IOException {
|
||||
TempFileManager manager = mock(TempFileManager.class);
|
||||
when(manager.createTempDirectory()).thenThrow(new IOException("Disk full"));
|
||||
|
||||
IOException ex = assertThrows(IOException.class, () -> new TempDirectory(manager));
|
||||
assertEquals("Disk full", ex.getMessage(), "Exception message should be propagated");
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
package stirling.software.common.util.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -106,7 +105,7 @@ class CustomColorReplaceStrategyTest {
|
||||
|
||||
} catch (Exception e) {
|
||||
// If we get here, the test failed
|
||||
org.junit.jupiter.api.Assertions.fail("Exception occurred: " + e.getMessage());
|
||||
fail("Exception occurred: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package stirling.software.common.util.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
+14
-17
@@ -1,8 +1,6 @@
|
||||
package stirling.software.common.util.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
@@ -33,22 +31,9 @@ import stirling.software.common.model.api.misc.ReplaceAndInvert;
|
||||
class InvertFullColorStrategyTest {
|
||||
|
||||
private InvertFullColorStrategy strategy;
|
||||
private MultipartFile mockPdfFile;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
// Create a simple PDF document for testing
|
||||
byte[] pdfBytes = createSimplePdfWithRectangle();
|
||||
mockPdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.pdf", MediaType.APPLICATION_PDF_VALUE, pdfBytes);
|
||||
|
||||
// Create the strategy instance
|
||||
strategy = new InvertFullColorStrategy(mockPdfFile, ReplaceAndInvert.FULL_INVERSION);
|
||||
}
|
||||
|
||||
/** Helper method to create a simple PDF with a colored rectangle for testing */
|
||||
private byte[] createSimplePdfWithRectangle() throws IOException {
|
||||
private static byte[] createSimplePdfWithRectangle() throws IOException {
|
||||
PDDocument document = new PDDocument();
|
||||
PDPage page = new PDPage(PDRectangle.A4);
|
||||
document.addPage(page);
|
||||
@@ -68,6 +53,18 @@ class InvertFullColorStrategyTest {
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
// Create a simple PDF document for testing
|
||||
byte[] pdfBytes = createSimplePdfWithRectangle();
|
||||
MultipartFile mockPdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.pdf", MediaType.APPLICATION_PDF_VALUE, pdfBytes);
|
||||
|
||||
// Create the strategy instance
|
||||
strategy = new InvertFullColorStrategy(mockPdfFile, ReplaceAndInvert.FULL_INVERSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReplace() throws IOException {
|
||||
// Test the replace method
|
||||
|
||||
+4
-6
@@ -1,7 +1,6 @@
|
||||
package stirling.software.common.util.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -16,7 +15,6 @@ class PdfTextStripperCustomTest {
|
||||
|
||||
private PdfTextStripperCustom stripper;
|
||||
private PDPage mockPage;
|
||||
private PDRectangle mockMediaBox;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
@@ -25,7 +23,7 @@ class PdfTextStripperCustomTest {
|
||||
|
||||
// Create mock objects
|
||||
mockPage = mock(PDPage.class);
|
||||
mockMediaBox = mock(PDRectangle.class);
|
||||
PDRectangle mockMediaBox = mock(PDRectangle.class);
|
||||
|
||||
// Configure mock behavior
|
||||
when(mockPage.getMediaBox()).thenReturn(mockMediaBox);
|
||||
@@ -43,14 +41,14 @@ class PdfTextStripperCustomTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBasicFunctionality() throws IOException {
|
||||
void testBasicFunctionality() {
|
||||
// Simply test that the method runs without exceptions
|
||||
try {
|
||||
stripper.addRegion("testRegion", new java.awt.geom.Rectangle2D.Float(0, 0, 100, 100));
|
||||
stripper.extractRegions(mockPage);
|
||||
assertTrue(true, "Should execute without errors");
|
||||
} catch (Exception e) {
|
||||
assertTrue(false, "Method should not throw exception: " + e.getMessage());
|
||||
fail("Method should not throw exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package stirling.software.common.util.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
+6
-9
@@ -1,9 +1,6 @@
|
||||
package stirling.software.common.util.propertyeditor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -33,7 +30,7 @@ class StringToArrayListPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
assertInstanceOf(List.class, value, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
@@ -63,7 +60,7 @@ class StringToArrayListPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
assertInstanceOf(List.class, value, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
@@ -91,7 +88,7 @@ class StringToArrayListPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
assertInstanceOf(List.class, value, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
@@ -106,7 +103,7 @@ class StringToArrayListPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
assertInstanceOf(List.class, value, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
@@ -125,7 +122,7 @@ class StringToArrayListPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
assertInstanceOf(List.class, value, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
|
||||
+5
-8
@@ -1,9 +1,6 @@
|
||||
package stirling.software.common.util.propertyeditor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -30,7 +27,7 @@ class StringToMapPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof Map, "Value should be a Map");
|
||||
assertInstanceOf(Map.class, value, "Value should be a Map");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> map = (Map<String, String>) value;
|
||||
@@ -50,7 +47,7 @@ class StringToMapPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof Map, "Value should be a Map");
|
||||
assertInstanceOf(Map.class, value, "Value should be a Map");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> map = (Map<String, String>) value;
|
||||
@@ -68,7 +65,7 @@ class StringToMapPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof Map, "Value should be a Map");
|
||||
assertInstanceOf(Map.class, value, "Value should be a Map");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> map = (Map<String, String>) value;
|
||||
@@ -87,7 +84,7 @@ class StringToMapPropertyEditorTest {
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof Map, "Value should be a Map");
|
||||
assertInstanceOf(Map.class, value, "Value should be a Map");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> map = (Map<String, String>) value;
|
||||
|
||||
Reference in New Issue
Block a user