orator 5.0.0 → 5.0.2

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,1322 @@
1
+ /**
2
+ * Unit tests for Orator 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
+ const libHTTP = require('http');
18
+
19
+ const defaultFableSettings = (
20
+ {
21
+ Product:'Orator-StaticServingTests',
22
+ ProductVersion: '0.0.0',
23
+ APIServerPort: 0
24
+ });
25
+
26
+ const _StaticContentPath = libPath.normalize(__dirname + '/static_content/');
27
+
28
+ // Port counter for test HTTP servers to avoid collisions
29
+ let _NextTestPort = 19100;
30
+ function getNextTestPort()
31
+ {
32
+ return _NextTestPort++;
33
+ }
34
+
35
+ /**
36
+ * Create a lightweight HTTP server that routes GET requests through Orator's IPC router.
37
+ * This allows testing static file serving (which requires a real HTTP response stream)
38
+ * without needing the orator-serviceserver-restify dependency.
39
+ *
40
+ * @param {Object} pOrator - The Orator instance with routes already registered.
41
+ * @param {number} pPort - The port to listen on.
42
+ * @param {Function} fCallback - Called with (server, port) when the server is ready.
43
+ */
44
+ function createTestHTTPServer(pOrator, pPort, fCallback)
45
+ {
46
+ let tmpServer = libHTTP.createServer(
47
+ (pRequest, pResponse) =>
48
+ {
49
+ let tmpHandler = pOrator.serviceServer.router.find(pRequest.method, pRequest.url);
50
+ if (tmpHandler)
51
+ {
52
+ pRequest.params = tmpHandler.params || {};
53
+ pRequest.searchParams = tmpHandler.searchParams || {};
54
+ tmpHandler.handler(pRequest, pResponse, null);
55
+ }
56
+ else
57
+ {
58
+ pResponse.writeHead(404);
59
+ pResponse.end('Not found');
60
+ }
61
+ });
62
+
63
+ tmpServer.listen(pPort,
64
+ () =>
65
+ {
66
+ return fCallback(tmpServer, tmpServer.address().port);
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Make an HTTP GET request and collect the response.
72
+ *
73
+ * @param {number} pPort - Port to connect to.
74
+ * @param {string} pPath - URL path to request.
75
+ * @param {Function} fCallback - Called with (error, statusCode, headers, body).
76
+ * @param {Object} [pHeaders] - Optional extra headers for the request.
77
+ */
78
+ function makeRequest(pPort, pPath, fCallback, pHeaders)
79
+ {
80
+ let tmpOptions = (
81
+ {
82
+ hostname: 'localhost',
83
+ port: pPort,
84
+ path: pPath,
85
+ method: 'GET',
86
+ headers: Object.assign({}, pHeaders || {})
87
+ });
88
+
89
+ let tmpRequest = libHTTP.request(tmpOptions,
90
+ (pResponse) =>
91
+ {
92
+ let tmpData = '';
93
+ pResponse.on('data',
94
+ (pChunk) =>
95
+ {
96
+ tmpData += pChunk;
97
+ });
98
+ pResponse.on('end',
99
+ () =>
100
+ {
101
+ return fCallback(null, pResponse.statusCode, pResponse.headers, tmpData);
102
+ });
103
+ });
104
+
105
+ tmpRequest.on('error',
106
+ (pError) =>
107
+ {
108
+ return fCallback(pError);
109
+ });
110
+
111
+ tmpRequest.end();
112
+ }
113
+
114
+ suite
115
+ (
116
+ 'Orator',
117
+ () =>
118
+ {
119
+ suite
120
+ (
121
+ 'Static File Serving - Parameter Validation',
122
+ () =>
123
+ {
124
+ test
125
+ (
126
+ 'addStaticRoute should return false when no file path is provided',
127
+ (fDone) =>
128
+ {
129
+ let tmpFable = new libFable(defaultFableSettings);
130
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
131
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
132
+ tmpOrator.initialize(
133
+ () =>
134
+ {
135
+ let tmpResult = tmpOrator.addStaticRoute();
136
+ Expect(tmpResult).to.equal(false);
137
+ tmpOrator.log.info('addStaticRoute correctly rejected missing file path');
138
+ return fDone();
139
+ });
140
+ }
141
+ );
142
+
143
+ test
144
+ (
145
+ 'addStaticRoute should reject non-string file paths',
146
+ (fDone) =>
147
+ {
148
+ let tmpFable = new libFable(defaultFableSettings);
149
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
150
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
151
+ tmpOrator.initialize(
152
+ () =>
153
+ {
154
+ Expect(tmpOrator.addStaticRoute(42)).to.equal(false);
155
+ Expect(tmpOrator.addStaticRoute(null)).to.equal(false);
156
+ Expect(tmpOrator.addStaticRoute({})).to.equal(false);
157
+ Expect(tmpOrator.addStaticRoute(true)).to.equal(false);
158
+ Expect(tmpOrator.addStaticRoute(undefined)).to.equal(false);
159
+ Expect(tmpOrator.addStaticRoute([])).to.equal(false);
160
+ tmpOrator.log.info('addStaticRoute rejected all non-string file paths');
161
+ return fDone();
162
+ });
163
+ }
164
+ );
165
+
166
+ test
167
+ (
168
+ 'addStaticRoute should return true with a valid file path',
169
+ (fDone) =>
170
+ {
171
+ let tmpFable = new libFable(defaultFableSettings);
172
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
173
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
174
+ tmpOrator.initialize(
175
+ () =>
176
+ {
177
+ let tmpResult = tmpOrator.addStaticRoute(_StaticContentPath);
178
+ Expect(tmpResult).to.equal(true);
179
+ tmpOrator.log.info(`addStaticRoute mapped [${_StaticContentPath}] successfully`);
180
+ return fDone();
181
+ });
182
+ }
183
+ );
184
+
185
+ test
186
+ (
187
+ 'addStaticRoute should accept all optional parameters',
188
+ (fDone) =>
189
+ {
190
+ let tmpFable = new libFable(defaultFableSettings);
191
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
192
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
193
+ tmpOrator.initialize(
194
+ () =>
195
+ {
196
+ let tmpResult = tmpOrator.addStaticRoute(_StaticContentPath, 'about.html', '/content/*', '/content/', {maxAge: '1d'});
197
+ Expect(tmpResult).to.equal(true);
198
+ tmpOrator.log.info('addStaticRoute accepted all optional parameters');
199
+ return fDone();
200
+ });
201
+ }
202
+ );
203
+
204
+ test
205
+ (
206
+ 'addStaticRoute should use default route /* when no route is specified',
207
+ (fDone) =>
208
+ {
209
+ let tmpFable = new libFable(defaultFableSettings);
210
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
211
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
212
+ tmpOrator.startService();
213
+
214
+ let tmpResult = tmpOrator.addStaticRoute(_StaticContentPath);
215
+ Expect(tmpResult).to.equal(true);
216
+
217
+ // Verify the wildcard route is registered by checking the router
218
+ let tmpHandler = tmpOrator.serviceServer.router.find('GET', '/anything');
219
+ Expect(tmpHandler).to.not.equal(null);
220
+ Expect(tmpHandler.handler).to.be.a('function');
221
+ tmpOrator.log.info('Default wildcard route /* was registered');
222
+ return fDone();
223
+ }
224
+ );
225
+
226
+ test
227
+ (
228
+ 'addStaticRoute should register a GET route on the service server',
229
+ (fDone) =>
230
+ {
231
+ let tmpFable = new libFable(defaultFableSettings);
232
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
233
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
234
+ tmpOrator.startService();
235
+
236
+ let tmpResult = tmpOrator.addStaticRoute(_StaticContentPath, 'index.html', '/static/*', '/static/');
237
+ Expect(tmpResult).to.equal(true);
238
+
239
+ // The route should match paths under /static/
240
+ let tmpHandler = tmpOrator.serviceServer.router.find('GET', '/static/test.html');
241
+ Expect(tmpHandler).to.not.equal(null);
242
+ Expect(tmpHandler.handler).to.be.a('function');
243
+
244
+ tmpOrator.log.info('Custom route /static/* was registered');
245
+ return fDone();
246
+ }
247
+ );
248
+ }
249
+ );
250
+
251
+ suite
252
+ (
253
+ 'Static File Serving - MIME Type Detection',
254
+ () =>
255
+ {
256
+ test
257
+ (
258
+ 'setMimeHeader should set Content-Type for HTML files',
259
+ (fDone) =>
260
+ {
261
+ let tmpFable = new libFable(defaultFableSettings);
262
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
263
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
264
+ tmpOrator.initialize(
265
+ () =>
266
+ {
267
+ let tmpCapturedHeaders = {};
268
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
269
+
270
+ tmpOrator.setMimeHeader('index.html', tmpMockResponse);
271
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('text/html');
272
+
273
+ tmpOrator.setMimeHeader('/path/to/page.html', tmpMockResponse);
274
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('text/html');
275
+
276
+ tmpOrator.log.info('HTML MIME type correctly detected');
277
+ return fDone();
278
+ });
279
+ }
280
+ );
281
+
282
+ test
283
+ (
284
+ 'setMimeHeader should set Content-Type for CSS files',
285
+ (fDone) =>
286
+ {
287
+ let tmpFable = new libFable(defaultFableSettings);
288
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
289
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
290
+ tmpOrator.initialize(
291
+ () =>
292
+ {
293
+ let tmpCapturedHeaders = {};
294
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
295
+
296
+ tmpOrator.setMimeHeader('style.css', tmpMockResponse);
297
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('text/css');
298
+
299
+ tmpOrator.log.info('CSS MIME type correctly detected');
300
+ return fDone();
301
+ });
302
+ }
303
+ );
304
+
305
+ test
306
+ (
307
+ 'setMimeHeader should set Content-Type for JSON files',
308
+ (fDone) =>
309
+ {
310
+ let tmpFable = new libFable(defaultFableSettings);
311
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
312
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
313
+ tmpOrator.initialize(
314
+ () =>
315
+ {
316
+ let tmpCapturedHeaders = {};
317
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
318
+
319
+ tmpOrator.setMimeHeader('data.json', tmpMockResponse);
320
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('application/json');
321
+
322
+ tmpOrator.log.info('JSON MIME type correctly detected');
323
+ return fDone();
324
+ });
325
+ }
326
+ );
327
+
328
+ test
329
+ (
330
+ 'setMimeHeader should set Content-Type for JavaScript files',
331
+ (fDone) =>
332
+ {
333
+ let tmpFable = new libFable(defaultFableSettings);
334
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
335
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
336
+ tmpOrator.initialize(
337
+ () =>
338
+ {
339
+ let tmpCapturedHeaders = {};
340
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
341
+
342
+ tmpOrator.setMimeHeader('app.js', tmpMockResponse);
343
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('application/javascript');
344
+
345
+ tmpOrator.log.info('JavaScript MIME type correctly detected');
346
+ return fDone();
347
+ });
348
+ }
349
+ );
350
+
351
+ test
352
+ (
353
+ 'setMimeHeader should set Content-Type for image files',
354
+ (fDone) =>
355
+ {
356
+ let tmpFable = new libFable(defaultFableSettings);
357
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
358
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
359
+ tmpOrator.initialize(
360
+ () =>
361
+ {
362
+ let tmpCapturedHeaders = {};
363
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
364
+
365
+ tmpOrator.setMimeHeader('photo.png', tmpMockResponse);
366
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('image/png');
367
+
368
+ tmpOrator.setMimeHeader('logo.jpg', tmpMockResponse);
369
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('image/jpeg');
370
+
371
+ tmpOrator.setMimeHeader('icon.gif', tmpMockResponse);
372
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('image/gif');
373
+
374
+ tmpOrator.setMimeHeader('vector.svg', tmpMockResponse);
375
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('image/svg+xml');
376
+
377
+ tmpOrator.log.info('Image MIME types correctly detected');
378
+ return fDone();
379
+ });
380
+ }
381
+ );
382
+
383
+ test
384
+ (
385
+ 'setMimeHeader should set Content-Type for common web font and document types',
386
+ (fDone) =>
387
+ {
388
+ let tmpFable = new libFable(defaultFableSettings);
389
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
390
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
391
+ tmpOrator.initialize(
392
+ () =>
393
+ {
394
+ let tmpCapturedHeaders = {};
395
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
396
+
397
+ tmpOrator.setMimeHeader('document.pdf', tmpMockResponse);
398
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('application/pdf');
399
+
400
+ tmpOrator.setMimeHeader('archive.zip', tmpMockResponse);
401
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('application/zip');
402
+
403
+ tmpOrator.setMimeHeader('data.xml', tmpMockResponse);
404
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('application/xml');
405
+
406
+ tmpOrator.setMimeHeader('readme.txt', tmpMockResponse);
407
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('text/plain');
408
+
409
+ tmpOrator.log.info('Document and archive MIME types correctly detected');
410
+ return fDone();
411
+ });
412
+ }
413
+ );
414
+
415
+ test
416
+ (
417
+ 'setMimeHeader should fall back to application/octet-stream for unknown extensions',
418
+ (fDone) =>
419
+ {
420
+ let tmpFable = new libFable(defaultFableSettings);
421
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
422
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
423
+ tmpOrator.initialize(
424
+ () =>
425
+ {
426
+ let tmpCapturedHeaders = {};
427
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
428
+
429
+ tmpOrator.setMimeHeader('mystery.xyz123', tmpMockResponse);
430
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('application/octet-stream');
431
+
432
+ tmpOrator.setMimeHeader('noextension', tmpMockResponse);
433
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('application/octet-stream');
434
+
435
+ tmpOrator.log.info('Unknown MIME types fall back to application/octet-stream');
436
+ return fDone();
437
+ });
438
+ }
439
+ );
440
+
441
+ test
442
+ (
443
+ 'setMimeHeader should handle file paths with directories',
444
+ (fDone) =>
445
+ {
446
+ let tmpFable = new libFable(defaultFableSettings);
447
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
448
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
449
+ tmpOrator.initialize(
450
+ () =>
451
+ {
452
+ let tmpCapturedHeaders = {};
453
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
454
+
455
+ tmpOrator.setMimeHeader('/assets/css/main.css', tmpMockResponse);
456
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('text/css');
457
+
458
+ tmpOrator.setMimeHeader('/deep/nested/path/to/image.png', tmpMockResponse);
459
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('image/png');
460
+
461
+ tmpOrator.log.info('MIME type detected correctly from paths with directories');
462
+ return fDone();
463
+ });
464
+ }
465
+ );
466
+ }
467
+ );
468
+
469
+ suite
470
+ (
471
+ 'Static File Serving - Serving HTML Files',
472
+ () =>
473
+ {
474
+ test
475
+ (
476
+ 'should serve index.html with correct content and Content-Type',
477
+ (fDone) =>
478
+ {
479
+ let tmpFable = new libFable(defaultFableSettings);
480
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
481
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
482
+ tmpOrator.startService();
483
+ tmpOrator.addStaticRoute(_StaticContentPath);
484
+
485
+ let tmpPort = getNextTestPort();
486
+ createTestHTTPServer(tmpOrator, tmpPort,
487
+ (pServer, pActualPort) =>
488
+ {
489
+ makeRequest(pActualPort, '/index.html',
490
+ (pError, pStatusCode, pHeaders, pBody) =>
491
+ {
492
+ Expect(pError).to.equal(null);
493
+ Expect(pStatusCode).to.equal(200);
494
+ Expect(pHeaders['content-type']).to.contain('text/html');
495
+ Expect(pBody).to.contain('Test Index');
496
+ Expect(pBody).to.contain('Welcome to the test server');
497
+ tmpOrator.log.info(`Served index.html: status=${pStatusCode} content-type=${pHeaders['content-type']}`);
498
+ pServer.close();
499
+ return fDone();
500
+ });
501
+ });
502
+ }
503
+ );
504
+
505
+ test
506
+ (
507
+ 'should serve about.html with correct content',
508
+ (fDone) =>
509
+ {
510
+ let tmpFable = new libFable(defaultFableSettings);
511
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
512
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
513
+ tmpOrator.startService();
514
+ tmpOrator.addStaticRoute(_StaticContentPath);
515
+
516
+ let tmpPort = getNextTestPort();
517
+ createTestHTTPServer(tmpOrator, tmpPort,
518
+ (pServer, pActualPort) =>
519
+ {
520
+ makeRequest(pActualPort, '/about.html',
521
+ (pError, pStatusCode, pHeaders, pBody) =>
522
+ {
523
+ Expect(pError).to.equal(null);
524
+ Expect(pStatusCode).to.equal(200);
525
+ Expect(pHeaders['content-type']).to.contain('text/html');
526
+ Expect(pBody).to.contain('About');
527
+ Expect(pBody).to.contain('About page content');
528
+ tmpOrator.log.info(`Served about.html: status=${pStatusCode}`);
529
+ pServer.close();
530
+ return fDone();
531
+ });
532
+ });
533
+ }
534
+ );
535
+
536
+ test
537
+ (
538
+ 'should serve default file (index.html) when requesting the root path',
539
+ (fDone) =>
540
+ {
541
+ let tmpFable = new libFable(defaultFableSettings);
542
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
543
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
544
+ tmpOrator.startService();
545
+ tmpOrator.addStaticRoute(_StaticContentPath);
546
+
547
+ let tmpPort = getNextTestPort();
548
+ createTestHTTPServer(tmpOrator, tmpPort,
549
+ (pServer, pActualPort) =>
550
+ {
551
+ makeRequest(pActualPort, '/',
552
+ (pError, pStatusCode, pHeaders, pBody) =>
553
+ {
554
+ Expect(pError).to.equal(null);
555
+ Expect(pStatusCode).to.equal(200);
556
+ // The Content-Type should be text/html even when requesting '/' (no extension)
557
+ Expect(pHeaders['content-type']).to.contain('text/html');
558
+ Expect(pBody).to.contain('Test Index');
559
+ tmpOrator.log.info(`Root path served default index.html: status=${pStatusCode} content-type=${pHeaders['content-type']}`);
560
+ pServer.close();
561
+ return fDone();
562
+ });
563
+ });
564
+ }
565
+ );
566
+
567
+ test
568
+ (
569
+ 'should serve a custom default file when specified',
570
+ (fDone) =>
571
+ {
572
+ let tmpFable = new libFable(defaultFableSettings);
573
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
574
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
575
+ tmpOrator.startService();
576
+ // Set about.html as the default file
577
+ tmpOrator.addStaticRoute(_StaticContentPath, 'about.html');
578
+
579
+ let tmpPort = getNextTestPort();
580
+ createTestHTTPServer(tmpOrator, tmpPort,
581
+ (pServer, pActualPort) =>
582
+ {
583
+ makeRequest(pActualPort, '/',
584
+ (pError, pStatusCode, pHeaders, pBody) =>
585
+ {
586
+ Expect(pError).to.equal(null);
587
+ Expect(pStatusCode).to.equal(200);
588
+ // Even with a custom default file, Content-Type should be text/html
589
+ Expect(pHeaders['content-type']).to.contain('text/html');
590
+ Expect(pBody).to.contain('About page content');
591
+ tmpOrator.log.info(`Custom default file about.html served: status=${pStatusCode} content-type=${pHeaders['content-type']}`);
592
+ pServer.close();
593
+ return fDone();
594
+ });
595
+ });
596
+ }
597
+ );
598
+ }
599
+ );
600
+
601
+ suite
602
+ (
603
+ 'Static File Serving - Directory MIME Type Resolution',
604
+ () =>
605
+ {
606
+ test
607
+ (
608
+ 'should set text/html Content-Type for directory paths with html default file',
609
+ (fDone) =>
610
+ {
611
+ let tmpFable = new libFable(defaultFableSettings);
612
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
613
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
614
+ tmpOrator.startService();
615
+ tmpOrator.addStaticRoute(_StaticContentPath, 'index.html', '/site/*', '/site/');
616
+
617
+ let tmpPort = getNextTestPort();
618
+ createTestHTTPServer(tmpOrator, tmpPort,
619
+ (pServer, pActualPort) =>
620
+ {
621
+ makeRequest(pActualPort, '/site/',
622
+ (pError, pStatusCode, pHeaders, pBody) =>
623
+ {
624
+ Expect(pError).to.equal(null);
625
+ Expect(pStatusCode).to.equal(200);
626
+ // Directory path should resolve MIME from default file, not the bare '/'
627
+ Expect(pHeaders['content-type']).to.contain('text/html');
628
+ Expect(pBody).to.contain('Test Index');
629
+ tmpOrator.log.info(`Directory path MIME resolved to text/html from default file`);
630
+ pServer.close();
631
+ return fDone();
632
+ });
633
+ });
634
+ }
635
+ );
636
+
637
+ test
638
+ (
639
+ 'should set correct Content-Type for extensionless paths based on default file',
640
+ (fDone) =>
641
+ {
642
+ let tmpFable = new libFable(defaultFableSettings);
643
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
644
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
645
+ tmpOrator.initialize(
646
+ () =>
647
+ {
648
+ // Test the MIME detection logic directly with a mock response
649
+ let tmpCapturedHeaders = {};
650
+ let tmpMockResponse = { setHeader: function(pName, pValue) { tmpCapturedHeaders[pName] = pValue; } };
651
+
652
+ // A URL like '/' has no extension, so should fall back to default
653
+ // This simulates what happens in addStaticRoute after the fix
654
+ let tmpUrl = '/';
655
+ let tmpMimeTarget = tmpUrl;
656
+ if (tmpMimeTarget.endsWith('/') || tmpMimeTarget.indexOf('.') < 0)
657
+ {
658
+ tmpMimeTarget = 'index.html';
659
+ }
660
+ tmpOrator.setMimeHeader(tmpMimeTarget, tmpMockResponse);
661
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('text/html');
662
+
663
+ // A URL like '/somepath' (no extension, no slash) should also fall back
664
+ tmpUrl = '/somepath';
665
+ tmpMimeTarget = tmpUrl;
666
+ if (tmpMimeTarget.endsWith('/') || tmpMimeTarget.indexOf('.') < 0)
667
+ {
668
+ tmpMimeTarget = 'index.html';
669
+ }
670
+ tmpOrator.setMimeHeader(tmpMimeTarget, tmpMockResponse);
671
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('text/html');
672
+
673
+ // A URL with an extension should use its own extension
674
+ tmpUrl = '/style.css';
675
+ tmpMimeTarget = tmpUrl;
676
+ if (tmpMimeTarget.endsWith('/') || tmpMimeTarget.indexOf('.') < 0)
677
+ {
678
+ tmpMimeTarget = 'index.html';
679
+ }
680
+ tmpOrator.setMimeHeader(tmpMimeTarget, tmpMockResponse);
681
+ Expect(tmpCapturedHeaders['Content-Type']).to.equal('text/css');
682
+
683
+ tmpOrator.log.info('Directory and extensionless MIME detection verified');
684
+ return fDone();
685
+ });
686
+ }
687
+ );
688
+ }
689
+ );
690
+
691
+ suite
692
+ (
693
+ 'Static File Serving - FilePersistence Auto-Instantiation',
694
+ () =>
695
+ {
696
+ test
697
+ (
698
+ 'Orator constructor should auto-instantiate FilePersistence if not present',
699
+ (fDone) =>
700
+ {
701
+ let tmpFable = new libFable(defaultFableSettings);
702
+ // Verify FilePersistence is NOT yet on fable before Orator construction
703
+ Expect(tmpFable.FilePersistence).to.equal(undefined);
704
+
705
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
706
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
707
+
708
+ // After constructing Orator, FilePersistence should now be available
709
+ Expect(tmpFable.FilePersistence).to.be.an('object');
710
+ Expect(tmpFable.FilePersistence.libFS).to.be.an('object');
711
+ Expect(tmpFable.FilePersistence.libFS.existsSync).to.be.a('function');
712
+ tmpOrator.log.info('FilePersistence auto-instantiated by Orator constructor');
713
+ return fDone();
714
+ }
715
+ );
716
+
717
+ test
718
+ (
719
+ 'Orator constructor should not re-instantiate FilePersistence if already present',
720
+ (fDone) =>
721
+ {
722
+ let tmpFable = new libFable(defaultFableSettings);
723
+ // Pre-instantiate FilePersistence
724
+ let tmpOriginal = tmpFable.serviceManager.instantiateServiceProvider('FilePersistence');
725
+ Expect(tmpFable.FilePersistence).to.equal(tmpOriginal);
726
+
727
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
728
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
729
+
730
+ // Should still be the same instance, not a new one
731
+ Expect(tmpFable.FilePersistence).to.equal(tmpOriginal);
732
+ tmpOrator.log.info('FilePersistence preserved when already present');
733
+ return fDone();
734
+ }
735
+ );
736
+ }
737
+ );
738
+
739
+ suite
740
+ (
741
+ 'Static File Serving - CSS and JSON Files',
742
+ () =>
743
+ {
744
+ test
745
+ (
746
+ 'should serve CSS files with correct Content-Type',
747
+ (fDone) =>
748
+ {
749
+ let tmpFable = new libFable(defaultFableSettings);
750
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
751
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
752
+ tmpOrator.startService();
753
+ tmpOrator.addStaticRoute(_StaticContentPath);
754
+
755
+ let tmpPort = getNextTestPort();
756
+ createTestHTTPServer(tmpOrator, tmpPort,
757
+ (pServer, pActualPort) =>
758
+ {
759
+ makeRequest(pActualPort, '/style.css',
760
+ (pError, pStatusCode, pHeaders, pBody) =>
761
+ {
762
+ Expect(pError).to.equal(null);
763
+ Expect(pStatusCode).to.equal(200);
764
+ Expect(pHeaders['content-type']).to.contain('text/css');
765
+ Expect(pBody).to.contain('font-family');
766
+ Expect(pBody).to.contain('sans-serif');
767
+ tmpOrator.log.info(`Served style.css: content-type=${pHeaders['content-type']}`);
768
+ pServer.close();
769
+ return fDone();
770
+ });
771
+ });
772
+ }
773
+ );
774
+
775
+ test
776
+ (
777
+ 'should serve JSON files with correct Content-Type and parseable content',
778
+ (fDone) =>
779
+ {
780
+ let tmpFable = new libFable(defaultFableSettings);
781
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
782
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
783
+ tmpOrator.startService();
784
+ tmpOrator.addStaticRoute(_StaticContentPath);
785
+
786
+ let tmpPort = getNextTestPort();
787
+ createTestHTTPServer(tmpOrator, tmpPort,
788
+ (pServer, pActualPort) =>
789
+ {
790
+ makeRequest(pActualPort, '/data.json',
791
+ (pError, pStatusCode, pHeaders, pBody) =>
792
+ {
793
+ Expect(pError).to.equal(null);
794
+ Expect(pStatusCode).to.equal(200);
795
+ Expect(pHeaders['content-type']).to.contain('application/json');
796
+ let tmpParsed = JSON.parse(pBody);
797
+ Expect(tmpParsed).to.have.a.property('TestKey');
798
+ Expect(tmpParsed.TestKey).to.equal('TestValue');
799
+ Expect(tmpParsed.Numbers).to.be.an('array');
800
+ Expect(tmpParsed.Numbers).to.have.lengthOf(3);
801
+ tmpOrator.log.info(`Served data.json: parsed successfully with TestKey=${tmpParsed.TestKey}`);
802
+ pServer.close();
803
+ return fDone();
804
+ });
805
+ });
806
+ }
807
+ );
808
+ }
809
+ );
810
+
811
+ suite
812
+ (
813
+ 'Static File Serving - Route Stripping',
814
+ () =>
815
+ {
816
+ test
817
+ (
818
+ 'should strip route prefix from URL before serving files',
819
+ (fDone) =>
820
+ {
821
+ let tmpFable = new libFable(defaultFableSettings);
822
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
823
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
824
+ tmpOrator.startService();
825
+ // Serve static content at /assets/* and strip /assets/ from the path
826
+ tmpOrator.addStaticRoute(_StaticContentPath, 'index.html', '/assets/*', '/assets/');
827
+
828
+ let tmpPort = getNextTestPort();
829
+ createTestHTTPServer(tmpOrator, tmpPort,
830
+ (pServer, pActualPort) =>
831
+ {
832
+ makeRequest(pActualPort, '/assets/style.css',
833
+ (pError, pStatusCode, pHeaders, pBody) =>
834
+ {
835
+ Expect(pError).to.equal(null);
836
+ Expect(pStatusCode).to.equal(200);
837
+ Expect(pHeaders['content-type']).to.contain('text/css');
838
+ Expect(pBody).to.contain('font-family');
839
+ tmpOrator.log.info(`Route stripping: /assets/style.css served correctly with content-type=${pHeaders['content-type']}`);
840
+ pServer.close();
841
+ return fDone();
842
+ });
843
+ });
844
+ }
845
+ );
846
+
847
+ test
848
+ (
849
+ 'should serve the default file at the stripped route root',
850
+ (fDone) =>
851
+ {
852
+ let tmpFable = new libFable(defaultFableSettings);
853
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
854
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
855
+ tmpOrator.startService();
856
+ tmpOrator.addStaticRoute(_StaticContentPath, 'index.html', '/docs/*', '/docs/');
857
+
858
+ let tmpPort = getNextTestPort();
859
+ createTestHTTPServer(tmpOrator, tmpPort,
860
+ (pServer, pActualPort) =>
861
+ {
862
+ makeRequest(pActualPort, '/docs/',
863
+ (pError, pStatusCode, pHeaders, pBody) =>
864
+ {
865
+ Expect(pError).to.equal(null);
866
+ Expect(pStatusCode).to.equal(200);
867
+ Expect(pBody).to.contain('Test Index');
868
+ tmpOrator.log.info(`Route root /docs/ served default index.html`);
869
+ pServer.close();
870
+ return fDone();
871
+ });
872
+ });
873
+ }
874
+ );
875
+
876
+ test
877
+ (
878
+ 'should serve JSON through a stripped route',
879
+ (fDone) =>
880
+ {
881
+ let tmpFable = new libFable(defaultFableSettings);
882
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
883
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
884
+ tmpOrator.startService();
885
+ tmpOrator.addStaticRoute(_StaticContentPath, 'index.html', '/api/static/*', '/api/static/');
886
+
887
+ let tmpPort = getNextTestPort();
888
+ createTestHTTPServer(tmpOrator, tmpPort,
889
+ (pServer, pActualPort) =>
890
+ {
891
+ makeRequest(pActualPort, '/api/static/data.json',
892
+ (pError, pStatusCode, pHeaders, pBody) =>
893
+ {
894
+ Expect(pError).to.equal(null);
895
+ Expect(pStatusCode).to.equal(200);
896
+ Expect(pHeaders['content-type']).to.contain('application/json');
897
+ let tmpParsed = JSON.parse(pBody);
898
+ Expect(tmpParsed.TestKey).to.equal('TestValue');
899
+ tmpOrator.log.info(`Served data.json through stripped route /api/static/data.json`);
900
+ pServer.close();
901
+ return fDone();
902
+ });
903
+ });
904
+ }
905
+ );
906
+ }
907
+ );
908
+
909
+ suite
910
+ (
911
+ 'Static File Serving - Query String Handling',
912
+ () =>
913
+ {
914
+ test
915
+ (
916
+ 'should strip query strings from URLs before serving files',
917
+ (fDone) =>
918
+ {
919
+ let tmpFable = new libFable(defaultFableSettings);
920
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
921
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
922
+ tmpOrator.startService();
923
+ tmpOrator.addStaticRoute(_StaticContentPath);
924
+
925
+ let tmpPort = getNextTestPort();
926
+ createTestHTTPServer(tmpOrator, tmpPort,
927
+ (pServer, pActualPort) =>
928
+ {
929
+ makeRequest(pActualPort, '/style.css?v=1.0.0&bust=true',
930
+ (pError, pStatusCode, pHeaders, pBody) =>
931
+ {
932
+ Expect(pError).to.equal(null);
933
+ Expect(pStatusCode).to.equal(200);
934
+ Expect(pHeaders['content-type']).to.contain('text/css');
935
+ Expect(pBody).to.contain('font-family');
936
+ tmpOrator.log.info('Query string was stripped and file served correctly');
937
+ pServer.close();
938
+ return fDone();
939
+ });
940
+ });
941
+ }
942
+ );
943
+ }
944
+ );
945
+
946
+ suite
947
+ (
948
+ 'Static File Serving - Missing Files',
949
+ () =>
950
+ {
951
+ test
952
+ (
953
+ 'should return 404 for a file that does not exist',
954
+ (fDone) =>
955
+ {
956
+ let tmpFable = new libFable(defaultFableSettings);
957
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
958
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
959
+ tmpOrator.startService();
960
+ tmpOrator.addStaticRoute(_StaticContentPath);
961
+
962
+ let tmpPort = getNextTestPort();
963
+ createTestHTTPServer(tmpOrator, tmpPort,
964
+ (pServer, pActualPort) =>
965
+ {
966
+ makeRequest(pActualPort, '/nonexistent.html',
967
+ (pError, pStatusCode, pHeaders, pBody) =>
968
+ {
969
+ Expect(pError).to.equal(null);
970
+ Expect(pStatusCode).to.equal(404);
971
+ tmpOrator.log.info(`Missing file correctly returned 404`);
972
+ pServer.close();
973
+ return fDone();
974
+ });
975
+ });
976
+ }
977
+ );
978
+
979
+ test
980
+ (
981
+ 'should return 404 for a path traversal attempt',
982
+ (fDone) =>
983
+ {
984
+ let tmpFable = new libFable(defaultFableSettings);
985
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
986
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
987
+ tmpOrator.startService();
988
+ tmpOrator.addStaticRoute(_StaticContentPath, 'index.html', '/safe/*', '/safe/');
989
+
990
+ let tmpPort = getNextTestPort();
991
+ createTestHTTPServer(tmpOrator, tmpPort,
992
+ (pServer, pActualPort) =>
993
+ {
994
+ makeRequest(pActualPort, '/safe/../../../etc/passwd',
995
+ (pError, pStatusCode, pHeaders, pBody) =>
996
+ {
997
+ Expect(pError).to.equal(null);
998
+ // serve-static should prevent path traversal
999
+ Expect(pStatusCode).to.be.oneOf([400, 403, 404]);
1000
+ tmpOrator.log.info(`Path traversal attempt correctly blocked with status ${pStatusCode}`);
1001
+ pServer.close();
1002
+ return fDone();
1003
+ });
1004
+ });
1005
+ }
1006
+ );
1007
+ }
1008
+ );
1009
+
1010
+ suite
1011
+ (
1012
+ 'Static File Serving - Response Headers',
1013
+ () =>
1014
+ {
1015
+ test
1016
+ (
1017
+ 'should include standard caching headers in responses',
1018
+ (fDone) =>
1019
+ {
1020
+ let tmpFable = new libFable(defaultFableSettings);
1021
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
1022
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
1023
+ tmpOrator.startService();
1024
+ tmpOrator.addStaticRoute(_StaticContentPath);
1025
+
1026
+ let tmpPort = getNextTestPort();
1027
+ createTestHTTPServer(tmpOrator, tmpPort,
1028
+ (pServer, pActualPort) =>
1029
+ {
1030
+ makeRequest(pActualPort, '/index.html',
1031
+ (pError, pStatusCode, pHeaders, pBody) =>
1032
+ {
1033
+ Expect(pError).to.equal(null);
1034
+ Expect(pStatusCode).to.equal(200);
1035
+ // serve-static sets these headers by default
1036
+ Expect(pHeaders).to.have.a.property('etag');
1037
+ Expect(pHeaders).to.have.a.property('last-modified');
1038
+ Expect(pHeaders).to.have.a.property('content-length');
1039
+ Expect(pHeaders).to.have.a.property('accept-ranges');
1040
+ tmpOrator.log.info(`Standard headers present: etag=${pHeaders['etag']} last-modified=${pHeaders['last-modified']}`);
1041
+ pServer.close();
1042
+ return fDone();
1043
+ });
1044
+ });
1045
+ }
1046
+ );
1047
+
1048
+ test
1049
+ (
1050
+ 'should pass custom serve-static params through to the library',
1051
+ (fDone) =>
1052
+ {
1053
+ let tmpFable = new libFable(defaultFableSettings);
1054
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
1055
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
1056
+ tmpOrator.startService();
1057
+ // Pass maxAge as a custom parameter to serve-static
1058
+ tmpOrator.addStaticRoute(_StaticContentPath, 'index.html', '/*', '/', {maxAge: 86400000});
1059
+
1060
+ let tmpPort = getNextTestPort();
1061
+ createTestHTTPServer(tmpOrator, tmpPort,
1062
+ (pServer, pActualPort) =>
1063
+ {
1064
+ makeRequest(pActualPort, '/index.html',
1065
+ (pError, pStatusCode, pHeaders, pBody) =>
1066
+ {
1067
+ Expect(pError).to.equal(null);
1068
+ Expect(pStatusCode).to.equal(200);
1069
+ // maxAge should be reflected in the Cache-Control header
1070
+ Expect(pHeaders['cache-control']).to.contain('max-age=86400');
1071
+ tmpOrator.log.info(`Custom params applied: cache-control=${pHeaders['cache-control']}`);
1072
+ pServer.close();
1073
+ return fDone();
1074
+ });
1075
+ });
1076
+ }
1077
+ );
1078
+ }
1079
+ );
1080
+
1081
+ suite
1082
+ (
1083
+ 'Static File Serving - Subdomain Magic Subfolder Routing',
1084
+ () =>
1085
+ {
1086
+ test
1087
+ (
1088
+ 'should serve from a subfolder when the hostname matches an existing subfolder',
1089
+ (fDone) =>
1090
+ {
1091
+ let tmpFable = new libFable(defaultFableSettings);
1092
+ // FilePersistence is now auto-instantiated by Orator's constructor
1093
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
1094
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
1095
+ tmpOrator.startService();
1096
+ tmpOrator.addStaticRoute(_StaticContentPath);
1097
+
1098
+ let tmpPort = getNextTestPort();
1099
+ createTestHTTPServer(tmpOrator, tmpPort,
1100
+ (pServer, pActualPort) =>
1101
+ {
1102
+ // Request with host 'subsite.example.com' -- the 'subsite' prefix matches the subfolder
1103
+ makeRequest(pActualPort, '/',
1104
+ (pError, pStatusCode, pHeaders, pBody) =>
1105
+ {
1106
+ Expect(pError).to.equal(null);
1107
+ Expect(pStatusCode).to.equal(200);
1108
+ // Should serve from static_content/subsite/index.html
1109
+ Expect(pBody).to.contain('Subsite');
1110
+ Expect(pBody).to.contain('Subsite index page');
1111
+ tmpOrator.log.info(`Subdomain magic routing: host=subsite.example.com served subsite content`);
1112
+ pServer.close();
1113
+ return fDone();
1114
+ },
1115
+ { 'Host': 'subsite.example.com' });
1116
+ });
1117
+ }
1118
+ );
1119
+
1120
+ test
1121
+ (
1122
+ 'should serve from the root folder when the hostname does not match any subfolder',
1123
+ (fDone) =>
1124
+ {
1125
+ let tmpFable = new libFable(defaultFableSettings);
1126
+ // FilePersistence is now auto-instantiated by Orator's constructor
1127
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
1128
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
1129
+ tmpOrator.startService();
1130
+ tmpOrator.addStaticRoute(_StaticContentPath);
1131
+
1132
+ let tmpPort = getNextTestPort();
1133
+ createTestHTTPServer(tmpOrator, tmpPort,
1134
+ (pServer, pActualPort) =>
1135
+ {
1136
+ // Request with host 'nonexistent.example.com' -- no matching subfolder
1137
+ makeRequest(pActualPort, '/',
1138
+ (pError, pStatusCode, pHeaders, pBody) =>
1139
+ {
1140
+ Expect(pError).to.equal(null);
1141
+ Expect(pStatusCode).to.equal(200);
1142
+ // Should serve from static_content/index.html (root)
1143
+ Expect(pBody).to.contain('Test Index');
1144
+ tmpOrator.log.info(`Non-matching subdomain served root content correctly`);
1145
+ pServer.close();
1146
+ return fDone();
1147
+ },
1148
+ { 'Host': 'nonexistent.example.com' });
1149
+ });
1150
+ }
1151
+ );
1152
+
1153
+ test
1154
+ (
1155
+ 'should serve from the root folder when hostname has only one segment (no dots)',
1156
+ (fDone) =>
1157
+ {
1158
+ let tmpFable = new libFable(defaultFableSettings);
1159
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
1160
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
1161
+ tmpOrator.startService();
1162
+ tmpOrator.addStaticRoute(_StaticContentPath);
1163
+
1164
+ let tmpPort = getNextTestPort();
1165
+ createTestHTTPServer(tmpOrator, tmpPort,
1166
+ (pServer, pActualPort) =>
1167
+ {
1168
+ // 'localhost' has no dots, so no subdomain magic should apply
1169
+ makeRequest(pActualPort, '/',
1170
+ (pError, pStatusCode, pHeaders, pBody) =>
1171
+ {
1172
+ Expect(pError).to.equal(null);
1173
+ Expect(pStatusCode).to.equal(200);
1174
+ Expect(pBody).to.contain('Test Index');
1175
+ tmpOrator.log.info(`Single-segment hostname served root content`);
1176
+ pServer.close();
1177
+ return fDone();
1178
+ },
1179
+ { 'Host': 'localhost' });
1180
+ });
1181
+ }
1182
+ );
1183
+ }
1184
+ );
1185
+
1186
+ suite
1187
+ (
1188
+ 'Static File Serving - Subsite Direct Access',
1189
+ () =>
1190
+ {
1191
+ test
1192
+ (
1193
+ 'should serve files from a subdirectory path directly',
1194
+ (fDone) =>
1195
+ {
1196
+ let tmpFable = new libFable(defaultFableSettings);
1197
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
1198
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
1199
+ tmpOrator.startService();
1200
+ tmpOrator.addStaticRoute(_StaticContentPath);
1201
+
1202
+ let tmpPort = getNextTestPort();
1203
+ createTestHTTPServer(tmpOrator, tmpPort,
1204
+ (pServer, pActualPort) =>
1205
+ {
1206
+ makeRequest(pActualPort, '/subsite/index.html',
1207
+ (pError, pStatusCode, pHeaders, pBody) =>
1208
+ {
1209
+ Expect(pError).to.equal(null);
1210
+ Expect(pStatusCode).to.equal(200);
1211
+ Expect(pHeaders['content-type']).to.contain('text/html');
1212
+ Expect(pBody).to.contain('Subsite');
1213
+ tmpOrator.log.info(`Subsite direct path access: /subsite/index.html served correctly`);
1214
+ pServer.close();
1215
+ return fDone();
1216
+ });
1217
+ });
1218
+ }
1219
+ );
1220
+ }
1221
+ );
1222
+
1223
+ suite
1224
+ (
1225
+ 'Static File Serving - Multiple Static Routes',
1226
+ () =>
1227
+ {
1228
+ test
1229
+ (
1230
+ 'should be able to register multiple static routes on different paths',
1231
+ (fDone) =>
1232
+ {
1233
+ let tmpFable = new libFable(defaultFableSettings);
1234
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
1235
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
1236
+ tmpOrator.startService();
1237
+
1238
+ // Register the subsite as a separate static route
1239
+ let tmpSubsitePath = libPath.normalize(__dirname + '/static_content/subsite/');
1240
+ let tmpResult1 = tmpOrator.addStaticRoute(_StaticContentPath, 'index.html', '/main/*', '/main/');
1241
+ let tmpResult2 = tmpOrator.addStaticRoute(tmpSubsitePath, 'index.html', '/sub/*', '/sub/');
1242
+
1243
+ Expect(tmpResult1).to.equal(true);
1244
+ Expect(tmpResult2).to.equal(true);
1245
+
1246
+ let tmpPort = getNextTestPort();
1247
+ createTestHTTPServer(tmpOrator, tmpPort,
1248
+ (pServer, pActualPort) =>
1249
+ {
1250
+ tmpFable.Utility.waterfall([
1251
+ (fStageComplete) =>
1252
+ {
1253
+ makeRequest(pActualPort, '/main/data.json',
1254
+ (pError, pStatusCode, pHeaders, pBody) =>
1255
+ {
1256
+ Expect(pStatusCode).to.equal(200);
1257
+ let tmpParsed = JSON.parse(pBody);
1258
+ Expect(tmpParsed.TestKey).to.equal('TestValue');
1259
+ tmpOrator.log.info('Route /main/ served data.json from main content');
1260
+ return fStageComplete();
1261
+ });
1262
+ },
1263
+ (fStageComplete) =>
1264
+ {
1265
+ makeRequest(pActualPort, '/sub/',
1266
+ (pError, pStatusCode, pHeaders, pBody) =>
1267
+ {
1268
+ Expect(pStatusCode).to.equal(200);
1269
+ Expect(pBody).to.contain('Subsite');
1270
+ tmpOrator.log.info('Route /sub/ served index.html from subsite content');
1271
+ return fStageComplete();
1272
+ });
1273
+ }
1274
+ ],
1275
+ (pError) =>
1276
+ {
1277
+ pServer.close();
1278
+ return fDone();
1279
+ });
1280
+ });
1281
+ }
1282
+ );
1283
+ }
1284
+ );
1285
+
1286
+ suite
1287
+ (
1288
+ 'Static File Serving - oldLibMime Compatibility Flag',
1289
+ () =>
1290
+ {
1291
+ test
1292
+ (
1293
+ 'should correctly detect the mime library version',
1294
+ (fDone) =>
1295
+ {
1296
+ let tmpFable = new libFable(defaultFableSettings);
1297
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
1298
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
1299
+ tmpOrator.initialize(
1300
+ () =>
1301
+ {
1302
+ // The oldLibMime flag should be a boolean
1303
+ Expect(tmpOrator.oldLibMime).to.be.a('boolean');
1304
+ // With the current version of the mime library, it should use the lookup function
1305
+ let libMime = require('mime');
1306
+ if ('lookup' in libMime)
1307
+ {
1308
+ Expect(tmpOrator.oldLibMime).to.equal(true);
1309
+ }
1310
+ else
1311
+ {
1312
+ Expect(tmpOrator.oldLibMime).to.equal(false);
1313
+ }
1314
+ tmpOrator.log.info(`oldLibMime flag is ${tmpOrator.oldLibMime}`);
1315
+ return fDone();
1316
+ });
1317
+ }
1318
+ );
1319
+ }
1320
+ );
1321
+ }
1322
+ );