orator-static-server 1.0.0 → 1.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.
- package/LICENSE +1 -1
- package/README.md +102 -3
- package/docs/.nojekyll +0 -0
- package/docs/README.md +49 -0
- package/docs/_sidebar.md +9 -0
- package/docs/api-reference.md +68 -0
- package/docs/cover.md +11 -0
- package/docs/getting-started.md +84 -0
- package/docs/index.html +39 -0
- package/docs/subdomain-routing.md +63 -0
- package/package.json +10 -7
- package/source/Orator-Static-Server.js +152 -59
- package/test/Orator-Static-Server_tests.js +1413 -0
- package/test/static_content/about.html +1 -0
- package/test/static_content/app.js +1 -0
- package/test/static_content/data.json +1 -0
- package/test/static_content/index.html +1 -0
- package/test/static_content/style.css +1 -0
- package/test/static_content/subsite/index.html +1 -0
- package/.vscode/launch.json +0 -46
- package/debug/Harness.js +0 -72
- package/debug/serve/index.html +0 -9
- package/test/Orator-Static-Server-basic_tests.js +0 -62
|
@@ -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
|
+
);
|