1 package emissary.directory;
2
3 import emissary.core.DataObjectFactory;
4 import emissary.core.Form;
5 import emissary.core.HDMobileAgent;
6 import emissary.core.IBaseDataObject;
7 import emissary.place.IServiceProviderPlace;
8 import emissary.place.sample.DelayPlace;
9 import emissary.test.core.junit5.UnitTest;
10 import emissary.util.io.ResourceReader;
11
12 import org.junit.jupiter.api.AfterEach;
13 import org.junit.jupiter.api.BeforeEach;
14 import org.junit.jupiter.api.Test;
15
16 import java.io.IOException;
17 import java.util.ArrayList;
18 import java.util.List;
19 import javax.annotation.Nullable;
20
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertNotNull;
23 import static org.junit.jupiter.api.Assertions.assertNull;
24
25 class RoutingAlgorithmTest extends UnitTest {
26 private MyDirectoryPlace dir;
27 private IBaseDataObject payload;
28 private MyMobileAgent agent;
29 private final List<DirectoryEntry> unknowns = createUnknownEntries();
30 private final List<DirectoryEntry> transforms = createTransformEntries();
31 private final List<DirectoryEntry> analyzers = createAnalyzeEntries();
32
33
34
35
36
37 @Override
38 @BeforeEach
39 public void setUp() throws Exception {
40 super.setUp();
41
42 this.payload = DataObjectFactory.getInstance();
43 this.payload.setFilename("testpayload");
44 this.dir = new MyDirectoryPlace("http://example.com:8001/MyDirectoryPlace");
45 this.agent = new MyMobileAgent();
46 }
47
48 @Override
49 @AfterEach
50 public void tearDown() throws Exception {
51 super.tearDown();
52 this.dir.clearAllEntries();
53 this.dir.shutDown();
54 this.agent.killAgent();
55 }
56
57 private static List<DirectoryEntry> createUnknownEntries() {
58 final List<DirectoryEntry> unknowns = new ArrayList<>();
59 unknowns.add(new DirectoryEntry("UNKNOWN.s1.ID.http://example.com:8001/U$5050"));
60 unknowns.add(new DirectoryEntry("UNKNOWN.s2.ID.http://example.com:8001/U$5060"));
61 unknowns.add(new DirectoryEntry("UNKNOWN.s3.ID.http://example.com:8001/U$6050"));
62 unknowns.add(new DirectoryEntry("UNKNOWN.s4.ID.http://example.com:8001/U$7050"));
63 return unknowns;
64 }
65
66 private static List<DirectoryEntry> createTransformEntries() {
67 final List<DirectoryEntry> transforms = new ArrayList<>();
68 transforms.add(new DirectoryEntry("XFORM.s1.TRANSFORM.http://example.com:8001/T$5050"));
69 transforms.add(new DirectoryEntry("XFORM.s2.TRANSFORM.http://example.com:8001/T$5060"));
70 transforms.add(new DirectoryEntry("XFORM.s3.TRANSFORM.http://example.com:8001/T$6050"));
71 transforms.add(new DirectoryEntry("XFORM.s4.TRANSFORM.http://example.com:8001/T$7050"));
72 return transforms;
73 }
74
75 private static List<DirectoryEntry> createAnalyzeEntries() {
76 final List<DirectoryEntry> analyzers = new ArrayList<>();
77 analyzers.add(new DirectoryEntry("ANALYZE.s1.ANALYZE.http://example.com:8001/A$5050"));
78 analyzers.add(new DirectoryEntry("ANALYZE.s2.ANALYZE.http://example.com:8001/A$5060"));
79 analyzers.add(new DirectoryEntry("ANALYZE.s3.ANALYZE.http://example.com:8001/A$6050"));
80 analyzers.add(new DirectoryEntry("ANALYZE.s4.ANALYZE.http://example.com:8001/A$7050"));
81 return analyzers;
82 }
83
84 private void loadAllTestEntries() {
85 this.dir.addTestEntries(this.unknowns);
86 this.dir.addTestEntries(this.transforms);
87 this.dir.addTestEntries(this.analyzers);
88 }
89
90 @Test
91 void testFindsBuriedCurrentFormAndPullsToTop() {
92 this.dir.addTestEntries(this.unknowns);
93 this.payload.pushCurrentForm("UNKNOWN");
94 this.payload.pushCurrentForm("FOO");
95 this.payload.pushCurrentForm("BAR");
96 final int oldSize = this.payload.currentFormSize();
97 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
98 assertEquals(this.unknowns.get(0).getKey(), result.getKey(), "Next keys returns lowest cost");
99 assertEquals(oldSize, this.payload.currentFormSize(), "Form stack must have same size");
100 assertEquals("UNKNOWN", this.payload.currentForm(), "Payload form used pulled to top");
101 }
102
103 @Test
104 void testIdsInOrderWithNullLastPlace() {
105 this.dir.addTestEntries(this.unknowns);
106 this.payload.pushCurrentForm("UNKNOWN");
107 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
108 assertEquals(this.unknowns.get(0).getKey(), result.getKey(), "Next keys returns lowest cost");
109 }
110
111 @Test
112 void testIdsInOrderWithCheaperLastPlace() {
113 this.dir.addTestEntries(this.unknowns);
114 this.payload.pushCurrentForm("UNKNOWN");
115 this.payload.appendTransformHistory(this.unknowns.get(0).getFullKey());
116 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
117 assertEquals(this.unknowns.get(1).getKey(), result.getKey(), "Next keys should return next in cost/quality order");
118 }
119
120 @Test
121 void testIdsInOrderGettingLastPlaceOnList() {
122 this.dir.addTestEntries(this.unknowns);
123 this.payload.pushCurrentForm("UNKNOWN");
124 this.payload.appendTransformHistory(this.unknowns.get(this.unknowns.size() - 2).getFullKey());
125 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
126 assertEquals(this.unknowns.get(this.unknowns.size() - 1).getKey(), result.getKey(), "Next keys returns next cost");
127 }
128
129 @Test
130 void testIdsInOrderGettingNoResultAtEndOfList() {
131 this.dir.addTestEntries(this.unknowns);
132 this.payload.pushCurrentForm("UNKNOWN");
133 this.payload.appendTransformHistory(this.unknowns.get(this.unknowns.size() - 1).getFullKey());
134 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
135 assertNull(result, "Result must be null with all unknown entries loaded not " + result);
136 }
137
138 @Test
139 void testHighestIdWithAllEntriesLoaded() {
140 loadAllTestEntries();
141 this.unknowns.get(this.unknowns.size() - 1);
142 this.payload.pushCurrentForm("UNKNOWN");
143 this.payload.appendTransformHistory(this.unknowns.get(this.unknowns.size() - 1).getFullKey());
144 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
145 assertNull(result, "Result must be null with all entries loaded not " + result);
146 }
147
148 @Test
149 void testBackToIdPhaseAfterTransform() {
150 final DirectoryEntry lastPlace = new DirectoryEntry("FOO.s2.TRANSFORM.http://example.com:8001/T$7050");
151 this.dir.addTestEntry(new DirectoryEntry("FOO.s1.ID.http://example.com:8001/I$7050"));
152 this.dir.addTestEntry(lastPlace);
153 this.dir.addTestEntry(new DirectoryEntry("FOO.s3.ANALYZE.http://example.com:8001/A$7050"));
154
155 this.payload.pushCurrentForm("FOO");
156 this.payload.appendTransformHistory(lastPlace.getFullKey());
157 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
158 assertEquals("ID", result.getServiceType(), "Should go to ID place after transform");
159 }
160
161 @Test
162 void testNotBackToIdPhaseAfterAnalyze() {
163 final DirectoryEntry lastPlace = new DirectoryEntry("FOO.s3.ANALYZE.http://example.com:8001/T$7050");
164 this.dir.addTestEntry(new DirectoryEntry("FOO.s1.ID.http://example.com:8001/I$7050"));
165 this.dir.addTestEntry(new DirectoryEntry("FOO.s2.TRANSFORM.http://example.com:8001/A$7050"));
166 this.dir.addTestEntry(lastPlace);
167
168 this.payload.pushCurrentForm("FOO");
169 this.payload.appendTransformHistory(lastPlace.getFullKey());
170 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
171 assertNull(result, "Should not return to ID place after analyze");
172 }
173
174 @Test
175 void testBackToIdPhaseAfterCoordinate() {
176 final DirectoryEntry lastPlace = new DirectoryEntry("UNKNOWN.s1.ID.http://example.com:8001/I$1010");
177 this.dir.addTestEntry(lastPlace);
178 this.dir.addTestEntry(new DirectoryEntry("UNKNOWN.s3.ID.http://example.com:8001/A$3030"));
179 this.dir.addTestEntry(new DirectoryEntry("UNKNOWN.s4.ANALYZE.http://example.com:8001/A$4040"));
180
181
182 this.payload.pushCurrentForm("UNKNOWN");
183 this.payload.appendTransformHistory("UNKNOWN.s1.ID.http://example.com:8001/I$1010");
184 this.payload.appendTransformHistory("UNKNOWN.s2.ANALYZE.http://example.com:8001/T$2020", true);
185 assertEquals("ID", this.agent.getNextKeyAccess(this.dir, this.payload).getServiceType(), "Should go to ID place after coordinate");
186
187
188 this.payload.clearTransformHistory();
189 this.payload.appendTransformHistory("UNKNOWN.s1.ID.http://example.com:8001/I$1010");
190 this.payload.appendTransformHistory("UNKNOWN.s2.ANALYZE.http://example.com:8001/T$2020", false);
191 assertEquals("ANALYZE", this.agent.getNextKeyAccess(this.dir, this.payload).getServiceType(), "Should not return to ID place after analyze");
192 }
193
194 @Test
195 void testCheckTransformProxyWithTwoFormsDoesNotRepeat() {
196 loadAllTestEntries();
197
198
199 final DirectoryEntry foo = new DirectoryEntry("FOO.s2.TRANSFORM.http://example.com:8001/T$7050");
200 final DirectoryEntry bar = new DirectoryEntry("BAR.s2.TRANSFORM.http://example.com:8001/T$7050");
201 this.dir.addTestEntry(foo);
202 this.dir.addTestEntry(bar);
203
204 this.payload.pushCurrentForm("FOO");
205 this.payload.pushCurrentForm("BAR");
206 this.payload.appendTransformHistory(this.unknowns.get(0).getFullKey());
207 this.payload.appendTransformHistory(foo.getFullKey());
208
209 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
210 assertNull(result, "No place remains without looping error but we got " + result + " on " + this.payload.getAllCurrentForms() + " lastPlace="
211 + this.payload.getLastPlaceVisited());
212 }
213
214 @Test
215 void testCannotProceedPastMaxItinerarySteps() {
216 loadAllTestEntries();
217 this.payload.pushCurrentForm("UNKNOWN");
218 for (int i = 0; i <= this.agent.getMaxItinerarySteps(); i++) {
219 this.payload.appendTransformHistory(this.unknowns.get(0).getFullKey());
220 }
221 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
222 assertNull(result, "Must not proceed past MAX steps but we got " + result);
223 }
224
225 @Test
226 void testCannotProceedPastMaxItineraryStepsWithSetValue() {
227 loadAllTestEntries();
228 this.payload.pushCurrentForm("UNKNOWN");
229 this.agent.setMaxItinerarySteps(10);
230 for (int i = 0; i <= this.agent.getMaxItinerarySteps(); i++) {
231 this.payload.appendTransformHistory(this.unknowns.get(0).getFullKey());
232 }
233 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
234 assertNull(result, "Must not proceed past MAX steps but we got " + result);
235 }
236
237 @Test
238 void testContractWithNextKeysPlace() {
239 loadAllTestEntries();
240 this.payload.pushCurrentForm("UNKNOWN");
241 final DirectoryEntry result = this.agent.getNextKeyAccess(null, this.payload);
242 assertNull(result, "Must not return result with null place");
243 }
244
245 @Test
246 void testContractWithNextKeysPayload() {
247 loadAllTestEntries();
248 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, null);
249 assertNull(result, "Must not return result with null payload");
250 }
251
252 @Test
253 void testPayloadWithNoCurrentForm() {
254 loadAllTestEntries();
255 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
256 assertNull(result, "Must not return result when payload has no current form");
257 }
258
259 @Test
260 void testPayloadWithDoneForm() {
261 loadAllTestEntries();
262 this.dir.addTestEntry(new DirectoryEntry("DONE.d1.IO.http://example.com:8001/D$5050"));
263 this.payload.pushCurrentForm(Form.DONE);
264 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
265 assertNull(result, "Must not return result when payload has DONE form");
266 }
267
268 @Test
269 void testErrorHandlingPopsCurrentForms() {
270 loadAllTestEntries();
271 final DirectoryEntry eplace = new DirectoryEntry("ERROR.e1.IO.http://example.com:8001/E$5050");
272 this.dir.addTestEntry(eplace);
273 this.payload.pushCurrentForm("FOO");
274 this.payload.pushCurrentForm("BAR");
275 this.payload.pushCurrentForm("BAZ");
276 this.payload.pushCurrentForm(Form.ERROR);
277 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
278 assertEquals(eplace.getKey(), result.getKey(), "Routing to error handling place should occur");
279 assertEquals(1, this.payload.currentFormSize(), "Routing to error handling place removes other forms");
280 }
281
282 @Test
283 void testErrorHandlingErrorPopsAllCurrentForms() {
284 loadAllTestEntries();
285 final DirectoryEntry eplace = new DirectoryEntry("ERROR.e1.IO.http://example.com:8001/E$5050");
286 this.dir.addTestEntry(eplace);
287 this.payload.pushCurrentForm(Form.ERROR);
288 this.payload.pushCurrentForm(Form.ERROR);
289 this.payload.pushCurrentForm(Form.ERROR);
290 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
291 assertEquals(0, this.payload.currentFormSize(), "Error in error handling place removes all forms");
292 assertNull(result, "Error in Error handling must not re-route to error handler but we got " + result);
293 }
294
295 @Test
296 void testNoRepeatWhenTransformAddsFormButDoesNotRemoveOwnProxyAndIsntEvenOnTop() {
297 loadAllTestEntries();
298 final DirectoryEntry foo = new DirectoryEntry("FOO.s2.TRANSFORM.http://example.com:8001/T$7050");
299 final DirectoryEntry bar = new DirectoryEntry("BAR.s2.ANALYZE.http://example.com:8001/A$1050");
300 this.dir.addTestEntry(foo);
301 this.dir.addTestEntry(bar);
302
303 this.payload.pushCurrentForm("BAR");
304 this.payload.pushCurrentForm("FOO");
305 this.payload.appendTransformHistory(this.unknowns.get(0).getFullKey());
306 this.payload.appendTransformHistory(foo.getFullKey());
307
308 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
309 assertNotNull(result, "Must move along to analyze with current form of xform still on stack");
310 assertEquals(bar.getKey(), result.getKey(), "Must move along to analyze even with current form of xform place still on stack");
311 }
312
313 @Test
314 void testVisitedPlaceNoRepeatListForAnalyzeStage() {
315 loadAllTestEntries();
316 final DirectoryEntry foo = new DirectoryEntry("FOO.s2.ANALYZE.http://example.com:8001/A$7050");
317 final DirectoryEntry bar = new DirectoryEntry("BAR.s2.ANALYZE.http://example.com:8001/A$1050");
318 this.dir.addTestEntry(foo);
319 this.dir.addTestEntry(bar);
320
321 this.payload.pushCurrentForm("BAR");
322 this.payload.pushCurrentForm("FOO");
323 this.payload.appendTransformHistory(this.unknowns.get(0).getFullKey());
324 this.payload.appendTransformHistory(foo.getFullKey());
325
326 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
327 assertNull(result, "Must not repeat analyze place even with two forms but we got " + result);
328 }
329
330 @Test
331 void testNoRepeatWhenTransformAddsFormButDoesNotRemoveOwnProxy() {
332 loadAllTestEntries();
333 final DirectoryEntry foo = new DirectoryEntry("FOO.s2.TRANSFORM.http://example.com:8001/T$7050");
334 final DirectoryEntry bar = new DirectoryEntry("BAR.s2.ANALYZE.http://example.com:8001/A$1050");
335 this.dir.addTestEntry(foo);
336 this.dir.addTestEntry(bar);
337
338 this.payload.pushCurrentForm("FOO");
339 this.payload.pushCurrentForm("BAR");
340 this.payload.appendTransformHistory(this.unknowns.get(0).getFullKey());
341 this.payload.appendTransformHistory(foo.getFullKey());
342
343 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
344 assertNotNull(result, "Must move along to analyze with current form of xform still on stack");
345 assertEquals(bar.getKey(), result.getKey(), "Must move along to analyze even with current form of xform place still on stack");
346 }
347
348 @Test
349 void testNextKeyFromQueue() {
350 loadAllTestEntries();
351 final DirectoryEntry foo = new DirectoryEntry("FOO.s2.TRANSFORM.http://example.com:8001/T$7050");
352 this.agent.addEntryToQueue(foo);
353 final int sz = this.agent.queueSize();
354 this.payload.pushCurrentForm("BAR");
355 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
356 assertEquals(foo.getKey(), result.getKey(), "Next key must be from queue when queue non-empty");
357 assertEquals(sz - 1, this.agent.queueSize(), "Queue should drain by one");
358 }
359
360 @Test
361 void testCompleteKeyRouting() {
362 loadAllTestEntries();
363 final DirectoryEntry foo1 = new DirectoryEntry("FOO.s2.TRANSFORM.http://example.com:8001/T$7050");
364 final DirectoryEntry foo2 = new DirectoryEntry("FOO.s3.TRANSFORM.http://example.com:9999/T$7050");
365 this.dir.addTestEntry(foo1);
366 this.payload.pushCurrentForm(foo2.getKey());
367 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
368 assertEquals(foo2.getKey(), result.getKey(), "Routing must take place to fully qualified key");
369 }
370
371 @Test
372 void testWildCardProxyHonorsDenyList() throws IOException {
373 loadAllTestEntries();
374
375 this.payload.pushCurrentForm("MYFORM");
376
377
378 DelayPlace deniedWildcardPlace = new DelayPlace(new ResourceReader().getConfigDataName(DelayPlace.class).replace("/main/", "/test/"));
379 this.dir.addTestEntry(deniedWildcardPlace.getDirectoryEntry());
380
381
382
383 DirectoryEntry expected = new DirectoryEntry("MYFORM.s4.ANALYZE.http://example.com:8001/A$9999");
384 this.dir.addTestEntry(expected);
385
386 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
387 assertEquals(expected, result, "After a denied entry, should get next matching entry for the same stage");
388 }
389
390 @Test
391 void testWildCardProxyWithDeniedEntry() throws IOException {
392 loadAllTestEntries();
393
394 this.payload.pushCurrentForm("OTHERFORM");
395
396
397 DelayPlace deniedWildcardPlace = new DelayPlace(new ResourceReader().getConfigDataName(DelayPlace.class).replace("/main/", "/test/"));
398 this.dir.addTestEntry(deniedWildcardPlace.getDirectoryEntry());
399
400
401 final DirectoryEntry result = this.agent.getNextKeyAccess(this.dir, this.payload);
402 assertEquals(deniedWildcardPlace.getKey(), result.getKey(), "Should get matching entry for wildcard place");
403
404 this.payload.pushCurrentForm("MYFORM");
405
406
407 final DirectoryEntry nullResult = this.agent.getNextKeyAccess(this.dir, this.payload);
408 assertNull(nullResult, "MYFORM should be denied");
409 }
410
411
412
413
414 class MyDirectoryPlace extends DirectoryPlace {
415 public MyDirectoryPlace(final String placeLoc) throws IOException {
416 super(new ResourceReader().getConfigDataAsStream(thisPackage + ".MyDirectoryPlace.cfg"), placeLoc, new EmissaryNode());
417 }
418
419 public void clearAllEntries() {
420 entryMap.clear();
421 }
422
423 public void addTestEntry(final DirectoryEntry newEntry) {
424 addEntry(newEntry);
425 }
426
427 public void addTestEntries(final List<DirectoryEntry> newEntryList) {
428 addEntries(newEntryList);
429 }
430 }
431
432 private static final class MyMobileAgent extends HDMobileAgent {
433
434
435
436 private static final long serialVersionUID = 6667669555504467253L;
437
438 public DirectoryEntry getNextKeyAccess(@Nullable final IServiceProviderPlace place, @Nullable final IBaseDataObject payload) {
439 return getNextKey(place, payload);
440 }
441
442 public void addEntryToQueue(final DirectoryEntry entry) {
443 nextKeyQueue.add(entry);
444 }
445
446 public int queueSize() {
447 return nextKeyQueue.size();
448 }
449 }
450 }