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 <[email protected]>
Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
This commit is contained in:
Anthony Stirling
2025-05-19 14:12:06 +01:00
committed by GitHub
co-authored by Copilot pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
parent f94b8c3b22
commit 21832729d2
35 changed files with 4572 additions and 76 deletions
@@ -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
}
}