diff --git a/.eslintrc b/.eslintrc index fefcb720..9f4ff8a9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -51,7 +51,6 @@ "no-trailing-spaces": 2, "no-underscore-dangle": 0, "no-unneeded-ternary": 1, - "one-var": 2, "quotes": [2, "single", "avoid-escape"], "semi": [2, "always"], "keyword-spacing": 2, diff --git a/lib/responses.js b/lib/responses.js index db984f95..502b96f1 100644 --- a/lib/responses.js +++ b/lib/responses.js @@ -445,6 +445,47 @@ function expectJSON(fn, res) { }).catch(handleError(res)); } +/** + * Checks the page size requested to make sure it's a Number and within the maximum. + * Returns the default or 0 if there's no default or size is not a Number. + * @param {Object} req + * @returns {number} + */ +function checkPageSize(req) { + // Casts value to a number > 0 or undefined. + const toInt = (n) => Math.floor(Math.abs(Number(n))) || undefined; + + const sizeDefault = toInt(process.env.CLAY_STORAGE_DEFAULT_PAGE_SIZE); + const sizeParam = toInt(req.query.size); + const sizeMax = toInt(process.env.CLAY_STORAGE_MAX_PAGE_SIZE); + + const size = sizeParam || sizeDefault; + + if (size) { + return Math.min(sizeMax, size) || size; + } + + return sizeMax; +} + +/** + * Checks whether the previous value for a page request matches up with the uri, + * ie. all the ids returned should contain the request uri. + * @param {Object} req + * @param {Object} res + * @returns {string|undefined} + */ +function checkPrevious(req, res) { + const prefix = req.uri; + const previous = req.query.prev; + + if (previous && prefix && !previous.includes(prefix)) { + return handleError(res)(Error('Client: Invalid previous')); + } + + return previous; +} + /** * List all things in the db * @param {object} [options] @@ -458,7 +499,9 @@ function list(options) { let list, listOptions = _.assign({ prefix: req.uri, - values: false + values: false, + previous: checkPrevious(req, res), + size: checkPageSize(req) }, options); list = db.list(listOptions); @@ -478,7 +521,7 @@ function listWithoutVersions() { return [filter({wantStrings: true}, function (str) { return str.indexOf('@') === -1; })]; - } + }, }; return list(options); @@ -603,3 +646,5 @@ module.exports.deleteRouteFromDB = deleteRouteFromDB; // For testing module.exports.setLog = mock => log = mock; module.exports.setDb = mock => db = mock; +module.exports.checkPrevious = checkPrevious; +module.exports.checkPageSize = checkPageSize; diff --git a/lib/responses.test.js b/lib/responses.test.js index 5e00f51a..cd97fd0a 100644 --- a/lib/responses.test.js +++ b/lib/responses.test.js @@ -314,6 +314,124 @@ describe(_.startCase(filename), function () { }); }); + describe('checkPageSize', function () { + const fn = lib[this.title]; + + afterEach(function () { + delete process.env.CLAY_STORAGE_DEFAULT_PAGE_SIZE; + delete process.env.CLAY_STORAGE_MAX_PAGE_SIZE; + }); + + it('falls back to default if there is no size param', function () { + const req = { + query: {}, + uri: 'domain.com/_components/component/instances', + }; + + const res = createMockRes(); + + process.env.CLAY_STORAGE_DEFAULT_PAGE_SIZE = '10'; + + expect(fn(req, res)).to.equal(10); + }); + + it('returns max when max is defined and size > max', function () { + const req = { + query: { size: '300' }, + uri: 'domain.com/_components/component/instances', + }; + + const res = createMockRes(); + + process.env.CLAY_STORAGE_DEFAULT_PAGE_SIZE = '10'; + process.env.CLAY_STORAGE_MAX_PAGE_SIZE = '100'; + + expect(fn(req, res)).to.equal(100); + }); + + it('returns valid size that is smaller than max', function () { + const req = { + query: { size: '42' }, + uri: 'domain.com/_components/component/instances', + }; + + const res = createMockRes(); + + process.env.CLAY_STORAGE_DEFAULT_PAGE_SIZE = '10'; + process.env.CLAY_STORAGE_MAX_PAGE_SIZE = '100'; + + expect(fn(req, res)).to.equal(42); + }); + + it('returns default for an invalid size query param', function () { + const req = { + query: { size: 'string' }, + uri: 'domain.com/_components/component/instances', + }; + + const res = createMockRes(); + + process.env.CLAY_STORAGE_DEFAULT_PAGE_SIZE = '10'; + process.env.CLAY_STORAGE_MAX_PAGE_SIZE = '100'; + + expect(fn(req, res)).to.equal(10); + }); + + it('returns max when no size or default', function () { + const req = { + query: {}, + uri: 'domain.com/_components/component/instances', + }; + + const res = createMockRes(); + + process.env.CLAY_STORAGE_MAX_PAGE_SIZE = '100'; + + expect(fn(req, res)).to.equal(100); + }); + }); + + describe('checkPrevious', function () { + const fn = lib[this.title]; + + it('returns valid previous query param value', function () { + const req = { + query: { + prev: 'domain.com/_components/component/instances/aaa', + }, + uri: 'domain.com/_components/component/instances', + }; + const res = createMockRes(); + + expect(fn(req, res)).to.equal( + 'domain.com/_components/component/instances/aaa' + ); + }); + + it('throws and responds with 400 for an invalid previous value', function () { + const req = { + query: { + prev: 'random value', + }, + uri: 'domain.com/_components/component/instances', + }; + const res = createMockRes(); + + expectStatus(res, 400); + fn(req, res); + }); + + it('returns undefined if no previous value', function () { + const req = { + query: {}, + uri: 'domain.com/_components/component/instances', + }; + const res = createMockRes(); + + expect(fn(req, res)).to.equal(undefined); + }); + }); + describe('list', function () { const fn = lib[this.title];