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