Merge PdfUtils tests into single class

This commit is contained in:
Anthony Stirling 2025-05-20 08:39:13 +01:00
parent e6a9e7a584
commit f15566e295
8 changed files with 396 additions and 178 deletions

View File

@ -49,7 +49,8 @@ public class KeygenLicenseVerifier {
private final ApplicationProperties applicationProperties;
// Shared HTTP client for connection pooling
private static final HttpClient httpClient = HttpClient.newBuilder()
private static final HttpClient httpClient =
HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(java.time.Duration.ofSeconds(10))
.build();
@ -416,7 +417,9 @@ public class KeygenLicenseVerifier {
if (policyFloating) {
context.isFloatingLicense = true;
context.maxMachines = policyMaxMachines;
log.info("Policy defines floating license with max machines: {}", context.maxMachines);
log.info(
"Policy defines floating license with max machines: {}",
context.maxMachines);
}
// Extract max users and isEnterprise from policy or metadata
@ -474,7 +477,8 @@ public class KeygenLicenseVerifier {
activateMachine(licenseKey, licenseId, machineFingerprint, context);
if (activated) {
// Revalidate after activation
validationResponse = validateLicense(licenseKey, machineFingerprint, context);
validationResponse =
validateLicense(licenseKey, machineFingerprint, context);
isValid =
validationResponse != null
&& validationResponse
@ -494,8 +498,8 @@ public class KeygenLicenseVerifier {
}
}
private JsonNode validateLicense(String licenseKey, String machineFingerprint, LicenseContext context)
throws Exception {
private JsonNode validateLicense(
String licenseKey, String machineFingerprint, LicenseContext context) throws Exception {
String requestBody =
String.format(
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
@ -514,7 +518,8 @@ public class KeygenLicenseVerifier {
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("ValidateLicenseResponse body: {}", response.body());
JsonNode jsonResponse = objectMapper.readTree(response.body());
if (response.statusCode() == 200) {
@ -534,8 +539,10 @@ public class KeygenLicenseVerifier {
context.isFloatingLicense = licenseAttrs.path("floating").asBoolean(false);
context.maxMachines = licenseAttrs.path("maxMachines").asInt(1);
log.info("License floating (from license): {}, maxMachines: {}",
context.isFloatingLicense, context.maxMachines);
log.info(
"License floating (from license): {}, maxMachines: {}",
context.isFloatingLicense,
context.maxMachines);
}
// Also check the policy for floating license support if included
@ -553,7 +560,8 @@ public class KeygenLicenseVerifier {
if (policyNode != null) {
// Check if this is a floating license from policy
boolean policyFloating = policyNode.path("attributes").path("floating").asBoolean(false);
boolean policyFloating =
policyNode.path("attributes").path("floating").asBoolean(false);
int policyMaxMachines = policyNode.path("attributes").path("maxMachines").asInt(1);
// Policy takes precedence over license attributes
@ -562,8 +570,10 @@ public class KeygenLicenseVerifier {
context.maxMachines = policyMaxMachines;
}
log.info("License floating (from policy): {}, maxMachines: {}",
context.isFloatingLicense, context.maxMachines);
log.info(
"License floating (from policy): {}, maxMachines: {}",
context.isFloatingLicense,
context.maxMachines);
}
// Extract user count, default to 1 if not specified
@ -593,11 +603,14 @@ public class KeygenLicenseVerifier {
return jsonResponse;
}
private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint,
LicenseContext context) throws Exception {
private boolean activateMachine(
String licenseKey, String licenseId, String machineFingerprint, LicenseContext context)
throws Exception {
// For floating licenses, we first need to check if we need to deregister any machines
if (context.isFloatingLicense) {
log.info("Processing floating license activation. Max machines allowed: {}", context.maxMachines);
log.info(
"Processing floating license activation. Max machines allowed: {}",
context.maxMachines);
// Get the current machines for this license
JsonNode machinesResponse = fetchMachinesForLicense(licenseKey, licenseId);
@ -605,17 +618,23 @@ public class KeygenLicenseVerifier {
JsonNode machines = machinesResponse.path("data");
int currentMachines = machines.size();
log.info("Current machine count: {}, Max allowed: {}", currentMachines, context.maxMachines);
log.info(
"Current machine count: {}, Max allowed: {}",
currentMachines,
context.maxMachines);
// Check if the current fingerprint is already activated
boolean isCurrentMachineActivated = false;
String currentMachineId = null;
for (JsonNode machine : machines) {
if (machineFingerprint.equals(machine.path("attributes").path("fingerprint").asText())) {
if (machineFingerprint.equals(
machine.path("attributes").path("fingerprint").asText())) {
isCurrentMachineActivated = true;
currentMachineId = machine.path("id").asText();
log.info("Current machine is already activated with ID: {}", currentMachineId);
log.info(
"Current machine is already activated with ID: {}",
currentMachineId);
break;
}
}
@ -628,7 +647,8 @@ public class KeygenLicenseVerifier {
// If we've reached the max machines limit, we need to deregister the oldest machine
if (currentMachines >= context.maxMachines) {
log.info("Max machines reached. Deregistering oldest machine to make room for the new machine.");
log.info(
"Max machines reached. Deregistering oldest machine to make room for the new machine.");
// Find the oldest machine based on creation timestamp
if (machines.size() > 0) {
@ -637,23 +657,28 @@ public class KeygenLicenseVerifier {
java.time.Instant oldestTime = null;
for (JsonNode machine : machines) {
String createdStr = machine.path("attributes").path("created").asText(null);
String createdStr =
machine.path("attributes").path("created").asText(null);
if (createdStr != null && !createdStr.isEmpty()) {
try {
java.time.Instant createdTime = java.time.Instant.parse(createdStr);
java.time.Instant createdTime =
java.time.Instant.parse(createdStr);
if (oldestTime == null || createdTime.isBefore(oldestTime)) {
oldestTime = createdTime;
oldestMachineId = machine.path("id").asText();
}
} catch (Exception e) {
log.warn("Could not parse creation time for machine: {}", e.getMessage());
log.warn(
"Could not parse creation time for machine: {}",
e.getMessage());
}
}
}
// If we couldn't determine the oldest by timestamp, use the first one
if (oldestMachineId == null) {
log.warn("Could not determine oldest machine by timestamp, using first machine in list");
log.warn(
"Could not determine oldest machine by timestamp, using first machine in list");
oldestMachineId = machines.path(0).path("id").asText();
}
@ -661,12 +686,15 @@ public class KeygenLicenseVerifier {
boolean deregistered = deregisterMachine(licenseKey, oldestMachineId);
if (!deregistered) {
log.error("Failed to deregister machine. Cannot proceed with activation.");
log.error(
"Failed to deregister machine. Cannot proceed with activation.");
return false;
}
log.info("Machine deregistered successfully. Proceeding with activation of new machine.");
log.info(
"Machine deregistered successfully. Proceeding with activation of new machine.");
} else {
log.error("License has reached machine limit but no machines were found to deregister. This is unexpected.");
log.error(
"License has reached machine limit but no machines were found to deregister. This is unexpected.");
// We'll still try to activate, but it might fail
}
}
@ -720,7 +748,8 @@ public class KeygenLicenseVerifier {
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("activateMachine Response body: " + response.body());
if (response.statusCode() == 201) {
log.info("Machine activated successfully");
@ -748,22 +777,33 @@ public class KeygenLicenseVerifier {
* @throws Exception if an error occurs during the HTTP request
*/
private JsonNode fetchMachinesForLicense(String licenseKey, String licenseId) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/licenses/" + licenseId + "/machines"))
HttpRequest request =
HttpRequest.newBuilder()
.uri(
URI.create(
BASE_URL
+ "/"
+ ACCOUNT_ID
+ "/licenses/"
+ licenseId
+ "/machines"))
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.header("Authorization", "License " + licenseKey)
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
log.info("fetchMachinesForLicense Response body: {}", response.body());
if (response.statusCode() == 200) {
return objectMapper.readTree(response.body());
} else {
log.error("Error fetching machines for license. Status code: {}, error: {}",
response.statusCode(), response.body());
log.error(
"Error fetching machines for license. Status code: {}, error: {}",
response.statusCode(),
response.body());
return null;
}
}
@ -777,7 +817,8 @@ public class KeygenLicenseVerifier {
*/
private boolean deregisterMachine(String licenseKey, String machineId) {
try {
HttpRequest request = HttpRequest.newBuilder()
HttpRequest request =
HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines/" + machineId))
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
@ -785,14 +826,17 @@ public class KeygenLicenseVerifier {
.DELETE()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 204) {
log.info("Machine {} successfully deregistered", machineId);
return true;
} else {
log.error("Error deregistering machine. Status code: {}, error: {}",
response.statusCode(), response.body());
log.error(
"Error deregistering machine. Status code: {}, error: {}",
response.statusCode(),
response.body());
return false;
}
} catch (Exception e) {

View File

@ -0,0 +1,77 @@
package stirling.software.SPDF.EE;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.SPDF.EE.KeygenLicenseVerifier.License;
import stirling.software.SPDF.model.ApplicationProperties;
@ExtendWith(MockitoExtension.class)
class LicenseKeyCheckerTest {
@Mock private KeygenLicenseVerifier verifier;
@Test
void premiumDisabled_skipsVerification() {
ApplicationProperties props = new ApplicationProperties();
props.getPremium().setEnabled(false);
props.getPremium().setKey("dummy");
LicenseKeyChecker checker = new LicenseKeyChecker(verifier, props);
assertEquals(License.NORMAL, checker.getPremiumLicenseEnabledResult());
verifyNoInteractions(verifier);
}
@Test
void directKey_verified() {
ApplicationProperties props = new ApplicationProperties();
props.getPremium().setEnabled(true);
props.getPremium().setKey("abc");
when(verifier.verifyLicense("abc")).thenReturn(License.PRO);
LicenseKeyChecker checker = new LicenseKeyChecker(verifier, props);
assertEquals(License.PRO, checker.getPremiumLicenseEnabledResult());
verify(verifier).verifyLicense("abc");
}
@Test
void fileKey_verified(@TempDir Path temp) throws IOException {
Path file = temp.resolve("license.txt");
Files.writeString(file, "filekey");
ApplicationProperties props = new ApplicationProperties();
props.getPremium().setEnabled(true);
props.getPremium().setKey("file:" + file.toString());
when(verifier.verifyLicense("filekey")).thenReturn(License.ENTERPRISE);
LicenseKeyChecker checker = new LicenseKeyChecker(verifier, props);
assertEquals(License.ENTERPRISE, checker.getPremiumLicenseEnabledResult());
verify(verifier).verifyLicense("filekey");
}
@Test
void missingFile_resultsNormal(@TempDir Path temp) {
Path file = temp.resolve("missing.txt");
ApplicationProperties props = new ApplicationProperties();
props.getPremium().setEnabled(true);
props.getPremium().setKey("file:" + file.toString());
LicenseKeyChecker checker = new LicenseKeyChecker(verifier, props);
assertEquals(License.NORMAL, checker.getPremiumLicenseEnabledResult());
verifyNoInteractions(verifier);
}
}

View File

@ -1,18 +1,17 @@
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.nio.file.Files;
import java.util.Arrays;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName;
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;
@ -43,12 +42,7 @@ class CustomPDFDocumentFactoryTest {
}
@ParameterizedTest
@CsvSource({
"5,MEMORY_ONLY",
"20,MIXED",
"60,TEMP_FILE"
})
@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)) {
@ -57,12 +51,7 @@ class CustomPDFDocumentFactoryTest {
}
@ParameterizedTest
@CsvSource({
"5,MEMORY_ONLY",
"20,MIXED",
"60,TEMP_FILE"
})
@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)) {
@ -71,12 +60,7 @@ class CustomPDFDocumentFactoryTest {
}
@ParameterizedTest
@CsvSource({
"5,MEMORY_ONLY",
"20,MIXED",
"60,TEMP_FILE"
})
@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))) {
@ -85,30 +69,22 @@ class CustomPDFDocumentFactoryTest {
}
@ParameterizedTest
@CsvSource({
"5,MEMORY_ONLY",
"20,MIXED",
"60,TEMP_FILE"
})
@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);
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"
})
@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);
MockMultipartFile multipart =
new MockMultipartFile("file", "doc.pdf", "application/pdf", inflated);
PDFFile pdfFile = new PDFFile();
pdfFile.setFileInput(multipart);
try (PDDocument doc = factory.load(pdfFile)) {
@ -125,7 +101,9 @@ class CustomPDFDocumentFactoryTest {
stream.getCOSObject().setItem(COSName.TYPE, COSName.XOBJECT);
stream.getCOSObject().setItem(COSName.SUBTYPE, COSName.IMAGE);
doc.getDocumentCatalog().getCOSObject().setItem(COSName.getPDFName("DummyBigStream"), stream.getCOSObject());
doc.getDocumentCatalog()
.getCOSObject()
.setItem(COSName.getPDFName("DummyBigStream"), stream.getCOSObject());
ByteArrayOutputStream out = new ByteArrayOutputStream();
doc.save(out);
@ -151,28 +129,28 @@ class CustomPDFDocumentFactoryTest {
}
// 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 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 {
@ -186,7 +164,6 @@ class CustomPDFDocumentFactoryTest {
}
}
@Test
void testCreateNewDocument() throws IOException {
try (PDDocument doc = factory.createNewDocument()) {
@ -243,5 +220,4 @@ class CustomPDFDocumentFactoryTest {
void cleanup() {
System.gc();
}
}

View File

@ -1,12 +1,12 @@
package stirling.software.SPDF.service;
import org.apache.pdfbox.io.RandomAccessStreamCache.StreamCacheCreateFunction;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.service.PdfMetadataService;
import org.apache.pdfbox.io.RandomAccessStreamCache.StreamCacheCreateFunction;
class SpyPDFDocumentFactory extends CustomPDFDocumentFactory {
enum StrategyType {
MEMORY_ONLY, MIXED, TEMP_FILE
MEMORY_ONLY,
MIXED,
TEMP_FILE
}
public StrategyType lastStrategyUsed;

View File

@ -29,23 +29,26 @@ class CustomHtmlSanitizerTest {
return Stream.of(
Arguments.of(
"<p>This is <strong>valid</strong> HTML with <em>formatting</em>.</p>",
new String[] {"<p>", "<strong>", "<em>"}
),
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>"}
),
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>"}
)
);
new String[] {
"<div>", "<h1>", "<h6>", "<blockquote>", "<ul>", "<ol>", "<li>"
}));
}
@Test

View File

@ -0,0 +1,41 @@
package stirling.software.SPDF.utils;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class GeneralUtilsAdditionalTest {
@Test
void testConvertSizeToBytes() {
assertEquals(1024L, GeneralUtils.convertSizeToBytes("1KB"));
assertEquals(1024L * 1024, GeneralUtils.convertSizeToBytes("1MB"));
assertEquals(1024L * 1024 * 1024, GeneralUtils.convertSizeToBytes("1GB"));
assertEquals(100L * 1024 * 1024, GeneralUtils.convertSizeToBytes("100"));
assertNull(GeneralUtils.convertSizeToBytes("invalid"));
assertNull(GeneralUtils.convertSizeToBytes(null));
}
@Test
void testFormatBytes() {
assertEquals("512 B", GeneralUtils.formatBytes(512));
assertEquals("1.00 KB", GeneralUtils.formatBytes(1024));
assertEquals("1.00 MB", GeneralUtils.formatBytes(1024L * 1024));
assertEquals("1.00 GB", GeneralUtils.formatBytes(1024L * 1024 * 1024));
}
@Test
void testURLHelpersAndUUID() {
assertTrue(GeneralUtils.isValidURL("https://example.com"));
assertFalse(GeneralUtils.isValidURL("htp:/bad"));
assertFalse(GeneralUtils.isURLReachable("http://localhost"));
assertFalse(GeneralUtils.isURLReachable("ftp://example.com"));
assertTrue(GeneralUtils.isValidUUID("123e4567-e89b-12d3-a456-426614174000"));
assertFalse(GeneralUtils.isValidUUID("not-a-uuid"));
assertFalse(GeneralUtils.isVersionHigher(null, "1.0"));
assertTrue(GeneralUtils.isVersionHigher("2.0", "1.9"));
assertFalse(GeneralUtils.isVersionHigher("1.0", "1.0.1"));
}
}

View File

@ -1,6 +1,5 @@
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;
@ -29,6 +28,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.ZipSecurity;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
/**
@ -214,7 +215,8 @@ class PDFToFileTest {
// Verify the content by unzipping it
try (ZipInputStream zipStream =
ZipSecurity.createHardenedInputStream(new java.io.ByteArrayInputStream(response.getBody()))) {
ZipSecurity.createHardenedInputStream(
new java.io.ByteArrayInputStream(response.getBody()))) {
ZipEntry entry;
boolean foundMdFiles = false;
boolean foundImage = false;
@ -286,7 +288,8 @@ class PDFToFileTest {
// Verify the content by unzipping it
try (ZipInputStream zipStream =
ZipSecurity.createHardenedInputStream(new java.io.ByteArrayInputStream(response.getBody()))) {
ZipSecurity.createHardenedInputStream(
new java.io.ByteArrayInputStream(response.getBody()))) {
ZipEntry entry;
boolean foundMainHtml = false;
boolean foundIndexHtml = false;
@ -437,7 +440,8 @@ class PDFToFileTest {
// Verify the content by unzipping it
try (ZipInputStream zipStream =
ZipSecurity.createHardenedInputStream(new java.io.ByteArrayInputStream(response.getBody()))) {
ZipSecurity.createHardenedInputStream(
new java.io.ByteArrayInputStream(response.getBody()))) {
ZipEntry entry;
boolean foundMainFile = false;
boolean foundMediaFiles = false;

View File

@ -5,12 +5,17 @@ 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 java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
@ -18,6 +23,10 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.service.PdfMetadataService;
public class PdfUtilsTest {
@Test
@ -49,4 +58,68 @@ public class PdfUtilsTest {
assertTrue(PdfUtils.hasImagesOnPage(page));
}
@Test
void testPageCountComparators() throws Exception {
PDDocument doc1 = new PDDocument();
doc1.addPage(new PDPage());
doc1.addPage(new PDPage());
doc1.addPage(new PDPage());
PdfUtils utils = new PdfUtils();
assertTrue(utils.pageCount(doc1, 2, "greater"));
PDDocument doc2 = new PDDocument();
doc2.addPage(new PDPage());
doc2.addPage(new PDPage());
doc2.addPage(new PDPage());
assertTrue(utils.pageCount(doc2, 3, "equal"));
PDDocument doc3 = new PDDocument();
doc3.addPage(new PDPage());
doc3.addPage(new PDPage());
assertTrue(utils.pageCount(doc3, 5, "less"));
PDDocument doc4 = new PDDocument();
doc4.addPage(new PDPage());
assertThrows(IllegalArgumentException.class, () -> utils.pageCount(doc4, 1, "bad"));
}
@Test
void testPageSize() throws Exception {
PDDocument doc = new PDDocument();
PDPage page = new PDPage(PDRectangle.A4);
doc.addPage(page);
PDRectangle rect = page.getMediaBox();
String expected = rect.getWidth() + "x" + rect.getHeight();
PdfUtils utils = new PdfUtils();
assertTrue(utils.pageSize(doc, expected));
}
@Test
void testOverlayImage() throws Exception {
PDDocument doc = new PDDocument();
doc.addPage(new PDPage(PDRectangle.A4));
ByteArrayOutputStream pdfOut = new ByteArrayOutputStream();
doc.save(pdfOut);
doc.close();
BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.RED);
g.fillRect(0, 0, 10, 10);
g.dispose();
ByteArrayOutputStream imgOut = new ByteArrayOutputStream();
javax.imageio.ImageIO.write(image, "png", imgOut);
PdfMetadataService meta =
new PdfMetadataService(new ApplicationProperties(), "label", false, null);
CustomPDFDocumentFactory factory = new CustomPDFDocumentFactory(meta);
byte[] result =
PdfUtils.overlayImage(
factory, pdfOut.toByteArray(), imgOut.toByteArray(), 0, 0, false);
try (PDDocument resultDoc = factory.load(result)) {
assertEquals(1, resultDoc.getNumberOfPages());
}
}
}