orator 5.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,949 @@
1
+ /**
2
+ * Unit tests for Orator complex routes and static file serving
3
+ *
4
+ * @license MIT
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ */
8
+
9
+ const libOrator = require('../source/Orator.js');
10
+
11
+ const Chai = require("chai");
12
+ const Expect = Chai.expect;
13
+ const Assert = Chai.assert;
14
+
15
+ const libFable = require('fable');
16
+ const libPath = require('path');
17
+
18
+ const defaultFableSettings = (
19
+ {
20
+ Product:'Orator-ComplexRouteTests',
21
+ ProductVersion: '0.0.0',
22
+ APIServerPort: 0
23
+ });
24
+
25
+ suite
26
+ (
27
+ 'Orator',
28
+ () =>
29
+ {
30
+ suite
31
+ (
32
+ 'Multiple HTTP Verbs',
33
+ () =>
34
+ {
35
+ test
36
+ (
37
+ 'ipc should be able to handle POST routes',
38
+ (fDone) =>
39
+ {
40
+ let tmpFable = new libFable(defaultFableSettings);
41
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
42
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
43
+ tmpOrator.startService();
44
+
45
+ tmpOrator.serviceServer.post
46
+ (
47
+ '/api/item',
48
+ (pRequest, pResponse, fNext) =>
49
+ {
50
+ pResponse.send({Created: true, Method: 'POST'});
51
+ return fNext();
52
+ }
53
+ );
54
+
55
+ tmpOrator.invoke('POST', '/api/item', {Name: 'TestItem'},
56
+ (pError, pResponseData) =>
57
+ {
58
+ let tmpResponseObject = JSON.parse(pResponseData);
59
+ Expect(tmpResponseObject).to.have.a.property('Created');
60
+ Expect(tmpResponseObject.Created).to.equal(true);
61
+ Expect(tmpResponseObject.Method).to.equal('POST');
62
+ tmpOrator.log.info(`POST /api/item responded with [${pResponseData}]`);
63
+ return fDone();
64
+ });
65
+ }
66
+ );
67
+
68
+ test
69
+ (
70
+ 'ipc should be able to handle PUT routes',
71
+ (fDone) =>
72
+ {
73
+ let tmpFable = new libFable(defaultFableSettings);
74
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
75
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
76
+ tmpOrator.startService();
77
+
78
+ tmpOrator.serviceServer.put
79
+ (
80
+ '/api/item/:id',
81
+ (pRequest, pResponse, fNext) =>
82
+ {
83
+ pResponse.send({Updated: true, ID: pRequest.params.id, Method: 'PUT'});
84
+ return fNext();
85
+ }
86
+ );
87
+
88
+ tmpOrator.invoke('PUT', '/api/item/42', {Name: 'UpdatedItem'},
89
+ (pError, pResponseData) =>
90
+ {
91
+ let tmpResponseObject = JSON.parse(pResponseData);
92
+ Expect(tmpResponseObject).to.have.a.property('Updated');
93
+ Expect(tmpResponseObject.Updated).to.equal(true);
94
+ Expect(tmpResponseObject.ID).to.equal('42');
95
+ Expect(tmpResponseObject.Method).to.equal('PUT');
96
+ tmpOrator.log.info(`PUT /api/item/42 responded with [${pResponseData}]`);
97
+ return fDone();
98
+ });
99
+ }
100
+ );
101
+
102
+ test
103
+ (
104
+ 'ipc should be able to handle DELETE routes',
105
+ (fDone) =>
106
+ {
107
+ let tmpFable = new libFable(defaultFableSettings);
108
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
109
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
110
+ tmpOrator.startService();
111
+
112
+ tmpOrator.serviceServer.del
113
+ (
114
+ '/api/item/:id',
115
+ (pRequest, pResponse, fNext) =>
116
+ {
117
+ pResponse.send({Deleted: true, ID: pRequest.params.id, Method: 'DELETE'});
118
+ return fNext();
119
+ }
120
+ );
121
+
122
+ tmpOrator.invoke('DELETE', '/api/item/99', null,
123
+ (pError, pResponseData) =>
124
+ {
125
+ let tmpResponseObject = JSON.parse(pResponseData);
126
+ Expect(tmpResponseObject).to.have.a.property('Deleted');
127
+ Expect(tmpResponseObject.Deleted).to.equal(true);
128
+ Expect(tmpResponseObject.ID).to.equal('99');
129
+ Expect(tmpResponseObject.Method).to.equal('DELETE');
130
+ tmpOrator.log.info(`DELETE /api/item/99 responded with [${pResponseData}]`);
131
+ return fDone();
132
+ });
133
+ }
134
+ );
135
+
136
+ test
137
+ (
138
+ 'ipc should be able to handle PATCH routes',
139
+ (fDone) =>
140
+ {
141
+ let tmpFable = new libFable(defaultFableSettings);
142
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
143
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
144
+ tmpOrator.startService();
145
+
146
+ tmpOrator.serviceServer.patch
147
+ (
148
+ '/api/item/:id',
149
+ (pRequest, pResponse, fNext) =>
150
+ {
151
+ pResponse.send({Patched: true, ID: pRequest.params.id, Method: 'PATCH'});
152
+ return fNext();
153
+ }
154
+ );
155
+
156
+ tmpOrator.invoke('PATCH', '/api/item/7', null,
157
+ (pError, pResponseData) =>
158
+ {
159
+ let tmpResponseObject = JSON.parse(pResponseData);
160
+ Expect(tmpResponseObject).to.have.a.property('Patched');
161
+ Expect(tmpResponseObject.Patched).to.equal(true);
162
+ Expect(tmpResponseObject.ID).to.equal('7');
163
+ Expect(tmpResponseObject.Method).to.equal('PATCH');
164
+ tmpOrator.log.info(`PATCH /api/item/7 responded with [${pResponseData}]`);
165
+ return fDone();
166
+ });
167
+ }
168
+ );
169
+
170
+ test
171
+ (
172
+ 'ipc should be able to register and invoke all verb types in the same service',
173
+ (fDone) =>
174
+ {
175
+ let tmpFable = new libFable(defaultFableSettings);
176
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
177
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
178
+ tmpOrator.startService();
179
+
180
+ // Register multiple verbs on the same path pattern
181
+ tmpOrator.serviceServer.get('/api/resource/:id',
182
+ (pRequest, pResponse, fNext) =>
183
+ {
184
+ pResponse.send({Action: 'read', ID: pRequest.params.id});
185
+ return fNext();
186
+ });
187
+
188
+ tmpOrator.serviceServer.post('/api/resource',
189
+ (pRequest, pResponse, fNext) =>
190
+ {
191
+ pResponse.send({Action: 'create'});
192
+ return fNext();
193
+ });
194
+
195
+ tmpOrator.serviceServer.put('/api/resource/:id',
196
+ (pRequest, pResponse, fNext) =>
197
+ {
198
+ pResponse.send({Action: 'update', ID: pRequest.params.id});
199
+ return fNext();
200
+ });
201
+
202
+ tmpOrator.serviceServer.del('/api/resource/:id',
203
+ (pRequest, pResponse, fNext) =>
204
+ {
205
+ pResponse.send({Action: 'delete', ID: pRequest.params.id});
206
+ return fNext();
207
+ });
208
+
209
+ tmpFable.Utility.waterfall([
210
+ (fStageComplete) =>
211
+ {
212
+ tmpOrator.invoke('GET', '/api/resource/10', null,
213
+ (pError, pResponseData) =>
214
+ {
215
+ let tmpResponseObject = JSON.parse(pResponseData);
216
+ Expect(tmpResponseObject.Action).to.equal('read');
217
+ Expect(tmpResponseObject.ID).to.equal('10');
218
+ return fStageComplete();
219
+ });
220
+ },
221
+ (fStageComplete) =>
222
+ {
223
+ tmpOrator.invoke('POST', '/api/resource', {Name: 'New'},
224
+ (pError, pResponseData) =>
225
+ {
226
+ let tmpResponseObject = JSON.parse(pResponseData);
227
+ Expect(tmpResponseObject.Action).to.equal('create');
228
+ return fStageComplete();
229
+ });
230
+ },
231
+ (fStageComplete) =>
232
+ {
233
+ tmpOrator.invoke('PUT', '/api/resource/10', {Name: 'Updated'},
234
+ (pError, pResponseData) =>
235
+ {
236
+ let tmpResponseObject = JSON.parse(pResponseData);
237
+ Expect(tmpResponseObject.Action).to.equal('update');
238
+ Expect(tmpResponseObject.ID).to.equal('10');
239
+ return fStageComplete();
240
+ });
241
+ },
242
+ (fStageComplete) =>
243
+ {
244
+ tmpOrator.invoke('DELETE', '/api/resource/10', null,
245
+ (pError, pResponseData) =>
246
+ {
247
+ let tmpResponseObject = JSON.parse(pResponseData);
248
+ Expect(tmpResponseObject.Action).to.equal('delete');
249
+ Expect(tmpResponseObject.ID).to.equal('10');
250
+ return fStageComplete();
251
+ });
252
+ }
253
+ ],
254
+ (pError) =>
255
+ {
256
+ tmpOrator.log.info('All CRUD operations completed successfully via IPC');
257
+ return fDone();
258
+ });
259
+ }
260
+ );
261
+ }
262
+ );
263
+
264
+ suite
265
+ (
266
+ 'Complex Route Patterns',
267
+ () =>
268
+ {
269
+ test
270
+ (
271
+ 'ipc should be able to handle multiple URL parameters',
272
+ (fDone) =>
273
+ {
274
+ let tmpFable = new libFable(defaultFableSettings);
275
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
276
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
277
+ tmpOrator.startService();
278
+
279
+ tmpOrator.serviceServer.get
280
+ (
281
+ '/api/:entity/:id/child/:childId',
282
+ (pRequest, pResponse, fNext) =>
283
+ {
284
+ pResponse.send(
285
+ {
286
+ Entity: pRequest.params.entity,
287
+ ID: pRequest.params.id,
288
+ ChildID: pRequest.params.childId
289
+ });
290
+ return fNext();
291
+ }
292
+ );
293
+
294
+ tmpOrator.invoke('GET', '/api/Author/5/child/12', null,
295
+ (pError, pResponseData) =>
296
+ {
297
+ let tmpResponseObject = JSON.parse(pResponseData);
298
+ Expect(tmpResponseObject.Entity).to.equal('Author');
299
+ Expect(tmpResponseObject.ID).to.equal('5');
300
+ Expect(tmpResponseObject.ChildID).to.equal('12');
301
+ tmpOrator.log.info(`Nested route responded with entity [${tmpResponseObject.Entity}] id [${tmpResponseObject.ID}] child [${tmpResponseObject.ChildID}]`);
302
+ return fDone();
303
+ });
304
+ }
305
+ );
306
+
307
+ test
308
+ (
309
+ 'ipc should be able to handle routes that return string responses',
310
+ (fDone) =>
311
+ {
312
+ let tmpFable = new libFable(defaultFableSettings);
313
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
314
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
315
+ tmpOrator.startService();
316
+
317
+ tmpOrator.serviceServer.get
318
+ (
319
+ '/api/version',
320
+ (pRequest, pResponse, fNext) =>
321
+ {
322
+ pResponse.send('1.0.0');
323
+ return fNext();
324
+ }
325
+ );
326
+
327
+ tmpOrator.invoke('GET', '/api/version', null,
328
+ (pError, pResponseData) =>
329
+ {
330
+ Expect(pResponseData).to.equal('1.0.0');
331
+ tmpOrator.log.info(`String endpoint responded with [${pResponseData}]`);
332
+ return fDone();
333
+ });
334
+ }
335
+ );
336
+
337
+ test
338
+ (
339
+ 'ipc should handle chained route handlers sequentially',
340
+ (fDone) =>
341
+ {
342
+ let tmpFable = new libFable(defaultFableSettings);
343
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
344
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
345
+ tmpOrator.startService();
346
+
347
+ // Register a route with multiple handler functions chained together
348
+ tmpOrator.serviceServer.get
349
+ (
350
+ '/api/chained/:value',
351
+ // First handler: decorate the request
352
+ (pRequest, pResponse, fNext) =>
353
+ {
354
+ pRequest.Decorated = true;
355
+ pRequest.ProcessingSteps = ['step1'];
356
+ return fNext();
357
+ },
358
+ // Second handler: add more decoration
359
+ (pRequest, pResponse, fNext) =>
360
+ {
361
+ pRequest.ProcessingSteps.push('step2');
362
+ return fNext();
363
+ },
364
+ // Third handler: send the response
365
+ (pRequest, pResponse, fNext) =>
366
+ {
367
+ pResponse.send(
368
+ {
369
+ Value: pRequest.params.value,
370
+ Decorated: pRequest.Decorated,
371
+ Steps: pRequest.ProcessingSteps
372
+ });
373
+ return fNext();
374
+ }
375
+ );
376
+
377
+ tmpOrator.invoke('GET', '/api/chained/TestValue', null,
378
+ (pError, pResponseData) =>
379
+ {
380
+ let tmpResponseObject = JSON.parse(pResponseData);
381
+ Expect(tmpResponseObject.Value).to.equal('TestValue');
382
+ Expect(tmpResponseObject.Decorated).to.equal(true);
383
+ Expect(tmpResponseObject.Steps).to.be.an('array');
384
+ Expect(tmpResponseObject.Steps).to.have.lengthOf(2);
385
+ Expect(tmpResponseObject.Steps[0]).to.equal('step1');
386
+ Expect(tmpResponseObject.Steps[1]).to.equal('step2');
387
+ tmpOrator.log.info(`Chained handler responded after ${tmpResponseObject.Steps.length} processing steps`);
388
+ return fDone();
389
+ });
390
+ }
391
+ );
392
+
393
+ test
394
+ (
395
+ 'ipc should handle post-behavior functions after route handlers',
396
+ (fDone) =>
397
+ {
398
+ let tmpFable = new libFable(defaultFableSettings);
399
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
400
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
401
+ tmpOrator.startService();
402
+
403
+ let tmpPostBehaviorExecuted = false;
404
+
405
+ // Add a post-behavior function
406
+ tmpOrator.serviceServer.addPostBehaviorFunction(
407
+ (pRequest, pResponse, fNext) =>
408
+ {
409
+ tmpPostBehaviorExecuted = true;
410
+ return fNext();
411
+ });
412
+
413
+ tmpOrator.serviceServer.get
414
+ (
415
+ '/api/postbehavior',
416
+ (pRequest, pResponse, fNext) =>
417
+ {
418
+ pResponse.send({Message: 'Handled'});
419
+ return fNext();
420
+ }
421
+ );
422
+
423
+ tmpOrator.invoke('GET', '/api/postbehavior', null,
424
+ (pError, pResponseData) =>
425
+ {
426
+ let tmpResponseObject = JSON.parse(pResponseData);
427
+ Expect(tmpResponseObject.Message).to.equal('Handled');
428
+ Expect(tmpPostBehaviorExecuted).to.equal(true, 'Post-behavior function should have executed');
429
+ tmpOrator.log.info(`Post-behavior test completed, postBehaviorExecuted: ${tmpPostBehaviorExecuted}`);
430
+ return fDone();
431
+ });
432
+ }
433
+ );
434
+ }
435
+ );
436
+
437
+ suite
438
+ (
439
+ 'Service Lifecycle',
440
+ () =>
441
+ {
442
+ test
443
+ (
444
+ 'orator should be able to stop and report inactive status',
445
+ (fDone) =>
446
+ {
447
+ let tmpFable = new libFable(defaultFableSettings);
448
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
449
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
450
+ tmpOrator.startService(
451
+ () =>
452
+ {
453
+ Expect(tmpOrator.serviceServer.Active).to.equal(true);
454
+ tmpOrator.stopService(
455
+ () =>
456
+ {
457
+ Expect(tmpOrator.serviceServer.Active).to.equal(false);
458
+ tmpOrator.log.info('Service started and stopped successfully');
459
+ return fDone();
460
+ });
461
+ });
462
+ }
463
+ );
464
+
465
+ test
466
+ (
467
+ 'orator should warn when stopping a service that has not been initialized',
468
+ (fDone) =>
469
+ {
470
+ let tmpFable = new libFable(defaultFableSettings);
471
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
472
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
473
+ // Try to stop without starting
474
+ tmpOrator.stopService(
475
+ (pError) =>
476
+ {
477
+ Expect(pError).to.be.a('string');
478
+ tmpOrator.log.info(`Stop-before-init warning: ${pError}`);
479
+ return fDone();
480
+ });
481
+ }
482
+ );
483
+
484
+ test
485
+ (
486
+ 'orator should use ServicePort from options and fall back to defaults',
487
+ (fDone) =>
488
+ {
489
+ // ServicePort passed directly in options should be used
490
+ let tmpFable = new libFable(
491
+ {
492
+ Product:'Orator-PortTest',
493
+ ProductVersion: '0.0.0'
494
+ });
495
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
496
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {ServicePort: 9999});
497
+ Expect(tmpOrator.options.ServicePort).to.equal(9999);
498
+
499
+ // Test the legacy APIServerPort migration from fable settings
500
+ let tmpFableLegacy = new libFable(
501
+ {
502
+ Product:'Orator-LegacyPortTest',
503
+ ProductVersion: '0.0.0',
504
+ APIServerPort: 7777
505
+ });
506
+ tmpFableLegacy.serviceManager.addServiceType('Orator', libOrator);
507
+ let tmpOratorLegacy = tmpFableLegacy.serviceManager.instantiateServiceProvider('Orator', {});
508
+ Expect(tmpOratorLegacy.options.ServicePort).to.equal(7777);
509
+
510
+ // Test the default port when nothing is configured
511
+ let tmpFableDefault = new libFable(
512
+ {
513
+ Product:'Orator-DefaultPortTest',
514
+ ProductVersion: '0.0.0'
515
+ });
516
+ tmpFableDefault.serviceManager.addServiceType('Orator', libOrator);
517
+ let tmpOratorDefault = tmpFableDefault.serviceManager.instantiateServiceProvider('Orator', {});
518
+ Expect(tmpOratorDefault.options.ServicePort).to.equal(8080);
519
+
520
+ tmpOrator.log.info('Port configuration tests passed');
521
+ return fDone();
522
+ }
523
+ );
524
+
525
+ test
526
+ (
527
+ 'orator should provide a legacy webServer property',
528
+ (fDone) =>
529
+ {
530
+ let tmpFable = new libFable(defaultFableSettings);
531
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
532
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
533
+ tmpOrator.initialize(
534
+ () =>
535
+ {
536
+ Expect(tmpOrator.webServer).to.be.an('object');
537
+ Expect(tmpOrator.webServer).to.equal(tmpOrator.serviceServer);
538
+ Expect(tmpOrator.webServer.ServiceServerType).to.equal('IPC');
539
+ tmpOrator.log.info('Legacy webServer alias verified');
540
+ return fDone();
541
+ });
542
+ }
543
+ );
544
+
545
+ test
546
+ (
547
+ 'orator legacy startWebServer and stopWebServer should work',
548
+ (fDone) =>
549
+ {
550
+ let tmpFable = new libFable(defaultFableSettings);
551
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
552
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
553
+ tmpOrator.startWebServer(
554
+ () =>
555
+ {
556
+ Expect(tmpOrator.serviceServer.Active).to.equal(true);
557
+ tmpOrator.stopWebServer(
558
+ () =>
559
+ {
560
+ Expect(tmpOrator.serviceServer.Active).to.equal(false);
561
+ tmpOrator.log.info('Legacy start/stop methods verified');
562
+ return fDone();
563
+ });
564
+ });
565
+ }
566
+ );
567
+ }
568
+ );
569
+
570
+ suite
571
+ (
572
+ 'Static File Serving',
573
+ () =>
574
+ {
575
+ test
576
+ (
577
+ 'addStaticRoute should return false when no file path is provided',
578
+ (fDone) =>
579
+ {
580
+ let tmpFable = new libFable(defaultFableSettings);
581
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
582
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
583
+ tmpOrator.initialize(
584
+ () =>
585
+ {
586
+ let tmpResult = tmpOrator.addStaticRoute();
587
+ Expect(tmpResult).to.equal(false);
588
+ tmpOrator.log.info('addStaticRoute correctly rejected missing file path');
589
+ return fDone();
590
+ });
591
+ }
592
+ );
593
+
594
+ test
595
+ (
596
+ 'addStaticRoute should return true with a valid file path',
597
+ (fDone) =>
598
+ {
599
+ let tmpFable = new libFable(defaultFableSettings);
600
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
601
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
602
+ tmpOrator.initialize(
603
+ () =>
604
+ {
605
+ let tmpStaticPath = libPath.normalize(__dirname + '/static_content/');
606
+ let tmpResult = tmpOrator.addStaticRoute(tmpStaticPath);
607
+ Expect(tmpResult).to.equal(true);
608
+ tmpOrator.log.info(`addStaticRoute mapped [${tmpStaticPath}] successfully`);
609
+ return fDone();
610
+ });
611
+ }
612
+ );
613
+
614
+ test
615
+ (
616
+ 'addStaticRoute should accept all optional parameters',
617
+ (fDone) =>
618
+ {
619
+ let tmpFable = new libFable(defaultFableSettings);
620
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
621
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
622
+ tmpOrator.initialize(
623
+ () =>
624
+ {
625
+ let tmpStaticPath = libPath.normalize(__dirname + '/static_content/');
626
+ let tmpResult = tmpOrator.addStaticRoute(tmpStaticPath, 'about.html', '/content/*', '/content/', {maxAge: '1d'});
627
+ Expect(tmpResult).to.equal(true);
628
+ tmpOrator.log.info('addStaticRoute accepted all optional parameters');
629
+ return fDone();
630
+ });
631
+ }
632
+ );
633
+
634
+ test
635
+ (
636
+ 'addStaticRoute should reject non-string file paths',
637
+ (fDone) =>
638
+ {
639
+ let tmpFable = new libFable(defaultFableSettings);
640
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
641
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
642
+ tmpOrator.initialize(
643
+ () =>
644
+ {
645
+ Expect(tmpOrator.addStaticRoute(42)).to.equal(false);
646
+ Expect(tmpOrator.addStaticRoute(null)).to.equal(false);
647
+ Expect(tmpOrator.addStaticRoute({})).to.equal(false);
648
+ Expect(tmpOrator.addStaticRoute(true)).to.equal(false);
649
+ tmpOrator.log.info('addStaticRoute rejected all non-string file paths');
650
+ return fDone();
651
+ });
652
+ }
653
+ );
654
+ }
655
+ );
656
+
657
+ suite
658
+ (
659
+ 'Route Validation',
660
+ () =>
661
+ {
662
+ test
663
+ (
664
+ 'service server should reject non-string route parameters',
665
+ (fDone) =>
666
+ {
667
+ let tmpFable = new libFable(defaultFableSettings);
668
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
669
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
670
+ tmpOrator.initialize(
671
+ () =>
672
+ {
673
+ Expect(tmpOrator.serviceServer.get(42)).to.equal(false);
674
+ Expect(tmpOrator.serviceServer.post(null)).to.equal(false);
675
+ Expect(tmpOrator.serviceServer.put(undefined)).to.equal(false);
676
+ Expect(tmpOrator.serviceServer.del({})).to.equal(false);
677
+ Expect(tmpOrator.serviceServer.patch([])).to.equal(false);
678
+ Expect(tmpOrator.serviceServer.opts(true)).to.equal(false);
679
+ tmpOrator.log.info('All verb methods correctly rejected non-string routes');
680
+ return fDone();
681
+ });
682
+ }
683
+ );
684
+
685
+ test
686
+ (
687
+ 'service server use should reject non-function handlers',
688
+ (fDone) =>
689
+ {
690
+ let tmpFable = new libFable(defaultFableSettings);
691
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
692
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
693
+ tmpOrator.initialize(
694
+ () =>
695
+ {
696
+ Expect(tmpOrator.serviceServer.use('not a function')).to.equal(false);
697
+ Expect(tmpOrator.serviceServer.use(42)).to.equal(false);
698
+ Expect(tmpOrator.serviceServer.use(null)).to.equal(false);
699
+ tmpOrator.log.info('use() correctly rejected non-function handlers');
700
+ return fDone();
701
+ });
702
+ }
703
+ );
704
+ }
705
+ );
706
+
707
+ suite
708
+ (
709
+ 'Middleware and Complex Behavior Pipeline',
710
+ () =>
711
+ {
712
+ test
713
+ (
714
+ 'multiple pre-behavior functions should execute in order',
715
+ (fDone) =>
716
+ {
717
+ let tmpFable = new libFable(defaultFableSettings);
718
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
719
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
720
+ tmpOrator.startService();
721
+
722
+ // Add three pre-behavior functions that build up an array
723
+ tmpOrator.serviceServer.use(
724
+ (pRequest, pResponse, fNext) =>
725
+ {
726
+ pRequest.Pipeline = ['first'];
727
+ return fNext();
728
+ });
729
+
730
+ tmpOrator.serviceServer.use(
731
+ (pRequest, pResponse, fNext) =>
732
+ {
733
+ pRequest.Pipeline.push('second');
734
+ return fNext();
735
+ });
736
+
737
+ tmpOrator.serviceServer.use(
738
+ (pRequest, pResponse, fNext) =>
739
+ {
740
+ pRequest.Pipeline.push('third');
741
+ return fNext();
742
+ });
743
+
744
+ tmpOrator.serviceServer.get
745
+ (
746
+ '/api/pipeline',
747
+ (pRequest, pResponse, fNext) =>
748
+ {
749
+ pResponse.send({Pipeline: pRequest.Pipeline});
750
+ return fNext();
751
+ }
752
+ );
753
+
754
+ tmpOrator.invoke('GET', '/api/pipeline', null,
755
+ (pError, pResponseData) =>
756
+ {
757
+ let tmpResponseObject = JSON.parse(pResponseData);
758
+ Expect(tmpResponseObject.Pipeline).to.be.an('array');
759
+ Expect(tmpResponseObject.Pipeline).to.have.lengthOf(3);
760
+ Expect(tmpResponseObject.Pipeline[0]).to.equal('first');
761
+ Expect(tmpResponseObject.Pipeline[1]).to.equal('second');
762
+ Expect(tmpResponseObject.Pipeline[2]).to.equal('third');
763
+ tmpOrator.log.info(`Pipeline executed in order: ${tmpResponseObject.Pipeline.join(' -> ')}`);
764
+ return fDone();
765
+ });
766
+ }
767
+ );
768
+
769
+ test
770
+ (
771
+ 'pre-behavior and post-behavior functions should bracket the route handler',
772
+ (fDone) =>
773
+ {
774
+ let tmpFable = new libFable(defaultFableSettings);
775
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
776
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
777
+ tmpOrator.startService();
778
+
779
+ let tmpExecutionOrder = [];
780
+
781
+ tmpOrator.serviceServer.use(
782
+ (pRequest, pResponse, fNext) =>
783
+ {
784
+ tmpExecutionOrder.push('pre');
785
+ return fNext();
786
+ });
787
+
788
+ tmpOrator.serviceServer.addPostBehaviorFunction(
789
+ (pRequest, pResponse, fNext) =>
790
+ {
791
+ tmpExecutionOrder.push('post');
792
+ return fNext();
793
+ });
794
+
795
+ tmpOrator.serviceServer.get
796
+ (
797
+ '/api/bracket',
798
+ (pRequest, pResponse, fNext) =>
799
+ {
800
+ tmpExecutionOrder.push('handler');
801
+ pResponse.send({Order: 'captured'});
802
+ return fNext();
803
+ }
804
+ );
805
+
806
+ tmpOrator.invoke('GET', '/api/bracket', null,
807
+ (pError, pResponseData) =>
808
+ {
809
+ Expect(tmpExecutionOrder).to.have.lengthOf(3);
810
+ Expect(tmpExecutionOrder[0]).to.equal('pre');
811
+ Expect(tmpExecutionOrder[1]).to.equal('handler');
812
+ Expect(tmpExecutionOrder[2]).to.equal('post');
813
+ tmpOrator.log.info(`Execution order: ${tmpExecutionOrder.join(' -> ')}`);
814
+ return fDone();
815
+ });
816
+ }
817
+ );
818
+
819
+ test
820
+ (
821
+ 'middleware should share request state across separate invoke calls',
822
+ (fDone) =>
823
+ {
824
+ let tmpFable = new libFable(defaultFableSettings);
825
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
826
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
827
+ tmpOrator.startService();
828
+
829
+ let tmpRequestCount = 0;
830
+
831
+ // Middleware that counts requests
832
+ tmpOrator.serviceServer.use(
833
+ (pRequest, pResponse, fNext) =>
834
+ {
835
+ tmpRequestCount++;
836
+ pRequest.RequestNumber = tmpRequestCount;
837
+ return fNext();
838
+ });
839
+
840
+ tmpOrator.serviceServer.get
841
+ (
842
+ '/api/counted',
843
+ (pRequest, pResponse, fNext) =>
844
+ {
845
+ pResponse.send({RequestNumber: pRequest.RequestNumber});
846
+ return fNext();
847
+ }
848
+ );
849
+
850
+ tmpFable.Utility.waterfall([
851
+ (fStageComplete) =>
852
+ {
853
+ tmpOrator.invoke('GET', '/api/counted', null,
854
+ (pError, pResponseData) =>
855
+ {
856
+ let tmpResponseObject = JSON.parse(pResponseData);
857
+ Expect(tmpResponseObject.RequestNumber).to.equal(1);
858
+ return fStageComplete();
859
+ });
860
+ },
861
+ (fStageComplete) =>
862
+ {
863
+ tmpOrator.invoke('GET', '/api/counted', null,
864
+ (pError, pResponseData) =>
865
+ {
866
+ let tmpResponseObject = JSON.parse(pResponseData);
867
+ Expect(tmpResponseObject.RequestNumber).to.equal(2);
868
+ return fStageComplete();
869
+ });
870
+ },
871
+ (fStageComplete) =>
872
+ {
873
+ tmpOrator.invoke('GET', '/api/counted', null,
874
+ (pError, pResponseData) =>
875
+ {
876
+ let tmpResponseObject = JSON.parse(pResponseData);
877
+ Expect(tmpResponseObject.RequestNumber).to.equal(3);
878
+ return fStageComplete();
879
+ });
880
+ }
881
+ ],
882
+ (pError) =>
883
+ {
884
+ Expect(tmpRequestCount).to.equal(3);
885
+ tmpOrator.log.info(`Request counter middleware tracked ${tmpRequestCount} invocations`);
886
+ return fDone();
887
+ });
888
+ }
889
+ );
890
+ }
891
+ );
892
+
893
+ suite
894
+ (
895
+ 'IPC Server Properties',
896
+ () =>
897
+ {
898
+ test
899
+ (
900
+ 'ipc service server should have correct properties',
901
+ (fDone) =>
902
+ {
903
+ let tmpFable = new libFable(defaultFableSettings);
904
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
905
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
906
+ tmpOrator.initialize(
907
+ () =>
908
+ {
909
+ Expect(tmpOrator.serviceServer.ServiceServerType).to.equal('IPC');
910
+ Expect(tmpOrator.serviceServer.URL).to.equal('IPC');
911
+ Expect(tmpOrator.serviceServer.Port).to.equal(0);
912
+ Expect(tmpOrator.serviceServer.Active).to.equal(false);
913
+ Expect(tmpOrator.serviceServer.Name).to.equal('Orator-ComplexRouteTests');
914
+ tmpOrator.log.info('IPC service server properties verified');
915
+ return fDone();
916
+ });
917
+ }
918
+ );
919
+
920
+ test
921
+ (
922
+ 'ipc bodyParser should return a passthrough function',
923
+ (fDone) =>
924
+ {
925
+ let tmpFable = new libFable(defaultFableSettings);
926
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
927
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
928
+ tmpOrator.initialize(
929
+ () =>
930
+ {
931
+ let tmpBodyParser = tmpOrator.serviceServer.bodyParser();
932
+ Expect(tmpBodyParser).to.be.a('function');
933
+ // The IPC body parser should be a no-op passthrough
934
+ let tmpNextCalled = false;
935
+ tmpBodyParser({}, {},
936
+ () =>
937
+ {
938
+ tmpNextCalled = true;
939
+ });
940
+ Expect(tmpNextCalled).to.equal(true);
941
+ tmpOrator.log.info('IPC bodyParser is a passthrough function');
942
+ return fDone();
943
+ });
944
+ }
945
+ );
946
+ }
947
+ );
948
+ }
949
+ );