View Javadoc
1   package emissary.util;
2   
3   import emissary.core.DataObjectFactory;
4   import emissary.core.Family;
5   import emissary.core.Form;
6   import emissary.core.IBaseDataObject;
7   import emissary.test.core.junit5.UnitTest;
8   
9   import org.junit.jupiter.api.AfterAll;
10  import org.junit.jupiter.api.BeforeAll;
11  import org.junit.jupiter.api.Test;
12  
13  import java.time.Instant;
14  import java.util.ArrayList;
15  import java.util.HashSet;
16  import java.util.List;
17  import java.util.Locale;
18  import java.util.Random;
19  import java.util.Set;
20  
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  class PayloadUtilTest extends UnitTest {
26  
27      @SuppressWarnings("NonFinalStaticField")
28      private static String timezone = "GMT";
29      private static final String validFormCharsString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-)(/+";
30      private static final Set<Character> validFormChars = new HashSet<>();
31  
32      @BeforeAll
33      public static void setup() {
34          // Needed to ensure correct output strings on the dates
35          timezone = System.getProperty("user.timezone");
36          System.setProperty("user.timezone", "GMT");
37  
38          validFormCharsString.chars().forEach(character -> validFormChars.add((char) character));
39      }
40  
41      @AfterAll
42      public static void teardown() {
43          System.setProperty("user.timezone", timezone);
44      }
45  
46      @Test
47      void testOneLineString() {
48  
49          Instant now = Instant.now();
50  
51          // setup
52          final String fn = "fname" + Family.SEP + "4";
53          final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
54          d.appendTransformHistory("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace");
55          d.appendTransformHistory("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace");
56          d.appendTransformHistory("BAR.BURP.BURPPLACE.http://example.com:1234/BurpPlace");
57          d.setCreationTimestamp(now);
58          final String expected = "att-4 FOO,BAR>>[UNKNOWN]//UNKNOWN//" + now;
59  
60          // test
61          final String answer = PayloadUtil.getPayloadDisplayString(d, true);
62  
63          // verify
64          assertFalse(answer.contains("\n"), "Must be one line string");
65          assertEquals(expected, answer, "Answer string did not equal the expected string");
66      }
67  
68      @Test
69      void testOneLineStringOnTLD() {
70          Instant now = Instant.now();
71  
72          // setup
73          final String fn = "fname";
74          final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
75          d.setCreationTimestamp(now);
76          d.appendTransformHistory("BOGUSKEYELEMENT");
77          final String expected = ">>[UNKNOWN]//UNKNOWN//" + now;
78  
79          // test
80          final String answer = PayloadUtil.getPayloadDisplayString(d, true);
81  
82          // verify
83          assertFalse(answer.contains("\n"), "Must be one line string");
84          assertEquals(expected, answer, "Answer string did not equal the expected string");
85      }
86  
87      @Test
88      void testMultiLineString() {
89  
90          Instant now = Instant.now();
91  
92          // setup
93          final String fn = "fname" + Family.SEP + "4";
94          final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
95          d.appendTransformHistory("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace");
96          d.appendTransformHistory("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace");
97          d.setCreationTimestamp(now);
98  
99          // test
100         final String answer = PayloadUtil.getPayloadDisplayString(d, false);
101 
102         // verify
103         assertTrue(answer.contains("\n"), "Must be multi-line string");
104         assertTrue(answer.contains("filename: fname-att-4"), "Answer did not contain the correct filename");
105         assertTrue(answer.contains("creationTimestamp: " + now), "Answer did not contain the creationTimestamp");
106         assertTrue(answer.contains("currentForms: [UNKNOWN]"), "Answer did not contain the currentForms");
107         assertTrue(answer.contains("filetype: UNKNOWN"), "Answer did not contain the correct filetype");
108         assertTrue(answer.contains("transform history (2)"), "Answer did not contain the transform history number");
109         assertTrue(answer.contains("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace"),
110                 "Answer did not contain the correct transform history entry");
111         assertTrue(answer.contains("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace"),
112                 "Answer did not contain the correct transform history entry");
113     }
114 
115     @Test
116     void testReducedHistory() {
117         // setup
118         final String fn = "testReducedHistory";
119         final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
120         d.appendTransformHistory("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace");
121         d.appendTransformHistory("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace");
122         d.appendTransformHistory("TEST.DROPOFFPLACE.http://example.com:1234/DropOffPlace");
123         d.setCreationTimestamp(Instant.now());
124 
125         // test
126         PayloadUtil.historyPreference.put("UNKNOWN", "REDUCED_HISTORY");
127         final String answer = PayloadUtil.getPayloadDisplayString(d, false);
128         PayloadUtil.historyPreference.clear();
129 
130         // verify
131         assertTrue(answer.contains("\n"), "Must be multi-line string");
132         assertTrue(answer.contains("filename: testReducedHistory"), "Answer did not contain the correct filename");
133         assertTrue(answer.contains("transform history (3)"), "Answer did not contain the transform history number");
134         assertFalse(answer.contains("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace"),
135                 "Answer should not contain this transform history entry");
136         assertFalse(answer.contains("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace"),
137                 "Answer should not contain this transform history entry");
138         assertTrue(answer.contains("** reduced transform history **"), "Answer should contain 'reduced transform history'");
139         assertTrue(answer.contains("dropOff -> TEST.DROPOFFPLACE.http://example.com:1234/DropOffPlace"), "Answer should show dropoff");
140     }
141 
142     @Test
143     void testNoUrlHistory() {
144         // setup
145         final String fn = "noUrlHistory";
146         final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
147         d.appendTransformHistory("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace");
148         d.appendTransformHistory("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace");
149         d.setCreationTimestamp(Instant.now());
150 
151         // test
152         PayloadUtil.historyPreference.put("UNKNOWN", "NO_URL");
153         final String answer = PayloadUtil.getPayloadDisplayString(d, false);
154         PayloadUtil.historyPreference.clear();
155 
156         // verify
157         assertTrue(answer.contains("\n"), "Must be multi-line string");
158         assertTrue(answer.contains("filename: noUrlHistory"), "Answer did not contain the correct filename");
159         assertTrue(answer.contains("FOO.UNKNOWN.FOOPLACE"),
160                 "Answer should not contain the URL");
161         assertFalse(answer.contains("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace"), "Answer should not contain full URL");
162         assertTrue(answer.contains("BAR.UNKNOWN.BARPLACE"),
163                 "Answer should not contain the URL");
164         assertFalse(answer.contains("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace"), "Answer should not contain full URL");
165     }
166 
167     @Test
168     void testHistoryDoesNotReduce() {
169         // setup
170         final String fn = "noMatch";
171         final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
172         d.appendTransformHistory("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace");
173         d.appendTransformHistory("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace");
174         d.setCreationTimestamp(Instant.now());
175 
176         // test
177         PayloadUtil.historyPreference.put("REDUCED", "REDUCED_HISTORY");
178         PayloadUtil.historyPreference.put("NOURL", "NO_URL");
179         final String answer = PayloadUtil.getPayloadDisplayString(d, false);
180         PayloadUtil.historyPreference.clear();
181 
182         // verify
183         assertTrue(answer.contains("\n"), "Must be multi-line string");
184         assertTrue(answer.contains("filename: noMatch"), "Answer did not contain the correct filename");
185         assertTrue(answer.contains("FOO.UNKNOWN.FOOPLACE.http://example.com:1234/FooPlace"),
186                 "Answer should not reduce history due to no matching form");
187         assertTrue(answer.contains("BAR.UNKNOWN.BARPLACE.http://example.com:1234/BarPlace"),
188                 "Answer should not reduce history due to no matching form");
189     }
190 
191     @Test
192     void testNameOfSimpleObject() {
193         final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), "ab/fn", Form.UNKNOWN);
194         assertEquals("fn", PayloadUtil.getName(d), "Name of simple payload is shortname");
195     }
196 
197     @Test
198     void testNameOfCollection() {
199         final List<IBaseDataObject> list = new ArrayList<>();
200         for (int i = 0; i < 3; i++) {
201             final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), "ab/fn" + i, Form.UNKNOWN);
202             list.add(d);
203         }
204         assertEquals("fn0(3)", PayloadUtil.getName(list), "Name of collection payload is shortname with count");
205     }
206 
207     @Test
208     void testNameOfEmptyCollection() {
209         final List<IBaseDataObject> list = new ArrayList<>();
210         assertEquals("java.util.ArrayList", PayloadUtil.getName(list), "Name of empty collection is class name");
211     }
212 
213     @Test
214     void testNameOfBadArgument() {
215         final String s = "foo";
216         assertEquals(s.getClass().getName(), PayloadUtil.getName(s), "Name of unexpected argument is class name");
217     }
218 
219     @Test
220     void testXmlSerizliaztion() {
221         final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), "testfile", Form.UNKNOWN);
222         d.addAlternateView("AV", "def".getBytes());
223         d.putParameter("P", "ghi");
224         d.addProcessingError("jkl");
225         d.setHeader("mno".getBytes());
226         d.setFooter("pqr".getBytes());
227         d.appendTransformHistory("stu");
228 
229         final String xml = PayloadUtil.toXmlString(d);
230         assertTrue(xml.contains("abc"), "Xml serialization must include payload data");
231         assertTrue(xml.contains("def"), "Xml serialization must include av data");
232         assertTrue(xml.contains("ghi"), "Xml serialization must include param data");
233         assertTrue(xml.contains("jkl"), "Xml serialization must include error data");
234         assertTrue(xml.contains("mno"), "Xml serialization must include header data");
235         assertTrue(xml.contains("pqr"), "Xml serialization must include footer data");
236         assertTrue(xml.contains("stu"), "Xml serialization must include history data");
237 
238         final List<IBaseDataObject> list = new ArrayList<>();
239         list.add(d);
240         final String lxml = PayloadUtil.toXmlString(list);
241         assertTrue(lxml.contains("abc"), "Xml serialization must include payload data");
242         assertTrue(lxml.contains("def"), "Xml serialization must include av data");
243         assertTrue(lxml.contains("ghi"), "Xml serialization must include param data");
244         assertTrue(lxml.contains("jkl"), "Xml serialization must include error data");
245         assertTrue(lxml.contains("mno"), "Xml serialization must include header data");
246         assertTrue(lxml.contains("pqr"), "Xml serialization must include footer data");
247         assertTrue(lxml.contains("stu"), "Xml serialization must include history data");
248     }
249 
250     @Test
251     void testIsValidForm() {
252         // Check that all expected valid characters are valid
253         String alphaLow = "abcdefghijklmnopqrstuvwxyz";
254         assertTrue(PayloadUtil.isValidForm(alphaLow), "Lower case alpha characters are expected to be valid");
255         assertTrue(PayloadUtil.isValidForm(alphaLow.toUpperCase(Locale.getDefault())), "Upper case alpha characters are expected to be valid");
256         assertTrue(PayloadUtil.isValidForm("0123456789"), "Numeric characters are expected to be valid");
257         assertTrue(PayloadUtil.isValidForm("-_"), "'-' and '_' are expected to be valid form characters");
258         assertTrue(PayloadUtil.isValidForm("formName-(suffixInParens)"), "Parentheses are expected to be valid form characters");
259         assertTrue(PayloadUtil.isValidForm("formName-(application/xml)"), "'/' is expected to be a valid form character");
260         assertFalse(PayloadUtil.isValidForm("."), "Dot should not be considered a valid form character");
261         assertFalse(PayloadUtil.isValidForm(" "), "Space should not be considered a valid form character");
262         assertTrue(PayloadUtil.isValidForm("+"), "'+' is expected to be a valid form character");
263 
264         // Cycle through all characters and see how many are valid and that we have the expected number
265         int validChars = 0;
266         for (int i = 0; i < Character.MAX_VALUE; i++) {
267             if (PayloadUtil.isValidForm(Character.toString((char) i))) {
268                 validChars++;
269             }
270         }
271         assertEquals(validFormChars.size(), validChars, "Unexpected number of valid characters.");
272 
273         // Create a set with possible form characters for randomly generated cases
274         Set<Character> formChars = new HashSet<>(validFormChars);
275         // Add an invalid character to generate some false cases
276         formChars.add('.');
277 
278         Character[] formCharArray = new Character[formChars.size()];
279         formChars.toArray(formCharArray);
280         // Seed the random for consistent output
281         Random rand = new Random(0);
282         // Generate N example forms and test if set and regex implementation produce identical results
283         for (int i = 0; i < 4000000; i++) {
284             StringBuilder word = new StringBuilder();
285             int size = rand.nextInt(20);
286             for (int n = 0; n < size; n++) {
287                 word.append(formCharArray[rand.nextInt(formChars.size())]);
288             }
289             String form = word.toString();
290             assertEquals(PayloadUtil.isValidForm(form),
291                     isValidFormSetImplementation(form),
292                     "Regex and Set implementations of form check differ for form \"" + form + "\"");
293         }
294     }
295 
296     @Test
297     void testCompactHistory() {
298         // setup
299         final String fn = "noMatch";
300         final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
301         d.appendTransformHistory("FOO.PLACE_ONE.FOOPLACE.http://example.com:1234/FooPlace");
302         d.appendTransformHistory("BAR.PLACE_TWO.BARPLACE.http://example.com:1234/BarPlace");
303         d.appendTransformHistory("BAR.PLACE_THREE.NONEPLACE.http://example.com:1234/NonePlace", true);
304         d.setCreationTimestamp(Instant.now());
305 
306         // test
307         PayloadUtil.compactHistory = true;
308         final String answer = PayloadUtil.getPayloadDisplayString(d);
309         PayloadUtil.compactHistory = false;
310 
311         // verify
312         assertTrue(answer.contains("transform history (3)"), "Answer history count is wrong");
313         assertTrue(answer.contains("FOO.FOOPLACE: FooPlace"), "Answer should have compacted history");
314         assertTrue(answer.contains("BAR.BARPLACE: BarPlace(NonePlace)"), "Answer should have compacted history");
315     }
316 
317     @Test
318     void testCompactHistoryFormStack() {
319         // setup
320         final String fn = "noMatch";
321         final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
322         d.appendTransformHistory("FOO.PLACE_ONE.FOOPLACE.http://example.com:1234/FooPlace");
323         d.appendTransformHistory("[BAR-UPDATED].PLACE_TWO.BARPLACE.http://example.com:1234/BarPlace");
324         d.appendTransformHistory("[BAR].PLACE_THREE.NONEPLACE.http://example.com:1234/NonePlace");
325         d.setCreationTimestamp(Instant.now());
326 
327         // test
328         PayloadUtil.compactHistory = true;
329         final String answer = PayloadUtil.getPayloadDisplayString(d);
330         PayloadUtil.compactHistory = false;
331 
332         // verify
333         assertTrue(answer.contains("transform history (3)"), "Answer history count is wrong");
334         assertTrue(answer.contains("FOO.FOOPLACE: FooPlace"), "Answer should have compacted history");
335         assertTrue(answer.contains("[BAR-UPDATED].BARPLACE: BarPlace"), "Answer should have compacted history");
336         assertTrue(answer.contains("[BAR].NONEPLACE: NonePlace"), "Answer should have compacted history");
337     }
338 
339     @Test
340     void testCompactHistorySprout() {
341         // setup
342         final String fn = "noMatch";
343         final IBaseDataObject d = DataObjectFactory.getInstance("abc".getBytes(), fn, Form.UNKNOWN);
344         d.appendTransformHistory("FOO.PLACE_ONE.FOOPLACE.http://example.com:1234/FooPlace");
345         d.appendTransformHistory("*.*.<SPROUT>.http://example.com:1234/FooPlace");
346         d.appendTransformHistory("BAR.PLACE_TWO.BARPLACE.http://example.com:1234/BarPlace");
347         d.appendTransformHistory("BAR.PLACE_THREE.NONEPLACE.http://example.com:1234/NonePlace", true);
348         d.setCreationTimestamp(Instant.now());
349 
350         // test
351         PayloadUtil.compactHistory = true;
352         final String answer = PayloadUtil.getPayloadDisplayString(d);
353         PayloadUtil.compactHistory = false;
354 
355         // verify
356         assertTrue(answer.contains("transform history (4)"), "Answer history count is wrong");
357         assertTrue(answer.contains("FOO.<SPROUT>: FooPlace"), "Answer should have compacted history");
358         assertTrue(answer.contains("BAR.BARPLACE: BarPlace(NonePlace)"), "Answer should have compacted history");
359     }
360 
361     /**
362      * Compares the form to a set of valid characters
363      * <p>
364      * This implementation is marginally faster than the regex implementation, but is less maintainable
365      *
366      * @param form The form to be tested
367      * @return Whether the form is considered valid
368      */
369     private static boolean isValidFormSetImplementation(String form) {
370         if (form.length() > 0) {
371             for (int i = 0; i < form.length(); i++) {
372                 if (!validFormChars.contains(form.charAt(i))) {
373                     return false;
374                 }
375             }
376             return true;
377         }
378         return false;
379     }
380 }