From 95a128ca31033795fc9ba20c05639d80419751ff Mon Sep 17 00:00:00 2001 From: Dario Ghunney Ware Date: Wed, 18 Jun 2025 11:35:15 +0100 Subject: [PATCH] added tests --- .../software/common/util/EmlToPdf.java | 3 +- .../common/util/PDFAttachmentUtils.java | 7 +- .../common/util/WebResponseUtils.java | 1 - .../api/misc/AttachmentsController.java | 3 - .../controller/web/OtherWebController.java | 2 +- .../SPDF/service/PDFAttachmentService.java | 4 +- .../main/resources/messages_en_GB.properties | 6 +- .../main/resources/messages_en_US.properties | 4 +- .../service/PDFAttachmentServiceTest.java | 119 ++++++++++++++++++ 9 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 stirling-pdf/src/test/java/stirling/software/SPDF/service/PDFAttachmentServiceTest.java diff --git a/common/src/main/java/stirling/software/common/util/EmlToPdf.java b/common/src/main/java/stirling/software/common/util/EmlToPdf.java index fe7638fb9..2cd984ef6 100644 --- a/common/src/main/java/stirling/software/common/util/EmlToPdf.java +++ b/common/src/main/java/stirling/software/common/util/EmlToPdf.java @@ -27,6 +27,7 @@ import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PageMode; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; @@ -1271,7 +1272,7 @@ public class EmlToPdf { efTree.setNames(efMap); // Set catalog viewer preferences to automatically show attachments pane - setCatalogViewerPreferences(document); + setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS); } // Add attachment annotations to the first page for each embedded file diff --git a/common/src/main/java/stirling/software/common/util/PDFAttachmentUtils.java b/common/src/main/java/stirling/software/common/util/PDFAttachmentUtils.java index a20c1fb00..79737b0c2 100644 --- a/common/src/main/java/stirling/software/common/util/PDFAttachmentUtils.java +++ b/common/src/main/java/stirling/software/common/util/PDFAttachmentUtils.java @@ -10,7 +10,7 @@ import org.apache.pdfbox.pdmodel.PageMode; @Slf4j public class PDFAttachmentUtils { - public static void setCatalogViewerPreferences(PDDocument document) { + public static void setCatalogViewerPreferences(PDDocument document, PageMode pageMode) { try { PDDocumentCatalog catalog = document.getDocumentCatalog(); if (catalog != null) { @@ -19,7 +19,8 @@ public class PDFAttachmentUtils { // Set PageMode to UseAttachments - this is the standard PDF specification approach // PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC, UseAttachments - catalogDict.setName(COSName.PAGE_MODE, PageMode.USE_ATTACHMENTS.stringValue()); + catalog.setPageMode(pageMode); + catalogDict.setName(COSName.PAGE_MODE, pageMode.stringValue()); // Also set viewer preferences for better attachment viewing experience COSDictionary viewerPrefs = (COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES); @@ -29,7 +30,7 @@ public class PDFAttachmentUtils { } // Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support it - viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), PageMode.USE_ATTACHMENTS.stringValue()); + viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), pageMode.stringValue()); // Additional viewer preferences that may help with attachment display viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true); diff --git a/common/src/main/java/stirling/software/common/util/WebResponseUtils.java b/common/src/main/java/stirling/software/common/util/WebResponseUtils.java index c96ff16b1..96d446c06 100644 --- a/common/src/main/java/stirling/software/common/util/WebResponseUtils.java +++ b/common/src/main/java/stirling/software/common/util/WebResponseUtils.java @@ -61,7 +61,6 @@ public class WebResponseUtils { // Open Byte Array and save document to it ByteArrayOutputStream baos = new ByteArrayOutputStream(); document.save(baos); - // Close the document document.close(); return baosToWebResponse(baos, docName); diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java index d709b0000..5e67a5294 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java @@ -62,9 +62,6 @@ public class AttachmentsController { catalog.setNames(documentNames); pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments); - // Set PageMode to UseAttachments to show the attachments panel - catalog.setPageMode(PageMode.USE_ATTACHMENTS); - return WebResponseUtils.pdfDocToWebResponse( document, Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java index 84b656e40..aac9cb6a0 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java @@ -195,7 +195,7 @@ public class OtherWebController { @GetMapping("/add-attachments") @Hidden public String attachmentsForm(Model model) { - model.addAttribute("currentPage", "attachments"); + model.addAttribute("currentPage", "add-attachments"); return "misc/add-attachments"; } } diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java index 6c5bcfad1..78e9639e5 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java @@ -9,6 +9,7 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.PageMode; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.encryption.AccessPermission; @@ -91,9 +92,8 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface { embeddedFilesTree.setNames(existingNames); - // Ensure document has proper access permissions for embedded files grantAccessPermissions(document); - PDFAttachmentUtils.setCatalogViewerPreferences(document); + PDFAttachmentUtils.setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS); } private void grantAccessPermissions(PDDocument document) { diff --git a/stirling-pdf/src/main/resources/messages_en_GB.properties b/stirling-pdf/src/main/resources/messages_en_GB.properties index be1ea8600..8b127171e 100644 --- a/stirling-pdf/src/main/resources/messages_en_GB.properties +++ b/stirling-pdf/src/main/resources/messages_en_GB.properties @@ -525,7 +525,7 @@ home.addImage.title=Add image home.addImage.desc=Adds a image onto a set location on the PDF addImage.tags=img,jpg,picture,photo -home.attachments.title=Attachments +home.attachments.title=Add Attachments home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF attachments.tags=embed,attach,file,attachment,attachments @@ -1210,8 +1210,8 @@ addImage.upload=Add image addImage.submit=Add image #attachments -attachments.title=Attachments -attachments.header=Add attachments to PDF +attachments.title=Add Attachments +attachments.header=Add attachments attachments.removeHeader=Remove attachments from PDF attachments.selectFiles=Select files to attach attachments.description=Allows you to add attachments to the PDF diff --git a/stirling-pdf/src/main/resources/messages_en_US.properties b/stirling-pdf/src/main/resources/messages_en_US.properties index d7ccef038..53ebb45d4 100644 --- a/stirling-pdf/src/main/resources/messages_en_US.properties +++ b/stirling-pdf/src/main/resources/messages_en_US.properties @@ -525,7 +525,7 @@ home.addImage.title=Add image home.addImage.desc=Adds a image onto a set location on the PDF addImage.tags=img,jpg,picture,photo -home.attachments.title=Attachments +home.attachments.title=Add Attachments home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF attachments.tags=embed,attach,file,attachment,attachments @@ -1211,7 +1211,7 @@ addImage.submit=Add image #attachments attachments.title=Attachments -attachments.header=Add attachments to PDF +attachments.header=Add attachments attachments.removeHeader=Remove attachments from PDF attachments.selectFiles=Select files to attach attachments.description=Allows you to add attachments to the PDF diff --git a/stirling-pdf/src/test/java/stirling/software/SPDF/service/PDFAttachmentServiceTest.java b/stirling-pdf/src/test/java/stirling/software/SPDF/service/PDFAttachmentServiceTest.java new file mode 100644 index 000000000..cd2b0eb51 --- /dev/null +++ b/stirling-pdf/src/test/java/stirling/software/SPDF/service/PDFAttachmentServiceTest.java @@ -0,0 +1,119 @@ +package stirling.software.SPDF.service; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.multipart.MultipartFile; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class PDFAttachmentServiceTest { + + private PDFAttachmentService pdfAttachmentService; + + @BeforeEach + void setUp() { + pdfAttachmentService = new PDFAttachmentService(); + } + + @Test + void addAttachmentToPDF() throws IOException { + try (var document = new PDDocument()) { + var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class); + var attachments = List.of(mock(MultipartFile.class)); + var existingNames = new HashMap(); + + when(embeddedFilesTree.getNames()).thenReturn(existingNames); + when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt"); + when(attachments.get(0).getInputStream()).thenReturn( + new ByteArrayInputStream("Test content".getBytes())); + when(attachments.get(0).getSize()).thenReturn(12L); + when(attachments.get(0).getContentType()).thenReturn("text/plain"); + + pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments); + + verify(embeddedFilesTree).setNames(anyMap()); + } + } + + @Test + void addAttachmentToPDF_WithNullExistingNames() throws IOException { + try (var document = new PDDocument()) { + var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class); + var attachments = List.of(mock(MultipartFile.class)); + + when(embeddedFilesTree.getNames()).thenReturn(null); + when(attachments.get(0).getOriginalFilename()).thenReturn("document.pdf"); + when(attachments.get(0).getInputStream()).thenReturn( + new ByteArrayInputStream("PDF content".getBytes())); + when(attachments.get(0).getSize()).thenReturn(15L); + when(attachments.get(0).getContentType()).thenReturn("application/pdf"); + + pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments); + + verify(embeddedFilesTree).setNames(anyMap()); + } + } + + @Test + void addAttachmentToPDF_WithBlankContentType() throws IOException { + try (var document = new PDDocument()) { + var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class); + var attachments = List.of(mock(MultipartFile.class)); + var existingNames = new HashMap(); + + when(embeddedFilesTree.getNames()).thenReturn(existingNames); + when(attachments.get(0).getOriginalFilename()).thenReturn("image.jpg"); + when(attachments.get(0).getInputStream()).thenReturn( + new ByteArrayInputStream("Image content".getBytes())); + when(attachments.get(0).getSize()).thenReturn(25L); + when(attachments.get(0).getContentType()).thenReturn(""); + + pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments); + + verify(embeddedFilesTree).setNames(anyMap()); + } + } + + @Test + void addAttachmentToPDF_GetNamesThrowsIOException() throws IOException { + var document = mock(PDDocument.class); + var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class); + var attachments = List.of(mock(MultipartFile.class)); + var ioException = new IOException("Failed to retrieve embedded files"); + + when(embeddedFilesTree.getNames()).thenThrow(ioException); + + assertThrows(IOException.class, () -> pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments)); + + verify(embeddedFilesTree).getNames(); + } + + @Test + void addAttachmentToPDF_AttachmentInputStreamThrowsIOException() throws IOException { + try (var document = new PDDocument()) { + var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class); + var attachments = List.of(mock(MultipartFile.class)); + var existingNames = new HashMap(); + var ioException = new IOException("Failed to read attachment stream"); + + when(embeddedFilesTree.getNames()).thenReturn(existingNames); + when(attachments.get(0).getOriginalFilename()).thenReturn("corrupted.file"); + when(attachments.get(0).getInputStream()).thenThrow(ioException); + when(attachments.get(0).getSize()).thenReturn(10L); + + pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments); + + verify(embeddedFilesTree).setNames(anyMap()); + } + } +}