X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=test%2Flib%2Fdingus.js;h=01094a7c8e94bc9647411b422d76f8339783a0cd;hb=5b18a1fa46ef9c41f6089e5db259af80f3e98b0a;hp=eeeaf3a8980bf17eb342833b0722721c96931905;hpb=c980310d2de988d5f4e3c932eb77ebe83daa27b9;p=squeep-api-dingus diff --git a/test/lib/dingus.js b/test/lib/dingus.js index eeeaf3a..01094a7 100644 --- a/test/lib/dingus.js +++ b/test/lib/dingus.js @@ -7,15 +7,24 @@ const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-requi 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'; +const _nop = () => undefined; +const _logFn = (process.env['VERBOSE_TESTS'] && console.log) || _nop; +const noLogger = { + debug: _logFn, + error: _logFn, +}; +sinon.spy(noLogger, 'debug'); +sinon.spy(noLogger, 'error'); + describe('Dingus', function () { let dingus; beforeEach(function () { - dingus = new Dingus(); + dingus = new Dingus(noLogger, {}); }); afterEach(function () { sinon.restore(); @@ -23,9 +32,8 @@ describe('Dingus', function () { describe('constructor', function () { it('covers', function () { - const d = new Dingus({}, {}); + const d = new Dingus(); assert(d); - assert('log' in d.logger); }); }); // constructor @@ -37,7 +45,7 @@ describe('Dingus', function () { }); 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); }); @@ -153,7 +161,7 @@ describe('Dingus', function () { const expected = { clientAddress: '', clientProtocol: 'http', - } + }; dingus.clientAddressContext(req, res, ctx); assert.deepStrictEqual(ctx, expected); assert(!req.getHeader.called); @@ -163,7 +171,7 @@ describe('Dingus', function () { const expected = { clientAddress: '::1', clientProtocol: 'https', - } + }; req.connection.remoteAddress = '::1'; req.connection.encrypted = true; dingus.clientAddressContext(req, res, ctx); @@ -172,6 +180,71 @@ describe('Dingus', function () { }); }); // clientAddressContext + describe('ingestCookie', function () { + let req, res, ctx; + beforeEach(function () { + req = { + getHeader: sinon.stub(), + }; + ctx = {}; + }); + it('covers no header', function () { + const expected = {}; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('covers non variable', function () { + req.getHeader.returns('foo'); + const expected = { + foo: null, + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('parses cookies', function () { + req.getHeader.returns('foo=bar; baz="quux"'); + const expected = { + foo: 'bar', + baz: 'quux', + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('parses nulls', function () { + req.getHeader.returns('foo=; bar='); + const expected = { + foo: '', + bar: '', + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('parses non-uri-encoded', function () { + req.getHeader.returns('foo%=%qux'); + const expected = { + 'foo%': '%qux', + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('covers nameless cookie', function () { + req.getHeader.returns('=bar'); + const expected = { + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + + }); + it('covers duplicate cookie', function () { + req.getHeader.returns('foo=bar; foo="quux"'); + const expected = { + foo: 'bar', + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + }); // ingestCookie + describe('getRequestContentType', function () { let req; beforeEach(function () { @@ -269,7 +342,7 @@ describe('Dingus', function () { }; 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'); @@ -277,6 +350,16 @@ describe('Dingus', function () { 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 () { @@ -341,6 +424,7 @@ describe('Dingus', function () { sinon.spy(dingus, 'handlerNotFound'); sinon.spy(dingus, 'handlerBadRequest'); sinon.spy(dingus, 'handlerInternalServerError'); + sinon.spy(Dingus, 'setHeadHandler'); stubHandler = sinon.stub(); }); afterEach(function () { @@ -439,7 +523,47 @@ describe('Dingus', 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 () { @@ -457,14 +581,14 @@ describe('Dingus', 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); @@ -474,8 +598,8 @@ describe('Dingus', function () { 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); }); @@ -508,17 +632,60 @@ describe('Dingus', function () { 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 @@ -552,7 +719,7 @@ describe('Dingus', function () { dingus.strictAccept = false; dingus.setResponseType(['my/type'], req, res, ctx); assert.strictEqual(ctx.responseType, 'my/type'); - }); + }); }); // setResponseType @@ -605,8 +772,9 @@ describe('Dingus', function () { }); it('covers no meta file', async function() { dingus._readFileInfo.resolves([null, null]); - await dingus._serveFileMetaHeaders(res, directory, fileName); + const result = await dingus._serveFileMetaHeaders(res, directory, fileName); assert(!res.setHeader.called); + assert.strictEqual(result, false); }); it('adds extra headers', async function () { dingus._readFileInfo.resolves([{}, Buffer.from(`Link: ; rel="relation" @@ -615,8 +783,9 @@ X-Folded-Header: data the fold Content-Type: image/sgi `)]); - await dingus._serveFileMetaHeaders(res, directory, fileName); + const result = await dingus._serveFileMetaHeaders(res, directory, fileName); assert(res.setHeader.called); + assert.strictEqual(result, true); }); }); // _serveFileMetaHeaders @@ -661,8 +830,8 @@ Content-Type: image/sgi 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', @@ -701,6 +870,11 @@ Content-Type: image/sgi 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(); @@ -757,6 +931,13 @@ Content-Type: image/sgi 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 () { @@ -905,4 +1086,54 @@ Content-Type: image/sgi 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 +});