const fs = require('fs');
const Dingus = require('../../lib/dingus');
-const { DingusError } = require('../../lib/errors');
+const { DingusError, RouterNoMethodError } = require('../../lib/errors');
const Enum = require('../../lib/enum');
const noExpectedException = 'did not get expected exception';
-describe('Dingus', function () {
- const dingus = new Dingus();
+const noLogger = {
+ debug: () => {},
+ error: () => {},
+};
+describe('Dingus', function () {
+ let dingus;
+ beforeEach(function () {
+ dingus = new Dingus(noLogger, {});
+ });
afterEach(function () {
sinon.restore();
});
describe('constructor', function () {
it('covers', function () {
- const d = new Dingus({}, {});
+ const d = new Dingus();
assert(d);
- assert('log' in d.logger);
});
}); // constructor
});
it('returns normal path', function () {
const p = '////a///b/./bar/..///c';
- const expected = '/a/b/c'
+ const expected = '/a/b/c';
const r = dingus._normalizePath(p);
assert.strictEqual(r, expected);
});
const expected = {
clientAddress: '',
clientProtocol: 'http',
- }
+ };
dingus.clientAddressContext(req, res, ctx);
assert.deepStrictEqual(ctx, expected);
assert(!req.getHeader.called);
const expected = {
clientAddress: '::1',
clientProtocol: 'https',
- }
+ };
req.connection.remoteAddress = '::1';
req.connection.encrypted = true;
dingus.clientAddressContext(req, res, ctx);
dingus.on('GET', '/', () => {});
assert(stubOn.called);
});
- });
+ }); // on
describe('setEndBodyHandler', function () {
let req, res, ctx, handler, origEnd, origWrite;
};
ctx = {};
});
- it('collects body without writing', function () {
+ it('collects response without writing', function () {
Dingus.setHeadHandler(req, res, ctx);
res.write(Buffer.from('foo'));
res.write('baz');
res.end('quux');
assert(!origWrite.called);
assert(origEnd.called);
+ assert.deepStrictEqual(ctx.responseBody, undefined);
+ });
+ it('collects response without writing, persists written data', function () {
+ Dingus.setHeadHandler(req, res, ctx, true);
+ res.write(Buffer.from('foo'));
+ res.write('baz');
+ res.write();
+ res.end('quux');
+ assert(!origWrite.called);
+ assert(origEnd.called);
assert.deepStrictEqual(ctx.responseBody, Buffer.from('foobazquux'));
});
it('ignores non-head method', function () {
sinon.spy(dingus, 'handlerNotFound');
sinon.spy(dingus, 'handlerBadRequest');
sinon.spy(dingus, 'handlerInternalServerError');
+ sinon.spy(Dingus, 'setHeadHandler');
stubHandler = sinon.stub();
});
afterEach(function () {
await dingus.dispatch(req, res, ctx);
assert(dingus.handlerBadRequest.called);
});
-
+ it('calls handler with additional arguments', async function () {
+ dingus.on('GET', '/', stubHandler, 'foo', 'bar');
+ await dingus.dispatch(req, res, ctx);
+ assert(stubHandler.called);
+ assert.strictEqual(stubHandler.args[0][3], 'foo');
+ assert.strictEqual(stubHandler.args[0][4], 'bar');
+ });
+ describe('intrinsic HEAD handling', function () {
+ it('covers no intrinsic HEAD handling', async function () {
+ dingus.intrinsicHeadMethod = false;
+ dingus.on('GET', '/', stubHandler);
+ req.method = 'HEAD';
+ await dingus.dispatch(req, res, ctx);
+ assert(!stubHandler.called);
+ assert(dingus.handlerMethodNotAllowed.called);
+ });
+ it('calls HEAD setup and GET handler', async function () {
+ dingus.on('GET', '/', stubHandler);
+ req.method = 'HEAD';
+ await dingus.dispatch(req, res, ctx);
+ assert(Dingus.setHeadHandler.called);
+ assert(stubHandler.called);
+ });
+ it('covers no GET handler', async function () {
+ dingus.on('POST', '/', stubHandler);
+ req.method = 'HEAD';
+ await dingus.dispatch(req, res, ctx);
+ assert(!stubHandler.called);
+ assert(dingus.handlerMethodNotAllowed.called);
+ });
+ it('covers unexpected router error', async function () {
+ sinon.stub(dingus.router, 'lookup')
+ .onFirstCall().throws(new RouterNoMethodError())
+ .onSecondCall().throws(new DingusError())
+ ;
+ dingus.on('GET', '/', stubHandler);
+ req.method = 'HEAD';
+ await dingus.dispatch(req, res, ctx);
+ assert(dingus.handlerInternalServerError.called);
+ });
+ });
}); // dispatch
describe('parseBody', function () {
});
it('parses json', function () {
const src = { foo: 'bar' };
- ctx.rawBody = JSON.stringify(src);
- dingus.parseBody(Enum.ContentType.ApplicationJson, ctx);
+ const rawBody = JSON.stringify(src);
+ dingus.parseBody(Enum.ContentType.ApplicationJson, ctx, rawBody);
assert.deepStrictEqual(ctx.parsedBody, src);
});
it('handles unparsable json', function () {
- ctx.rawBody = 'not json';
+ const rawBody = 'not json';
try {
- dingus.parseBody(Enum.ContentType.ApplicationJson, ctx);
+ dingus.parseBody(Enum.ContentType.ApplicationJson, ctx, rawBody);
assert.fail(noExpectedException);
} catch (e) {
assert.strictEqual(e.statusCode, 400);
const expected = Object.assign(Object.create(null), {
foo: 'bar',
});
- ctx.rawBody = 'foo=bar';
- dingus.parseBody('application/x-www-form-urlencoded', ctx);
+ const rawBody = 'foo=bar';
+ dingus.parseBody('application/x-www-form-urlencoded', ctx, rawBody);
assert.deepStrictEqual(ctx.parsedBody, expected);
});
assert.strictEqual(e, 'foo');
}
});
+ it('limits size', async function () {
+ const p = dingus.bodyData(res, 8);
+ resEvents['data'](Buffer.from('foobar'));
+ resEvents['data'](Buffer.from('bazquux'));
+ try {
+ await p;
+ assert.fail(noExpectedException);
+ } catch (e) {
+ assert.strictEqual(e.statusCode, 413);
+ }
+ });
+ it('provides buffer', async function () {
+ const p = dingus.bodyData(res, 0, false);
+ const expected = Buffer.from('bleat');
+ resEvents['data'](expected);
+ resEvents['end']();
+ const result = await p;
+ assert.deepStrictEqual(result, expected);
+ });
}); // bodyData
describe('ingestBody', function () {
- it('covers', async function () {
+ it('ingests json', async function () {
const req = {};
const res = {};
const ctx = {};
- sinon.stub(dingus, 'bodyData').resolves('{"foo":"bar"}')
+ sinon.stub(dingus, 'bodyData').resolves('{"foo":"bar"}');
sinon.stub(Dingus, 'getRequestContentType').returns(Enum.ContentType.ApplicationJson);
await dingus.ingestBody(req, res, ctx);
assert.deepStrictEqual(ctx.parsedBody, { foo: 'bar' });
+ assert.deepStrictEqual(ctx.rawBody, undefined);
+ });
+ it('persists rawBody', async function () {
+ const req = {};
+ const res = {};
+ const ctx = {};
+ const body = '{"foo":"bar"}';
+ sinon.stub(dingus, 'bodyData').resolves(body);
+ sinon.stub(Dingus, 'getRequestContentType').returns(Enum.ContentType.ApplicationJson);
+ await dingus.ingestBody(req, res, ctx, { persistRawBody: true });
+ assert.deepStrictEqual(ctx.parsedBody, { foo: 'bar' });
+ assert.deepStrictEqual(ctx.rawBody, body);
+ });
+ it('skips parsing empty body', async function () {
+ const req = {};
+ const res = {};
+ const ctx = {};
+ const body = '';
+ sinon.stub(dingus, 'bodyData').resolves(body);
+ sinon.stub(Dingus, 'getRequestContentType').returns(Enum.ContentType.ApplicationJson);
+ sinon.spy(dingus, 'parseBody');
+ await dingus.ingestBody(req, res, ctx, { parseEmptyBody: false });
+ assert.deepStrictEqual(ctx.parsedBody, undefined);
+ assert(dingus.parseBody.notCalled);
});
}); // ingestBody
dingus.strictAccept = false;
dingus.setResponseType(['my/type'], req, res, ctx);
assert.strictEqual(ctx.responseType, 'my/type');
- });
+ });
}); // setResponseType
});
}); // _readFileInfo
+ describe('_serveFileMetaHeaders', function () {
+ let res, directory, fileName;
+ beforeEach(function () {
+ sinon.stub(dingus, '_readFileInfo');
+ res = {
+ setHeader: sinon.stub(),
+ };
+ directory = '/path';
+ fileName = 'filename';
+ });
+ it('covers no meta file', async function() {
+ dingus._readFileInfo.resolves([null, null]);
+ await dingus._serveFileMetaHeaders(res, directory, fileName);
+ assert(!res.setHeader.called);
+ });
+ it('adds extra headers', async function () {
+ dingus._readFileInfo.resolves([{}, Buffer.from(`Link: <https://example.com/>; rel="relation"
+X-Folded-Header: data
+ data under
+ the fold
+Content-Type: image/sgi
+`)]);
+ await dingus._serveFileMetaHeaders(res, directory, fileName);
+ assert(res.setHeader.called);
+ });
+ }); // _serveFileMetaHeaders
+
describe('serveFile', function () {
const path = require('path');
let ctx, req, res, directory, fileName, filestats;
size: 8,
blocks: 17,
atimeMs: 1613253436842.815,
- mtimeMs: 1603485933192.8610,
- ctimeMs: 1603485933192.8610,
+ mtimeMs: 1603485933192.861,
+ ctimeMs: 1603485933192.861,
birthtimeMs: 0,
atime: '2021-02-13T21:57:16.843Z',
mtime: '2020-10-23T13:45:33.193Z',
assert(fs.promises.readFile.called);
assert(!dingus.handlerNotFound.called);
});
+ it('covers no meta headers', async function () {
+ dingus.staticMetadata = false;
+ await dingus.serveFile(req, res, ctx, directory, fileName);
+ assert(fs.promises.readFile.called);
+ assert(!dingus.handlerNotFound.called);
+ });
it('does not serve dot-file', async function () {
fileName = '.example';
await dingus.serveFile(req, res, ctx, directory, fileName);
await dingus.serveFile(req, res, ctx, directory, fileName);
assert(dingus.handlerNotFound.called);
});
+ it('requires directory be specified', async function () {
+ await dingus.serveFile(req, res, ctx, '', fileName);
+ assert(!fs.promises.readFile.called);
+ assert(dingus.handlerNotFound.called);
+ });
it('covers fs error', async function () {
const expectedException = new Error('blah');
fs.promises.stat.restore();
await dingus.serveFile(req, res, ctx, directory, fileName);
assert(res.end.called);
});
+ it('handles misconfigured encoding', async function () {
+ Enum.EncodingType.Flarp = 'flarp';
+ req._headers[Enum.Header.AcceptEncoding] = 'flarp, gzip';
+ await dingus.serveFile(req, res, ctx, directory, fileName);
+ delete Enum.EncodingType.Flarp;
+ assert(res.end.called);
+ });
}); // serveFile
describe('renderError', function () {
assert(pfxDingus.handlerNotFound.called);
});
}); // proxyPrefix
-});
\ No newline at end of file
+
+ describe('handlerRedirect', function () {
+ let req, res, ctx;
+ beforeEach(function () {
+ req = {
+ getHeader: sinon.stub(),
+ };
+ res = {
+ setHeader: sinon.stub(),
+ end: sinon.stub(),
+ };
+ ctx = {};
+ });
+ it('covers', async function () {
+ await dingus.handlerRedirect(req, res, ctx);
+ assert(res.setHeader.called);
+ assert(res.end.called);
+ });
+ it('covers non-defaults', async function () {
+ await dingus.handlerRedirect(req, res, ctx, 308);
+ assert(res.setHeader.called);
+ assert(res.end.called);
+ });
+ }); // handlerRedirect
+
+ describe('handlerGetStaticFile', function () {
+ let req, res, ctx;
+ beforeEach(function () {
+ req = {
+ getHeader: sinon.stub(),
+ };
+ res = {
+ setHeader: sinon.stub(),
+ };
+ ctx = {
+ params: {
+ file: '',
+ },
+ };
+ sinon.stub(dingus, 'serveFile');
+ });
+ it('covers', async function () {
+ await dingus.handlerGetStaticFile(req, res, ctx);
+ assert(dingus.serveFile.called);
+ });
+ it('covers specified file', async function () {
+ await dingus.handlerGetStaticFile(req, res, ctx, 'file.txt');
+ assert(dingus.serveFile.called);
+ });
+ }); // handlerGetStaticFile
+});