1 /* eslint-disable capitalized-comments */
5 const assert
= require('assert');
6 const sinon
= require('sinon'); // eslint-disable-line node/no-unpublished-require
7 const fs
= require('fs');
9 const Dingus
= require('../../lib/dingus');
10 const { DingusError
} = require('../../lib/errors');
11 const Enum
= require('../../lib/enum');
13 const noExpectedException
= 'did not get expected exception';
15 describe('Dingus', function () {
17 beforeEach(function () {
18 dingus
= new Dingus();
20 afterEach(function () {
24 describe('constructor', function () {
25 it('covers', function () {
26 const d
= new Dingus({}, {});
28 assert('log' in d
.logger
);
32 describe('_normalizePath', function () {
33 it('returns normal path', function () {
35 const r
= dingus
._normalizePath(p
);
36 assert
.strictEqual(r
, p
);
38 it('returns normal path', function () {
39 const p
= '////a///b/./bar/..///c';
40 const expected
= '/a/b/c'
41 const r
= dingus
._normalizePath(p
);
42 assert
.strictEqual(r
, expected
);
46 describe('_splitUrl', function () {
47 const nullObject
= Object
.create(null);
49 it('splits a simple path', function () {
53 queryParams: nullObject
,
55 const r
= dingus
._splitUrl(p
);
56 assert
.deepStrictEqual(r
, expected
);
58 it('splits a path with trailing slash preserved', function () {
62 queryParams: nullObject
,
64 const r
= dingus
._splitUrl(p
);
65 assert
.deepStrictEqual(r
, expected
);
67 it('splits a path with trailing slash ignored', function () {
71 queryParams: nullObject
,
73 dingus
.ignoreTrailingSlash
= true;
74 const r
= dingus
._splitUrl(p
);
75 assert
.deepStrictEqual(r
, expected
);
77 it('splits a path with empty query string', function () {
81 queryParams: nullObject
,
83 const r
= dingus
._splitUrl(p
);
84 assert
.deepStrictEqual(r
, expected
);
86 it('splits a path with query string', function () {
87 const p
= '/a/b/c?x=1&y=2&z';
90 queryParams: Object
.assign(Object
.create(null), {
93 z: '', // Subjective Editorial: disagree with the default querystring parser behavior here: null would be better than empty string, esp as result is null-prototyped object.
96 const r
= dingus
._splitUrl(p
);
97 assert
.deepStrictEqual(r
, expected
);
101 describe('tagContext', function () {
103 beforeEach(function () {
105 getHeader: sinon
.stub(),
106 setHeader: sinon
.stub(),
109 getHeader: sinon
.stub(),
110 setHeader: sinon
.stub(),
114 it ('sets id in context', function () {
115 const result
= Dingus
.tagContext(req
, res
, ctx
);
116 assert
.strictEqual(ctx
.requestId
, result
);
117 assert(res
.setHeader
.called
);
119 it ('sets provided header', function () {
120 req
.getHeader
.onCall(0).returns('abc'); // X-Request-ID
121 const result
= Dingus
.tagContext(req
, res
, ctx
);
122 assert
.strictEqual(ctx
.requestId
, result
);
123 assert
.strictEqual(res
.setHeader
.getCall(0).args
[0], 'Request-ID');
124 assert
.strictEqual(res
.setHeader
.getCall(1).args
[0], 'X-Request-ID');
125 assert
.strictEqual(res
.setHeader
.getCall(1).args
[1], 'abc');
126 assert
.strictEqual(res
.setHeader
.callCount
, 2);
130 describe('clientAddressContext', function () {
134 _tp
= dingus
.trustProxy
;
137 dingus
.trustProxy
= _tp
;
139 beforeEach(function () {
141 getHeader: sinon
.stub(),
142 setHeader: sinon
.stub(),
146 getHeader: sinon
.stub(),
147 setHeader: sinon
.stub(),
151 it ('covers untrusted proxy', function () {
152 dingus
.trustProxy
= false;
155 clientProtocol: 'http',
157 dingus
.clientAddressContext(req
, res
, ctx
);
158 assert
.deepStrictEqual(ctx
, expected
);
159 assert(!req
.getHeader
.called
);
161 it ('covers missing', function () {
162 dingus
.trustProxy
= true;
164 clientAddress: '::1',
165 clientProtocol: 'https',
167 req
.connection
.remoteAddress
= '::1';
168 req
.connection
.encrypted
= true;
169 dingus
.clientAddressContext(req
, res
, ctx
);
170 assert(req
.getHeader
.called
);
171 assert
.deepStrictEqual(ctx
, expected
);
173 }); // clientAddressContext
175 describe('getRequestContentType', function () {
177 beforeEach(function () {
179 getHeader: sinon
.stub(),
180 setHeader: sinon
.stub(),
183 it('handles missing header', function () {
184 const result
= Dingus
.getRequestContentType(req
);
185 assert
.strictEqual(result
, '');
187 it('parses simple type', function () {
188 req
.getHeader
.onCall(0).returns(Enum
.ContentType
.ApplicationJson
);
189 const result
= Dingus
.getRequestContentType(req
);
190 assert
.strictEqual(result
, Enum
.ContentType
.ApplicationJson
);
192 it('parses complex type', function () {
193 req
.getHeader
.onCall(0).returns('application/json ; charset=UTF-8');
194 const result
= Dingus
.getRequestContentType(req
);
195 assert
.strictEqual(result
, Enum
.ContentType
.ApplicationJson
);
197 }); // getRequestContentType
199 describe('setResponseContentType', function () {
200 let req
, responseTypes
;
201 beforeEach(function () {
204 setHeader: sinon
.stub(),
205 getHeader: sinon
.stub(),
208 it('handles missing header', function () {
209 const result
= Dingus
.getResponseContentType(responseTypes
, req
);
210 assert
.strictEqual(result
, undefined);
212 it('behaves as expected', function () {
213 responseTypes
.push(Enum
.ContentType
.ApplicationJson
);
214 req
.getHeader
.onCall(0).returns('text, image/png;q=0.5, application/*;q=0.2, audio;q=0.1');
215 const result
= Dingus
.getResponseContentType(responseTypes
, req
);
216 assert
.strictEqual(result
, Enum
.ContentType
.ApplicationJson
);
218 }); // setResponseContentType
220 describe('on', function () {
222 beforeEach(function () {
223 stubOn
= sinon
.stub(dingus
.router
, 'on');
225 it('covers', function () {
226 dingus
.on('GET', '/', () => {});
227 assert(stubOn
.called
);
231 describe('setEndBodyHandler', function () {
232 let req
, res
, ctx
, handler
, origEnd
, origWrite
;
233 beforeEach(function () {
234 origEnd
= sinon
.stub();
235 origWrite
= sinon
.stub();
242 handler
= sinon
.stub();
244 it('collects body and handles', function () {
245 Dingus
.setEndBodyHandler(req
, res
, ctx
, handler
);
246 res
.write(Buffer
.from('foo'));
250 assert(origWrite
.called
);
251 assert(origEnd
.called
);
252 assert
.deepStrictEqual(ctx
.responseBody
, Buffer
.from('foobazquux'));
253 assert(handler
.called
);
255 }); // setEndBodyHandler
257 describe('setHeadHandler', function () {
258 let req
, res
, ctx
, origEnd
, origWrite
;
259 beforeEach(function () {
260 origEnd
= sinon
.stub();
261 origWrite
= sinon
.stub();
268 setHeader: sinon
.stub(),
272 it('collects response without writing', function () {
273 Dingus
.setHeadHandler(req
, res
, ctx
);
274 res
.write(Buffer
.from('foo'));
278 assert(!origWrite
.called
);
279 assert(origEnd
.called
);
280 assert
.deepStrictEqual(ctx
.responseBody
, undefined);
282 it('collects response without writing, persists written data', function () {
283 Dingus
.setHeadHandler(req
, res
, ctx
, true);
284 res
.write(Buffer
.from('foo'));
288 assert(!origWrite
.called
);
289 assert(origEnd
.called
);
290 assert
.deepStrictEqual(ctx
.responseBody
, Buffer
.from('foobazquux'));
292 it('ignores non-head method', function () {
294 Dingus
.setHeadHandler(req
, res
, ctx
);
295 res
.write(Buffer
.from('foo'));
297 assert(origWrite
.called
);
298 assert(origEnd
.called
);
300 }); // setHeadHandler
302 describe('addEncodingHeader', function () {
304 beforeEach(function () {
307 // eslint-disable-next-line security/detect-object-injection
308 getHeader: (h
) => res
._headers
[h
],
309 // eslint-disable-next-line security/detect-object-injection
310 setHeader: (h
, v
) => res
._headers
[h
] = v
,
313 it('adds', function () {
315 Dingus
.addEncodingHeader(res
, encoding
);
316 assert
.strictEqual(res
._headers
[Enum
.Header
.ContentEncoding
], 'gzip');
318 it('extends', function () {
320 Dingus
.addEncodingHeader(res
, encoding
);
321 assert
.strictEqual(res
._headers
[Enum
.Header
.ContentEncoding
], 'utf8');
323 Dingus
.addEncodingHeader(res
, encoding
);
324 assert
.strictEqual(res
._headers
[Enum
.Header
.ContentEncoding
], 'gzip, utf8');
326 }); // addEncodingHeader
328 describe('dispatch', function () {
329 let pathsByLengthOrig
;
333 beforeEach(function () {
337 setHeader: sinon
.stub(),
338 getHeader: sinon
.stub(),
343 setHeader: sinon
.stub(),
344 hasHeader: sinon
.stub(),
345 getHeader: sinon
.stub(),
346 getHeaders: sinon
.stub(),
349 pathsByLengthOrig
= dingus
.pathsByLength
;
350 sinon
.spy(dingus
, 'handlerMethodNotAllowed');
351 sinon
.spy(dingus
, 'handlerNotFound');
352 sinon
.spy(dingus
, 'handlerBadRequest');
353 sinon
.spy(dingus
, 'handlerInternalServerError');
354 stubHandler
= sinon
.stub();
356 afterEach(function () {
357 dingus
.pathsByLength
= pathsByLengthOrig
;
360 it('calls handler', async
function () {
361 const urlPath
= '/:id';
362 const method
= 'GET';
363 dingus
.on(method
, urlPath
, stubHandler
);
367 await dingus
.dispatch(req
, res
, ctx
);
368 assert(stubHandler
.called
);
369 assert(!dingus
.handlerMethodNotAllowed
.called
);
370 assert(!dingus
.handlerNotFound
.called
);
372 it('calls handler without context', async
function () {
373 const urlPath
= '/:id';
374 const method
= 'GET';
375 dingus
.on(method
, urlPath
, stubHandler
);
379 await dingus
.dispatch(req
, res
);
380 assert(stubHandler
.called
);
381 assert(!dingus
.handlerMethodNotAllowed
.called
);
382 assert(!dingus
.handlerNotFound
.called
);
384 it('calls fallback handler', async
function () {
385 const urlPath
= '/abc/:id';
386 const method
= 'GET';
387 dingus
.on('*', urlPath
, stubHandler
);
388 req
.url
= '/abc/def';
391 await dingus
.dispatch(req
, res
, ctx
);
392 assert(stubHandler
.called
);
393 assert(!dingus
.handlerMethodNotAllowed
.called
);
394 assert(!dingus
.handlerNotFound
.called
);
396 it('handles error in handler', async
function () {
397 const urlPath
= '/:id';
398 const method
= 'GET';
399 dingus
.on(method
, urlPath
, stubHandler
);
402 stubHandler
.rejects(new Error('blah'));
404 await dingus
.dispatch(req
, res
, ctx
);
405 assert(stubHandler
.called
);
406 assert(!dingus
.handlerMethodNotAllowed
.called
);
407 assert(!dingus
.handlerNotFound
.called
);
409 it('calls unsupported method', async
function () {
410 const urlPath
= '/:id';
411 const method
= 'POST';
412 dingus
.on('GET', urlPath
, stubHandler
);
416 await dingus
.dispatch(req
, res
, ctx
);
417 assert(!stubHandler
.called
);
418 assert(dingus
.handlerMethodNotAllowed
.called
);
419 assert(!dingus
.handlerNotFound
.called
);
421 it('does not lookup nonexistent path', async
function () {
422 req
.url
= '/foo/bar';
425 await dingus
.dispatch(req
, res
, ctx
);
426 assert(!stubHandler
.called
);
427 assert(!dingus
.handlerMethodNotAllowed
.called
);
428 assert(dingus
.handlerNotFound
.called
);
430 it('covers unhandled dingus exception', async
function () {
431 const expectedException
= new DingusError('blah');
432 sinon
.stub(dingus
.router
, 'lookup').throws(expectedException
);
434 await dingus
.dispatch(req
, res
, ctx
);
435 assert(!stubHandler
.called
);
436 assert(dingus
.handlerInternalServerError
.called
);
438 it('covers other exception', async
function () {
439 const expectedException
= new Error('blah');
440 sinon
.stub(dingus
.router
, 'lookup').throws(expectedException
);
442 await dingus
.dispatch(req
, res
, ctx
);
443 assert(!stubHandler
.called
);
444 assert(dingus
.handlerInternalServerError
.called
);
446 it('covers bad uri', async
function () {
449 await dingus
.dispatch(req
, res
, ctx
);
450 assert(dingus
.handlerBadRequest
.called
);
452 it('calls handler with additional arguments', async
function () {
453 dingus
.on('GET', '/', stubHandler
, 'foo', 'bar');
454 await dingus
.dispatch(req
, res
, ctx
);
455 assert(stubHandler
.called
);
456 assert
.strictEqual(stubHandler
.args
[0][3], 'foo');
457 assert
.strictEqual(stubHandler
.args
[0][4], 'bar');
461 describe('parseBody', function () {
463 beforeEach(function () {
466 it('does not parse unknown type', function () {
468 dingus
.parseBody('unknown/type', ctx
);
469 assert
.fail(noExpectedException
);
471 assert
.strictEqual(e
.statusCode
, 415);
474 it('parses json', function () {
475 const src
= { foo: 'bar' };
476 ctx
.rawBody
= JSON
.stringify(src
);
477 dingus
.parseBody(Enum
.ContentType
.ApplicationJson
, ctx
);
478 assert
.deepStrictEqual(ctx
.parsedBody
, src
);
480 it('handles unparsable json', function () {
481 ctx
.rawBody
= 'not json';
483 dingus
.parseBody(Enum
.ContentType
.ApplicationJson
, ctx
);
484 assert
.fail(noExpectedException
);
486 assert
.strictEqual(e
.statusCode
, 400);
489 it('parses form', function () {
490 const expected
= Object
.assign(Object
.create(null), {
493 ctx
.rawBody
= 'foo=bar';
494 dingus
.parseBody('application/x-www-form-urlencoded', ctx
);
495 assert
.deepStrictEqual(ctx
.parsedBody
, expected
);
500 describe('bodyData', function () {
502 beforeEach(function () {
505 // eslint-disable-next-line security/detect-object-injection
506 on: (ev
, fn
) => resEvents
[ev
] = fn
,
509 it('provides data', async
function () {
510 const p
= dingus
.bodyData(res
);
511 resEvents
['data'](Buffer
.from('foo'));
512 resEvents
['data'](Buffer
.from('bar'));
514 const result
= await p
;
515 assert
.strictEqual(result
, 'foobar');
517 it('handles error', async
function () {
518 const p
= dingus
.bodyData(res
);
519 resEvents
['error']('foo');
522 assert
.fail(noExpectedException
);
524 assert
.strictEqual(e
, 'foo');
527 it('limits size', async
function () {
528 const p
= dingus
.bodyData(res
, 8);
529 resEvents
['data'](Buffer
.from('foobar'));
530 resEvents
['data'](Buffer
.from('bazquux'));
533 assert
.fail(noExpectedException
);
535 assert
.strictEqual(e
.statusCode
, 413);
538 it('provides buffer', async
function () {
539 const p
= dingus
.bodyData(res
, 0, false);
540 const expected
= Buffer
.from('bleat');
541 resEvents
['data'](expected
);
543 const result
= await p
;
544 assert
.deepStrictEqual(result
, expected
);
548 describe('ingestBody', function () {
549 it('ingests json', async
function () {
553 sinon
.stub(dingus
, 'bodyData').resolves('{"foo":"bar"}')
554 sinon
.stub(Dingus
, 'getRequestContentType').returns(Enum
.ContentType
.ApplicationJson
);
555 await dingus
.ingestBody(req
, res
, ctx
);
556 assert
.deepStrictEqual(ctx
.parsedBody
, { foo: 'bar' });
557 assert
.deepStrictEqual(ctx
.rawBody
, undefined);
559 it('persists rawBody', async
function () {
563 const body
= '{"foo":"bar"}';
564 sinon
.stub(dingus
, 'bodyData').resolves(body
);
565 sinon
.stub(Dingus
, 'getRequestContentType').returns(Enum
.ContentType
.ApplicationJson
);
566 await dingus
.ingestBody(req
, res
, ctx
, { persistRawBody: true });
567 assert
.deepStrictEqual(ctx
.parsedBody
, { foo: 'bar' });
568 assert
.deepStrictEqual(ctx
.rawBody
, body
);
570 it('skips parsing empty body', async
function () {
575 sinon
.stub(dingus
, 'bodyData').resolves(body
);
576 sinon
.stub(Dingus
, 'getRequestContentType').returns(Enum
.ContentType
.ApplicationJson
);
577 sinon
.spy(dingus
, 'parseBody');
578 await dingus
.ingestBody(req
, res
, ctx
, { parseEmptyBody: false });
579 assert
.deepStrictEqual(ctx
.parsedBody
, undefined);
580 assert(dingus
.parseBody
.notCalled
);
584 describe('setResponseType', function () {
586 let _sa
; // Preserve strictAccept
588 _sa
= dingus
.strictAccept
;
591 dingus
.strictAccept
= _sa
;
593 beforeEach(function () {
597 setHeader: sinon
.stub(),
599 sinon
.stub(Dingus
, 'getResponseContentType').returns();
601 it('rejects missing', function () {
602 dingus
.strictAccept
= true;
604 dingus
.setResponseType(['my/type'], req
, res
, ctx
);
605 assert
.fail(noExpectedException
);
607 assert
.strictEqual(e
.statusCode
, 406, 'did not get expected status code');
610 it('accepts missing', function () {
611 dingus
.strictAccept
= false;
612 dingus
.setResponseType(['my/type'], req
, res
, ctx
);
613 assert
.strictEqual(ctx
.responseType
, 'my/type');
616 }); // setResponseType
618 describe('_readFileInfo', function () {
619 let stat
, data
, statRes
, dataRes
, filename
;
620 beforeEach(function () {
621 sinon
.stub(fs
.promises
, 'stat');
622 sinon
.stub(fs
.promises
, 'readFile');
624 mtimeMs:1612553697186,
627 filename
= 'dummy.txt';
629 it('succeeds', async
function () {
630 fs
.promises
.stat
.resolves(statRes
);
631 fs
.promises
.readFile
.resolves('data');
632 [stat
, data
] = await dingus
._readFileInfo(filename
);
633 assert
.deepStrictEqual(stat
, statRes
);
634 assert
.deepStrictEqual(data
, dataRes
);
636 it('returns null for non-existant file', async
function () {
640 fs
.promises
.stat
.rejects(noEnt
);
641 fs
.promises
.readFile
.rejects(noEnt
);
642 [stat
, data
] = await dingus
._readFileInfo(filename
);
643 assert
.strictEqual(stat
, null);
644 assert
.strictEqual(data
, null);
646 it('throws unexpected error', async
function () {
647 const expectedException
= new Error('blah');
648 fs
.promises
.stat
.rejects(expectedException
);
649 await assert
.rejects(async () => {
650 await dingus
._readFileInfo(filename
);
651 }, expectedException
);
655 describe('_serveFileMetaHeaders', function () {
656 let res
, directory
, fileName
;
657 beforeEach(function () {
658 sinon
.stub(dingus
, '_readFileInfo');
660 setHeader: sinon
.stub(),
663 fileName
= 'filename';
665 it('covers no meta file', async
function() {
666 dingus
._readFileInfo
.resolves([null, null]);
667 await dingus
._serveFileMetaHeaders(res
, directory
, fileName
);
668 assert(!res
.setHeader
.called
);
670 it('adds extra headers', async
function () {
671 dingus
._readFileInfo
.resolves([{}, Buffer
.from(`Link: <https://example.com/>; rel="relation"
672 X-Folded-Header: data
675 Content-Type: image/sgi
677 await dingus
._serveFileMetaHeaders(res
, directory
, fileName
);
678 assert(res
.setHeader
.called
);
680 }); // _serveFileMetaHeaders
682 describe('serveFile', function () {
683 const path
= require('path');
684 let ctx
, req
, res
, directory
, fileName
, filestats
;
685 beforeEach(function () {
686 directory
= path
.join(__dirname
, '..', 'test-data');
687 fileName
= 'example.html';
691 [Enum
.Header
.Accept
]: undefined,
692 [Enum
.Header
.IfModifiedSince
]: undefined,
693 [Enum
.Header
.AcceptEncoding
]: undefined,
694 [Enum
.Header
.IfNoneMatch
]: undefined,
696 getHeader: (header
) => {
697 if (header
in req
._headers
) {
698 // eslint-disable-next-line security/detect-object-injection
699 return req
._headers
[header
];
701 assert
.fail(`unexpected getHeader ${header}`);
706 getHeader: sinon
.stub(),
707 getHeaders: sinon
.stub(),
708 hasHeader: sinon
.stub().returns(true),
709 setHeader: sinon
.stub(),
722 atimeMs: 1613253436842.815,
723 mtimeMs: 1603485933192.861,
724 ctimeMs: 1603485933192.861,
726 atime: '2021-02-13T21:57:16.843Z',
727 mtime: '2020-10-23T13:45:33.193Z',
728 ctime: '2020-10-23T13:45:33.193Z',
729 birthtime: '1970-01-01T00:00:00.000Z',
731 sinon
.stub(dingus
, 'handlerNotFound');
732 sinon
.stub(fs
.promises
, 'stat').resolves(filestats
);
733 sinon
.spy(fs
.promises
, 'readFile');
735 it('serves a file', async
function () {
736 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
737 assert(fs
.promises
.readFile
.called
);
738 assert(!dingus
.handlerNotFound
.called
);
740 it('covers no meta headers', async
function () {
741 dingus
.staticMetadata
= false;
742 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
743 assert(fs
.promises
.readFile
.called
);
744 assert(!dingus
.handlerNotFound
.called
);
746 it('does not serve dot-file', async
function () {
747 fileName
= '.example';
748 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
749 assert(!fs
.promises
.readFile
.called
);
750 assert(dingus
.handlerNotFound
.called
);
752 it('does not serve encoded navigation', async
function () {
753 fileName
= '/example.html';
754 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
755 assert(!fs
.promises
.readFile
.called
);
756 assert(dingus
.handlerNotFound
.called
);
758 it('does not serve missing file', async
function () {
759 fileName
= 'no-file.here';
760 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
761 assert(dingus
.handlerNotFound
.called
);
763 it('requires directory be specified', async
function () {
764 await dingus
.serveFile(req
, res
, ctx
, '', fileName
);
765 assert(!fs
.promises
.readFile
.called
);
766 assert(dingus
.handlerNotFound
.called
);
768 it('covers fs error', async
function () {
769 const expectedException
= new Error('blah');
770 fs
.promises
.stat
.restore();
771 sinon
.stub(fs
.promises
, 'stat').rejects(expectedException
);
773 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
774 assert
.fail('should have thrown');
776 assert
.strictEqual(e
, expectedException
);
779 it('caches by modified', async
function () {
780 req
._headers
[Enum
.Header
.IfModifiedSince
] = 'Fri, 23 Oct 2020 23:11:16 GMT';
781 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
782 assert
.strictEqual(res
.statusCode
, 304);
784 it('does not cache old modified', async
function () {
785 req
._headers
[Enum
.Header
.IfModifiedSince
] = 'Fri, 23 Oct 2020 01:11:16 GMT';
786 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
787 assert
.notStrictEqual(res
.statusCode
, 304);
788 assert(!dingus
.handlerNotFound
.called
);
790 it('caches ETag match', async
function () {
791 req
._headers
[Enum
.Header
.IfNoneMatch
] = '"zPPQVfXV36sgXq4fRLdsm+7rRMb8IUfb/eJ6N6mnwWs"';
792 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
793 assert
.strictEqual(res
.statusCode
, 304);
795 it('does not cache ETag non-match', async
function () {
796 req
._headers
[Enum
.Header
.IfNoneMatch
] = '"foo", "bar"';
797 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
798 assert
.notStrictEqual(res
.statusCode
, 304);
799 assert(!dingus
.handlerNotFound
.called
);
801 it('handles no possible encodings', async
function () {
802 req
._headers
[Enum
.Header
.AcceptEncoding
] = '*;q=0';
803 await assert
.rejects(async () => {
804 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
806 name: 'ResponseError',
809 it('handles a valid encoding', async
function () {
810 req
._headers
[Enum
.Header
.AcceptEncoding
] = 'gzip';
811 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
812 assert(res
.end
.called
);
814 it('handles a valid encoding among others', async
function () {
815 req
._headers
[Enum
.Header
.AcceptEncoding
] = 'flarp, br, gzip';
816 fs
.promises
.stat
.restore();
817 sinon
.stub(fs
.promises
, 'stat')
818 .onCall(0).resolves(filestats
) // identity file
819 .onCall(1).resolves(null) // br encoding
820 .onCall(2).resolves(filestats
); // gzip encoding
821 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
822 assert(res
.end
.called
);
824 it('handles misconfigured encoding', async
function () {
825 Enum
.EncodingType
.Flarp
= 'flarp';
826 req
._headers
[Enum
.Header
.AcceptEncoding
] = 'flarp, gzip';
827 await dingus
.serveFile(req
, res
, ctx
, directory
, fileName
);
828 delete Enum
.EncodingType
.Flarp
;
829 assert(res
.end
.called
);
833 describe('renderError', function () {
835 beforeEach(function () {
839 details: 'hunkydorey',
842 it('renders unknown type', function () {
843 const contentType
= 'unknown/type';
844 const result
= dingus
.renderError(contentType
, err
);
845 assert
.deepStrictEqual(result
, 'OK\r\nhunkydorey');
847 it('renders text', function () {
848 const contentType
= 'text/plain';
849 const result
= dingus
.renderError(contentType
, err
);
850 assert
.deepStrictEqual(result
, 'OK\r\nhunkydorey');
852 it('renders json', function () {
853 const contentType
= Enum
.ContentType
.ApplicationJson
;
854 const result
= dingus
.renderError(contentType
, err
);
855 assert
.deepStrictEqual(result
, JSON
.stringify(err
));
857 it('renders html without details', function () {
860 errorMessage: 'Created',
862 const contentType
= 'text/html';
863 const result
= dingus
.renderError(contentType
, err
);
864 assert
.deepStrictEqual(result
, `<!DOCTYPE html>
867 <title>${err.statusCode} ${err.errorMessage}</title>
870 <h1>${err.errorMessage}</h1>
874 it('renders html', function () {
875 const contentType
= 'text/html';
876 const result
= dingus
.renderError(contentType
, err
);
877 assert
.deepStrictEqual(result
, `<!DOCTYPE html>
880 <title>${err.statusCode} ${err.errorMessage}</title>
883 <h1>${err.errorMessage}</h1>
884 <p>${err.details}</p>
888 it('renders html, multiple details', function () {
889 const contentType
= 'text/html';
890 err
.details
= ['one detail', 'two detail'];
891 const result
= dingus
.renderError(contentType
, err
);
892 assert
.deepStrictEqual(result
, `<!DOCTYPE html>
895 <title>${err.statusCode} ${err.errorMessage}</title>
898 <h1>${err.errorMessage}</h1>
906 describe('sendErrorResponse', function () {
908 beforeEach(function () {
913 getHeader: sinon
.stub(),
914 getHeaders: sinon
.stub(),
915 hasHeader: sinon
.stub().returns(true),
916 setHeader: sinon
.stub(),
918 sinon
.stub(dingus
, 'renderError');
920 it('covers', function () {
924 dingus
.sendErrorResponse(err
, req
, res
, ctx
);
925 assert(res
.end
.called
);
927 }); // sendErrorResponse
929 describe('proxyPrefix', function () {
930 let req
, res
, ctx
, stubHandler
, pfxDingus
;
933 beforeEach(function () {
934 pfxDingus
= new Dingus(console
, { proxyPrefix: pfx
});
936 setHeader: sinon
.stub(),
937 getHeader: sinon
.stub(),
942 setHeader: sinon
.stub(),
943 getHeader: sinon
.stub(),
946 sinon
.stub(pfxDingus
, 'handlerMethodNotAllowed');
947 sinon
.stub(pfxDingus
, 'handlerNotFound');
948 stubHandler
= sinon
.stub();
950 afterEach(function () {
954 it('handles prefixed route', async
function () {
955 const urlPath
= '/:id';
956 const method
= 'GET';
957 pfxDingus
.on(method
, urlPath
, stubHandler
);
958 req
.url
= pfx
+ '/abc';
961 await pfxDingus
.dispatch(req
, res
, ctx
);
962 assert(stubHandler
.called
);
963 assert(!pfxDingus
.handlerMethodNotAllowed
.called
);
964 assert(!pfxDingus
.handlerNotFound
.called
);
966 it('does not handle prefixed route', async
function () {
967 const urlPath
= '/:id';
968 const method
= 'GET';
969 pfxDingus
.on(method
, urlPath
, stubHandler
);
970 req
.url
= '/wrongpfx/abc';
973 await pfxDingus
.dispatch(req
, res
, ctx
);
974 assert(!stubHandler
.called
);
975 assert(!pfxDingus
.handlerMethodNotAllowed
.called
);
976 assert(pfxDingus
.handlerNotFound
.called
);
980 describe('handlerRedirect', function () {
982 beforeEach(function () {
984 getHeader: sinon
.stub(),
987 setHeader: sinon
.stub(),
992 it('covers', async
function () {
993 await dingus
.handlerRedirect(req
, res
, ctx
);
994 assert(res
.setHeader
.called
);
995 assert(res
.end
.called
);
997 it('covers non-defaults', async
function () {
998 await dingus
.handlerRedirect(req
, res
, ctx
, 308);
999 assert(res
.setHeader
.called
);
1000 assert(res
.end
.called
);
1002 }); // handlerRedirect
1004 describe('handlerGetStaticFile', function () {
1006 beforeEach(function () {
1008 getHeader: sinon
.stub(),
1011 setHeader: sinon
.stub(),
1018 sinon
.stub(dingus
, 'serveFile');
1020 it('covers', async
function () {
1021 await dingus
.handlerGetStaticFile(req
, res
, ctx
);
1022 assert(dingus
.serveFile
.called
);
1024 it('covers specified file', async
function () {
1025 await dingus
.handlerGetStaticFile(req
, res
, ctx
, 'file.txt');
1026 assert(dingus
.serveFile
.called
);
1028 }); // handlerGetStaticFile