View Javadoc
1   /*
2    *    Copyright 2011-2026 the original author or authors.
3    *
4    *    This program is free software; you can redistribute it and/or
5    *    modify it under the terms of the GNU General Public License
6    *    as published by the Free Software Foundation; either version 2
7    *    of the License, or (at your option) any later version.
8    *
9    *    You may obtain a copy of the License at
10   *
11   *       https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
12   *
13   *    This program is distributed in the hope that it will be useful,
14   *    but WITHOUT ANY WARRANTY; without even the implied warranty of
15   *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   *    GNU General Public License for more details.
17   */
18  package com.github.hazendaz.maven.makeself_maven_plugin;
19  
20  import java.lang.reflect.Field;
21  import java.lang.reflect.Method;
22  
23  import org.apache.maven.plugin.logging.Log;
24  import org.junit.jupiter.api.Assertions;
25  import org.junit.jupiter.api.Test;
26  import org.junit.jupiter.api.extension.ExtendWith;
27  import org.mockito.Mock;
28  import org.mockito.Mockito;
29  import org.mockito.junit.jupiter.MockitoExtension;
30  
31  /**
32   * Tests for {@link HelpMojo}.
33   */
34  @ExtendWith(MockitoExtension.class)
35  class HelpMojoTest {
36  
37      /** Mock Maven log. */
38      @Mock
39      private Log log;
40  
41      /**
42       * Sets a private field on the given object via reflection.
43       *
44       * @param obj
45       *            the object
46       * @param fieldName
47       *            the field name
48       * @param value
49       *            the value to set
50       *
51       * @throws Exception
52       *             if the field cannot be found or set
53       */
54      private static void setField(final Object obj, final String fieldName, final Object value) throws Exception {
55          final Field field = obj.getClass().getDeclaredField(fieldName);
56          field.setAccessible(true);
57          field.set(obj, value);
58      }
59  
60      /**
61       * Test default execute loads plugin-help.xml and prints all goals.
62       *
63       * @throws Exception
64       *             the exception
65       */
66      @Test
67      void testExecuteDefault() throws Exception {
68          final HelpMojo mojo = new HelpMojo();
69          mojo.setLog(log);
70          Mockito.when(log.isInfoEnabled()).thenReturn(true);
71  
72          mojo.execute();
73  
74          Mockito.verify(log).isInfoEnabled();
75          Mockito.verify(log).info(Mockito.anyString());
76      }
77  
78      /**
79       * Test execute with detail=true outputs parameter information for all goals.
80       *
81       * @throws Exception
82       *             the exception
83       */
84      @Test
85      void testExecuteWithDetailTrue() throws Exception {
86          final HelpMojo mojo = new HelpMojo();
87          mojo.setLog(log);
88          setField(mojo, "detail", true);
89          Mockito.when(log.isInfoEnabled()).thenReturn(true);
90  
91          mojo.execute();
92  
93          Mockito.verify(log).isInfoEnabled();
94          Mockito.verify(log).info(Mockito.anyString());
95      }
96  
97      /**
98       * Test execute filtered to the 'makeself' goal only.
99       *
100      * @throws Exception
101      *             the exception
102      */
103     @Test
104     void testExecuteWithGoalMakeself() throws Exception {
105         final HelpMojo mojo = new HelpMojo();
106         mojo.setLog(log);
107         setField(mojo, "goal", "makeself");
108         Mockito.when(log.isInfoEnabled()).thenReturn(true);
109 
110         mojo.execute();
111 
112         Mockito.verify(log).isInfoEnabled();
113     }
114 
115     /**
116      * Test execute filtered to the 'git' goal only.
117      *
118      * @throws Exception
119      *             the exception
120      */
121     @Test
122     void testExecuteWithGoalGit() throws Exception {
123         final HelpMojo mojo = new HelpMojo();
124         mojo.setLog(log);
125         setField(mojo, "goal", "git");
126         Mockito.when(log.isInfoEnabled()).thenReturn(true);
127 
128         mojo.execute();
129 
130         Mockito.verify(log).isInfoEnabled();
131     }
132 
133     /**
134      * Test execute filtered to a goal that doesn't exist produces no goal output.
135      *
136      * @throws Exception
137      *             the exception
138      */
139     @Test
140     void testExecuteWithUnknownGoal() throws Exception {
141         final HelpMojo mojo = new HelpMojo();
142         mojo.setLog(log);
143         setField(mojo, "goal", "nonexistent-goal");
144         Mockito.when(log.isInfoEnabled()).thenReturn(true);
145 
146         mojo.execute();
147 
148         Mockito.verify(log).isInfoEnabled();
149     }
150 
151     /**
152      * Test execute with detail=true and a specific goal.
153      *
154      * @throws Exception
155      *             the exception
156      */
157     @Test
158     void testExecuteWithDetailAndGoal() throws Exception {
159         final HelpMojo mojo = new HelpMojo();
160         mojo.setLog(log);
161         setField(mojo, "detail", true);
162         setField(mojo, "goal", "makeself");
163         Mockito.when(log.isInfoEnabled()).thenReturn(true);
164 
165         mojo.execute();
166 
167         Mockito.verify(log).isInfoEnabled();
168     }
169 
170     /**
171      * Test execute when lineLength is zero or negative triggers warning and resets to default.
172      *
173      * @throws Exception
174      *             the exception
175      */
176     @Test
177     void testExecuteWithZeroLineLength() throws Exception {
178         final HelpMojo mojo = new HelpMojo();
179         mojo.setLog(log);
180         setField(mojo, "lineLength", 0);
181         Mockito.when(log.isInfoEnabled()).thenReturn(true);
182 
183         mojo.execute();
184 
185         Mockito.verify(log).warn("The parameter 'lineLength' should be positive, using '80' as default.");
186     }
187 
188     /**
189      * Test execute when indentSize is zero or negative triggers warning and resets to default.
190      *
191      * @throws Exception
192      *             the exception
193      */
194     @Test
195     void testExecuteWithZeroIndentSize() throws Exception {
196         final HelpMojo mojo = new HelpMojo();
197         mojo.setLog(log);
198         setField(mojo, "indentSize", 0);
199         Mockito.when(log.isInfoEnabled()).thenReturn(true);
200 
201         mojo.execute();
202 
203         Mockito.verify(log).warn("The parameter 'indentSize' should be positive, using '2' as default.");
204     }
205 
206     /**
207      * Test execute when lineLength is negative triggers warning.
208      *
209      * @throws Exception
210      *             the exception
211      */
212     @Test
213     void testExecuteWithNegativeLineLength() throws Exception {
214         final HelpMojo mojo = new HelpMojo();
215         mojo.setLog(log);
216         setField(mojo, "lineLength", -5);
217         Mockito.when(log.isInfoEnabled()).thenReturn(true);
218 
219         mojo.execute();
220 
221         Mockito.verify(log).warn("The parameter 'lineLength' should be positive, using '80' as default.");
222     }
223 
224     /**
225      * Test execute when isInfoEnabled returns false, info is not logged.
226      *
227      * @throws Exception
228      *             the exception
229      */
230     @Test
231     void testExecuteInfoNotEnabledSkipsInfoLog() throws Exception {
232         final HelpMojo mojo = new HelpMojo();
233         mojo.setLog(log);
234         Mockito.when(log.isInfoEnabled()).thenReturn(false);
235 
236         mojo.execute();
237 
238         Mockito.verify(log).isInfoEnabled();
239         Mockito.verify(log, Mockito.never()).info(Mockito.anyString());
240     }
241 
242     /**
243      * Test execute with detail=true and the git goal to exercise parameter writing.
244      *
245      * @throws Exception
246      *             the exception
247      */
248     @Test
249     void testExecuteDetailWithGitGoal() throws Exception {
250         final HelpMojo mojo = new HelpMojo();
251         mojo.setLog(log);
252         setField(mojo, "detail", true);
253         setField(mojo, "goal", "git");
254         Mockito.when(log.isInfoEnabled()).thenReturn(true);
255 
256         mojo.execute();
257 
258         Mockito.verify(log).isInfoEnabled();
259     }
260 
261     /**
262      * Test that the getPropertyFromExpression method correctly strips ${} wrapper.
263      *
264      * @throws Exception
265      *             the exception
266      */
267     @Test
268     void testGetPropertyFromExpression() throws Exception {
269         final Method method = HelpMojo.class.getDeclaredMethod("getPropertyFromExpression", String.class);
270         method.setAccessible(true);
271 
272         Assertions.assertAll(
273                 // Standard expression
274                 () -> Assertions.assertEquals("myProp", method.invoke(null, "${myProp}")),
275                 // Dotted expression (common Maven property like ${project.version})
276                 () -> Assertions.assertEquals("project.version", method.invoke(null, "${project.version}")),
277                 // Null input
278                 () -> Assertions.assertNull(method.invoke(null, (Object) null)),
279                 // No ${} wrapper
280                 () -> Assertions.assertNull(method.invoke(null, "plain-value")),
281                 // Nested expression - should return null
282                 () -> Assertions.assertNull(method.invoke(null, "${outer.${inner}}")),
283                 // Missing closing brace
284                 () -> Assertions.assertNull(method.invoke(null, "${noClosure")));
285     }
286 
287     /**
288      * Test that the isNotEmpty method correctly identifies non-empty strings.
289      *
290      * @throws Exception
291      *             the exception
292      */
293     @Test
294     void testIsNotEmpty() throws Exception {
295         final Method method = HelpMojo.class.getDeclaredMethod("isNotEmpty", String.class);
296         method.setAccessible(true);
297 
298         Assertions.assertAll(() -> Assertions.assertFalse((boolean) method.invoke(null, (Object) null)),
299                 () -> Assertions.assertFalse((boolean) method.invoke(null, "")),
300                 () -> Assertions.assertTrue((boolean) method.invoke(null, "hello")));
301     }
302 
303     /**
304      * Test that the repeat method correctly repeats strings.
305      *
306      * @throws Exception
307      *             the exception
308      */
309     @Test
310     void testRepeat() throws Exception {
311         final Method method = HelpMojo.class.getDeclaredMethod("repeat", String.class, int.class);
312         method.setAccessible(true);
313 
314         Assertions.assertAll(() -> Assertions.assertEquals("", method.invoke(null, "ab", 0)),
315                 () -> Assertions.assertEquals("ab", method.invoke(null, "ab", 1)),
316                 () -> Assertions.assertEquals("ababab", method.invoke(null, "ab", 3)));
317     }
318 
319     /**
320      * Test that the getIndentLevel method correctly counts tab indentation.
321      *
322      * @throws Exception
323      *             the exception
324      */
325     @Test
326     void testGetIndentLevel() throws Exception {
327         final Method method = HelpMojo.class.getDeclaredMethod("getIndentLevel", String.class);
328         method.setAccessible(true);
329 
330         Assertions.assertAll(
331                 // No indentation
332                 () -> Assertions.assertEquals(0, method.invoke(null, "no indent")),
333                 // One tab
334                 () -> Assertions.assertEquals(1, method.invoke(null, "\tsingle")),
335                 // Two tabs
336                 () -> Assertions.assertEquals(2, method.invoke(null, "\t\tdouble")),
337                 // One tab followed by a space then another tab: second loop detects the extra tab → level+1
338                 () -> Assertions.assertEquals(2, method.invoke(null, "\t \t")));
339     }
340 
341     /**
342      * Test that toLines correctly replaces non-breaking spaces (U+00A0) with regular spaces during line-wrapping.
343      *
344      * @throws Exception
345      *             the exception
346      */
347     @Test
348     void testToLinesNonBreakingSpace() throws Exception {
349         final Method method = HelpMojo.class.getDeclaredMethod("toLines", java.util.List.class, String.class, int.class,
350                 int.class);
351         method.setAccessible(true);
352 
353         final java.util.List<String> lines = new java.util.ArrayList<>();
354         // A line containing a non-breaking space (U+00A0) between words
355         method.invoke(null, lines, "hello\u00A0world", 2, 80);
356 
357         Assertions.assertFalse(lines.isEmpty(), "toLines should produce at least one output line");
358         // The non-breaking space should have been converted to a regular space
359         Assertions.assertTrue(lines.stream().anyMatch(l -> l.contains(" ")),
360                 "Output should contain a regular space where the non-breaking space was");
361     }
362 
363 }