View Javadoc
1   package com.acumenvelocity.ath.pagematcher;
2   
3   import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4   import static org.junit.jupiter.api.Assertions.assertEquals;
5   import static org.junit.jupiter.api.Assertions.assertNotNull;
6   import static org.junit.jupiter.api.Assertions.assertTrue;
7   
8   import java.io.File;
9   import java.io.InputStream;
10  import java.nio.file.Files;
11  import java.nio.file.Path;
12  import java.nio.file.StandardCopyOption;
13  
14  import org.apache.pdfbox.Loader;
15  import org.apache.pdfbox.pdmodel.PDDocument;
16  import org.junit.jupiter.api.AfterAll;
17  import org.junit.jupiter.api.AfterEach;
18  import org.junit.jupiter.api.BeforeAll;
19  import org.junit.jupiter.api.BeforeEach;
20  import org.junit.jupiter.api.Test;
21  import org.junit.jupiter.api.io.TempDir;
22  
23  import net.sf.okapi.filters.openxml.OpenXMLFilter;
24  
25  @SuppressWarnings("unused")
26  class TestDocxPageMatcher_IT {
27  
28    @TempDir
29    static Path tempDir;
30  
31    @BeforeAll
32    static void setUp() throws Exception {
33      DocxPageMatcher.init();
34    }
35  
36    @AfterAll
37    static void tearDown() throws Exception {
38      DocxPageMatcher.done();
39    }
40  
41    @Test
42    void testMatchPageCount_WithPdfOriginal_ReducesTranslationPages() throws Exception {
43      // Given: A 2-page PDF original and a 3-page DOCX translation
44      InputStream pdfStream = getResourceAsStream("/test-original-2pages.pdf");
45      File docxTranslation = copyResourceToTemp("/test-translation-3pages.docx");
46  
47      int originalPages = getPdfPageCount(getResourceAsStream("/test-original-2pages.pdf"));
48      int translationPagesBefore = DocxPageMatcher.getDocxPageCount(docxTranslation);
49  
50      assertTrue(translationPagesBefore > originalPages,
51          "Translation should have more pages than original");
52  
53      // When: matchPageCount is called
54      DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, new OpenXMLFilter());
55  
56      // Then: Translation pages should be reduced to match or be less than original
57      int translationPagesAfter = DocxPageMatcher.getDocxPageCount(docxTranslation);
58      assertTrue(translationPagesAfter <= originalPages,
59          String.format("Translation pages (%d) should be <= original pages (%d)",
60              translationPagesAfter, originalPages));
61    }
62  
63    @Test
64    void testMatchPageCount_WithDocxOriginal_ReducesTranslationPages() throws Exception {
65      // Given: A 2-page DOCX original and a 3-page DOCX translation
66      InputStream docxStream = getResourceAsStream("/test-original-2pages.docx");
67      File docxTranslation = copyResourceToTemp("/test-translation-3pages.docx");
68  
69      OpenXMLFilter filter = new OpenXMLFilter();
70  
71      int translationPagesBefore = DocxPageMatcher.getDocxPageCount(docxTranslation);
72      assertTrue(translationPagesBefore > 2, "Translation should have more than 2 pages");
73  
74      // When: matchPageCount is called
75      DocxPageMatcher.matchPageCount(docxStream, docxTranslation, filter);
76  
77      // Then: Translation pages should be reduced
78      int translationPagesAfter = DocxPageMatcher.getDocxPageCount(docxTranslation);
79      assertTrue(translationPagesAfter <= 2,
80          String.format("Translation pages (%d) should be <= 2", translationPagesAfter));
81    }
82  
83    @Test
84    void testMatchPageCount_TranslationAlreadyAcceptable_NoChanges() throws Exception {
85      // Given: A 2-page PDF original and a 2-page DOCX translation
86      InputStream pdfStream = getResourceAsStream("/test-original-2pages.pdf");
87      File docxTranslation = copyResourceToTemp("/test-translation-2pages.docx");
88  
89      long lastModifiedBefore = docxTranslation.lastModified();
90      byte[] contentBefore = Files.readAllBytes(docxTranslation.toPath());
91  
92      // When: matchPageCount is called
93      DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, new OpenXMLFilter());
94  
95      // Then: File should remain unchanged
96      byte[] contentAfter = Files.readAllBytes(docxTranslation.toPath());
97      assertArrayEquals(contentBefore, contentAfter,
98          "Document should not be modified when page count is acceptable");
99    }
100 
101   @Test
102   void testMatchPageCount_TranslationHasFewerPages_NoChanges() throws Exception {
103     // Given: A 3-page PDF original and a 2-page DOCX translation
104     InputStream pdfStream = getResourceAsStream("/test-original-3pages.pdf");
105     File docxTranslation = copyResourceToTemp("/test-translation-2pages.docx");
106 
107     byte[] contentBefore = Files.readAllBytes(docxTranslation.toPath());
108     int pagesBefore = DocxPageMatcher.getDocxPageCount(docxTranslation);
109 
110     // When: matchPageCount is called
111     DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, new OpenXMLFilter());
112 
113     // Then: File should remain unchanged
114     byte[] contentAfter = Files.readAllBytes(docxTranslation.toPath());
115     int pagesAfter = DocxPageMatcher.getDocxPageCount(docxTranslation);
116 
117     assertArrayEquals(contentBefore, contentAfter,
118         "Document should not be modified when translation has fewer pages");
119     assertEquals(pagesBefore, pagesAfter, "Page count should not change");
120   }
121 
122   @Test
123   void testMatchPageCount_WithUnknownFilter_NoChanges() throws Exception {
124     // Given: A PDF original and DOCX translation with a non-OpenXML filter
125     InputStream pdfStream = getResourceAsStream("/test-original-2pages.pdf");
126     File docxTranslation = copyResourceToTemp("/test-translation-3pages.docx");
127 
128     byte[] contentBefore = Files.readAllBytes(docxTranslation.toPath());
129 
130     // Create a dummy filter that is not OpenXMLFilter
131     net.sf.okapi.common.filters.IFilter unknownFilter = new net.sf.okapi.filters.plaintext.PlainTextFilter();
132 
133     // When: matchPageCount is called with unknown filter
134     DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, unknownFilter);
135 
136     // Then: File should remain unchanged
137     byte[] contentAfter = Files.readAllBytes(docxTranslation.toPath());
138     assertArrayEquals(contentBefore, contentAfter,
139         "Document should not be modified with unknown filter");
140   }
141 
142   @Test
143   void testMatchPageCount_LargePageDifference_ReducesGradually() throws Exception {
144     // Given: A 2-page PDF original and a 5-page DOCX translation
145     InputStream pdfStream = getResourceAsStream("/test-original-2pages.pdf");
146     File docxTranslation = copyResourceToTemp("/test-translation-5pages.docx");
147 
148     int translationPagesBefore = DocxPageMatcher.getDocxPageCount(docxTranslation);
149     assertTrue(translationPagesBefore >= 5, "Translation should have 5+ pages");
150 
151     // When: matchPageCount is called
152     DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, new OpenXMLFilter());
153 
154     // Then: Translation should be reduced to 2 pages or less
155     int translationPagesAfter = DocxPageMatcher.getDocxPageCount(docxTranslation);
156     
157     assertTrue(translationPagesAfter <= 2,
158         String.format("Translation pages (%d) should be <= 2", translationPagesAfter));
159   }
160 
161   @Test
162   void testMatchPageCount_DocumentXmlFallback_WhenStylesXmlFails() throws Exception {
163     // Given: A DOCX translation without styles.xml modifications working
164     InputStream pdfStream = getResourceAsStream("/test-original-2pages.pdf");
165     File docxTranslation = copyResourceToTemp("/test-translation-no-styles-3pages.docx");
166 
167     int translationPagesBefore = DocxPageMatcher.getDocxPageCount(docxTranslation);
168 
169     // When: matchPageCount is called (will try styles.xml first, then document.xml)
170     DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, new OpenXMLFilter());
171 
172     // Then: Translation should still be reduced using document.xml approach
173     int translationPagesAfter = DocxPageMatcher.getDocxPageCount(docxTranslation);
174     assertTrue(translationPagesAfter <= 2,
175         "Translation should be reduced even without styles.xml working");
176   }
177 
178   @Test
179   void testMatchPageCount_PreservesDocumentStructure() throws Exception {
180     // Given: A DOCX translation with specific formatting
181     InputStream pdfStream = getResourceAsStream("/test-original-2pages.pdf");
182     File docxTranslation = copyResourceToTemp("/test-translation-formatted-3pages.docx");
183 
184     // When: matchPageCount is called
185     DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, new OpenXMLFilter());
186 
187     // Then: Document should still be valid and openable
188     int pagesAfter = DocxPageMatcher.getDocxPageCount(docxTranslation);
189     assertTrue(pagesAfter > 0, "Document should remain valid after modification");
190     assertTrue(docxTranslation.exists(), "Document file should exist");
191     assertTrue(docxTranslation.length() > 0, "Document should not be empty");
192   }
193 
194   @Test
195   void testMatchPageCount_HandlesMinimumFontSize() throws Exception {
196     // Given: A DOCX with very large page difference (may hit minimum font size)
197     InputStream pdfStream = getResourceAsStream("/test-original-1page.pdf");
198     File docxTranslation = copyResourceToTemp("/test-translation-10pages.docx");
199 
200     int translationPagesBefore = DocxPageMatcher.getDocxPageCount(docxTranslation);
201 
202     // When: matchPageCount is called
203     DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, new OpenXMLFilter());
204 
205     // Then: Document should be reduced but may not reach exactly 1 page due to min font size
206     int translationPagesAfter = DocxPageMatcher.getDocxPageCount(docxTranslation);
207     assertTrue(translationPagesAfter < translationPagesBefore,
208         "Translation should be reduced even if target cannot be reached");
209   }
210 
211   @Test
212   void testMatchPageCount_MaxIterationsLimit() throws Exception {
213     // Given: A DOCX that would require many iterations
214     InputStream pdfStream = getResourceAsStream("/test-original-1page.pdf");
215     File docxTranslation = copyResourceToTemp("/test-translation-15pages.docx");
216 
217     // When: matchPageCount is called (should stop at 10 iterations)
218     DocxPageMatcher.matchPageCount(pdfStream, docxTranslation, new OpenXMLFilter());
219 
220     // Then: Document should be modified but may not reach exact target
221     int translationPagesAfter = DocxPageMatcher.getDocxPageCount(docxTranslation);
222     assertTrue(translationPagesAfter > 0, "Document should remain valid");
223     // The method should have stopped after 10 iterations
224   }
225 
226   // Helper methods
227 
228   private InputStream getResourceAsStream(String resourcePath) {
229     InputStream stream = getClass().getResourceAsStream(resourcePath);
230     assertNotNull(stream, "Test resource not found: " + resourcePath);
231     return stream;
232   }
233 
234   private File copyResourceToTemp(String resourcePath) throws Exception {
235     InputStream inputStream = getResourceAsStream(resourcePath);
236     File tempFile = Files.createTempFile(tempDir, "test-", ".docx").toFile();
237     Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
238     inputStream.close();
239     return tempFile;
240   }
241 
242   private int getPdfPageCount(InputStream pdfStream) throws Exception {
243     File tempPdf = Files.createTempFile(tempDir, "pdf-", ".pdf").toFile();
244     Files.copy(pdfStream, tempPdf.toPath(), StandardCopyOption.REPLACE_EXISTING);
245 
246     try (PDDocument pdfDoc = Loader.loadPDF(tempPdf)) {
247       return pdfDoc.getNumberOfPages();
248     } finally {
249       tempPdf.delete();
250     }
251   }
252 
253 }