View Javadoc
1   /*
2    * SPDX-License-Identifier: Apache-2.0
3    * See LICENSE file for details.
4    *
5    * Copyright 2018-2026 Hazendaz
6    * Copyright 2011-2018 tunyk
7    */
8   package com.tunyk.mvn.plugins.htmlcompressor;
9   
10  import java.io.IOException;
11  import java.nio.charset.Charset;
12  import java.nio.charset.StandardCharsets;
13  import java.nio.file.Files;
14  import java.nio.file.Path;
15  import java.util.HashMap;
16  import java.util.Map;
17  
18  import org.json.JSONException;
19  import org.json.JSONObject;
20  import org.junit.jupiter.api.AfterAll;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.BeforeAll;
23  import org.junit.jupiter.api.BeforeEach;
24  import org.junit.jupiter.api.Test;
25  import org.junit.jupiter.api.io.TempDir;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  /**
30   * The Class FileToolTest.
31   */
32  class FileToolTest {
33  
34      /** The Constant LOG. */
35      private static final Logger LOG = LoggerFactory.getLogger(FileToolTest.class);
36  
37      /**
38       * Sets the up class.
39       */
40      @BeforeAll
41      static void setUpClass() {
42          LOG.info("Setting up class...");
43      }
44  
45      /**
46       * Tear down class.
47       */
48      @AfterAll
49      static void tearDownClass() {
50          LOG.info("Test finished.");
51      }
52  
53      /**
54       * Sets the up.
55       */
56      @BeforeEach
57      void setUp() {
58          LOG.info("Setting up data for testing...");
59      }
60  
61      /**
62       * Test get files finds the correct HTML files.
63       *
64       * @throws IOException
65       *             Signals that an I/O exception has occurred.
66       */
67      @Test
68      void testGetFiles() throws IOException {
69          LOG.info("Testing getFiles method...");
70  
71          FileTool fileTool = new FileTool("src/test/resources/html", new String[] { "htm", "html" }, true);
72          Map<String, String> map = fileTool.getFiles();
73  
74          Assertions.assertEquals(3, map.size(), "Should find exactly 3 HTML files");
75          Assertions.assertTrue(map.containsKey("templates/Template1.html"));
76          Assertions.assertTrue(map.containsKey("templates/Template2.html"));
77          Assertions.assertTrue(map.containsKey("templates/recursive/Template.html"));
78  
79          // Verify .js file is excluded by the HTML extension filter
80          Assertions.assertFalse(map.containsKey("integration.js"), "JS file should not be included with HTML filter");
81  
82          LOG.info("Passed");
83      }
84  
85      /**
86       * Test get files content verification.
87       *
88       * @throws IOException
89       *             Signals that an I/O exception has occurred.
90       */
91      @Test
92      void testGetFilesContent() throws IOException {
93          LOG.info("Testing getFiles content verification...");
94  
95          FileTool fileTool = new FileTool("src/test/resources/html", new String[] { "htm", "html" }, true);
96          Map<String, String> map = fileTool.getFiles();
97  
98          Assertions.assertTrue(map.get("templates/Template1.html").contains("template #1"),
99                  "Template1 content should contain 'template #1'");
100         Assertions.assertTrue(map.get("templates/Template2.html").contains("template #2"),
101                 "Template2 content should contain 'template #2'");
102         Assertions.assertTrue(map.get("templates/recursive/Template.html").contains("subfolders"),
103                 "Recursive template content should contain 'subfolders'");
104 
105         LOG.info("Passed");
106     }
107 
108     /**
109      * Test get files extension filtering excludes non-matching files.
110      *
111      * @throws IOException
112      *             Signals that an I/O exception has occurred.
113      */
114     @Test
115     void testGetFilesExtensionFiltering() throws IOException {
116         LOG.info("Testing getFiles extension filtering...");
117 
118         // Filter only JS files - should include integration.js but not HTML templates
119         FileTool jsFileTool = new FileTool("src/test/resources/html", new String[] { "js" }, true);
120         Map<String, String> jsMap = jsFileTool.getFiles();
121 
122         Assertions.assertEquals(1, jsMap.size(), "Should find exactly 1 JS file");
123         Assertions.assertTrue(jsMap.containsKey("integration.js"), "JS file should be included with JS filter");
124         Assertions.assertFalse(jsMap.containsKey("templates/Template1.html"),
125                 "HTML file should not be included with JS filter");
126 
127         LOG.info("Passed");
128     }
129 
130     /**
131      * Test write files.
132      *
133      * @param tempDir
134      *            the temp dir provided by JUnit
135      *
136      * @throws IOException
137      *             Signals that an I/O exception has occurred.
138      */
139     @Test
140     void testWriteFiles(@TempDir Path tempDir) throws IOException {
141         LOG.info("Testing writeFiles method...");
142 
143         String targetDir = tempDir.toString();
144         FileTool fileTool = new FileTool(targetDir, new String[] { "htm", "html" }, true);
145         Map<String, String> map = new HashMap<String, String>();
146         map.put("file.html", "root file");
147         map.put("/file2.html", "another root file");
148         map.put("file3.html/", "another root file like folder");
149         map.put("/template/file.html", "template file");
150         map.put("template/file01.html", "template file 01");
151         map.put("/template/subfolder/file.html", "template subfolder file");
152         fileTool.writeFiles(map, targetDir);
153 
154         Map<String, String> files = fileTool.getFiles();
155         Assertions.assertTrue(files.containsKey("file.html"));
156         Assertions.assertTrue(files.containsKey("file2.html"));
157         Assertions.assertTrue(files.containsKey("file3.html"));
158         Assertions.assertTrue(files.containsKey("template/file.html"));
159         Assertions.assertTrue(files.containsKey("template/file01.html"));
160         Assertions.assertTrue(files.containsKey("template/subfolder/file.html"));
161 
162         LOG.info("Passed");
163     }
164 
165     /**
166      * Test write files content verification - checks that written content can be read back correctly.
167      *
168      * @param tempDir
169      *            the temp dir provided by JUnit
170      *
171      * @throws IOException
172      *             Signals that an I/O exception has occurred.
173      */
174     @Test
175     void testWriteFilesContentVerification(@TempDir Path tempDir) throws IOException {
176         LOG.info("Testing writeFiles content verification...");
177 
178         String targetDir = tempDir.toString();
179         FileTool fileTool = new FileTool(targetDir, new String[] { "html" }, true);
180         Map<String, String> writeMap = new HashMap<>();
181         writeMap.put("page.html", "<html><body>Hello World</body></html>");
182         writeMap.put("sub/page.html", "<html><body>Sub Page</body></html>");
183         fileTool.writeFiles(writeMap, targetDir);
184 
185         Map<String, String> readMap = fileTool.getFiles();
186         Assertions.assertEquals("<html><body>Hello World</body></html>", readMap.get("page.html"),
187                 "Root file content should match what was written");
188         Assertions.assertEquals("<html><body>Sub Page</body></html>", readMap.get("sub/page.html"),
189                 "Sub folder file content should match what was written");
190 
191         LOG.info("Passed");
192     }
193 
194     /**
195      * Test write to json file creates the file with expected structure.
196      *
197      * @throws IOException
198      *             Signals that an I/O exception has occurred.
199      * @throws JSONException
200      *             the JSON exception
201      */
202     @Test
203     void testWriteToJsonFile() throws IOException, JSONException {
204         LOG.info("Testing writeToJsonFile method...");
205 
206         String targetDir = "target/test/filetool/";
207         String targetFile = targetDir + "json.js";
208         FileTool fileTool = new FileTool(targetDir, new String[] { "htm", "html" }, true);
209         Map<String, String> map = new HashMap<String, String>();
210         map.put("file.html", "root file");
211         map.put("template/file.html", "template file");
212         map.put("template/subfolder/file.html", "template subfolder file");
213         fileTool.writeToJsonFile(map, targetFile, "var templates = \"%s\";");
214 
215         Path outputPath = Path.of(targetFile);
216         Assertions.assertTrue(Files.exists(outputPath), "JSON output file should exist");
217         String content = Files.readString(outputPath);
218         Assertions.assertTrue(content.startsWith("var templates = "), "Content should start with var declaration");
219         Assertions.assertTrue(content.contains("file.html"), "Content should contain file.html key");
220         Assertions.assertTrue(content.contains("root file"), "Content should contain root file value");
221         Assertions.assertTrue(content.endsWith(";"), "Content should end with semicolon");
222 
223         LOG.info("Passed");
224     }
225 
226     /**
227      * Test write to json file with null integration code uses default pattern.
228      *
229      * @param tempDir
230      *            the temp dir provided by JUnit
231      *
232      * @throws IOException
233      *             Signals that an I/O exception has occurred.
234      * @throws JSONException
235      *             the JSON exception
236      */
237     @Test
238     void testWriteToJsonFileNullIntegrationCode(@TempDir Path tempDir) throws IOException, JSONException {
239         LOG.info("Testing writeToJsonFile with null integration code...");
240 
241         String targetFile = tempDir.resolve("output.js").toString();
242         FileTool fileTool = new FileTool(tempDir.toString(), new String[] { "html" }, true);
243         Map<String, String> map = new HashMap<>();
244         map.put("key.html", "value content");
245         fileTool.writeToJsonFile(map, targetFile, null);
246 
247         Path outputPath = Path.of(targetFile);
248         Assertions.assertTrue(Files.exists(outputPath), "JSON output file should exist with null integration code");
249         String content = Files.readString(outputPath);
250         Assertions.assertTrue(content.contains("key.html"), "Content should contain the map key");
251         Assertions.assertTrue(content.contains("value content"), "Content should contain the map value");
252 
253         LOG.info("Passed");
254     }
255 
256     /**
257      * Test write to json file without placeholder appends it automatically.
258      *
259      * @param tempDir
260      *            the temp dir provided by JUnit
261      *
262      * @throws IOException
263      *             Signals that an I/O exception has occurred.
264      * @throws JSONException
265      *             the JSON exception
266      */
267     @Test
268     void testWriteToJsonFileNoPlaceholder(@TempDir Path tempDir) throws IOException, JSONException {
269         LOG.info("Testing writeToJsonFile with integration code without placeholder...");
270 
271         String targetFile = tempDir.resolve("output.js").toString();
272         FileTool fileTool = new FileTool(tempDir.toString(), new String[] { "html" }, true);
273         Map<String, String> map = new HashMap<>();
274         map.put("key.html", "value content");
275         // integrationCode without "%s" - should have "%s" appended and then replaced with JSON
276         fileTool.writeToJsonFile(map, targetFile, "var data = ");
277 
278         Path outputPath = Path.of(targetFile);
279         Assertions.assertTrue(Files.exists(outputPath), "JSON output file should exist");
280         String content = Files.readString(outputPath);
281         Assertions.assertTrue(content.startsWith("var data = "), "Content should start with var data prefix");
282         Assertions.assertTrue(content.contains("key.html"), "Content should contain the map key");
283         Assertions.assertTrue(content.contains("value content"), "Content should contain the map value");
284 
285         LOG.info("Passed");
286     }
287 
288     /**
289      * Test write to json file produces parseable JSON content.
290      *
291      * @param tempDir
292      *            the temp dir provided by JUnit
293      *
294      * @throws IOException
295      *             Signals that an I/O exception has occurred.
296      * @throws JSONException
297      *             the JSON exception
298      */
299     @Test
300     void testWriteToJsonFileJsonContent(@TempDir Path tempDir) throws IOException, JSONException {
301         LOG.info("Testing writeToJsonFile JSON content validity...");
302 
303         String targetFile = tempDir.resolve("output.js").toString();
304         FileTool fileTool = new FileTool(tempDir.toString(), new String[] { "html" }, true);
305         Map<String, String> map = new HashMap<>();
306         map.put("page1.html", "<div>Content 1</div>");
307         map.put("page2.html", "<div>Content 2</div>");
308         // Use "\"%s\"" so the JSON object replaces the quoted placeholder, leaving pure JSON
309         fileTool.writeToJsonFile(map, targetFile, "\"%s\"");
310 
311         String content = Files.readString(Path.of(targetFile));
312         JSONObject parsed = new JSONObject(content);
313         Assertions.assertTrue(parsed.has("page1.html"), "Parsed JSON should contain page1.html");
314         Assertions.assertTrue(parsed.has("page2.html"), "Parsed JSON should contain page2.html");
315         Assertions.assertEquals("<div>Content 1</div>", parsed.getString("page1.html"));
316         Assertions.assertEquals("<div>Content 2</div>", parsed.getString("page2.html"));
317 
318         LOG.info("Passed");
319     }
320 
321     /**
322      * Test human readable byte count with SI units (base 1000).
323      */
324     @Test
325     void testHumanReadableByteCountSI() {
326         LOG.info("Testing humanReadableByteCount with SI units...");
327 
328         // Below 1 unit threshold
329         Assertions.assertEquals("0 B", FileTool.humanReadableByteCount(0, true));
330         Assertions.assertEquals("1 B", FileTool.humanReadableByteCount(1, true));
331         Assertions.assertEquals("999 B", FileTool.humanReadableByteCount(999, true));
332 
333         // Kilobytes (1000^1)
334         Assertions.assertEquals("1.0 kB", FileTool.humanReadableByteCount(1_000, true));
335         Assertions.assertEquals("1.5 kB", FileTool.humanReadableByteCount(1_500, true));
336 
337         // Megabytes (1000^2)
338         Assertions.assertEquals("1.0 MB", FileTool.humanReadableByteCount(1_000_000, true));
339 
340         // Gigabytes (1000^3)
341         Assertions.assertEquals("1.0 GB", FileTool.humanReadableByteCount(1_000_000_000, true));
342 
343         LOG.info("Passed");
344     }
345 
346     /**
347      * Test human readable byte count with binary units (base 1024).
348      */
349     @Test
350     void testHumanReadableByteCountBinary() {
351         LOG.info("Testing humanReadableByteCount with binary units...");
352 
353         // Below 1 unit threshold
354         Assertions.assertEquals("0 B", FileTool.humanReadableByteCount(0, false));
355         Assertions.assertEquals("1 B", FileTool.humanReadableByteCount(1, false));
356         Assertions.assertEquals("1023 B", FileTool.humanReadableByteCount(1_023, false));
357 
358         // Kibibytes (1024^1)
359         Assertions.assertEquals("1.0 KiB", FileTool.humanReadableByteCount(1_024, false));
360         Assertions.assertEquals("1.5 KiB", FileTool.humanReadableByteCount(1_536, false));
361 
362         // Mebibytes (1024^2)
363         Assertions.assertEquals("1.0 MiB", FileTool.humanReadableByteCount(1_048_576, false));
364 
365         LOG.info("Passed");
366     }
367 
368     /**
369      * Test get elapsed HMS time for various durations.
370      */
371     @Test
372     void testGetElapsedHMSTime() {
373         LOG.info("Testing getElapsedHMSTime...");
374 
375         Assertions.assertEquals("00:00:00", FileTool.getElapsedHMSTime(0L));
376         Assertions.assertEquals("00:00:01", FileTool.getElapsedHMSTime(1_000L));
377         Assertions.assertEquals("00:00:59", FileTool.getElapsedHMSTime(59_000L));
378         Assertions.assertEquals("00:01:00", FileTool.getElapsedHMSTime(60_000L));
379         Assertions.assertEquals("00:59:59", FileTool.getElapsedHMSTime(3_599_000L));
380         Assertions.assertEquals("01:00:00", FileTool.getElapsedHMSTime(3_600_000L));
381         Assertions.assertEquals("01:01:01", FileTool.getElapsedHMSTime(3_661_000L));
382         Assertions.assertEquals("10:30:45", FileTool.getElapsedHMSTime(37_845_000L));
383 
384         LOG.info("Passed");
385     }
386 
387     /**
388      * Test FileTool getters and setters.
389      *
390      * @throws IOException
391      *             Signals that an I/O exception has occurred.
392      */
393     @Test
394     void testFileToolGettersSetters() throws IOException {
395         LOG.info("Testing FileTool getters and setters...");
396 
397         FileTool fileTool = new FileTool("src/test/resources/html", new String[] { "html" }, true);
398 
399         // Test recursive flag getter/setter
400         Assertions.assertTrue(fileTool.isRecursive());
401         fileTool.setRecursive(false);
402         Assertions.assertFalse(fileTool.isRecursive());
403 
404         // Test file extensions getter/setter
405         String[] newExtensions = { "htm", "xhtml" };
406         fileTool.setFileExtensions(newExtensions);
407         Assertions.assertArrayEquals(newExtensions, fileTool.getFileExtensions());
408 
409         // Test root dir path getter (should be canonical/absolute)
410         String rootDirPath = fileTool.getRootDirPath();
411         Assertions.assertNotNull(rootDirPath);
412         Assertions.assertTrue(Path.of(rootDirPath).isAbsolute(), "Root dir path should be absolute");
413         Assertions.assertFalse(rootDirPath.endsWith("/"), "Root dir path should not have trailing slash");
414 
415         // Test encoding getter defaults to system charset when not set
416         Assertions.assertEquals(Charset.defaultCharset(), fileTool.getFileEncoding());
417 
418         // Test encoding setter with explicit value
419         fileTool.setFileEncoding(StandardCharsets.UTF_8);
420         Assertions.assertEquals(StandardCharsets.UTF_8, fileTool.getFileEncoding());
421 
422         // Test encoding setter with null reverts to default charset
423         fileTool.setFileEncoding(null);
424         Assertions.assertEquals(Charset.defaultCharset(), fileTool.getFileEncoding());
425 
426         LOG.info("Passed");
427     }
428 
429     /**
430      * Test setRootDirPath normalizes paths correctly.
431      *
432      * @throws IOException
433      *             Signals that an I/O exception has occurred.
434      */
435     @Test
436     void testSetRootDirPathNormalization() throws IOException {
437         LOG.info("Testing setRootDirPath normalization...");
438 
439         FileTool fileTool = new FileTool("src/test/resources/html", new String[] { "html" }, true);
440         String rootPath = fileTool.getRootDirPath();
441 
442         // Should be normalized to absolute canonical path
443         Assertions.assertTrue(Path.of(rootPath).isAbsolute(), "Root dir path should be absolute after normalization");
444         Assertions.assertFalse(rootPath.endsWith("/"), "Root dir path should not end with a trailing slash");
445 
446         // Path should not use backslashes
447         Assertions.assertFalse(rootPath.contains("\\"), "Root dir path should use forward slashes, not backslashes");
448 
449         // Setting another path should update it
450         fileTool.setRootDirPath("src/test/resources/xml");
451         Assertions.assertTrue(fileTool.getRootDirPath().endsWith("xml"),
452                 "Root dir path should reflect the updated path");
453 
454         LOG.info("Passed");
455     }
456 }