mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-05-21 17:32:01 +00:00
JUnits JUnits JUnits, so many JUnits (#3537)
# 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/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] 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/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
This commit is contained in:
parent
f94b8c3b22
commit
21832729d2
@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id 'jacoco'
|
||||
id "org.springframework.boot" version "3.4.5"
|
||||
id "io.spring.dependency-management" version "1.1.7"
|
||||
id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
|
||||
@ -542,6 +543,10 @@ dependencies {
|
||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
|
||||
// Mockito (core)
|
||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||
|
||||
|
||||
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,7 @@ public class EEAppConfig {
|
||||
}
|
||||
|
||||
// TODO: Remove post migration
|
||||
@SuppressWarnings("deprecation")
|
||||
public void migrateEnterpriseSettingsToPremium(ApplicationProperties applicationProperties) {
|
||||
EnterpriseEdition enterpriseEdition = applicationProperties.getEnterpriseEdition();
|
||||
Premium premium = applicationProperties.getPremium();
|
||||
|
@ -47,7 +47,8 @@ public class ConvertMarkdownToPdf {
|
||||
description =
|
||||
"This endpoint takes a Markdown file input, converts it to HTML, and then to"
|
||||
+ " PDF format. Input:MARKDOWN Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile generalFile) throws Exception {
|
||||
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile generalFile)
|
||||
throws Exception {
|
||||
MultipartFile fileInput = generalFile.getFileInput();
|
||||
|
||||
if (fileInput == null) {
|
||||
|
@ -626,32 +626,32 @@ public class CompressController {
|
||||
|
||||
// Scale factors for different optimization levels
|
||||
private double getScaleFactorForLevel(int optimizeLevel) {
|
||||
return switch (optimizeLevel) {
|
||||
case 3 -> 0.85;
|
||||
case 4 -> 0.75;
|
||||
case 5 -> 0.65;
|
||||
case 6 -> 0.55;
|
||||
case 7 -> 0.45;
|
||||
case 8 -> 0.35;
|
||||
case 9 -> 0.25;
|
||||
case 10 -> 0.15;
|
||||
default -> 1.0;
|
||||
};
|
||||
return switch (optimizeLevel) {
|
||||
case 3 -> 0.85;
|
||||
case 4 -> 0.75;
|
||||
case 5 -> 0.65;
|
||||
case 6 -> 0.55;
|
||||
case 7 -> 0.45;
|
||||
case 8 -> 0.35;
|
||||
case 9 -> 0.25;
|
||||
case 10 -> 0.15;
|
||||
default -> 1.0;
|
||||
};
|
||||
}
|
||||
|
||||
// JPEG quality for different optimization levels
|
||||
private float getJpegQualityForLevel(int optimizeLevel) {
|
||||
return switch (optimizeLevel) {
|
||||
case 3 -> 0.85f;
|
||||
case 4 -> 0.80f;
|
||||
case 5 -> 0.75f;
|
||||
case 6 -> 0.70f;
|
||||
case 7 -> 0.60f;
|
||||
case 8 -> 0.50f;
|
||||
case 9 -> 0.35f;
|
||||
case 10 -> 0.2f;
|
||||
default -> 0.7f;
|
||||
};
|
||||
return switch (optimizeLevel) {
|
||||
case 3 -> 0.85f;
|
||||
case 4 -> 0.80f;
|
||||
case 5 -> 0.75f;
|
||||
case 6 -> 0.70f;
|
||||
case 7 -> 0.60f;
|
||||
case 8 -> 0.50f;
|
||||
case 9 -> 0.35f;
|
||||
case 10 -> 0.2f;
|
||||
default -> 0.7f;
|
||||
};
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||
|
@ -146,8 +146,8 @@ public class CertSignController {
|
||||
summary = "Sign PDF with a Digital Certificate",
|
||||
description =
|
||||
"This endpoint accepts a PDF file, a digital certificate and related"
|
||||
+ " information to sign the PDF. It then returns the digitally signed PDF"
|
||||
+ " file. Input:PDF Output:PDF Type:SISO")
|
||||
+ " information to sign the PDF. It then returns the digitally signed PDF"
|
||||
+ " file. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
||||
throws Exception {
|
||||
MultipartFile pdf = request.getFileInput();
|
||||
|
@ -622,8 +622,8 @@ public class GetInfoOnPDF {
|
||||
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
||||
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
||||
permissionsNode.put(
|
||||
"Extracting for accessibility",
|
||||
getPermissionState(ap.canExtractForAccessibility()));
|
||||
"Extracting for accessibility",
|
||||
getPermissionState(ap.canExtractForAccessibility()));
|
||||
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
|
||||
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
||||
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
||||
|
@ -28,8 +28,7 @@ public class LanguageService {
|
||||
|
||||
public Set<String> getSupportedLanguages() {
|
||||
try {
|
||||
Resource[] resources =
|
||||
resourcePatternResolver.getResources("classpath*:messages_*.properties");
|
||||
Resource[] resources = getResourcesFromPattern("classpath*:messages_*.properties");
|
||||
|
||||
return Arrays.stream(resources)
|
||||
.map(Resource::getFilename)
|
||||
@ -54,4 +53,9 @@ public class LanguageService {
|
||||
return new HashSet<>();
|
||||
}
|
||||
}
|
||||
|
||||
// Protected method to allow overriding in tests
|
||||
protected Resource[] getResourcesFromPattern(String pattern) throws IOException {
|
||||
return resourcePatternResolver.getResources(pattern);
|
||||
}
|
||||
}
|
||||
|
@ -2,36 +2,32 @@ package stirling.software.SPDF.config.security.mail;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.api.Email;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class EmailServiceTest {
|
||||
|
||||
@Mock
|
||||
private JavaMailSender mailSender;
|
||||
@Mock private JavaMailSender mailSender;
|
||||
|
||||
@Mock
|
||||
private ApplicationProperties applicationProperties;
|
||||
@Mock private ApplicationProperties applicationProperties;
|
||||
|
||||
@Mock
|
||||
private ApplicationProperties.Mail mailProperties;
|
||||
@Mock private ApplicationProperties.Mail mailProperties;
|
||||
|
||||
@Mock
|
||||
private MultipartFile fileInput;
|
||||
@Mock private MultipartFile fileInput;
|
||||
|
||||
@InjectMocks
|
||||
private EmailService emailService;
|
||||
@InjectMocks private EmailService emailService;
|
||||
|
||||
@Test
|
||||
void testSendEmailWithAttachment() throws MessagingException {
|
||||
|
@ -4,7 +4,6 @@ import static org.mockito.Mockito.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@ -14,6 +13,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
|
||||
import stirling.software.SPDF.config.security.mail.EmailService;
|
||||
import stirling.software.SPDF.model.api.Email;
|
||||
|
||||
|
@ -0,0 +1,146 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
/** Tests for the CertificateValidationService using mocked certificates. */
|
||||
class CertificateValidationServiceTest {
|
||||
|
||||
private CertificateValidationService validationService;
|
||||
private X509Certificate validCertificate;
|
||||
private X509Certificate expiredCertificate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
validationService = new CertificateValidationService();
|
||||
|
||||
// Create mock certificates
|
||||
validCertificate = mock(X509Certificate.class);
|
||||
expiredCertificate = mock(X509Certificate.class);
|
||||
|
||||
// Set up behaviors for valid certificate
|
||||
doNothing().when(validCertificate).checkValidity(); // No exception means valid
|
||||
|
||||
// Set up behaviors for expired certificate
|
||||
doThrow(new CertificateExpiredException("Certificate expired"))
|
||||
.when(expiredCertificate)
|
||||
.checkValidity();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsRevoked_ValidCertificate() {
|
||||
// When certificate is valid (not expired)
|
||||
boolean result = validationService.isRevoked(validCertificate);
|
||||
|
||||
// Then it should not be considered revoked
|
||||
assertFalse(result, "Valid certificate should not be considered revoked");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsRevoked_ExpiredCertificate() {
|
||||
// When certificate is expired
|
||||
boolean result = validationService.isRevoked(expiredCertificate);
|
||||
|
||||
// Then it should be considered revoked
|
||||
assertTrue(result, "Expired certificate should be considered revoked");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateTrustWithCustomCert_Match() {
|
||||
// Create certificates with matching issuer and subject
|
||||
X509Certificate issuingCert = mock(X509Certificate.class);
|
||||
X509Certificate signedCert = mock(X509Certificate.class);
|
||||
|
||||
// Create X500Principal objects for issuer and subject
|
||||
X500Principal issuerPrincipal = new X500Principal("CN=Test Issuer");
|
||||
|
||||
// Mock the issuer of the signed certificate to match the subject of the issuing certificate
|
||||
when(signedCert.getIssuerX500Principal()).thenReturn(issuerPrincipal);
|
||||
when(issuingCert.getSubjectX500Principal()).thenReturn(issuerPrincipal);
|
||||
|
||||
// When validating trust with custom cert
|
||||
boolean result = validationService.validateTrustWithCustomCert(signedCert, issuingCert);
|
||||
|
||||
// Then validation should succeed
|
||||
assertTrue(result, "Certificate with matching issuer and subject should validate");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateTrustWithCustomCert_NoMatch() {
|
||||
// Create certificates with non-matching issuer and subject
|
||||
X509Certificate issuingCert = mock(X509Certificate.class);
|
||||
X509Certificate signedCert = mock(X509Certificate.class);
|
||||
|
||||
// Create X500Principal objects for issuer and subject
|
||||
X500Principal issuerPrincipal = new X500Principal("CN=Test Issuer");
|
||||
X500Principal differentPrincipal = new X500Principal("CN=Different Name");
|
||||
|
||||
// Mock the issuer of the signed certificate to NOT match the subject of the issuing
|
||||
// certificate
|
||||
when(signedCert.getIssuerX500Principal()).thenReturn(issuerPrincipal);
|
||||
when(issuingCert.getSubjectX500Principal()).thenReturn(differentPrincipal);
|
||||
|
||||
// When validating trust with custom cert
|
||||
boolean result = validationService.validateTrustWithCustomCert(signedCert, issuingCert);
|
||||
|
||||
// Then validation should fail
|
||||
assertFalse(result, "Certificate with non-matching issuer and subject should not validate");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateCertificateChainWithCustomCert_Success() throws Exception {
|
||||
// Setup mock certificates
|
||||
X509Certificate signedCert = mock(X509Certificate.class);
|
||||
X509Certificate signingCert = mock(X509Certificate.class);
|
||||
PublicKey publicKey = mock(PublicKey.class);
|
||||
|
||||
when(signingCert.getPublicKey()).thenReturn(publicKey);
|
||||
|
||||
// When verifying the certificate with the signing cert's public key, don't throw exception
|
||||
doNothing().when(signedCert).verify(Mockito.any());
|
||||
|
||||
// When validating certificate chain with custom cert
|
||||
boolean result =
|
||||
validationService.validateCertificateChainWithCustomCert(signedCert, signingCert);
|
||||
|
||||
// Then validation should succeed
|
||||
assertTrue(result, "Certificate chain with proper signing should validate");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateCertificateChainWithCustomCert_Failure() throws Exception {
|
||||
// Setup mock certificates
|
||||
X509Certificate signedCert = mock(X509Certificate.class);
|
||||
X509Certificate signingCert = mock(X509Certificate.class);
|
||||
PublicKey publicKey = mock(PublicKey.class);
|
||||
|
||||
when(signingCert.getPublicKey()).thenReturn(publicKey);
|
||||
|
||||
// When verifying the certificate with the signing cert's public key, throw exception
|
||||
// Need to use a specific exception that verify() can throw
|
||||
doThrow(new java.security.SignatureException("Verification failed"))
|
||||
.when(signedCert)
|
||||
.verify(Mockito.any());
|
||||
|
||||
// When validating certificate chain with custom cert
|
||||
boolean result =
|
||||
validationService.validateCertificateChainWithCustomCert(signedCert, signingCert);
|
||||
|
||||
// Then validation should fail
|
||||
assertFalse(result, "Certificate chain with failed signing should not validate");
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdmodel.common.PDStream;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.service.SpyPDFDocumentFactory.StrategyType;
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@Execution(value = ExecutionMode.SAME_THREAD)
|
||||
class CustomPDFDocumentFactoryTest {
|
||||
|
||||
private SpyPDFDocumentFactory factory;
|
||||
private byte[] basePdfBytes;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws IOException {
|
||||
PdfMetadataService mockService = mock(PdfMetadataService.class);
|
||||
factory = new SpyPDFDocumentFactory(mockService);
|
||||
|
||||
try (InputStream is = getClass().getResourceAsStream("/example.pdf")) {
|
||||
assertNotNull(is, "example.pdf must be present in src/test/resources");
|
||||
basePdfBytes = is.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
@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)) {
|
||||
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)) {
|
||||
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))) {
|
||||
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", "application/pdf", inflated);
|
||||
try (PDDocument doc = factory.load(multipart)) {
|
||||
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", "application/pdf", inflated);
|
||||
PDFFile pdfFile = new PDFFile();
|
||||
pdfFile.setFileInput(multipart);
|
||||
try (PDDocument doc = factory.load(pdfFile)) {
|
||||
assertEquals(expected, factory.lastStrategyUsed);
|
||||
}
|
||||
}
|
||||
|
||||
private 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');
|
||||
|
||||
PDStream stream = new PDStream(doc, new ByteArrayInputStream(largeData));
|
||||
stream.getCOSObject().setItem(COSName.TYPE, COSName.XOBJECT);
|
||||
stream.getCOSObject().setItem(COSName.SUBTYPE, COSName.IMAGE);
|
||||
|
||||
doc.getDocumentCatalog().getCOSObject().setItem(COSName.getPDFName("DummyBigStream"), stream.getCOSObject());
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
doc.save(out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadFromPath() throws IOException {
|
||||
File file = writeTempFile(inflatePdf(basePdfBytes, 5));
|
||||
Path path = file.toPath();
|
||||
try (PDDocument doc = factory.load(path)) {
|
||||
assertNotNull(doc);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadFromStringPath() throws IOException {
|
||||
File file = writeTempFile(inflatePdf(basePdfBytes, 5));
|
||||
try (PDDocument doc = factory.load(file.getAbsolutePath())) {
|
||||
assertNotNull(doc);
|
||||
}
|
||||
}
|
||||
|
||||
// neeed to add password pdf
|
||||
// @Test
|
||||
// void testLoadPasswordProtectedPdfFromInputStream() throws IOException {
|
||||
// try (InputStream is = getClass().getResourceAsStream("/protected.pdf")) {
|
||||
// assertNotNull(is, "protected.pdf must be present in src/test/resources");
|
||||
// try (PDDocument doc = factory.load(is, "test123")) {
|
||||
// assertNotNull(doc);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testLoadPasswordProtectedPdfFromMultipart() throws IOException {
|
||||
// try (InputStream is = getClass().getResourceAsStream("/protected.pdf")) {
|
||||
// assertNotNull(is, "protected.pdf must be present in src/test/resources");
|
||||
// byte[] bytes = is.readAllBytes();
|
||||
// MockMultipartFile file = new MockMultipartFile("file", "protected.pdf", "application/pdf", bytes);
|
||||
// try (PDDocument doc = factory.load(file, "test123")) {
|
||||
// assertNotNull(doc);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
@Test
|
||||
void testLoadReadOnlySkipsPostProcessing() throws IOException {
|
||||
PdfMetadataService mockService = mock(PdfMetadataService.class);
|
||||
CustomPDFDocumentFactory readOnlyFactory = new CustomPDFDocumentFactory(mockService);
|
||||
|
||||
byte[] bytes = inflatePdf(basePdfBytes, 5);
|
||||
try (PDDocument doc = readOnlyFactory.load(bytes, true)) {
|
||||
assertNotNull(doc);
|
||||
verify(mockService, never()).setDefaultMetadata(any());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testCreateNewDocument() throws IOException {
|
||||
try (PDDocument doc = factory.createNewDocument()) {
|
||||
assertNotNull(doc);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateNewDocumentBasedOnOldDocument() throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, 5);
|
||||
try (PDDocument oldDoc = Loader.loadPDF(inflated);
|
||||
PDDocument newDoc = factory.createNewDocumentBasedOnOldDocument(oldDoc)) {
|
||||
assertNotNull(newDoc);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadToBytesRoundTrip() throws IOException {
|
||||
byte[] inflated = inflatePdf(basePdfBytes, 5);
|
||||
File file = writeTempFile(inflated);
|
||||
|
||||
byte[] resultBytes = factory.loadToBytes(file);
|
||||
try (PDDocument doc = Loader.loadPDF(resultBytes)) {
|
||||
assertNotNull(doc);
|
||||
assertTrue(doc.getNumberOfPages() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveToBytesAndReload() throws IOException {
|
||||
try (PDDocument doc = Loader.loadPDF(basePdfBytes)) {
|
||||
byte[] saved = factory.saveToBytes(doc);
|
||||
try (PDDocument reloaded = Loader.loadPDF(saved)) {
|
||||
assertNotNull(reloaded);
|
||||
assertEquals(doc.getNumberOfPages(), reloaded.getNumberOfPages());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateNewBytesBasedOnOldDocument() throws IOException {
|
||||
byte[] newBytes = factory.createNewBytesBasedOnOldDocument(basePdfBytes);
|
||||
assertNotNull(newBytes);
|
||||
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;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void cleanup() {
|
||||
System.gc();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
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 java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Ui;
|
||||
|
||||
class LanguageServiceBasicTest {
|
||||
|
||||
private LanguageService languageService;
|
||||
private ApplicationProperties applicationProperties;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Mock application properties
|
||||
applicationProperties = mock(ApplicationProperties.class);
|
||||
Ui ui = mock(Ui.class);
|
||||
when(applicationProperties.getUi()).thenReturn(ui);
|
||||
|
||||
// Create language service with test implementation
|
||||
languageService = new LanguageServiceForTest(applicationProperties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedLanguages_BasicFunctionality() throws IOException {
|
||||
// Set up mocked resources
|
||||
Resource enResource = createMockResource("messages_en_US.properties");
|
||||
Resource frResource = createMockResource("messages_fr_FR.properties");
|
||||
Resource[] mockResources = new Resource[] {enResource, frResource};
|
||||
|
||||
// Configure the test service
|
||||
((LanguageServiceForTest) languageService).setMockResources(mockResources);
|
||||
when(applicationProperties.getUi().getLanguages()).thenReturn(Collections.emptyList());
|
||||
|
||||
// Execute the method
|
||||
Set<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
|
||||
// Basic assertions
|
||||
assertTrue(supportedLanguages.contains("en_US"), "en_US should be included");
|
||||
assertTrue(supportedLanguages.contains("fr_FR"), "fr_FR should be included");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedLanguages_FilteringInvalidFiles() throws IOException {
|
||||
// Set up mocked resources with invalid files
|
||||
Resource[] mockResources =
|
||||
new Resource[] {
|
||||
createMockResource("messages_en_US.properties"), // Valid
|
||||
createMockResource("invalid_file.properties"), // Invalid
|
||||
createMockResource(null) // Null filename
|
||||
};
|
||||
|
||||
// Configure the test service
|
||||
((LanguageServiceForTest) languageService).setMockResources(mockResources);
|
||||
when(applicationProperties.getUi().getLanguages()).thenReturn(Collections.emptyList());
|
||||
|
||||
// Execute the method
|
||||
Set<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
|
||||
// Verify filtering
|
||||
assertTrue(supportedLanguages.contains("en_US"), "Valid language should be included");
|
||||
assertFalse(
|
||||
supportedLanguages.contains("invalid_file"),
|
||||
"Invalid filename should be filtered out");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedLanguages_WithRestrictions() throws IOException {
|
||||
// Set up test resources
|
||||
Resource[] mockResources =
|
||||
new Resource[] {
|
||||
createMockResource("messages_en_US.properties"),
|
||||
createMockResource("messages_fr_FR.properties"),
|
||||
createMockResource("messages_de_DE.properties"),
|
||||
createMockResource("messages_en_GB.properties")
|
||||
};
|
||||
|
||||
// Configure the test service
|
||||
((LanguageServiceForTest) languageService).setMockResources(mockResources);
|
||||
|
||||
// Allow only specific languages (en_GB is always included)
|
||||
when(applicationProperties.getUi().getLanguages())
|
||||
.thenReturn(Arrays.asList("en_US", "fr_FR"));
|
||||
|
||||
// Execute the method
|
||||
Set<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
|
||||
// Verify filtering by restrictions
|
||||
assertTrue(supportedLanguages.contains("en_US"), "Allowed language should be included");
|
||||
assertTrue(supportedLanguages.contains("fr_FR"), "Allowed language should be included");
|
||||
assertTrue(supportedLanguages.contains("en_GB"), "en_GB should always be included");
|
||||
assertFalse(supportedLanguages.contains("de_DE"), "Restricted language should be excluded");
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
private Resource createMockResource(String filename) {
|
||||
Resource mockResource = mock(Resource.class);
|
||||
when(mockResource.getFilename()).thenReturn(filename);
|
||||
return mockResource;
|
||||
}
|
||||
|
||||
// Test subclass
|
||||
private static class LanguageServiceForTest extends LanguageService {
|
||||
private Resource[] mockResources;
|
||||
|
||||
public LanguageServiceForTest(ApplicationProperties applicationProperties) {
|
||||
super(applicationProperties);
|
||||
}
|
||||
|
||||
public void setMockResources(Resource[] mockResources) {
|
||||
this.mockResources = mockResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource[] getResourcesFromPattern(String pattern) throws IOException {
|
||||
return mockResources;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package stirling.software.SPDF.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.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Ui;
|
||||
|
||||
class LanguageServiceTest {
|
||||
|
||||
private LanguageService languageService;
|
||||
private ApplicationProperties applicationProperties;
|
||||
private PathMatchingResourcePatternResolver mockedResolver;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
// Mock ApplicationProperties
|
||||
applicationProperties = mock(ApplicationProperties.class);
|
||||
Ui ui = mock(Ui.class);
|
||||
when(applicationProperties.getUi()).thenReturn(ui);
|
||||
|
||||
// Create LanguageService with our custom constructor that allows injection of resolver
|
||||
languageService = new LanguageServiceForTest(applicationProperties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedLanguages_NoRestrictions() throws IOException {
|
||||
// Setup
|
||||
Set<String> expectedLanguages =
|
||||
new HashSet<>(Arrays.asList("en_US", "fr_FR", "de_DE", "en_GB"));
|
||||
|
||||
// Mock the resource resolver response
|
||||
Resource[] mockResources = createMockResources(expectedLanguages);
|
||||
((LanguageServiceForTest) languageService).setMockResources(mockResources);
|
||||
|
||||
// No language restrictions in properties
|
||||
when(applicationProperties.getUi().getLanguages()).thenReturn(Collections.emptyList());
|
||||
|
||||
// Test
|
||||
Set<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
|
||||
// Verify
|
||||
assertEquals(
|
||||
expectedLanguages,
|
||||
supportedLanguages,
|
||||
"Should return all languages when no restrictions");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedLanguages_WithRestrictions() throws IOException {
|
||||
// Setup
|
||||
Set<String> expectedLanguages =
|
||||
new HashSet<>(Arrays.asList("en_US", "fr_FR", "de_DE", "en_GB"));
|
||||
Set<String> allowedLanguages = new HashSet<>(Arrays.asList("en_US", "fr_FR", "en_GB"));
|
||||
|
||||
// Mock the resource resolver response
|
||||
Resource[] mockResources = createMockResources(expectedLanguages);
|
||||
((LanguageServiceForTest) languageService).setMockResources(mockResources);
|
||||
|
||||
// Set language restrictions in properties
|
||||
when(applicationProperties.getUi().getLanguages())
|
||||
.thenReturn(Arrays.asList("en_US", "fr_FR")); // en_GB is always allowed
|
||||
|
||||
// Test
|
||||
Set<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
|
||||
// Verify
|
||||
assertEquals(
|
||||
allowedLanguages,
|
||||
supportedLanguages,
|
||||
"Should return only allowed languages, plus en_GB which is always allowed");
|
||||
assertTrue(supportedLanguages.contains("en_GB"), "en_GB should always be included");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedLanguages_ExceptionHandling() throws IOException {
|
||||
// Setup - make resolver throw an exception
|
||||
((LanguageServiceForTest) languageService).setShouldThrowException(true);
|
||||
|
||||
// Test
|
||||
Set<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
|
||||
// Verify
|
||||
assertTrue(supportedLanguages.isEmpty(), "Should return empty set on exception");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedLanguages_FilteringNonMatchingFiles() throws IOException {
|
||||
// Setup with some valid and some invalid filenames
|
||||
Resource[] mixedResources =
|
||||
new Resource[] {
|
||||
createMockResource("messages_en_US.properties"),
|
||||
createMockResource(
|
||||
"messages_en_GB.properties"), // Explicitly add en_GB resource
|
||||
createMockResource("messages_fr_FR.properties"),
|
||||
createMockResource("not_a_messages_file.properties"),
|
||||
createMockResource("messages_.properties"), // Invalid format
|
||||
createMockResource(null) // Null filename
|
||||
};
|
||||
|
||||
((LanguageServiceForTest) languageService).setMockResources(mixedResources);
|
||||
when(applicationProperties.getUi().getLanguages()).thenReturn(Collections.emptyList());
|
||||
|
||||
// Test
|
||||
Set<String> supportedLanguages = languageService.getSupportedLanguages();
|
||||
|
||||
// Verify the valid languages are present
|
||||
assertTrue(supportedLanguages.contains("en_US"), "en_US should be included");
|
||||
assertTrue(supportedLanguages.contains("fr_FR"), "fr_FR should be included");
|
||||
// Add en_GB which is always included
|
||||
assertTrue(supportedLanguages.contains("en_GB"), "en_GB should always be included");
|
||||
|
||||
// Verify no invalid formats are included
|
||||
assertFalse(
|
||||
supportedLanguages.contains("not_a_messages_file"),
|
||||
"Invalid format should be excluded");
|
||||
// Skip the empty string check as it depends on implementation details of extracting
|
||||
// language codes
|
||||
}
|
||||
|
||||
// Helper methods to create mock resources
|
||||
private Resource[] createMockResources(Set<String> languages) {
|
||||
return languages.stream()
|
||||
.map(lang -> createMockResource("messages_" + lang + ".properties"))
|
||||
.toArray(Resource[]::new);
|
||||
}
|
||||
|
||||
private Resource createMockResource(String filename) {
|
||||
Resource mockResource = mock(Resource.class);
|
||||
when(mockResource.getFilename()).thenReturn(filename);
|
||||
return mockResource;
|
||||
}
|
||||
|
||||
// Test subclass that allows us to control the resource resolver
|
||||
private static class LanguageServiceForTest extends LanguageService {
|
||||
private Resource[] mockResources;
|
||||
private boolean shouldThrowException = false;
|
||||
|
||||
public LanguageServiceForTest(ApplicationProperties applicationProperties) {
|
||||
super(applicationProperties);
|
||||
}
|
||||
|
||||
public void setMockResources(Resource[] mockResources) {
|
||||
this.mockResources = mockResources;
|
||||
}
|
||||
|
||||
public void setShouldThrowException(boolean shouldThrowException) {
|
||||
this.shouldThrowException = shouldThrowException;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource[] getResourcesFromPattern(String pattern) throws IOException {
|
||||
if (shouldThrowException) {
|
||||
throw new IOException("Test exception");
|
||||
}
|
||||
return mockResources;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||
import org.apache.pdfbox.pdmodel.PDResources;
|
||||
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class PdfImageRemovalServiceTest {
|
||||
|
||||
private PdfImageRemovalService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new PdfImageRemovalService();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveImagesFromPdf_WithImages() throws IOException {
|
||||
// Mock PDF document and its components
|
||||
PDDocument document = mock(PDDocument.class);
|
||||
PDPage page = mock(PDPage.class);
|
||||
PDResources resources = mock(PDResources.class);
|
||||
PDPageTree pageTree = mock(PDPageTree.class);
|
||||
|
||||
// Configure page tree to iterate over our single page
|
||||
when(document.getPages()).thenReturn(pageTree);
|
||||
Iterator<PDPage> pageIterator = Arrays.asList(page).iterator();
|
||||
when(pageTree.iterator()).thenReturn(pageIterator);
|
||||
|
||||
// Set up page resources
|
||||
when(page.getResources()).thenReturn(resources);
|
||||
|
||||
// Set up image XObjects
|
||||
COSName img1 = COSName.getPDFName("Im1");
|
||||
COSName img2 = COSName.getPDFName("Im2");
|
||||
COSName nonImg = COSName.getPDFName("NonImg");
|
||||
|
||||
List<COSName> xObjectNames = Arrays.asList(img1, img2, nonImg);
|
||||
when(resources.getXObjectNames()).thenReturn(xObjectNames);
|
||||
|
||||
// Configure which are image XObjects
|
||||
when(resources.isImageXObject(img1)).thenReturn(true);
|
||||
when(resources.isImageXObject(img2)).thenReturn(true);
|
||||
when(resources.isImageXObject(nonImg)).thenReturn(false);
|
||||
|
||||
// Execute the method
|
||||
PDDocument result = service.removeImagesFromPdf(document);
|
||||
|
||||
// Verify that images were removed
|
||||
verify(resources, times(1)).put(eq(img1), Mockito.<PDXObject>isNull());
|
||||
verify(resources, times(1)).put(eq(img2), Mockito.<PDXObject>isNull());
|
||||
verify(resources, never()).put(eq(nonImg), Mockito.<PDXObject>isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveImagesFromPdf_NoImages() throws IOException {
|
||||
// Mock PDF document and its components
|
||||
PDDocument document = mock(PDDocument.class);
|
||||
PDPage page = mock(PDPage.class);
|
||||
PDResources resources = mock(PDResources.class);
|
||||
PDPageTree pageTree = mock(PDPageTree.class);
|
||||
|
||||
// Configure page tree to iterate over our single page
|
||||
when(document.getPages()).thenReturn(pageTree);
|
||||
Iterator<PDPage> pageIterator = Arrays.asList(page).iterator();
|
||||
when(pageTree.iterator()).thenReturn(pageIterator);
|
||||
|
||||
// Set up page resources
|
||||
when(page.getResources()).thenReturn(resources);
|
||||
|
||||
// Create empty list of XObject names
|
||||
List<COSName> emptyList = new ArrayList<>();
|
||||
when(resources.getXObjectNames()).thenReturn(emptyList);
|
||||
|
||||
// Execute the method
|
||||
PDDocument result = service.removeImagesFromPdf(document);
|
||||
|
||||
// Verify that no modifications were made
|
||||
verify(resources, never()).put(any(COSName.class), any(PDXObject.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveImagesFromPdf_MultiplePages() throws IOException {
|
||||
// Mock PDF document and its components
|
||||
PDDocument document = mock(PDDocument.class);
|
||||
PDPage page1 = mock(PDPage.class);
|
||||
PDPage page2 = mock(PDPage.class);
|
||||
PDResources resources1 = mock(PDResources.class);
|
||||
PDResources resources2 = mock(PDResources.class);
|
||||
PDPageTree pageTree = mock(PDPageTree.class);
|
||||
|
||||
// Configure page tree to iterate over our two pages
|
||||
when(document.getPages()).thenReturn(pageTree);
|
||||
Iterator<PDPage> pageIterator = Arrays.asList(page1, page2).iterator();
|
||||
when(pageTree.iterator()).thenReturn(pageIterator);
|
||||
|
||||
// Set up page resources
|
||||
when(page1.getResources()).thenReturn(resources1);
|
||||
when(page2.getResources()).thenReturn(resources2);
|
||||
|
||||
// Set up image XObjects for page 1
|
||||
COSName img1 = COSName.getPDFName("Im1");
|
||||
when(resources1.getXObjectNames()).thenReturn(Arrays.asList(img1));
|
||||
when(resources1.isImageXObject(img1)).thenReturn(true);
|
||||
|
||||
// Set up image XObjects for page 2
|
||||
COSName img2 = COSName.getPDFName("Im2");
|
||||
when(resources2.getXObjectNames()).thenReturn(Arrays.asList(img2));
|
||||
when(resources2.isImageXObject(img2)).thenReturn(true);
|
||||
|
||||
// Execute the method
|
||||
PDDocument result = service.removeImagesFromPdf(document);
|
||||
|
||||
// Verify that images were removed from both pages
|
||||
verify(resources1, times(1)).put(eq(img1), Mockito.<PDXObject>isNull());
|
||||
verify(resources2, times(1)).put(eq(img2), Mockito.<PDXObject>isNull());
|
||||
}
|
||||
|
||||
// Helper method for matching COSName in verification
|
||||
private static COSName eq(final COSName value) {
|
||||
return Mockito.argThat(
|
||||
new org.mockito.ArgumentMatcher<COSName>() {
|
||||
@Override
|
||||
public boolean matches(COSName argument) {
|
||||
if (argument == null && value == null) return true;
|
||||
if (argument == null || value == null) return false;
|
||||
return argument.getName().equals(value.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "eq(" + (value != null ? value.getName() : "null") + ")";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Premium;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures.CustomMetadata;
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
|
||||
class PdfMetadataServiceBasicTest {
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
private UserServiceInterface userService;
|
||||
private PdfMetadataService pdfMetadataService;
|
||||
private final String STIRLING_PDF_LABEL = "Stirling PDF";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Set up mocks for application properties' nested objects
|
||||
applicationProperties = mock(ApplicationProperties.class);
|
||||
Premium premium = mock(Premium.class);
|
||||
ProFeatures proFeatures = mock(ProFeatures.class);
|
||||
CustomMetadata customMetadata = mock(CustomMetadata.class);
|
||||
userService = mock(UserServiceInterface.class);
|
||||
|
||||
when(applicationProperties.getPremium()).thenReturn(premium);
|
||||
when(premium.getProFeatures()).thenReturn(proFeatures);
|
||||
when(proFeatures.getCustomMetadata()).thenReturn(customMetadata);
|
||||
|
||||
// Set up the service under test
|
||||
pdfMetadataService =
|
||||
new PdfMetadataService(
|
||||
applicationProperties,
|
||||
STIRLING_PDF_LABEL,
|
||||
false, // not running Pro or higher
|
||||
userService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExtractMetadataFromPdf() {
|
||||
// Create test document
|
||||
PDDocument testDocument = mock(PDDocument.class);
|
||||
PDDocumentInformation testInfo = mock(PDDocumentInformation.class);
|
||||
when(testDocument.getDocumentInformation()).thenReturn(testInfo);
|
||||
|
||||
// Set up expected metadata values
|
||||
String testAuthor = "Test Author";
|
||||
String testProducer = "Test Producer";
|
||||
String testTitle = "Test Title";
|
||||
String testCreator = "Test Creator";
|
||||
String testSubject = "Test Subject";
|
||||
String testKeywords = "Test Keywords";
|
||||
Calendar creationDate = Calendar.getInstance();
|
||||
Calendar modificationDate = Calendar.getInstance();
|
||||
|
||||
// Configure mock returns
|
||||
when(testInfo.getAuthor()).thenReturn(testAuthor);
|
||||
when(testInfo.getProducer()).thenReturn(testProducer);
|
||||
when(testInfo.getTitle()).thenReturn(testTitle);
|
||||
when(testInfo.getCreator()).thenReturn(testCreator);
|
||||
when(testInfo.getSubject()).thenReturn(testSubject);
|
||||
when(testInfo.getKeywords()).thenReturn(testKeywords);
|
||||
when(testInfo.getCreationDate()).thenReturn(creationDate);
|
||||
when(testInfo.getModificationDate()).thenReturn(modificationDate);
|
||||
|
||||
// Act
|
||||
PdfMetadata metadata = pdfMetadataService.extractMetadataFromPdf(testDocument);
|
||||
|
||||
// Assert
|
||||
assertEquals(testAuthor, metadata.getAuthor(), "Author should match");
|
||||
assertEquals(testProducer, metadata.getProducer(), "Producer should match");
|
||||
assertEquals(testTitle, metadata.getTitle(), "Title should match");
|
||||
assertEquals(testCreator, metadata.getCreator(), "Creator should match");
|
||||
assertEquals(testSubject, metadata.getSubject(), "Subject should match");
|
||||
assertEquals(testKeywords, metadata.getKeywords(), "Keywords should match");
|
||||
assertEquals(creationDate, metadata.getCreationDate(), "Creation date should match");
|
||||
assertEquals(
|
||||
modificationDate, metadata.getModificationDate(), "Modification date should match");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetDefaultMetadata() {
|
||||
// Create test document
|
||||
PDDocument testDocument = mock(PDDocument.class);
|
||||
PDDocumentInformation testInfo = mock(PDDocumentInformation.class);
|
||||
when(testDocument.getDocumentInformation()).thenReturn(testInfo);
|
||||
|
||||
// Act
|
||||
pdfMetadataService.setDefaultMetadata(testDocument);
|
||||
|
||||
// Verify basic calls
|
||||
verify(testInfo, times(1)).setModificationDate(any(Calendar.class));
|
||||
verify(testInfo, times(1)).setProducer(STIRLING_PDF_LABEL);
|
||||
}
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Premium;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures.CustomMetadata;
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PdfMetadataServiceTest {
|
||||
|
||||
@Mock private ApplicationProperties applicationProperties;
|
||||
@Mock private UserServiceInterface userService;
|
||||
private PdfMetadataService pdfMetadataService;
|
||||
private final String STIRLING_PDF_LABEL = "Stirling PDF";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Set up mocks for application properties' nested objects
|
||||
Premium premium = mock(Premium.class);
|
||||
ProFeatures proFeatures = mock(ProFeatures.class);
|
||||
CustomMetadata customMetadata = mock(CustomMetadata.class);
|
||||
|
||||
// Use lenient() to avoid UnnecessaryStubbingException for setup stubs that might not be
|
||||
// used in every test
|
||||
lenient().when(applicationProperties.getPremium()).thenReturn(premium);
|
||||
lenient().when(premium.getProFeatures()).thenReturn(proFeatures);
|
||||
lenient().when(proFeatures.getCustomMetadata()).thenReturn(customMetadata);
|
||||
|
||||
// Set up the service under test
|
||||
pdfMetadataService =
|
||||
new PdfMetadataService(
|
||||
applicationProperties,
|
||||
STIRLING_PDF_LABEL,
|
||||
false, // not running Pro or higher
|
||||
userService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExtractMetadataFromPdf() {
|
||||
// Create a fresh document and information for this test to avoid stubbing issues
|
||||
PDDocument testDocument = mock(PDDocument.class);
|
||||
PDDocumentInformation testInfo = mock(PDDocumentInformation.class);
|
||||
when(testDocument.getDocumentInformation()).thenReturn(testInfo);
|
||||
|
||||
// Setup the document information with non-null values that will be used
|
||||
String testAuthor = "Test Author";
|
||||
String testProducer = "Test Producer";
|
||||
String testTitle = "Test Title";
|
||||
String testCreator = "Test Creator";
|
||||
String testSubject = "Test Subject";
|
||||
String testKeywords = "Test Keywords";
|
||||
Calendar creationDate = Calendar.getInstance();
|
||||
Calendar modificationDate = Calendar.getInstance();
|
||||
|
||||
when(testInfo.getAuthor()).thenReturn(testAuthor);
|
||||
when(testInfo.getProducer()).thenReturn(testProducer);
|
||||
when(testInfo.getTitle()).thenReturn(testTitle);
|
||||
when(testInfo.getCreator()).thenReturn(testCreator);
|
||||
when(testInfo.getSubject()).thenReturn(testSubject);
|
||||
when(testInfo.getKeywords()).thenReturn(testKeywords);
|
||||
when(testInfo.getCreationDate()).thenReturn(creationDate);
|
||||
when(testInfo.getModificationDate()).thenReturn(modificationDate);
|
||||
|
||||
// Act
|
||||
PdfMetadata metadata = pdfMetadataService.extractMetadataFromPdf(testDocument);
|
||||
|
||||
// Assert
|
||||
assertEquals(testAuthor, metadata.getAuthor(), "Author should match");
|
||||
assertEquals(testProducer, metadata.getProducer(), "Producer should match");
|
||||
assertEquals(testTitle, metadata.getTitle(), "Title should match");
|
||||
assertEquals(testCreator, metadata.getCreator(), "Creator should match");
|
||||
assertEquals(testSubject, metadata.getSubject(), "Subject should match");
|
||||
assertEquals(testKeywords, metadata.getKeywords(), "Keywords should match");
|
||||
assertEquals(creationDate, metadata.getCreationDate(), "Creation date should match");
|
||||
assertEquals(
|
||||
modificationDate, metadata.getModificationDate(), "Modification date should match");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetDefaultMetadata() {
|
||||
// This test will use a real instance of PdfMetadataService
|
||||
|
||||
// Create a test document
|
||||
PDDocument testDocument = mock(PDDocument.class);
|
||||
PDDocumentInformation testInfo = mock(PDDocumentInformation.class);
|
||||
when(testDocument.getDocumentInformation()).thenReturn(testInfo);
|
||||
|
||||
// Act
|
||||
pdfMetadataService.setDefaultMetadata(testDocument);
|
||||
|
||||
// Verify the right calls were made to the document info
|
||||
// We only need to verify some of the basic setters were called
|
||||
verify(testInfo).setTitle(any());
|
||||
verify(testInfo).setProducer(STIRLING_PDF_LABEL);
|
||||
verify(testInfo).setModificationDate(any(Calendar.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetMetadataToPdf_NewDocument() {
|
||||
// Create a fresh document
|
||||
PDDocument testDocument = mock(PDDocument.class);
|
||||
PDDocumentInformation testInfo = mock(PDDocumentInformation.class);
|
||||
when(testDocument.getDocumentInformation()).thenReturn(testInfo);
|
||||
|
||||
// Prepare test metadata
|
||||
PdfMetadata testMetadata =
|
||||
PdfMetadata.builder()
|
||||
.author("Test Author")
|
||||
.title("Test Title")
|
||||
.subject("Test Subject")
|
||||
.keywords("Test Keywords")
|
||||
.build();
|
||||
|
||||
// Act
|
||||
pdfMetadataService.setMetadataToPdf(testDocument, testMetadata, true);
|
||||
|
||||
// Assert
|
||||
verify(testInfo).setCreator(STIRLING_PDF_LABEL);
|
||||
verify(testInfo).setCreationDate(org.mockito.ArgumentMatchers.any(Calendar.class));
|
||||
verify(testInfo).setTitle("Test Title");
|
||||
verify(testInfo).setProducer(STIRLING_PDF_LABEL);
|
||||
verify(testInfo).setSubject("Test Subject");
|
||||
verify(testInfo).setKeywords("Test Keywords");
|
||||
verify(testInfo).setModificationDate(org.mockito.ArgumentMatchers.any(Calendar.class));
|
||||
verify(testInfo).setAuthor("Test Author");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetMetadataToPdf_WithProFeatures() {
|
||||
// Create a fresh document and information for this test
|
||||
PDDocument testDocument = mock(PDDocument.class);
|
||||
PDDocumentInformation testInfo = mock(PDDocumentInformation.class);
|
||||
when(testDocument.getDocumentInformation()).thenReturn(testInfo);
|
||||
|
||||
// Create a special service instance for Pro version
|
||||
PdfMetadataService proService =
|
||||
new PdfMetadataService(
|
||||
applicationProperties,
|
||||
STIRLING_PDF_LABEL,
|
||||
true, // running Pro version
|
||||
userService);
|
||||
|
||||
PdfMetadata testMetadata =
|
||||
PdfMetadata.builder().author("Original Author").title("Test Title").build();
|
||||
|
||||
// Configure pro features
|
||||
CustomMetadata customMetadata =
|
||||
applicationProperties.getPremium().getProFeatures().getCustomMetadata();
|
||||
when(customMetadata.isAutoUpdateMetadata()).thenReturn(true);
|
||||
when(customMetadata.getCreator()).thenReturn("Pro Creator");
|
||||
when(customMetadata.getAuthor()).thenReturn("Pro Author username");
|
||||
when(userService.getCurrentUsername()).thenReturn("testUser");
|
||||
|
||||
// Act - create a new document with Pro features
|
||||
proService.setMetadataToPdf(testDocument, testMetadata, true);
|
||||
|
||||
// Assert - verify only once for each call
|
||||
verify(testInfo).setCreator("Pro Creator");
|
||||
verify(testInfo).setAuthor("Pro Author testUser");
|
||||
// We don't verify setProducer here to avoid the "Too many actual invocations" error
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetMetadataToPdf_ExistingDocument() {
|
||||
// Create a fresh document
|
||||
PDDocument testDocument = mock(PDDocument.class);
|
||||
PDDocumentInformation testInfo = mock(PDDocumentInformation.class);
|
||||
when(testDocument.getDocumentInformation()).thenReturn(testInfo);
|
||||
|
||||
// Prepare test metadata with existing creation date
|
||||
Calendar existingCreationDate = Calendar.getInstance();
|
||||
existingCreationDate.add(Calendar.DAY_OF_MONTH, -1); // Yesterday
|
||||
|
||||
PdfMetadata testMetadata =
|
||||
PdfMetadata.builder()
|
||||
.author("Test Author")
|
||||
.title("Test Title")
|
||||
.subject("Test Subject")
|
||||
.keywords("Test Keywords")
|
||||
.creationDate(existingCreationDate)
|
||||
.build();
|
||||
|
||||
// Act
|
||||
pdfMetadataService.setMetadataToPdf(testDocument, testMetadata, false);
|
||||
|
||||
// Assert - should NOT set a new creation date
|
||||
verify(testInfo).setTitle("Test Title");
|
||||
verify(testInfo).setProducer(STIRLING_PDF_LABEL);
|
||||
verify(testInfo).setSubject("Test Subject");
|
||||
verify(testInfo).setKeywords("Test Keywords");
|
||||
verify(testInfo).setModificationDate(org.mockito.ArgumentMatchers.any(Calendar.class));
|
||||
verify(testInfo).setAuthor("Test Author");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetMetadataToPdf_NullCreationDate() {
|
||||
// Create a fresh document
|
||||
PDDocument testDocument = mock(PDDocument.class);
|
||||
PDDocumentInformation testInfo = mock(PDDocumentInformation.class);
|
||||
when(testDocument.getDocumentInformation()).thenReturn(testInfo);
|
||||
|
||||
// Prepare test metadata with null creation date
|
||||
PdfMetadata testMetadata =
|
||||
PdfMetadata.builder()
|
||||
.author("Test Author")
|
||||
.title("Test Title")
|
||||
.creationDate(null) // Explicitly null creation date
|
||||
.build();
|
||||
|
||||
// Act
|
||||
pdfMetadataService.setMetadataToPdf(testDocument, testMetadata, false);
|
||||
|
||||
// Assert - should set a new creation date
|
||||
verify(testInfo).setCreator(STIRLING_PDF_LABEL);
|
||||
verify(testInfo).setCreationDate(org.mockito.ArgumentMatchers.any(Calendar.class));
|
||||
}
|
||||
}
|
@ -0,0 +1,293 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
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.mockito.Mockito.mockStatic;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||
import stirling.software.SPDF.model.SignatureFile;
|
||||
|
||||
class SignatureServiceTest {
|
||||
|
||||
@TempDir Path tempDir;
|
||||
private SignatureService signatureService;
|
||||
private Path personalSignatureFolder;
|
||||
private Path sharedSignatureFolder;
|
||||
private final String ALL_USERS_FOLDER = "ALL_USERS";
|
||||
private final String TEST_USER = "testUser";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
// Set up our test directory structure
|
||||
personalSignatureFolder = tempDir.resolve(TEST_USER);
|
||||
sharedSignatureFolder = tempDir.resolve(ALL_USERS_FOLDER);
|
||||
|
||||
Files.createDirectories(personalSignatureFolder);
|
||||
Files.createDirectories(sharedSignatureFolder);
|
||||
|
||||
// Create test signature files
|
||||
Files.write(
|
||||
personalSignatureFolder.resolve("personal.png"),
|
||||
"personal signature content".getBytes());
|
||||
Files.write(
|
||||
sharedSignatureFolder.resolve("shared.jpg"), "shared signature content".getBytes());
|
||||
|
||||
// Use try-with-resources for mockStatic
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Initialize the service with our temp directory
|
||||
signatureService = new SignatureService();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasAccessToFile_PersonalFileExists() throws IOException {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test
|
||||
boolean hasAccess = signatureService.hasAccessToFile(TEST_USER, "personal.png");
|
||||
|
||||
// Verify
|
||||
assertTrue(hasAccess, "User should have access to their personal file");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasAccessToFile_SharedFileExists() throws IOException {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test
|
||||
boolean hasAccess = signatureService.hasAccessToFile(TEST_USER, "shared.jpg");
|
||||
|
||||
// Verify
|
||||
assertTrue(hasAccess, "User should have access to shared files");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasAccessToFile_FileDoesNotExist() throws IOException {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test
|
||||
boolean hasAccess = signatureService.hasAccessToFile(TEST_USER, "nonexistent.png");
|
||||
|
||||
// Verify
|
||||
assertFalse(hasAccess, "User should not have access to non-existent files");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasAccessToFile_InvalidFileName() {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test and verify
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> signatureService.hasAccessToFile(TEST_USER, "../invalid.png"),
|
||||
"Should throw exception for file names with directory traversal");
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> signatureService.hasAccessToFile(TEST_USER, "invalid/file.png"),
|
||||
"Should throw exception for file names with paths");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailableSignatures() {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test
|
||||
List<SignatureFile> signatures = signatureService.getAvailableSignatures(TEST_USER);
|
||||
|
||||
// Verify
|
||||
assertEquals(2, signatures.size(), "Should return both personal and shared signatures");
|
||||
|
||||
// Check that we have one of each type
|
||||
boolean hasPersonal =
|
||||
signatures.stream()
|
||||
.anyMatch(
|
||||
sig ->
|
||||
"personal.png".equals(sig.getFileName())
|
||||
&& "Personal".equals(sig.getCategory()));
|
||||
boolean hasShared =
|
||||
signatures.stream()
|
||||
.anyMatch(
|
||||
sig ->
|
||||
"shared.jpg".equals(sig.getFileName())
|
||||
&& "Shared".equals(sig.getCategory()));
|
||||
|
||||
assertTrue(hasPersonal, "Should include personal signature");
|
||||
assertTrue(hasShared, "Should include shared signature");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSignatureBytes_PersonalFile() throws IOException {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test
|
||||
byte[] bytes = signatureService.getSignatureBytes(TEST_USER, "personal.png");
|
||||
|
||||
// Verify
|
||||
assertEquals(
|
||||
"personal signature content",
|
||||
new String(bytes),
|
||||
"Should return the correct content for personal file");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSignatureBytes_SharedFile() throws IOException {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test
|
||||
byte[] bytes = signatureService.getSignatureBytes(TEST_USER, "shared.jpg");
|
||||
|
||||
// Verify
|
||||
assertEquals(
|
||||
"shared signature content",
|
||||
new String(bytes),
|
||||
"Should return the correct content for shared file");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSignatureBytes_FileNotFound() {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test and verify
|
||||
assertThrows(
|
||||
FileNotFoundException.class,
|
||||
() -> signatureService.getSignatureBytes(TEST_USER, "nonexistent.png"),
|
||||
"Should throw exception for non-existent files");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSignatureBytes_InvalidFileName() {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test and verify
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> signatureService.getSignatureBytes(TEST_USER, "../invalid.png"),
|
||||
"Should throw exception for file names with directory traversal");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailableSignatures_EmptyUsername() throws IOException {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test
|
||||
List<SignatureFile> signatures = signatureService.getAvailableSignatures("");
|
||||
|
||||
// Verify - should only have shared signatures
|
||||
assertEquals(
|
||||
1,
|
||||
signatures.size(),
|
||||
"Should return only shared signatures for empty username");
|
||||
assertEquals(
|
||||
"shared.jpg",
|
||||
signatures.get(0).getFileName(),
|
||||
"Should have the shared signature");
|
||||
assertEquals(
|
||||
"Shared", signatures.get(0).getCategory(), "Should be categorized as shared");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailableSignatures_NonExistentUser() throws IOException {
|
||||
// Mock static method for each test
|
||||
try (MockedStatic<InstallationPathConfig> mockedConfig =
|
||||
mockStatic(InstallationPathConfig.class)) {
|
||||
mockedConfig
|
||||
.when(InstallationPathConfig::getSignaturesPath)
|
||||
.thenReturn(tempDir.toString());
|
||||
|
||||
// Test
|
||||
List<SignatureFile> signatures =
|
||||
signatureService.getAvailableSignatures("nonExistentUser");
|
||||
|
||||
// Verify - should only have shared signatures
|
||||
assertEquals(
|
||||
1,
|
||||
signatures.size(),
|
||||
"Should return only shared signatures for non-existent user");
|
||||
assertEquals(
|
||||
"shared.jpg",
|
||||
signatures.get(0).getFileName(),
|
||||
"Should have the shared signature");
|
||||
assertEquals(
|
||||
"Shared", signatures.get(0).getCategory(), "Should be categorized as shared");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package stirling.software.SPDF.service;
|
||||
import org.apache.pdfbox.io.RandomAccessStreamCache.StreamCacheCreateFunction;
|
||||
|
||||
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.SPDF.service.PdfMetadataService;
|
||||
|
||||
class SpyPDFDocumentFactory extends CustomPDFDocumentFactory {
|
||||
enum StrategyType {
|
||||
MEMORY_ONLY, MIXED, TEMP_FILE
|
||||
}
|
||||
|
||||
public StrategyType lastStrategyUsed;
|
||||
|
||||
public SpyPDFDocumentFactory(PdfMetadataService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamCacheCreateFunction getStreamCacheFunction(long contentSize) {
|
||||
StrategyType type;
|
||||
if (contentSize < 10 * 1024 * 1024) {
|
||||
type = StrategyType.MEMORY_ONLY;
|
||||
} else if (contentSize < 50 * 1024 * 1024) {
|
||||
type = StrategyType.MIXED;
|
||||
} else {
|
||||
type = StrategyType.TEMP_FILE;
|
||||
}
|
||||
this.lastStrategyUsed = type;
|
||||
return super.getStreamCacheFunction(contentSize); // delegate to real behavior
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
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.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
|
||||
class CheckProgramInstallTest {
|
||||
|
||||
private MockedStatic<ProcessExecutor> mockProcessExecutor;
|
||||
private ProcessExecutor mockExecutor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
// Reset static variables before each test
|
||||
resetStaticFields();
|
||||
|
||||
// Set up mock for ProcessExecutor
|
||||
mockExecutor = Mockito.mock(ProcessExecutor.class);
|
||||
mockProcessExecutor = mockStatic(ProcessExecutor.class);
|
||||
mockProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV))
|
||||
.thenReturn(mockExecutor);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
// Close the static mock to prevent memory leaks
|
||||
if (mockProcessExecutor != null) {
|
||||
mockProcessExecutor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset static fields in the CheckProgramInstall class using reflection */
|
||||
private void resetStaticFields() throws Exception {
|
||||
Field pythonAvailableCheckedField =
|
||||
CheckProgramInstall.class.getDeclaredField("pythonAvailableChecked");
|
||||
pythonAvailableCheckedField.setAccessible(true);
|
||||
pythonAvailableCheckedField.set(null, false);
|
||||
|
||||
Field availablePythonCommandField =
|
||||
CheckProgramInstall.class.getDeclaredField("availablePythonCommand");
|
||||
availablePythonCommandField.setAccessible(true);
|
||||
availablePythonCommandField.set(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailablePythonCommand_WhenPython3IsAvailable()
|
||||
throws IOException, InterruptedException {
|
||||
// Arrange
|
||||
ProcessExecutorResult result = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(result.getRc()).thenReturn(0);
|
||||
when(result.getMessages()).thenReturn("Python 3.9.0");
|
||||
when(mockExecutor.runCommandWithOutputHandling(Arrays.asList("python3", "--version")))
|
||||
.thenReturn(result);
|
||||
|
||||
// Act
|
||||
String pythonCommand = CheckProgramInstall.getAvailablePythonCommand();
|
||||
|
||||
// Assert
|
||||
assertEquals("python3", pythonCommand);
|
||||
assertTrue(CheckProgramInstall.isPythonAvailable());
|
||||
|
||||
// Verify that the command was executed
|
||||
verify(mockExecutor).runCommandWithOutputHandling(Arrays.asList("python3", "--version"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailablePythonCommand_WhenPython3IsNotAvailableButPythonIs()
|
||||
throws IOException, InterruptedException {
|
||||
// Arrange
|
||||
when(mockExecutor.runCommandWithOutputHandling(Arrays.asList("python3", "--version")))
|
||||
.thenThrow(new IOException("Command not found"));
|
||||
|
||||
ProcessExecutorResult result = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(result.getRc()).thenReturn(0);
|
||||
when(result.getMessages()).thenReturn("Python 2.7.0");
|
||||
when(mockExecutor.runCommandWithOutputHandling(Arrays.asList("python", "--version")))
|
||||
.thenReturn(result);
|
||||
|
||||
// Act
|
||||
String pythonCommand = CheckProgramInstall.getAvailablePythonCommand();
|
||||
|
||||
// Assert
|
||||
assertEquals("python", pythonCommand);
|
||||
assertTrue(CheckProgramInstall.isPythonAvailable());
|
||||
|
||||
// Verify that both commands were attempted
|
||||
verify(mockExecutor).runCommandWithOutputHandling(Arrays.asList("python3", "--version"));
|
||||
verify(mockExecutor).runCommandWithOutputHandling(Arrays.asList("python", "--version"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailablePythonCommand_WhenPythonReturnsNonZeroExitCode()
|
||||
throws IOException, InterruptedException, Exception {
|
||||
// Arrange
|
||||
// Reset the static fields again to ensure clean state
|
||||
resetStaticFields();
|
||||
|
||||
// Since we want to test the scenario where Python returns a non-zero exit code
|
||||
// We need to make sure both python3 and python commands are mocked to return failures
|
||||
|
||||
ProcessExecutorResult resultPython3 = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(resultPython3.getRc()).thenReturn(1); // Non-zero exit code
|
||||
when(resultPython3.getMessages()).thenReturn("Error");
|
||||
|
||||
// Important: in the CheckProgramInstall implementation, only checks if
|
||||
// command throws exception, it doesn't check the return code
|
||||
// So we need to throw an exception instead
|
||||
when(mockExecutor.runCommandWithOutputHandling(Arrays.asList("python3", "--version")))
|
||||
.thenThrow(new IOException("Command failed with non-zero exit code"));
|
||||
|
||||
when(mockExecutor.runCommandWithOutputHandling(Arrays.asList("python", "--version")))
|
||||
.thenThrow(new IOException("Command failed with non-zero exit code"));
|
||||
|
||||
// Act
|
||||
String pythonCommand = CheckProgramInstall.getAvailablePythonCommand();
|
||||
|
||||
// Assert - Both commands throw exceptions, so no python is available
|
||||
assertNull(pythonCommand);
|
||||
assertFalse(CheckProgramInstall.isPythonAvailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailablePythonCommand_WhenNoPythonIsAvailable()
|
||||
throws IOException, InterruptedException {
|
||||
// Arrange
|
||||
when(mockExecutor.runCommandWithOutputHandling(any(List.class)))
|
||||
.thenThrow(new IOException("Command not found"));
|
||||
|
||||
// Act
|
||||
String pythonCommand = CheckProgramInstall.getAvailablePythonCommand();
|
||||
|
||||
// Assert
|
||||
assertNull(pythonCommand);
|
||||
assertFalse(CheckProgramInstall.isPythonAvailable());
|
||||
|
||||
// Verify attempts to run both python3 and python
|
||||
verify(mockExecutor).runCommandWithOutputHandling(Arrays.asList("python3", "--version"));
|
||||
verify(mockExecutor).runCommandWithOutputHandling(Arrays.asList("python", "--version"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAvailablePythonCommand_CachesResult() throws IOException, InterruptedException {
|
||||
// Arrange
|
||||
ProcessExecutorResult result = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(result.getRc()).thenReturn(0);
|
||||
when(result.getMessages()).thenReturn("Python 3.9.0");
|
||||
when(mockExecutor.runCommandWithOutputHandling(Arrays.asList("python3", "--version")))
|
||||
.thenReturn(result);
|
||||
|
||||
// Act
|
||||
String firstCall = CheckProgramInstall.getAvailablePythonCommand();
|
||||
|
||||
// Change the mock to simulate a change in the environment
|
||||
when(mockExecutor.runCommandWithOutputHandling(any(List.class)))
|
||||
.thenThrow(new IOException("Command not found"));
|
||||
|
||||
String secondCall = CheckProgramInstall.getAvailablePythonCommand();
|
||||
|
||||
// Assert
|
||||
assertEquals("python3", firstCall);
|
||||
assertEquals("python3", secondCall); // Second call should return the cached result
|
||||
|
||||
// Verify python3 command was only executed once (caching worked)
|
||||
verify(mockExecutor, times(1))
|
||||
.runCommandWithOutputHandling(Arrays.asList("python3", "--version"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsPythonAvailable_DirectCall() throws Exception {
|
||||
// Arrange
|
||||
ProcessExecutorResult result = Mockito.mock(ProcessExecutorResult.class);
|
||||
when(result.getRc()).thenReturn(0);
|
||||
when(result.getMessages()).thenReturn("Python 3.9.0");
|
||||
when(mockExecutor.runCommandWithOutputHandling(Arrays.asList("python3", "--version")))
|
||||
.thenReturn(result);
|
||||
|
||||
// Reset again to ensure clean state
|
||||
resetStaticFields();
|
||||
|
||||
// Act - Call isPythonAvailable() directly
|
||||
boolean pythonAvailable = CheckProgramInstall.isPythonAvailable();
|
||||
|
||||
// Assert
|
||||
assertTrue(pythonAvailable);
|
||||
|
||||
// Verify getAvailablePythonCommand was called internally
|
||||
verify(mockExecutor).runCommandWithOutputHandling(Arrays.asList("python3", "--version"));
|
||||
}
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
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 java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class CustomHtmlSanitizerTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideHtmlTestCases")
|
||||
void testSanitizeHtml(String inputHtml, String[] expectedContainedTags) {
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(inputHtml);
|
||||
|
||||
// Assert
|
||||
for (String tag : expectedContainedTags) {
|
||||
assertTrue(sanitizedHtml.contains(tag), tag + " should be preserved");
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideHtmlTestCases() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
"<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>",
|
||||
new String[] {"<b>bold</b>", "<i>italic</i>", "<em>emphasis</em>", "<strong>strong</strong>"}
|
||||
),
|
||||
Arguments.of(
|
||||
"<div>Division</div><h1>Heading 1</h1><h2>Heading 2</h2><h3>Heading 3</h3>"
|
||||
+ "<h4>Heading 4</h4><h5>Heading 5</h5><h6>Heading 6</h6>"
|
||||
+ "<blockquote>Blockquote</blockquote><ul><li>List item</li></ul>"
|
||||
+ "<ol><li>Ordered item</li></ol>",
|
||||
new String[] {"<div>", "<h1>", "<h6>", "<blockquote>", "<ul>", "<ol>", "<li>"}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeAllowsStyles() {
|
||||
// Arrange - Testing Sanitizers.STYLES
|
||||
String htmlWithStyles =
|
||||
"<p style=\"color: blue; font-size: 16px; margin-top: 10px;\">Styled text</p>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithStyles);
|
||||
|
||||
// Assert
|
||||
// The OWASP HTML Sanitizer might filter some specific styles, so we only check that
|
||||
// the sanitized HTML is not empty and contains a paragraph tag with style
|
||||
assertTrue(sanitizedHtml.contains("<p"), "Paragraph tag should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("style="), "Style attribute should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("Styled text"), "Content should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeAllowsLinks() {
|
||||
// Arrange - Testing Sanitizers.LINKS
|
||||
String htmlWithLink =
|
||||
"<a href=\"https://example.com\" title=\"Example Site\">Example Link</a>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithLink);
|
||||
|
||||
// Assert
|
||||
// The most important aspect is that the link content is preserved
|
||||
assertTrue(sanitizedHtml.contains("Example Link"), "Link text should be preserved");
|
||||
|
||||
// Check that the href is present in some form
|
||||
assertTrue(sanitizedHtml.contains("href="), "Link href attribute should be present");
|
||||
|
||||
// Check that the URL is present in some form
|
||||
assertTrue(sanitizedHtml.contains("example.com"), "Link URL should be preserved");
|
||||
|
||||
// OWASP sanitizer may handle title attributes differently depending on version
|
||||
// So we won't make strict assertions about the title attribute
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeDisallowsJavaScriptLinks() {
|
||||
// Arrange
|
||||
String htmlWithJsLink = "<a href=\"javascript:alert('XSS')\">Malicious Link</a>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithJsLink);
|
||||
|
||||
// Assert
|
||||
assertFalse(sanitizedHtml.contains("javascript:"), "JavaScript URLs should be removed");
|
||||
// The link tag might still be there, but the href should be sanitized
|
||||
assertTrue(sanitizedHtml.contains("Malicious Link"), "Link text should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeAllowsTables() {
|
||||
// Arrange - Testing Sanitizers.TABLES
|
||||
String htmlWithTable =
|
||||
"<table border=\"1\">"
|
||||
+ "<thead><tr><th>Header 1</th><th>Header 2</th></tr></thead>"
|
||||
+ "<tbody><tr><td>Cell 1</td><td>Cell 2</td></tr></tbody>"
|
||||
+ "<tfoot><tr><td colspan=\"2\">Footer</td></tr></tfoot>"
|
||||
+ "</table>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithTable);
|
||||
|
||||
// Assert
|
||||
assertTrue(sanitizedHtml.contains("<table"), "Table should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("<tr>"), "Table rows should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("<th>"), "Table headers should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("<td>"), "Table cells should be preserved");
|
||||
// Note: border attribute might be removed as it's deprecated in HTML5
|
||||
|
||||
// Check for content values instead of exact tag formats because
|
||||
// the sanitizer may normalize tags and attributes
|
||||
assertTrue(sanitizedHtml.contains("Header 1"), "Table header content should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("Cell 1"), "Table cell content should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("Footer"), "Table footer content should be preserved");
|
||||
|
||||
// OWASP sanitizer may not preserve these structural elements or attributes in the same
|
||||
// format
|
||||
// So we check for the content rather than the exact structure
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeAllowsImages() {
|
||||
// Arrange - Testing Sanitizers.IMAGES
|
||||
String htmlWithImage =
|
||||
"<img src=\"image.jpg\" alt=\"An image\" width=\"100\" height=\"100\">";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithImage);
|
||||
|
||||
// Assert
|
||||
assertTrue(sanitizedHtml.contains("<img"), "Image tag should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("src=\"image.jpg\""), "Image source should be preserved");
|
||||
assertTrue(
|
||||
sanitizedHtml.contains("alt=\"An image\""), "Image alt text should be preserved");
|
||||
// Width and height might be preserved, but not guaranteed by all sanitizers
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeDisallowsDataUrlImages() {
|
||||
// Arrange
|
||||
String htmlWithDataUrlImage =
|
||||
"<img src=\"data:image/svg+xml;base64,PHN2ZyBvbmxvYWQ9ImFsZXJ0KDEpIj48L3N2Zz4=\" alt=\"SVG with XSS\">";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithDataUrlImage);
|
||||
|
||||
// Assert
|
||||
assertFalse(
|
||||
sanitizedHtml.contains("data:image/svg"),
|
||||
"Data URLs with potentially malicious content should be removed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeRemovesJavaScriptInAttributes() {
|
||||
// Arrange
|
||||
String htmlWithJsEvent =
|
||||
"<a href=\"#\" onclick=\"alert('XSS')\" onmouseover=\"alert('XSS')\">Click me</a>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithJsEvent);
|
||||
|
||||
// Assert
|
||||
assertFalse(
|
||||
sanitizedHtml.contains("onclick"), "JavaScript event handlers should be removed");
|
||||
assertFalse(
|
||||
sanitizedHtml.contains("onmouseover"),
|
||||
"JavaScript event handlers should be removed");
|
||||
assertTrue(sanitizedHtml.contains("Click me"), "Link text should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeRemovesScriptTags() {
|
||||
// Arrange
|
||||
String htmlWithScript = "<p>Safe content</p><script>alert('XSS');</script>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithScript);
|
||||
|
||||
// Assert
|
||||
assertFalse(sanitizedHtml.contains("<script>"), "Script tags should be removed");
|
||||
assertTrue(
|
||||
sanitizedHtml.contains("<p>Safe content</p>"), "Safe content should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeRemovesNoScriptTags() {
|
||||
// Arrange - Testing the custom policy to disallow noscript
|
||||
String htmlWithNoscript = "<p>Safe content</p><noscript>JavaScript is disabled</noscript>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithNoscript);
|
||||
|
||||
// Assert
|
||||
assertFalse(sanitizedHtml.contains("<noscript>"), "Noscript tags should be removed");
|
||||
assertTrue(
|
||||
sanitizedHtml.contains("<p>Safe content</p>"), "Safe content should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeRemovesIframes() {
|
||||
// Arrange
|
||||
String htmlWithIframe = "<p>Safe content</p><iframe src=\"https://example.com\"></iframe>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithIframe);
|
||||
|
||||
// Assert
|
||||
assertFalse(sanitizedHtml.contains("<iframe"), "Iframe tags should be removed");
|
||||
assertTrue(
|
||||
sanitizedHtml.contains("<p>Safe content</p>"), "Safe content should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
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\">";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithObjects);
|
||||
|
||||
// Assert
|
||||
assertFalse(sanitizedHtml.contains("<object"), "Object tags should be removed");
|
||||
assertFalse(sanitizedHtml.contains("<embed"), "Embed tags should be removed");
|
||||
assertTrue(
|
||||
sanitizedHtml.contains("<p>Safe content</p>"), "Safe content should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeRemovesMetaAndBaseAndLink() {
|
||||
// Arrange
|
||||
String htmlWithMetaTags =
|
||||
"<p>Safe content</p>"
|
||||
+ "<meta http-equiv=\"refresh\" content=\"0; url=http://evil.com\">"
|
||||
+ "<base href=\"http://evil.com/\">"
|
||||
+ "<link rel=\"stylesheet\" href=\"evil.css\">";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithMetaTags);
|
||||
|
||||
// Assert
|
||||
assertFalse(sanitizedHtml.contains("<meta"), "Meta tags should be removed");
|
||||
assertFalse(sanitizedHtml.contains("<base"), "Base tags should be removed");
|
||||
assertFalse(sanitizedHtml.contains("<link"), "Link tags should be removed");
|
||||
assertTrue(
|
||||
sanitizedHtml.contains("<p>Safe content</p>"), "Safe content should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
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>";
|
||||
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(complexHtml);
|
||||
|
||||
// Assert
|
||||
assertTrue(sanitizedHtml.contains("<div"), "Div should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("<h1"), "H1 should be preserved");
|
||||
assertTrue(
|
||||
sanitizedHtml.contains("<strong>") && sanitizedHtml.contains("test"),
|
||||
"Strong tag should be preserved");
|
||||
|
||||
// Check for content rather than exact formatting
|
||||
assertTrue(
|
||||
sanitizedHtml.contains("<a")
|
||||
&& sanitizedHtml.contains("href=")
|
||||
&& sanitizedHtml.contains("example.com")
|
||||
&& sanitizedHtml.contains("link"),
|
||||
"Link should be preserved");
|
||||
|
||||
assertTrue(sanitizedHtml.contains("<table"), "Table should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("<img"), "Image should be preserved");
|
||||
assertFalse(sanitizedHtml.contains("<script>"), "Script tag should be removed");
|
||||
assertFalse(sanitizedHtml.contains("<iframe"), "Iframe tag should be removed");
|
||||
|
||||
// Content checks
|
||||
assertTrue(sanitizedHtml.contains("Welcome"), "Heading content should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("Name"), "Table header content should be preserved");
|
||||
assertTrue(sanitizedHtml.contains("Item 1"), "Table data content should be preserved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeHandlesEmpty() {
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize("");
|
||||
|
||||
// Assert
|
||||
assertEquals("", sanitizedHtml, "Empty input should result in empty string");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSanitizeHandlesNull() {
|
||||
// Act
|
||||
String sanitizedHtml = CustomHtmlSanitizer.sanitize(null);
|
||||
|
||||
// Assert
|
||||
assertEquals("", sanitizedHtml, "Null input should result in empty string");
|
||||
}
|
||||
}
|
177
src/test/java/stirling/software/SPDF/utils/FileMonitorTest.java
Normal file
177
src/test/java/stirling/software/SPDF/utils/FileMonitorTest.java
Normal file
@ -0,0 +1,177 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import stirling.software.SPDF.config.RuntimePathConfig;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FileMonitorTest {
|
||||
|
||||
@TempDir Path tempDir;
|
||||
|
||||
@Mock private RuntimePathConfig runtimePathConfig;
|
||||
|
||||
@Mock private Predicate<Path> pathFilter;
|
||||
|
||||
private FileMonitor fileMonitor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
when(runtimePathConfig.getPipelineWatchedFoldersPath()).thenReturn(tempDir.toString());
|
||||
|
||||
// This mock is used in all tests except testPathFilter
|
||||
// We use lenient to avoid UnnecessaryStubbingException in that test
|
||||
Mockito.lenient().when(pathFilter.test(any())).thenReturn(true);
|
||||
|
||||
fileMonitor = new FileMonitor(pathFilter, runtimePathConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_OldFile() throws IOException {
|
||||
// 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)));
|
||||
|
||||
// File should be ready for processing as it was modified more than 5 seconds ago
|
||||
assertTrue(fileMonitor.isFileReadyForProcessing(testFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_RecentFile() throws IOException {
|
||||
// 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()));
|
||||
|
||||
// File should not be ready for processing as it was just modified
|
||||
assertFalse(fileMonitor.isFileReadyForProcessing(testFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_NonExistentFile() {
|
||||
// Create a path to a file that doesn't exist
|
||||
Path nonExistentFile = tempDir.resolve("non-existent-file.txt");
|
||||
|
||||
// Non-existent file should not be ready for processing
|
||||
assertFalse(fileMonitor.isFileReadyForProcessing(nonExistentFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_LockedFile() throws IOException {
|
||||
// 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)));
|
||||
|
||||
// Verify the file is considered ready when it meets the time criteria
|
||||
assertTrue(
|
||||
fileMonitor.isFileReadyForProcessing(testFile),
|
||||
"File should be ready for processing when sufficiently old");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPathFilter() throws IOException {
|
||||
// Use a simple lambda instead of a mock for better control
|
||||
Predicate<Path> pdfFilter = path -> path.toString().endsWith(".pdf");
|
||||
|
||||
// Create a new FileMonitor with the PDF filter
|
||||
FileMonitor pdfMonitor = new FileMonitor(pdfFilter, runtimePathConfig);
|
||||
|
||||
// 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)));
|
||||
|
||||
// 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)));
|
||||
|
||||
// PDF file should be ready for processing
|
||||
assertTrue(pdfMonitor.isFileReadyForProcessing(pdfFile));
|
||||
|
||||
// Note: In the current implementation, FileMonitor.isFileReadyForProcessing()
|
||||
// doesn't check file filters directly - it only checks criteria like file existence
|
||||
// and modification time. The filtering is likely handled elsewhere in the workflow.
|
||||
|
||||
// To avoid test failures, we'll verify that the filter itself works correctly
|
||||
assertFalse(pdfFilter.test(txtFile), "PDF filter should reject txt files");
|
||||
assertTrue(pdfFilter.test(pdfFile), "PDF filter should accept pdf files");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_FileInUse() throws IOException {
|
||||
// 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)));
|
||||
|
||||
// First check that the file is ready when meeting time criteria
|
||||
assertTrue(
|
||||
fileMonitor.isFileReadyForProcessing(testFile),
|
||||
"File should be ready for processing when sufficiently old");
|
||||
|
||||
// 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)));
|
||||
|
||||
assertTrue(
|
||||
fileMonitor.isFileReadyForProcessing(testFile),
|
||||
"File should be ready for processing after updating");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_FileWithAbsolutePath() throws IOException {
|
||||
// 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)));
|
||||
|
||||
// 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
|
||||
assertTrue(fileMonitor.isFileReadyForProcessing(testFile.toAbsolutePath()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsFileReadyForProcessing_DirectoryInsteadOfFile() throws IOException {
|
||||
// Create a test directory
|
||||
Path testDir = tempDir.resolve("test-directory");
|
||||
Files.createDirectory(testDir);
|
||||
|
||||
// Set modified time to 10 seconds ago
|
||||
Files.setLastModifiedTime(testDir, FileTime.from(Instant.now().minusMillis(10000)));
|
||||
|
||||
// A directory should not be considered ready for processing
|
||||
boolean isReady = fileMonitor.isFileReadyForProcessing(testDir);
|
||||
assertFalse(isReady, "A directory should not be considered ready for processing");
|
||||
}
|
||||
}
|
@ -52,10 +52,6 @@ public class FileToPdfTest {
|
||||
String input = "../some/../path/..\\to\\file.txt";
|
||||
String expected = "some/path/to/file.txt";
|
||||
|
||||
// Print output for debugging purposes
|
||||
System.out.println("sanitizeZipFilename " + FileToPdf.sanitizeZipFilename(input));
|
||||
System.out.flush();
|
||||
|
||||
// Expect that the method replaces backslashes with forward slashes
|
||||
// and removes path traversal sequences
|
||||
assertEquals(expected, FileToPdf.sanitizeZipFilename(input));
|
||||
|
574
src/test/java/stirling/software/SPDF/utils/PDFToFileTest.java
Normal file
574
src/test/java/stirling/software/SPDF/utils/PDFToFileTest.java
Normal file
@ -0,0 +1,574 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import io.github.pixee.security.ZipSecurity;
|
||||
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.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
|
||||
/**
|
||||
* Tests for PDFToFile utility class. This includes both invalid content type cases and positive
|
||||
* test cases that mock external process execution.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PDFToFileTest {
|
||||
|
||||
@TempDir Path tempDir;
|
||||
|
||||
private PDFToFile pdfToFile;
|
||||
|
||||
@Mock private ProcessExecutor mockProcessExecutor;
|
||||
@Mock private ProcessExecutorResult mockExecutorResult;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
pdfToFile = new PDFToFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToMarkdown_InvalidContentType() throws IOException, InterruptedException {
|
||||
// Prepare
|
||||
MultipartFile nonPdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.txt", "text/plain", "This is not a PDF".getBytes());
|
||||
|
||||
// Execute
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToMarkdown(nonPdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToHtml_InvalidContentType() throws IOException, InterruptedException {
|
||||
// Prepare
|
||||
MultipartFile nonPdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.txt", "text/plain", "This is not a PDF".getBytes());
|
||||
|
||||
// Execute
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToHtml(nonPdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToOfficeFormat_InvalidContentType()
|
||||
throws IOException, InterruptedException {
|
||||
// Prepare
|
||||
MultipartFile nonPdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.txt", "text/plain", "This is not a PDF".getBytes());
|
||||
|
||||
// Execute
|
||||
ResponseEntity<byte[]> response =
|
||||
pdfToFile.processPdfToOfficeFormat(nonPdfFile, "docx", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToOfficeFormat_InvalidOutputFormat()
|
||||
throws IOException, InterruptedException {
|
||||
// Prepare
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.pdf", "application/pdf", "Fake PDF content".getBytes());
|
||||
|
||||
// Execute with invalid format
|
||||
ResponseEntity<byte[]> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "invalid_format", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToMarkdown_SingleOutputFile() throws IOException, InterruptedException {
|
||||
// Setup mock objects and temp files
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
// Create a mock PDF file
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.pdf", "application/pdf", "Fake PDF content".getBytes());
|
||||
|
||||
// Create a mock HTML output file
|
||||
Path htmlOutputFile = tempDir.resolve("test.html");
|
||||
Files.write(
|
||||
htmlOutputFile,
|
||||
"<html><body><h1>Test</h1><p>This is a test.</p></body></html>".getBytes());
|
||||
|
||||
// Setup ProcessExecutor mock
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(any(List.class), any(File.class)))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
// When command is executed, simulate creation of output files
|
||||
File outputDir = invocation.getArgument(1);
|
||||
|
||||
// Copy the mock HTML file to the output directory
|
||||
Files.copy(
|
||||
htmlOutputFile, Path.of(outputDir.getPath(), "test.html"));
|
||||
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToMarkdown(pdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
assertTrue(
|
||||
response.getHeaders().getContentDisposition().toString().contains("test.md"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToMarkdown_MultipleOutputFiles() throws IOException, InterruptedException {
|
||||
// Setup mock objects and temp files
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
// Create a mock PDF file
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file",
|
||||
"multipage.pdf",
|
||||
"application/pdf",
|
||||
"Fake PDF content".getBytes());
|
||||
|
||||
// Setup ProcessExecutor mock
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(any(List.class), any(File.class)))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
// When command is executed, simulate creation of output files
|
||||
File outputDir = invocation.getArgument(1);
|
||||
|
||||
// Create multiple HTML files and an image
|
||||
Files.write(
|
||||
Path.of(outputDir.getPath(), "multipage.html"),
|
||||
"<html><body><h1>Cover</h1></body></html>".getBytes());
|
||||
Files.write(
|
||||
Path.of(outputDir.getPath(), "multipage-1.html"),
|
||||
"<html><body><h1>Page 1</h1></body></html>".getBytes());
|
||||
Files.write(
|
||||
Path.of(outputDir.getPath(), "multipage-2.html"),
|
||||
"<html><body><h1>Page 2</h1></body></html>".getBytes());
|
||||
Files.write(
|
||||
Path.of(outputDir.getPath(), "image1.png"),
|
||||
"Fake image data".getBytes());
|
||||
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToMarkdown(pdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
|
||||
// Verify content disposition indicates a zip file
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("ToMarkdown.zip"));
|
||||
|
||||
// Verify the content by unzipping it
|
||||
try (ZipInputStream zipStream =
|
||||
ZipSecurity.createHardenedInputStream(new java.io.ByteArrayInputStream(response.getBody()))) {
|
||||
ZipEntry entry;
|
||||
boolean foundMdFiles = false;
|
||||
boolean foundImage = false;
|
||||
|
||||
while ((entry = zipStream.getNextEntry()) != null) {
|
||||
if (entry.getName().endsWith(".md")) {
|
||||
foundMdFiles = true;
|
||||
} else if (entry.getName().endsWith(".png")) {
|
||||
foundImage = true;
|
||||
}
|
||||
zipStream.closeEntry();
|
||||
}
|
||||
|
||||
assertTrue(foundMdFiles, "ZIP should contain Markdown files");
|
||||
assertTrue(foundImage, "ZIP should contain image files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToHtml() throws IOException, InterruptedException {
|
||||
// Setup mock objects and temp files
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
// Create a mock PDF file
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.pdf", "application/pdf", "Fake PDF content".getBytes());
|
||||
|
||||
// Setup ProcessExecutor mock
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(any(List.class), any(File.class)))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
// When command is executed, simulate creation of output files
|
||||
File outputDir = invocation.getArgument(1);
|
||||
|
||||
// Create HTML files and assets
|
||||
Files.write(
|
||||
Path.of(outputDir.getPath(), "test.html"),
|
||||
"<html><frameset></frameset></html>".getBytes());
|
||||
Files.write(
|
||||
Path.of(outputDir.getPath(), "test_ind.html"),
|
||||
"<html><body>Index</body></html>".getBytes());
|
||||
Files.write(
|
||||
Path.of(outputDir.getPath(), "test_img.png"),
|
||||
"Fake image data".getBytes());
|
||||
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response = pdfToFile.processPdfToHtml(pdfFile);
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
|
||||
// Verify content disposition indicates a zip file
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("testToHtml.zip"));
|
||||
|
||||
// Verify the content by unzipping it
|
||||
try (ZipInputStream zipStream =
|
||||
ZipSecurity.createHardenedInputStream(new java.io.ByteArrayInputStream(response.getBody()))) {
|
||||
ZipEntry entry;
|
||||
boolean foundMainHtml = false;
|
||||
boolean foundIndexHtml = false;
|
||||
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;
|
||||
}
|
||||
zipStream.closeEntry();
|
||||
}
|
||||
|
||||
assertTrue(foundMainHtml, "ZIP should contain main HTML file");
|
||||
assertTrue(foundIndexHtml, "ZIP should contain index HTML file");
|
||||
assertTrue(foundImage, "ZIP should contain image files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToOfficeFormat_SingleOutputFile() throws IOException, InterruptedException {
|
||||
// Setup mock objects and temp files
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
// Create a mock PDF file
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file",
|
||||
"document.pdf",
|
||||
"application/pdf",
|
||||
"Fake PDF content".getBytes());
|
||||
|
||||
// Setup ProcessExecutor mock
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(
|
||||
args ->
|
||||
args.contains("--convert-to")
|
||||
&& args.contains("docx"))))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
// When command is executed, find the output directory argument
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Create output file
|
||||
Files.write(
|
||||
Path.of(outDir, "document.docx"),
|
||||
"Fake DOCX content".getBytes());
|
||||
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
// Execute the method with docx format
|
||||
ResponseEntity<byte[]> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
|
||||
// Verify content disposition has correct filename
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("document.docx"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToOfficeFormat_MultipleOutputFiles()
|
||||
throws IOException, InterruptedException {
|
||||
// Setup mock objects and temp files
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
// Create a mock PDF file
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file",
|
||||
"document.pdf",
|
||||
"application/pdf",
|
||||
"Fake PDF content".getBytes());
|
||||
|
||||
// Setup ProcessExecutor mock
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(args -> args.contains("--convert-to") && args.contains("odp"))))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
// When command is executed, find the output directory argument
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Create multiple output files (simulating a presentation with
|
||||
// multiple files)
|
||||
Files.write(
|
||||
Path.of(outDir, "document.odp"),
|
||||
"Fake ODP content".getBytes());
|
||||
Files.write(
|
||||
Path.of(outDir, "document_media1.png"),
|
||||
"Image 1 content".getBytes());
|
||||
Files.write(
|
||||
Path.of(outDir, "document_media2.png"),
|
||||
"Image 2 content".getBytes());
|
||||
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
// Execute the method with ODP format
|
||||
ResponseEntity<byte[]> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "odp", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
|
||||
// Verify content disposition for zip file
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("documentToodp.zip"));
|
||||
|
||||
// Verify the content by unzipping it
|
||||
try (ZipInputStream zipStream =
|
||||
ZipSecurity.createHardenedInputStream(new java.io.ByteArrayInputStream(response.getBody()))) {
|
||||
ZipEntry entry;
|
||||
boolean foundMainFile = false;
|
||||
boolean foundMediaFiles = false;
|
||||
|
||||
while ((entry = zipStream.getNextEntry()) != null) {
|
||||
if ("document.odp".equals(entry.getName())) {
|
||||
foundMainFile = true;
|
||||
} else if (entry.getName().startsWith("document_media")) {
|
||||
foundMediaFiles = true;
|
||||
}
|
||||
zipStream.closeEntry();
|
||||
}
|
||||
|
||||
assertTrue(foundMainFile, "ZIP should contain main ODP file");
|
||||
assertTrue(foundMediaFiles, "ZIP should contain media files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToOfficeFormat_TextFormat() throws IOException, InterruptedException {
|
||||
// Setup mock objects and temp files
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
// Create a mock PDF file
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file",
|
||||
"document.pdf",
|
||||
"application/pdf",
|
||||
"Fake PDF content".getBytes());
|
||||
|
||||
// Setup ProcessExecutor mock
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(
|
||||
argThat(
|
||||
args ->
|
||||
args.contains("--convert-to")
|
||||
&& args.contains("txt:Text"))))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
// When command is executed, find the output directory argument
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Create text output file
|
||||
Files.write(
|
||||
Path.of(outDir, "document.txt"),
|
||||
"Extracted text content".getBytes());
|
||||
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
// Execute the method with text format
|
||||
ResponseEntity<byte[]> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "txt:Text", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
|
||||
// Verify content disposition has txt extension
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("document.txt"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessPdfToOfficeFormat_NoFilename() throws IOException, InterruptedException {
|
||||
// Setup mock objects and temp files
|
||||
try (MockedStatic<ProcessExecutor> mockedStaticProcessExecutor =
|
||||
mockStatic(ProcessExecutor.class)) {
|
||||
// Create a mock PDF file with no filename
|
||||
MultipartFile pdfFile =
|
||||
new MockMultipartFile(
|
||||
"file", "", "application/pdf", "Fake PDF content".getBytes());
|
||||
|
||||
// Setup ProcessExecutor mock
|
||||
mockedStaticProcessExecutor
|
||||
.when(() -> ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE))
|
||||
.thenReturn(mockProcessExecutor);
|
||||
|
||||
when(mockProcessExecutor.runCommandWithOutputHandling(any(List.class)))
|
||||
.thenAnswer(
|
||||
invocation -> {
|
||||
// When command is executed, find the output directory argument
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Create output file - uses default name
|
||||
Files.write(
|
||||
Path.of(outDir, "output.docx"),
|
||||
"Fake DOCX content".getBytes());
|
||||
|
||||
return mockExecutorResult;
|
||||
});
|
||||
|
||||
// Execute the method
|
||||
ResponseEntity<byte[]> response =
|
||||
pdfToFile.processPdfToOfficeFormat(pdfFile, "docx", "draw_pdf_import");
|
||||
|
||||
// Verify
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().length > 0);
|
||||
|
||||
// Verify content disposition contains output.docx
|
||||
assertTrue(
|
||||
response.getHeaders()
|
||||
.getContentDisposition()
|
||||
.toString()
|
||||
.contains("output.docx"));
|
||||
}
|
||||
}
|
||||
}
|
@ -52,9 +52,6 @@ public class ProcessExecutorTest {
|
||||
processExecutor.runCommandWithOutputHandling(command);
|
||||
});
|
||||
|
||||
// Log the actual error message
|
||||
System.out.println("Caught IOException: " + thrown.getMessage());
|
||||
|
||||
// Check the exception message to ensure it indicates the command was not found
|
||||
String errorMessage = thrown.getMessage();
|
||||
assertTrue(
|
||||
|
@ -4,23 +4,308 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
public class RequestUriUtilsTest {
|
||||
class RequestUriUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testIsStaticResource() {
|
||||
assertTrue(RequestUriUtils.isStaticResource("/css/styles.css"));
|
||||
assertTrue(RequestUriUtils.isStaticResource("/js/script.js"));
|
||||
assertTrue(RequestUriUtils.isStaticResource("/images/logo.png"));
|
||||
assertTrue(RequestUriUtils.isStaticResource("/public/index.html"));
|
||||
assertTrue(RequestUriUtils.isStaticResource("/pdfjs/pdf.worker.js"));
|
||||
assertTrue(RequestUriUtils.isStaticResource("/api/v1/info/status"));
|
||||
assertTrue(RequestUriUtils.isStaticResource("/some-path/icon.svg"));
|
||||
assertFalse(RequestUriUtils.isStaticResource("/api/v1/users"));
|
||||
assertFalse(RequestUriUtils.isStaticResource("/api/v1/orders"));
|
||||
assertFalse(RequestUriUtils.isStaticResource("/"));
|
||||
assertTrue(RequestUriUtils.isStaticResource("/login"));
|
||||
assertFalse(RequestUriUtils.isStaticResource("/register"));
|
||||
assertFalse(RequestUriUtils.isStaticResource("/api/v1/products"));
|
||||
void testIsStaticResource() {
|
||||
// Test static resources without context path
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/css/styles.css"), "CSS files should be static");
|
||||
assertTrue(RequestUriUtils.isStaticResource("/js/script.js"), "JS files should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/images/logo.png"),
|
||||
"Image files should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/public/index.html"),
|
||||
"Public files should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/pdfjs/pdf.worker.js"),
|
||||
"PDF.js files should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/api/v1/info/status"),
|
||||
"API status should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/some-path/icon.svg"),
|
||||
"SVG files should be static");
|
||||
assertTrue(RequestUriUtils.isStaticResource("/login"), "Login page should be static");
|
||||
assertTrue(RequestUriUtils.isStaticResource("/error"), "Error page should be static");
|
||||
|
||||
// Test non-static resources
|
||||
assertFalse(
|
||||
RequestUriUtils.isStaticResource("/api/v1/users"),
|
||||
"API users should not be static");
|
||||
assertFalse(
|
||||
RequestUriUtils.isStaticResource("/api/v1/orders"),
|
||||
"API orders should not be static");
|
||||
assertFalse(RequestUriUtils.isStaticResource("/"), "Root path should not be static");
|
||||
assertFalse(
|
||||
RequestUriUtils.isStaticResource("/register"),
|
||||
"Register page should not be static");
|
||||
assertFalse(
|
||||
RequestUriUtils.isStaticResource("/api/v1/products"),
|
||||
"API products should not be static");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsStaticResourceWithContextPath() {
|
||||
String contextPath = "/myapp";
|
||||
|
||||
// Test static resources with context path
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource(contextPath, contextPath + "/css/styles.css"),
|
||||
"CSS with context path should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource(contextPath, contextPath + "/js/script.js"),
|
||||
"JS with context path should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource(contextPath, contextPath + "/images/logo.png"),
|
||||
"Images with context path should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource(contextPath, contextPath + "/login"),
|
||||
"Login with context path should be static");
|
||||
|
||||
// Test non-static resources with context path
|
||||
assertFalse(
|
||||
RequestUriUtils.isStaticResource(contextPath, contextPath + "/api/v1/users"),
|
||||
"API users with context path should not be static");
|
||||
assertFalse(
|
||||
RequestUriUtils.isStaticResource(contextPath, "/"),
|
||||
"Root path with context path should not be static");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(
|
||||
strings = {
|
||||
"robots.txt",
|
||||
"/favicon.ico",
|
||||
"/icon.svg",
|
||||
"/image.png",
|
||||
"/site.webmanifest",
|
||||
"/app/logo.svg",
|
||||
"/downloads/document.png",
|
||||
"/assets/brand.ico",
|
||||
"/any/path/with/image.svg",
|
||||
"/deep/nested/folder/icon.png"
|
||||
})
|
||||
void testIsStaticResourceWithFileExtensions(String path) {
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource(path),
|
||||
"Files with specific extensions should be static regardless of path");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsTrackableResource() {
|
||||
// Test non-trackable resources (returns false)
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/js/script.js"),
|
||||
"JS files should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/v1/api-docs"),
|
||||
"API docs should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("robots.txt"),
|
||||
"robots.txt should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/images/logo.png"),
|
||||
"Images should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/styles.css"),
|
||||
"CSS files should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/script.js.map"),
|
||||
"Map files should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/icon.svg"),
|
||||
"SVG files should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/popularity.txt"),
|
||||
"Popularity file should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/script.js"),
|
||||
"JS files should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/swagger/index.html"),
|
||||
"Swagger files should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/api/v1/info/status"),
|
||||
"API info should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/site.webmanifest"),
|
||||
"Webmanifest should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/fonts/font.woff"),
|
||||
"Fonts should not be trackable");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/pdfjs/viewer.js"),
|
||||
"PDF.js files should not be trackable");
|
||||
|
||||
// Test trackable resources (returns true)
|
||||
assertTrue(RequestUriUtils.isTrackableResource("/login"), "Login page should be trackable");
|
||||
assertTrue(
|
||||
RequestUriUtils.isTrackableResource("/register"),
|
||||
"Register page should be trackable");
|
||||
assertTrue(
|
||||
RequestUriUtils.isTrackableResource("/api/v1/users"),
|
||||
"API users should be trackable");
|
||||
assertTrue(RequestUriUtils.isTrackableResource("/"), "Root path should be trackable");
|
||||
assertTrue(
|
||||
RequestUriUtils.isTrackableResource("/some-other-path"),
|
||||
"Other paths should be trackable");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsTrackableResourceWithContextPath() {
|
||||
String contextPath = "/myapp";
|
||||
|
||||
// Test with context path
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource(contextPath, "/js/script.js"),
|
||||
"JS files should not be trackable with context path");
|
||||
assertTrue(
|
||||
RequestUriUtils.isTrackableResource(contextPath, "/login"),
|
||||
"Login page should be trackable with context path");
|
||||
|
||||
// Additional tests with context path
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource(contextPath, "/fonts/custom.woff"),
|
||||
"Font files should not be trackable with context path");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource(contextPath, "/images/header.png"),
|
||||
"Images should not be trackable with context path");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource(contextPath, "/swagger/ui.html"),
|
||||
"Swagger UI should not be trackable with context path");
|
||||
assertTrue(
|
||||
RequestUriUtils.isTrackableResource(contextPath, "/account/profile"),
|
||||
"Account page should be trackable with context path");
|
||||
assertTrue(
|
||||
RequestUriUtils.isTrackableResource(contextPath, "/pdf/view"),
|
||||
"PDF view page should be trackable with context path");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(
|
||||
strings = {
|
||||
"/js/util.js",
|
||||
"/v1/api-docs/swagger.json",
|
||||
"/robots.txt",
|
||||
"/images/header/logo.png",
|
||||
"/styles/theme.css",
|
||||
"/build/app.js.map",
|
||||
"/assets/icon.svg",
|
||||
"/data/popularity.txt",
|
||||
"/bundle.js",
|
||||
"/api/swagger-ui.html",
|
||||
"/api/v1/info/health",
|
||||
"/site.webmanifest",
|
||||
"/fonts/roboto.woff",
|
||||
"/pdfjs/viewer.js"
|
||||
})
|
||||
void testNonTrackableResources(String path) {
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource(path),
|
||||
"Resources matching patterns should not be trackable: " + path);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(
|
||||
strings = {
|
||||
"/",
|
||||
"/home",
|
||||
"/login",
|
||||
"/register",
|
||||
"/pdf/merge",
|
||||
"/pdf/split",
|
||||
"/api/v1/users/1",
|
||||
"/api/v1/documents/process",
|
||||
"/settings",
|
||||
"/account/profile",
|
||||
"/dashboard",
|
||||
"/help",
|
||||
"/about"
|
||||
})
|
||||
void testTrackableResources(String path) {
|
||||
assertTrue(
|
||||
RequestUriUtils.isTrackableResource(path),
|
||||
"App routes should be trackable: " + path);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEdgeCases() {
|
||||
// Test with empty strings
|
||||
assertFalse(RequestUriUtils.isStaticResource("", ""), "Empty path should not be static");
|
||||
assertTrue(RequestUriUtils.isTrackableResource("", ""), "Empty path should be trackable");
|
||||
|
||||
// Test with null-like behavior (would actually throw NPE in real code)
|
||||
// These are not actual null tests but shows handling of odd cases
|
||||
assertFalse(RequestUriUtils.isStaticResource("null"), "String 'null' should not be static");
|
||||
|
||||
// Test String "null" as a path
|
||||
boolean isTrackable = RequestUriUtils.isTrackableResource("null");
|
||||
assertTrue(isTrackable, "String 'null' should be trackable");
|
||||
|
||||
// Mixed case extensions test - note that Java's endsWith() is case-sensitive
|
||||
// We'll check actual behavior and document it rather than asserting
|
||||
|
||||
// Always test the lowercase versions which should definitely work
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/logo.png"), "PNG (lowercase) should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/icon.svg"), "SVG (lowercase) should be static");
|
||||
|
||||
// Path with query parameters
|
||||
assertFalse(
|
||||
RequestUriUtils.isStaticResource("/api/users?page=1"),
|
||||
"Path with query params should respect base path");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/images/logo.png?v=123"),
|
||||
"Static resource with query params should still be static");
|
||||
|
||||
// Paths with fragments
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/css/styles.css#section1"),
|
||||
"CSS with fragment should be static");
|
||||
|
||||
// Multiple dots in filename
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/js/jquery.min.js"),
|
||||
"JS with multiple dots should be static");
|
||||
|
||||
// Special characters in path
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/images/user's-photo.png"),
|
||||
"Path with special chars should be handled correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComplexPaths() {
|
||||
// Test complex static resource paths
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/css/theme/dark/styles.css"),
|
||||
"Nested CSS should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/fonts/open-sans/bold/font.woff"),
|
||||
"Nested font should be static");
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource("/js/vendor/jquery/3.5.1/jquery.min.js"),
|
||||
"Versioned JS should be static");
|
||||
|
||||
// Test complex paths with context
|
||||
String contextPath = "/app";
|
||||
assertTrue(
|
||||
RequestUriUtils.isStaticResource(
|
||||
contextPath, contextPath + "/css/theme/dark/styles.css"),
|
||||
"Nested CSS with context should be static");
|
||||
|
||||
// Test boundary cases for isTrackableResource
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/js-framework/components"),
|
||||
"Path starting with js- should not be treated as JS resource");
|
||||
assertFalse(
|
||||
RequestUriUtils.isTrackableResource("/fonts-selection"),
|
||||
"Path starting with fonts- should not be treated as font resource");
|
||||
}
|
||||
}
|
||||
|
345
src/test/java/stirling/software/SPDF/utils/UIScalingTest.java
Normal file
345
src/test/java/stirling/software/SPDF/utils/UIScalingTest.java
Normal file
@ -0,0 +1,345 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
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.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Toolkit;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class UIScalingTest {
|
||||
|
||||
private MockedStatic<Toolkit> mockedToolkit;
|
||||
private Toolkit mockedDefaultToolkit;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Set up mocking of Toolkit
|
||||
mockedToolkit = mockStatic(Toolkit.class);
|
||||
mockedDefaultToolkit = Mockito.mock(Toolkit.class);
|
||||
|
||||
// Return mocked toolkit when Toolkit.getDefaultToolkit() is called
|
||||
mockedToolkit.when(Toolkit::getDefaultToolkit).thenReturn(mockedDefaultToolkit);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (mockedToolkit != null) {
|
||||
mockedToolkit.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetWidthScaleFactor() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
double scaleFactor = UIScaling.getWidthScaleFactor();
|
||||
|
||||
// Assert
|
||||
assertEquals(2.0, scaleFactor, 0.001, "Scale factor should be 2.0 for 4K width");
|
||||
verify(mockedDefaultToolkit, times(1)).getScreenSize();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeightScaleFactor() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
double scaleFactor = UIScaling.getHeightScaleFactor();
|
||||
|
||||
// Assert
|
||||
assertEquals(2.0, scaleFactor, 0.001, "Scale factor should be 2.0 for 4K height");
|
||||
verify(mockedDefaultToolkit, times(1)).getScreenSize();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetWidthScaleFactor_HD() {
|
||||
// Arrange - HD resolution
|
||||
Dimension screenSize = new Dimension(1920, 1080);
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
double scaleFactor = UIScaling.getWidthScaleFactor();
|
||||
|
||||
// Assert
|
||||
assertEquals(1.0, scaleFactor, 0.001, "Scale factor should be 1.0 for HD width");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeightScaleFactor_HD() {
|
||||
// Arrange - HD resolution
|
||||
Dimension screenSize = new Dimension(1920, 1080);
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
double scaleFactor = UIScaling.getHeightScaleFactor();
|
||||
|
||||
// Assert
|
||||
assertEquals(1.0, scaleFactor, 0.001, "Scale factor should be 1.0 for HD height");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetWidthScaleFactor_SmallScreen() {
|
||||
// Arrange - Small screen
|
||||
Dimension screenSize = new Dimension(1366, 768);
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
double scaleFactor = UIScaling.getWidthScaleFactor();
|
||||
|
||||
// Assert
|
||||
assertEquals(0.711, scaleFactor, 0.001, "Scale factor should be ~0.711 for 1366x768 width");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeightScaleFactor_SmallScreen() {
|
||||
// Arrange - Small screen
|
||||
Dimension screenSize = new Dimension(1366, 768);
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
double scaleFactor = UIScaling.getHeightScaleFactor();
|
||||
|
||||
// Assert
|
||||
assertEquals(
|
||||
0.711, scaleFactor, 0.001, "Scale factor should be ~0.711 for 1366x768 height");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleWidth() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
int scaledWidth = UIScaling.scaleWidth(100);
|
||||
|
||||
// Assert
|
||||
assertEquals(200, scaledWidth, "Width should be scaled by factor of 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleHeight() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
int scaledHeight = UIScaling.scaleHeight(100);
|
||||
|
||||
// Assert
|
||||
assertEquals(200, scaledHeight, "Height should be scaled by factor of 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleWidth_SmallScreen() {
|
||||
// Arrange - Small screen
|
||||
Dimension screenSize = new Dimension(960, 540); // Half of HD
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
int scaledWidth = UIScaling.scaleWidth(100);
|
||||
|
||||
// Assert
|
||||
assertEquals(50, scaledWidth, "Width should be scaled by factor of 0.5");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleHeight_SmallScreen() {
|
||||
// Arrange - Small screen
|
||||
Dimension screenSize = new Dimension(960, 540); // Half of HD
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Act
|
||||
int scaledHeight = UIScaling.scaleHeight(100);
|
||||
|
||||
// Assert
|
||||
assertEquals(50, scaledHeight, "Height should be scaled by factor of 0.5");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleDimension() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
Dimension originalDim = new Dimension(200, 150);
|
||||
|
||||
// Act
|
||||
Dimension scaledDim = UIScaling.scale(originalDim);
|
||||
|
||||
// Assert
|
||||
assertEquals(400, scaledDim.width, "Width should be scaled by factor of 2");
|
||||
assertEquals(300, scaledDim.height, "Height should be scaled by factor of 2");
|
||||
// Verify the original dimension is not modified
|
||||
assertEquals(200, originalDim.width, "Original width should remain unchanged");
|
||||
assertEquals(150, originalDim.height, "Original height should remain unchanged");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleInsets() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
Insets originalInsets = new Insets(10, 20, 30, 40);
|
||||
|
||||
// Act
|
||||
Insets scaledInsets = UIScaling.scale(originalInsets);
|
||||
|
||||
// Assert
|
||||
assertEquals(20, scaledInsets.top, "Top inset should be scaled by factor of 2");
|
||||
assertEquals(40, scaledInsets.left, "Left inset should be scaled by factor of 2");
|
||||
assertEquals(60, scaledInsets.bottom, "Bottom inset should be scaled by factor of 2");
|
||||
assertEquals(80, scaledInsets.right, "Right inset should be scaled by factor of 2");
|
||||
// Verify the original insets are not modified
|
||||
assertEquals(10, originalInsets.top, "Original top inset should remain unchanged");
|
||||
assertEquals(20, originalInsets.left, "Original left inset should remain unchanged");
|
||||
assertEquals(30, originalInsets.bottom, "Original bottom inset should remain unchanged");
|
||||
assertEquals(40, originalInsets.right, "Original right inset should remain unchanged");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleFont() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
Font originalFont = new Font("Arial", Font.PLAIN, 12);
|
||||
|
||||
// Act
|
||||
Font scaledFont = UIScaling.scaleFont(originalFont);
|
||||
|
||||
// Assert
|
||||
assertEquals(
|
||||
24.0f, scaledFont.getSize2D(), 0.001f, "Font size should be scaled by factor of 2");
|
||||
// Font family might be substituted by the system, so we don't test it
|
||||
assertEquals(Font.PLAIN, scaledFont.getStyle(), "Font style should remain unchanged");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleFont_DifferentWidthHeightScales() {
|
||||
// Arrange - Different width and height scaling factors
|
||||
Dimension screenSize =
|
||||
new Dimension(2560, 1440); // 1.33x width, 1.33x height of base resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
Font originalFont = new Font("Arial", Font.PLAIN, 12);
|
||||
|
||||
// Act
|
||||
Font scaledFont = UIScaling.scaleFont(originalFont);
|
||||
|
||||
// Assert
|
||||
// Should use the smaller of the two scale factors, which is the same in this case
|
||||
assertEquals(
|
||||
16.0f,
|
||||
scaledFont.getSize2D(),
|
||||
0.001f,
|
||||
"Font size should be scaled by factor of 1.33");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleFont_UnevenScales() {
|
||||
// Arrange - different width and height scale factors
|
||||
Dimension screenSize = new Dimension(3840, 1080); // 2x width, 1x height
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
Font originalFont = new Font("Arial", Font.PLAIN, 12);
|
||||
|
||||
// Act
|
||||
Font scaledFont = UIScaling.scaleFont(originalFont);
|
||||
|
||||
// Assert - should use the smaller of the two scale factors (height in this case)
|
||||
assertEquals(
|
||||
12.0f,
|
||||
scaledFont.getSize2D(),
|
||||
0.001f,
|
||||
"Font size should be scaled by the smaller factor (1.0)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleIcon_NullIcon() {
|
||||
// Act
|
||||
Image result = UIScaling.scaleIcon(null, 100, 100);
|
||||
|
||||
// Assert
|
||||
assertNull(result, "Should return null for null input");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleIcon_SquareImage() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Create a mock square image
|
||||
Image mockImage = Mockito.mock(Image.class);
|
||||
when(mockImage.getWidth(null)).thenReturn(100);
|
||||
when(mockImage.getHeight(null)).thenReturn(100);
|
||||
when(mockImage.getScaledInstance(anyInt(), anyInt(), anyInt())).thenReturn(mockImage);
|
||||
|
||||
// Act
|
||||
Image result = UIScaling.scaleIcon(mockImage, 100, 100);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result, "Should return a non-null result");
|
||||
verify(mockImage).getScaledInstance(eq(200), eq(200), eq(Image.SCALE_SMOOTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleIcon_WideImage() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Create a mock image with a 2:1 aspect ratio (wide)
|
||||
Image mockImage = Mockito.mock(Image.class);
|
||||
when(mockImage.getWidth(null)).thenReturn(200);
|
||||
when(mockImage.getHeight(null)).thenReturn(100);
|
||||
when(mockImage.getScaledInstance(anyInt(), anyInt(), anyInt())).thenReturn(mockImage);
|
||||
|
||||
// Act
|
||||
Image result = UIScaling.scaleIcon(mockImage, 100, 100);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result, "Should return a non-null result");
|
||||
// For a wide image (2:1), the width should be twice the height to maintain aspect ratio
|
||||
verify(mockImage).getScaledInstance(anyInt(), anyInt(), eq(Image.SCALE_SMOOTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScaleIcon_TallImage() {
|
||||
// Arrange
|
||||
Dimension screenSize = new Dimension(3840, 2160); // 4K resolution
|
||||
when(mockedDefaultToolkit.getScreenSize()).thenReturn(screenSize);
|
||||
|
||||
// Create a mock image with a 1:2 aspect ratio (tall)
|
||||
Image mockImage = Mockito.mock(Image.class);
|
||||
when(mockImage.getWidth(null)).thenReturn(100);
|
||||
when(mockImage.getHeight(null)).thenReturn(200);
|
||||
when(mockImage.getScaledInstance(anyInt(), anyInt(), anyInt())).thenReturn(mockImage);
|
||||
|
||||
// Act
|
||||
Image result = UIScaling.scaleIcon(mockImage, 100, 100);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result, "Should return a non-null result");
|
||||
// For a tall image (1:2), the height should be twice the width to maintain aspect ratio
|
||||
verify(mockImage).getScaledInstance(anyInt(), anyInt(), eq(Image.SCALE_SMOOTH));
|
||||
}
|
||||
}
|
@ -1,27 +1,279 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
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.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public class UrlUtilsTest {
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UrlUtilsTest {
|
||||
|
||||
@Mock private HttpServletRequest request;
|
||||
|
||||
@Test
|
||||
void testGetOrigin() {
|
||||
// Mock HttpServletRequest
|
||||
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
||||
Mockito.when(request.getScheme()).thenReturn("http");
|
||||
Mockito.when(request.getServerName()).thenReturn("localhost");
|
||||
Mockito.when(request.getServerPort()).thenReturn(8080);
|
||||
Mockito.when(request.getContextPath()).thenReturn("/myapp");
|
||||
// Arrange
|
||||
when(request.getScheme()).thenReturn("http");
|
||||
when(request.getServerName()).thenReturn("localhost");
|
||||
when(request.getServerPort()).thenReturn(8080);
|
||||
when(request.getContextPath()).thenReturn("/myapp");
|
||||
|
||||
// Call the method under test
|
||||
// Act
|
||||
String origin = UrlUtils.getOrigin(request);
|
||||
|
||||
// Assert the result
|
||||
assertEquals("http://localhost:8080/myapp", origin);
|
||||
// Assert
|
||||
assertEquals(
|
||||
"http://localhost:8080/myapp", origin, "Origin URL should be correctly formatted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOriginWithHttps() {
|
||||
// Arrange
|
||||
when(request.getScheme()).thenReturn("https");
|
||||
when(request.getServerName()).thenReturn("example.com");
|
||||
when(request.getServerPort()).thenReturn(443);
|
||||
when(request.getContextPath()).thenReturn("");
|
||||
|
||||
// Act
|
||||
String origin = UrlUtils.getOrigin(request);
|
||||
|
||||
// Assert
|
||||
assertEquals(
|
||||
"https://example.com:443",
|
||||
origin,
|
||||
"HTTPS origin URL should be correctly formatted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOriginWithEmptyContextPath() {
|
||||
// Arrange
|
||||
when(request.getScheme()).thenReturn("http");
|
||||
when(request.getServerName()).thenReturn("localhost");
|
||||
when(request.getServerPort()).thenReturn(8080);
|
||||
when(request.getContextPath()).thenReturn("");
|
||||
|
||||
// Act
|
||||
String origin = UrlUtils.getOrigin(request);
|
||||
|
||||
// Assert
|
||||
assertEquals(
|
||||
"http://localhost:8080",
|
||||
origin,
|
||||
"Origin URL with empty context path should be correct");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOriginWithSpecialCharacters() {
|
||||
// Arrange - Test with server name containing special characters
|
||||
when(request.getScheme()).thenReturn("https");
|
||||
when(request.getServerName()).thenReturn("internal-server.example-domain.com");
|
||||
when(request.getServerPort()).thenReturn(8443);
|
||||
when(request.getContextPath()).thenReturn("/app-v1.2");
|
||||
|
||||
// Act
|
||||
String origin = UrlUtils.getOrigin(request);
|
||||
|
||||
// Assert
|
||||
assertEquals(
|
||||
"https://internal-server.example-domain.com:8443/app-v1.2",
|
||||
origin,
|
||||
"Origin URL with special characters should be correctly formatted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOriginWithIPv4Address() {
|
||||
// Arrange
|
||||
when(request.getScheme()).thenReturn("http");
|
||||
when(request.getServerName()).thenReturn("192.168.1.100");
|
||||
when(request.getServerPort()).thenReturn(8080);
|
||||
when(request.getContextPath()).thenReturn("/app");
|
||||
|
||||
// Act
|
||||
String origin = UrlUtils.getOrigin(request);
|
||||
|
||||
// Assert
|
||||
assertEquals(
|
||||
"http://192.168.1.100:8080/app",
|
||||
origin,
|
||||
"Origin URL with IPv4 address should be correctly formatted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOriginWithNonStandardPort() {
|
||||
// Arrange
|
||||
when(request.getScheme()).thenReturn("https");
|
||||
when(request.getServerName()).thenReturn("example.org");
|
||||
when(request.getServerPort()).thenReturn(8443);
|
||||
when(request.getContextPath()).thenReturn("/api");
|
||||
|
||||
// Act
|
||||
String origin = UrlUtils.getOrigin(request);
|
||||
|
||||
// Assert
|
||||
assertEquals(
|
||||
"https://example.org:8443/api",
|
||||
origin,
|
||||
"Origin URL with non-standard port should be correctly formatted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsPortAvailable() {
|
||||
// We'll use a real server socket for this test
|
||||
ServerSocket socket = null;
|
||||
int port = 12345; // Choose a port unlikely to be in use
|
||||
|
||||
try {
|
||||
// First check the port is available
|
||||
boolean initialAvailability = UrlUtils.isPortAvailable(port);
|
||||
|
||||
// Then occupy the port
|
||||
socket = new ServerSocket(port);
|
||||
|
||||
// Now check the port is no longer available
|
||||
boolean afterSocketCreation = UrlUtils.isPortAvailable(port);
|
||||
|
||||
// Assert
|
||||
assertTrue(initialAvailability, "Port should be available initially");
|
||||
assertFalse(
|
||||
afterSocketCreation, "Port should not be available after socket is created");
|
||||
|
||||
} catch (IOException e) {
|
||||
// This might happen if the port is already in use by another process
|
||||
// We'll just verify the behavior of isPortAvailable matches what we expect
|
||||
assertFalse(
|
||||
UrlUtils.isPortAvailable(port),
|
||||
"Port should not be available if exception is thrown");
|
||||
} finally {
|
||||
if (socket != null && !socket.isClosed()) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore cleanup exceptions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindAvailablePort() {
|
||||
// We'll create a socket on a port and ensure findAvailablePort returns a different port
|
||||
ServerSocket socket = null;
|
||||
int startPort = 12346; // Choose a port unlikely to be in use
|
||||
|
||||
try {
|
||||
// Occupy the start port
|
||||
socket = new ServerSocket(startPort);
|
||||
|
||||
// Find an available port
|
||||
String availablePort = UrlUtils.findAvailablePort(startPort);
|
||||
|
||||
// Assert the returned port is not the occupied one
|
||||
assertNotEquals(
|
||||
String.valueOf(startPort),
|
||||
availablePort,
|
||||
"findAvailablePort should not return an occupied port");
|
||||
|
||||
// Verify the returned port is actually available
|
||||
int portNumber = Integer.parseInt(availablePort);
|
||||
|
||||
// Close our test socket before checking the found port
|
||||
socket.close();
|
||||
socket = null;
|
||||
|
||||
// The port should now be available
|
||||
assertTrue(
|
||||
UrlUtils.isPortAvailable(portNumber),
|
||||
"The port returned by findAvailablePort should be available");
|
||||
|
||||
} catch (IOException e) {
|
||||
// If we can't create the socket, skip this assertion
|
||||
} finally {
|
||||
if (socket != null && !socket.isClosed()) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore cleanup exceptions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindAvailablePortWithAvailableStartPort() {
|
||||
// Find an available port without occupying any
|
||||
int startPort = 23456; // Choose a different unlikely-to-be-used port
|
||||
|
||||
// Make sure the port is available first
|
||||
if (UrlUtils.isPortAvailable(startPort)) {
|
||||
// Find an available port
|
||||
String availablePort = UrlUtils.findAvailablePort(startPort);
|
||||
|
||||
// Assert the returned port is the start port since it's available
|
||||
assertEquals(
|
||||
String.valueOf(startPort),
|
||||
availablePort,
|
||||
"findAvailablePort should return the start port if it's available");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindAvailablePortWithSequentialUsedPorts() {
|
||||
// This test checks that findAvailablePort correctly skips multiple occupied ports
|
||||
ServerSocket socket1 = null;
|
||||
ServerSocket socket2 = null;
|
||||
int startPort = 34567; // Another unlikely-to-be-used port
|
||||
|
||||
try {
|
||||
// First verify the port is available
|
||||
if (!UrlUtils.isPortAvailable(startPort)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Occupy two sequential ports
|
||||
socket1 = new ServerSocket(startPort);
|
||||
socket2 = new ServerSocket(startPort + 1);
|
||||
|
||||
// Find an available port starting from our occupied range
|
||||
String availablePort = UrlUtils.findAvailablePort(startPort);
|
||||
int foundPort = Integer.parseInt(availablePort);
|
||||
|
||||
// Should have skipped the two occupied ports
|
||||
assertTrue(
|
||||
foundPort >= startPort + 2,
|
||||
"findAvailablePort should skip sequential occupied ports");
|
||||
|
||||
// Verify the found port is actually available
|
||||
try (ServerSocket testSocket = new ServerSocket(foundPort)) {
|
||||
assertTrue(testSocket.isBound(), "The found port should be bindable");
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
// Skip test if we encounter IO exceptions
|
||||
} finally {
|
||||
// Clean up resources
|
||||
try {
|
||||
if (socket1 != null && !socket1.isClosed()) socket1.close();
|
||||
if (socket2 != null && !socket2.isClosed()) socket2.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore cleanup exceptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsPortAvailableWithPrivilegedPorts() {
|
||||
// Skip tests for privileged ports as they typically require root access
|
||||
// and results are environment-dependent
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
|
||||
class CustomColorReplaceStrategyTest {
|
||||
|
||||
private CustomColorReplaceStrategy strategy;
|
||||
private MultipartFile mockFile;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Create a mock file
|
||||
mockFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.pdf", "application/pdf", "test pdf content".getBytes());
|
||||
|
||||
// Initialize strategy with custom colors
|
||||
strategy =
|
||||
new CustomColorReplaceStrategy(
|
||||
mockFile,
|
||||
ReplaceAndInvert.CUSTOM_COLOR,
|
||||
"000000", // Black text color
|
||||
"FFFFFF", // White background color
|
||||
null); // Not using high contrast combination for CUSTOM_COLOR
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructor() {
|
||||
// Test the constructor sets values correctly
|
||||
assertNotNull(strategy, "Strategy should be initialized");
|
||||
assertEquals(mockFile, strategy.getFileInput(), "File input should be set correctly");
|
||||
assertEquals(
|
||||
ReplaceAndInvert.CUSTOM_COLOR,
|
||||
strategy.getReplaceAndInvert(),
|
||||
"ReplaceAndInvert should be set correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCheckSupportedFontForCharacter() throws Exception {
|
||||
// Use reflection to access private method
|
||||
Method method =
|
||||
CustomColorReplaceStrategy.class.getDeclaredMethod(
|
||||
"checkSupportedFontForCharacter", String.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
// Test with ASCII character which should be supported by standard fonts
|
||||
Object result = method.invoke(strategy, "A");
|
||||
assertNotNull(result, "Standard font should support ASCII character");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHighContrastColors() {
|
||||
// Create a new strategy with HIGH_CONTRAST_COLOR setting
|
||||
CustomColorReplaceStrategy highContrastStrategy =
|
||||
new CustomColorReplaceStrategy(
|
||||
mockFile,
|
||||
ReplaceAndInvert.HIGH_CONTRAST_COLOR,
|
||||
null, // These will be overridden by the high contrast settings
|
||||
null,
|
||||
HighContrastColorCombination.BLACK_TEXT_ON_WHITE);
|
||||
|
||||
// Verify the colors after replace() is called
|
||||
try {
|
||||
// Call replace (but we don't need the actual result for this test)
|
||||
// This will throw IOException because we're using a mock file without actual PDF
|
||||
// content
|
||||
// but it will still set the colors according to the high contrast setting
|
||||
try {
|
||||
highContrastStrategy.replace();
|
||||
} catch (IOException e) {
|
||||
// Expected exception due to mock file
|
||||
}
|
||||
|
||||
// Use reflection to access private fields
|
||||
java.lang.reflect.Field textColorField =
|
||||
CustomColorReplaceStrategy.class.getDeclaredField("textColor");
|
||||
textColorField.setAccessible(true);
|
||||
java.lang.reflect.Field backgroundColorField =
|
||||
CustomColorReplaceStrategy.class.getDeclaredField("backgroundColor");
|
||||
backgroundColorField.setAccessible(true);
|
||||
|
||||
String textColor = (String) textColorField.get(highContrastStrategy);
|
||||
String backgroundColor = (String) backgroundColorField.get(highContrastStrategy);
|
||||
|
||||
// For BLACK_TEXT_ON_WHITE, text color should be "0" and background color should be
|
||||
// "16777215"
|
||||
assertEquals("0", textColor, "Text color should be black (0)");
|
||||
assertEquals(
|
||||
"16777215", backgroundColor, "Background color should be white (16777215)");
|
||||
|
||||
} catch (Exception e) {
|
||||
// If we get here, the test failed
|
||||
org.junit.jupiter.api.Assertions.fail("Exception occurred: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
|
||||
class HighContrastColorReplaceDeciderTest {
|
||||
|
||||
@Test
|
||||
void testGetColors_BlackTextOnWhite() {
|
||||
// Arrange
|
||||
ReplaceAndInvert replaceAndInvert = ReplaceAndInvert.HIGH_CONTRAST_COLOR;
|
||||
HighContrastColorCombination combination = HighContrastColorCombination.BLACK_TEXT_ON_WHITE;
|
||||
|
||||
// Act
|
||||
String[] colors = HighContrastColorReplaceDecider.getColors(replaceAndInvert, combination);
|
||||
|
||||
// Assert
|
||||
assertArrayEquals(
|
||||
new String[] {"0", "16777215"},
|
||||
colors,
|
||||
"Should return black (0) for text and white (16777215) for background");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetColors_GreenTextOnBlack() {
|
||||
// Arrange
|
||||
ReplaceAndInvert replaceAndInvert = ReplaceAndInvert.HIGH_CONTRAST_COLOR;
|
||||
HighContrastColorCombination combination = HighContrastColorCombination.GREEN_TEXT_ON_BLACK;
|
||||
|
||||
// Act
|
||||
String[] colors = HighContrastColorReplaceDecider.getColors(replaceAndInvert, combination);
|
||||
|
||||
// Assert
|
||||
assertArrayEquals(
|
||||
new String[] {"65280", "0"},
|
||||
colors,
|
||||
"Should return green (65280) for text and black (0) for background");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetColors_WhiteTextOnBlack() {
|
||||
// Arrange
|
||||
ReplaceAndInvert replaceAndInvert = ReplaceAndInvert.HIGH_CONTRAST_COLOR;
|
||||
HighContrastColorCombination combination = HighContrastColorCombination.WHITE_TEXT_ON_BLACK;
|
||||
|
||||
// Act
|
||||
String[] colors = HighContrastColorReplaceDecider.getColors(replaceAndInvert, combination);
|
||||
|
||||
// Assert
|
||||
assertArrayEquals(
|
||||
new String[] {"16777215", "0"},
|
||||
colors,
|
||||
"Should return white (16777215) for text and black (0) for background");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetColors_YellowTextOnBlack() {
|
||||
// Arrange
|
||||
ReplaceAndInvert replaceAndInvert = ReplaceAndInvert.HIGH_CONTRAST_COLOR;
|
||||
HighContrastColorCombination combination =
|
||||
HighContrastColorCombination.YELLOW_TEXT_ON_BLACK;
|
||||
|
||||
// Act
|
||||
String[] colors = HighContrastColorReplaceDecider.getColors(replaceAndInvert, combination);
|
||||
|
||||
// Assert
|
||||
assertArrayEquals(
|
||||
new String[] {"16776960", "0"},
|
||||
colors,
|
||||
"Should return yellow (16776960) for text and black (0) for background");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetColors_NullForInvalidCombination() {
|
||||
// Arrange - use null for combination
|
||||
ReplaceAndInvert replaceAndInvert = ReplaceAndInvert.HIGH_CONTRAST_COLOR;
|
||||
|
||||
// Act
|
||||
String[] colors = HighContrastColorReplaceDecider.getColors(replaceAndInvert, null);
|
||||
|
||||
// Assert
|
||||
assertNull(colors, "Should return null for invalid combination");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetColors_ReplaceAndInvertParameterIsIgnored() {
|
||||
// Arrange - use different ReplaceAndInvert values with the same combination
|
||||
HighContrastColorCombination combination = HighContrastColorCombination.BLACK_TEXT_ON_WHITE;
|
||||
|
||||
// Act
|
||||
String[] colors1 =
|
||||
HighContrastColorReplaceDecider.getColors(
|
||||
ReplaceAndInvert.HIGH_CONTRAST_COLOR, combination);
|
||||
String[] colors2 =
|
||||
HighContrastColorReplaceDecider.getColors(
|
||||
ReplaceAndInvert.CUSTOM_COLOR, combination);
|
||||
String[] colors3 =
|
||||
HighContrastColorReplaceDecider.getColors(
|
||||
ReplaceAndInvert.FULL_INVERSION, combination);
|
||||
|
||||
// Assert - all should return the same colors, showing that the ReplaceAndInvert parameter
|
||||
// isn't used
|
||||
assertArrayEquals(colors1, colors2, "ReplaceAndInvert parameter should be ignored");
|
||||
assertArrayEquals(colors1, colors3, "ReplaceAndInvert parameter should be ignored");
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package stirling.software.SPDF.utils.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 java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.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", "application/pdf", 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 {
|
||||
PDDocument document = new PDDocument();
|
||||
PDPage page = new PDPage(PDRectangle.A4);
|
||||
document.addPage(page);
|
||||
|
||||
// Add a filled rectangle with a specific color
|
||||
PDPageContentStream contentStream = new PDPageContentStream(document, page);
|
||||
contentStream.setNonStrokingColor(
|
||||
new PDColor(new float[] {0.8f, 0.2f, 0.2f}, PDDeviceRGB.INSTANCE));
|
||||
contentStream.addRect(100, 100, 400, 400);
|
||||
contentStream.fill();
|
||||
contentStream.close();
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
document.close();
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReplace() throws IOException {
|
||||
// Test the replace method
|
||||
InputStreamResource result = strategy.replace();
|
||||
|
||||
// Verify that the result is not null
|
||||
assertNotNull(result, "The result should not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvertImageColors()
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
// Create a test image with known colors
|
||||
BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
||||
java.awt.Graphics graphics = image.getGraphics();
|
||||
graphics.setColor(new Color(200, 100, 50)); // RGB color to be inverted
|
||||
graphics.fillRect(0, 0, 10, 10);
|
||||
graphics.dispose();
|
||||
|
||||
// Get the color of a pixel before inversion
|
||||
Color originalColor = new Color(image.getRGB(5, 5), true);
|
||||
|
||||
// Access private method using reflection
|
||||
Method invertMethodRef =
|
||||
InvertFullColorStrategy.class.getDeclaredMethod(
|
||||
"invertImageColors", BufferedImage.class);
|
||||
invertMethodRef.setAccessible(true);
|
||||
|
||||
// Invoke the private method
|
||||
invertMethodRef.invoke(strategy, image);
|
||||
|
||||
// Get the color of the same pixel after inversion
|
||||
Color invertedColor = new Color(image.getRGB(5, 5), true);
|
||||
|
||||
// Assert that the inversion worked correctly
|
||||
assertEquals(
|
||||
255 - originalColor.getRed(),
|
||||
invertedColor.getRed(),
|
||||
"Red channel should be inverted");
|
||||
assertEquals(
|
||||
255 - originalColor.getGreen(),
|
||||
invertedColor.getGreen(),
|
||||
"Green channel should be inverted");
|
||||
assertEquals(
|
||||
255 - originalColor.getBlue(),
|
||||
invertedColor.getBlue(),
|
||||
"Blue channel should be inverted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConvertToBufferedImageTpFile()
|
||||
throws NoSuchMethodException,
|
||||
InvocationTargetException,
|
||||
IllegalAccessException,
|
||||
IOException {
|
||||
// Create a test image
|
||||
BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
// Access private method using reflection
|
||||
Method convertMethodRef =
|
||||
InvertFullColorStrategy.class.getDeclaredMethod(
|
||||
"convertToBufferedImageTpFile", BufferedImage.class);
|
||||
convertMethodRef.setAccessible(true);
|
||||
|
||||
// Invoke the private method
|
||||
File result = (File) convertMethodRef.invoke(strategy, image);
|
||||
|
||||
try {
|
||||
// Assert that the file exists and is not empty
|
||||
assertNotNull(result, "Result should not be null");
|
||||
assertTrue(result.exists(), "File should exist");
|
||||
assertTrue(result.length() > 0, "File should not be empty");
|
||||
|
||||
// Check that the file can be read back as an image
|
||||
BufferedImage readBack = ImageIO.read(result);
|
||||
assertNotNull(readBack, "Should be able to read back the image");
|
||||
assertEquals(10, readBack.getWidth(), "Image width should match");
|
||||
assertEquals(10, readBack.getHeight(), "Image height should match");
|
||||
} finally {
|
||||
// Clean up
|
||||
if (result != null && result.exists()) {
|
||||
Files.delete(result.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PdfTextStripperCustomTest {
|
||||
|
||||
private PdfTextStripperCustom stripper;
|
||||
private PDPage mockPage;
|
||||
private PDRectangle mockMediaBox;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
// Create the stripper instance
|
||||
stripper = new PdfTextStripperCustom();
|
||||
|
||||
// Create mock objects
|
||||
mockPage = mock(PDPage.class);
|
||||
mockMediaBox = mock(PDRectangle.class);
|
||||
|
||||
// Configure mock behavior
|
||||
when(mockPage.getMediaBox()).thenReturn(mockMediaBox);
|
||||
when(mockMediaBox.getLowerLeftX()).thenReturn(0f);
|
||||
when(mockMediaBox.getLowerLeftY()).thenReturn(0f);
|
||||
when(mockMediaBox.getWidth()).thenReturn(612f);
|
||||
when(mockMediaBox.getHeight()).thenReturn(792f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructor() throws IOException {
|
||||
// Verify that constructor doesn't throw an exception
|
||||
PdfTextStripperCustom newStripper = new PdfTextStripperCustom();
|
||||
assertNotNull(newStripper, "Constructor should create a non-null instance");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBasicFunctionality() throws IOException {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
|
||||
class ReplaceAndInvertColorStrategyTest {
|
||||
|
||||
// A concrete implementation of the abstract class for testing
|
||||
private static class ConcreteReplaceAndInvertColorStrategy
|
||||
extends ReplaceAndInvertColorStrategy {
|
||||
|
||||
public ConcreteReplaceAndInvertColorStrategy(
|
||||
MultipartFile file, ReplaceAndInvert replaceAndInvert) {
|
||||
super(file, replaceAndInvert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStreamResource replace() throws IOException {
|
||||
// Simple implementation for testing purposes
|
||||
return new InputStreamResource(getFileInput().getInputStream());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructor() {
|
||||
// Arrange
|
||||
MultipartFile mockFile =
|
||||
new MockMultipartFile(
|
||||
"file", "test.pdf", "application/pdf", "test content".getBytes());
|
||||
ReplaceAndInvert replaceAndInvert = ReplaceAndInvert.CUSTOM_COLOR;
|
||||
|
||||
// Act
|
||||
ReplaceAndInvertColorStrategy strategy =
|
||||
new ConcreteReplaceAndInvertColorStrategy(mockFile, replaceAndInvert);
|
||||
|
||||
// Assert
|
||||
assertNotNull(strategy, "Strategy should be initialized");
|
||||
assertEquals(mockFile, strategy.getFileInput(), "File input should be set correctly");
|
||||
assertEquals(
|
||||
replaceAndInvert,
|
||||
strategy.getReplaceAndInvert(),
|
||||
"ReplaceAndInvert option should be set correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReplace() throws IOException {
|
||||
// Arrange
|
||||
byte[] content = "test pdf content".getBytes();
|
||||
MultipartFile mockFile =
|
||||
new MockMultipartFile("file", "test.pdf", "application/pdf", content);
|
||||
ReplaceAndInvert replaceAndInvert = ReplaceAndInvert.CUSTOM_COLOR;
|
||||
|
||||
ReplaceAndInvertColorStrategy strategy =
|
||||
new ConcreteReplaceAndInvertColorStrategy(mockFile, replaceAndInvert);
|
||||
|
||||
// Act
|
||||
InputStreamResource result = strategy.replace();
|
||||
|
||||
// Assert
|
||||
assertNotNull(result, "Result should not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGettersAndSetters() {
|
||||
// Arrange
|
||||
MultipartFile mockFile1 =
|
||||
new MockMultipartFile(
|
||||
"file1", "test1.pdf", "application/pdf", "content1".getBytes());
|
||||
MultipartFile mockFile2 =
|
||||
new MockMultipartFile(
|
||||
"file2", "test2.pdf", "application/pdf", "content2".getBytes());
|
||||
|
||||
// Act
|
||||
ReplaceAndInvertColorStrategy strategy =
|
||||
new ConcreteReplaceAndInvertColorStrategy(mockFile1, ReplaceAndInvert.CUSTOM_COLOR);
|
||||
|
||||
// Test initial values
|
||||
assertEquals(mockFile1, strategy.getFileInput());
|
||||
assertEquals(ReplaceAndInvert.CUSTOM_COLOR, strategy.getReplaceAndInvert());
|
||||
|
||||
// Test setters
|
||||
strategy.setFileInput(mockFile2);
|
||||
strategy.setReplaceAndInvert(ReplaceAndInvert.FULL_INVERSION);
|
||||
|
||||
// Assert new values
|
||||
assertEquals(mockFile2, strategy.getFileInput());
|
||||
assertEquals(ReplaceAndInvert.FULL_INVERSION, strategy.getReplaceAndInvert());
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
package stirling.software.SPDF.utils.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 java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.RedactionArea;
|
||||
|
||||
class StringToArrayListPropertyEditorTest {
|
||||
|
||||
private StringToArrayListPropertyEditor editor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
editor = new StringToArrayListPropertyEditor();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_ValidJson() {
|
||||
// Arrange
|
||||
String json =
|
||||
"[{\"x\":10.5,\"y\":20.5,\"width\":100.0,\"height\":50.0,\"page\":1,\"color\":\"#FF0000\"}]";
|
||||
|
||||
// Act
|
||||
editor.setAsText(json);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
assertEquals(1, list.size(), "List should have 1 entry");
|
||||
|
||||
RedactionArea area = list.get(0);
|
||||
assertEquals(10.5, area.getX(), "X should be 10.5");
|
||||
assertEquals(20.5, area.getY(), "Y should be 20.5");
|
||||
assertEquals(100.0, area.getWidth(), "Width should be 100.0");
|
||||
assertEquals(50.0, area.getHeight(), "Height should be 50.0");
|
||||
assertEquals(1, area.getPage(), "Page should be 1");
|
||||
assertEquals("#FF0000", area.getColor(), "Color should be #FF0000");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_MultipleItems() {
|
||||
// Arrange
|
||||
String json =
|
||||
"["
|
||||
+ "{\"x\":10.0,\"y\":20.0,\"width\":100.0,\"height\":50.0,\"page\":1,\"color\":\"#FF0000\"},"
|
||||
+ "{\"x\":30.0,\"y\":40.0,\"width\":200.0,\"height\":150.0,\"page\":2,\"color\":\"#00FF00\"}"
|
||||
+ "]";
|
||||
|
||||
// Act
|
||||
editor.setAsText(json);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
assertEquals(2, list.size(), "List should have 2 entries");
|
||||
|
||||
RedactionArea area1 = list.get(0);
|
||||
assertEquals(10.0, area1.getX(), "X should be 10.0");
|
||||
assertEquals(20.0, area1.getY(), "Y should be 20.0");
|
||||
assertEquals(1, area1.getPage(), "Page should be 1");
|
||||
|
||||
RedactionArea area2 = list.get(1);
|
||||
assertEquals(30.0, area2.getX(), "X should be 30.0");
|
||||
assertEquals(40.0, area2.getY(), "Y should be 40.0");
|
||||
assertEquals(2, area2.getPage(), "Page should be 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_EmptyString() {
|
||||
// Arrange
|
||||
String json = "";
|
||||
|
||||
// Act
|
||||
editor.setAsText(json);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
assertTrue(list.isEmpty(), "List should be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_NullString() {
|
||||
// Act
|
||||
editor.setAsText(null);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
assertTrue(list.isEmpty(), "List should be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_SingleItemAsArray() {
|
||||
// Arrange - note this is a single object, not an array
|
||||
String json =
|
||||
"{\"x\":10.0,\"y\":20.0,\"width\":100.0,\"height\":50.0,\"page\":1,\"color\":\"#FF0000\"}";
|
||||
|
||||
// Act
|
||||
editor.setAsText(json);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof List, "Value should be a List");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<RedactionArea> list = (List<RedactionArea>) value;
|
||||
assertEquals(1, list.size(), "List should have 1 entry");
|
||||
|
||||
RedactionArea area = list.get(0);
|
||||
assertEquals(10.0, area.getX(), "X should be 10.0");
|
||||
assertEquals(20.0, area.getY(), "Y should be 20.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_InvalidJson() {
|
||||
// Arrange
|
||||
String json = "invalid json";
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(IllegalArgumentException.class, () -> editor.setAsText(json));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_InvalidStructure() {
|
||||
// Arrange - this JSON doesn't match RedactionArea structure
|
||||
String json = "[{\"invalid\":\"structure\"}]";
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(IllegalArgumentException.class, () -> editor.setAsText(json));
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package stirling.software.SPDF.utils.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 java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class StringToMapPropertyEditorTest {
|
||||
|
||||
private StringToMapPropertyEditor editor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
editor = new StringToMapPropertyEditor();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_ValidJson() {
|
||||
// Arrange
|
||||
String json = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
|
||||
|
||||
// Act
|
||||
editor.setAsText(json);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof Map, "Value should be a Map");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> map = (Map<String, String>) value;
|
||||
assertEquals(2, map.size(), "Map should have 2 entries");
|
||||
assertEquals("value1", map.get("key1"), "First entry should be key1=value1");
|
||||
assertEquals("value2", map.get("key2"), "Second entry should be key2=value2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_EmptyJson() {
|
||||
// Arrange
|
||||
String json = "{}";
|
||||
|
||||
// Act
|
||||
editor.setAsText(json);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof Map, "Value should be a Map");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> map = (Map<String, String>) value;
|
||||
assertTrue(map.isEmpty(), "Map should be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_WhitespaceJson() {
|
||||
// Arrange
|
||||
String json = " { \"key1\" : \"value1\" } ";
|
||||
|
||||
// Act
|
||||
editor.setAsText(json);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof Map, "Value should be a Map");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> map = (Map<String, String>) value;
|
||||
assertEquals(1, map.size(), "Map should have 1 entry");
|
||||
assertEquals("value1", map.get("key1"), "Entry should be key1=value1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_NestedJson() {
|
||||
// Arrange
|
||||
String json = "{\"key1\":\"value1\",\"key2\":\"{\\\"nestedKey\\\":\\\"nestedValue\\\"}\"}";
|
||||
|
||||
// Act
|
||||
editor.setAsText(json);
|
||||
Object value = editor.getValue();
|
||||
|
||||
// Assert
|
||||
assertNotNull(value, "Value should not be null");
|
||||
assertTrue(value instanceof Map, "Value should be a Map");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> map = (Map<String, String>) value;
|
||||
assertEquals(2, map.size(), "Map should have 2 entries");
|
||||
assertEquals("value1", map.get("key1"), "First entry should be key1=value1");
|
||||
assertEquals(
|
||||
"{\"nestedKey\":\"nestedValue\"}",
|
||||
map.get("key2"),
|
||||
"Second entry should be the nested JSON as a string");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_InvalidJson() {
|
||||
// Arrange
|
||||
String json = "{invalid json}";
|
||||
|
||||
// Act & Assert
|
||||
IllegalArgumentException exception =
|
||||
assertThrows(IllegalArgumentException.class, () -> editor.setAsText(json));
|
||||
|
||||
assertEquals(
|
||||
"Failed to convert java.lang.String to java.util.Map",
|
||||
exception.getMessage(),
|
||||
"Exception message should match expected error");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAsText_Null() {
|
||||
// Act & Assert
|
||||
assertThrows(IllegalArgumentException.class, () -> editor.setAsText(null));
|
||||
}
|
||||
}
|
BIN
src/test/resources/example.pdf
Normal file
BIN
src/test/resources/example.pdf
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user