diff --git a/stirling-pdf/src/test/java/stirling/software/SPDF/controller/api/misc/AttachmentsControllerTest.java b/stirling-pdf/src/test/java/stirling/software/SPDF/controller/api/misc/AttachmentsControllerTest.java new file mode 100644 index 000000000..00f0339e9 --- /dev/null +++ b/stirling-pdf/src/test/java/stirling/software/SPDF/controller/api/misc/AttachmentsControllerTest.java @@ -0,0 +1,209 @@ +package stirling.software.SPDF.controller.api.misc; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.PageMode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import stirling.software.SPDF.service.PDFAttachmentServiceInterface; +import stirling.software.common.service.CustomPDFDocumentFactory; + +@ExtendWith(MockitoExtension.class) +class AttachmentsControllerTest { + + @Mock + private CustomPDFDocumentFactory pdfDocumentFactory; + + @Mock + private PDFAttachmentServiceInterface pdfAttachmentService; + + @InjectMocks + private AttachmentsController attachmentsController; + + private MockMultipartFile pdfFile; + private MockMultipartFile attachment1; + private MockMultipartFile attachment2; + private PDDocument mockDocument; + private PDDocumentCatalog mockCatalog; + private PDDocumentNameDictionary mockNameDict; + private PDEmbeddedFilesNameTreeNode mockEmbeddedFilesTree; + + @BeforeEach + void setUp() { + pdfFile = new MockMultipartFile("fileInput", "test.pdf", "application/pdf", "PDF content".getBytes()); + attachment1 = new MockMultipartFile("attachment1", "file1.txt", "text/plain", "File 1 content".getBytes()); + attachment2 = new MockMultipartFile("attachment2", "file2.jpg", "image/jpeg", "Image content".getBytes()); + + mockDocument = mock(PDDocument.class); + mockCatalog = mock(PDDocumentCatalog.class); + mockNameDict = mock(PDDocumentNameDictionary.class); + mockEmbeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class); + } + + @Test + void addAttachments_WithExistingNames() throws IOException { + List attachments = List.of(attachment1, attachment2); + + when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument); + when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); + when(mockCatalog.getNames()).thenReturn(mockNameDict); + when(mockNameDict.getEmbeddedFiles()).thenReturn(mockEmbeddedFilesTree); + + ResponseEntity response = attachmentsController.addAttachments(pdfFile, attachments); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(pdfDocumentFactory).load(pdfFile, false); + verify(mockCatalog).setNames(mockNameDict); + verify(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, attachments); + } + + @Test + void addAttachments_WithoutExistingNames() throws IOException { + List attachments = List.of(attachment1); + + try (PDDocument realDocument = new PDDocument()) { + when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(realDocument); + + ResponseEntity response = attachmentsController.addAttachments(pdfFile, attachments); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(pdfDocumentFactory).load(pdfFile, false); + verify(pdfAttachmentService).addAttachment(eq(realDocument), any(PDEmbeddedFilesNameTreeNode.class), eq(attachments)); + } + } + + @Test + void addAttachments_IOExceptionFromPDFLoad() throws IOException { + List attachments = List.of(attachment1); + IOException ioException = new IOException("Failed to load PDF"); + + when(pdfDocumentFactory.load(pdfFile, false)).thenThrow(ioException); + + assertThrows(IOException.class, () -> attachmentsController.addAttachments(pdfFile, attachments)); + verify(pdfDocumentFactory).load(pdfFile, false); + verifyNoInteractions(pdfAttachmentService); + } + + @Test + void addAttachments_IOExceptionFromAttachmentService() throws IOException { + List attachments = List.of(attachment1); + IOException ioException = new IOException("Failed to add attachment"); + + when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument); + when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); + when(mockCatalog.getNames()).thenReturn(mockNameDict); + when(mockNameDict.getEmbeddedFiles()).thenReturn(mockEmbeddedFilesTree); + doThrow(ioException).when(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, attachments); + + assertThrows(IOException.class, () -> attachmentsController.addAttachments(pdfFile, attachments)); + verify(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, attachments); + } + + @Test + void removeAttachments_WithExistingNames() throws IOException { + when(pdfDocumentFactory.load(pdfFile)).thenReturn(mockDocument); + when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); + when(mockCatalog.getNames()).thenReturn(mockNameDict); + + ResponseEntity response = attachmentsController.removeAttachments(pdfFile); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(pdfDocumentFactory).load(pdfFile); + verify(mockNameDict).setEmbeddedFiles(null); + verify(mockCatalog).setPageMode(PageMode.USE_NONE); + } + + @Test + void removeAttachments_WithoutExistingNames() throws IOException { + when(pdfDocumentFactory.load(pdfFile)).thenReturn(mockDocument); + when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); + when(mockCatalog.getNames()).thenReturn(null); + + ResponseEntity response = attachmentsController.removeAttachments(pdfFile); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(pdfDocumentFactory).load(pdfFile); + verify(mockCatalog).setPageMode(PageMode.USE_NONE); + verifyNoInteractions(mockNameDict); + } + + @Test + void removeAttachments_IOExceptionFromPDFLoad() throws IOException { + IOException ioException = new IOException("Failed to load PDF"); + + when(pdfDocumentFactory.load(pdfFile)).thenThrow(ioException); + + assertThrows(IOException.class, () -> attachmentsController.removeAttachments(pdfFile)); + verify(pdfDocumentFactory).load(pdfFile); + } + + @Test + void addAttachments_EmptyAttachmentsList() throws IOException { + List emptyAttachments = List.of(); + + when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument); + when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); + when(mockCatalog.getNames()).thenReturn(mockNameDict); + when(mockNameDict.getEmbeddedFiles()).thenReturn(mockEmbeddedFilesTree); + + ResponseEntity response = attachmentsController.addAttachments(pdfFile, emptyAttachments); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, emptyAttachments); + } + + @Test + void addAttachments_NullFilename() throws IOException { + MockMultipartFile attachmentWithNullName = new MockMultipartFile("attachment", null, "text/plain", "content".getBytes()); + List attachments = List.of(attachmentWithNullName); + + when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument); + when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); + when(mockCatalog.getNames()).thenReturn(mockNameDict); + when(mockNameDict.getEmbeddedFiles()).thenReturn(mockEmbeddedFilesTree); + + ResponseEntity response = attachmentsController.addAttachments(pdfFile, attachments); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, attachments); + } + + @Test + void removeAttachments_NullPDFFilename() throws IOException { + MockMultipartFile pdfWithNullName = new MockMultipartFile("fileInput", null, "application/pdf", "PDF content".getBytes()); + + when(pdfDocumentFactory.load(pdfWithNullName)).thenReturn(mockDocument); + when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog); + when(mockCatalog.getNames()).thenReturn(null); + + ResponseEntity response = attachmentsController.removeAttachments(pdfWithNullName); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(mockCatalog).setPageMode(PageMode.USE_NONE); + } +} \ No newline at end of file