diff --git a/.eslintrc b/.eslintrc index bfdf823b..fefcb720 100644 --- a/.eslintrc +++ b/.eslintrc @@ -22,7 +22,7 @@ // best practices "complexity": [2, 8], "default-case": 2, - "guard-for-in": 2, + "guard-for-in": 0, "no-alert": 1, "no-floating-decimal": 1, "no-self-compare": 2, diff --git a/lib/auth.js b/lib/auth.js index 6cd7869b..8960a46f 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -2,7 +2,6 @@ const _ = require('lodash'), passport = require('passport'), users = require('./services/users'), - db = require('./services/db'), references = require('./services/references'), session = require('express-session'), flash = require('express-flash'), @@ -15,6 +14,7 @@ const _ = require('lodash'), ADMIN: 'admin', WRITE: 'write' }; +var db = require('./services/db'); /** * Check the auth level to see if a user @@ -130,7 +130,6 @@ function serializeUser(user, done) { function deserializeUser(uid, done) { return db.get(`/_users/${uid}`) - .then(JSON.parse) .then(function (user) { done(null, user); }) @@ -166,7 +165,6 @@ function verify(properties) { if (!req.user) { // first time logging in! update the user data return db.get(uid) - .then(JSON.parse) .then(function (data) { // only update the user data if the property doesn't exist (name might have been changed through the kiln UI) return _.defaults(data, { @@ -185,7 +183,6 @@ function verify(properties) { } else { // already authenticated. just grab the user data return db.get(uid) - .then(JSON.parse) .then((data) => done(null, data)) .catch(() => done(null, false, { message: 'User not found!' })); // no user found } @@ -595,3 +592,4 @@ module.exports.serializeUser = serializeUser; module.exports.deserializeUser = deserializeUser; module.exports.onLogin = onLogin; module.exports.onLogout = onLogout; +module.exports.setDb = mock => db = mock; diff --git a/lib/auth.test.js b/lib/auth.test.js index 03c2a082..3a9a22da 100644 --- a/lib/auth.test.js +++ b/lib/auth.test.js @@ -10,11 +10,11 @@ const filename = __filename.split('/').pop().split('.').shift(), passportGoogle = require('passport-google-oauth'), passportSlack = require('passport-slack'), passportAPIKey = require('passport-http-header-token'), - db = require('./services/db'), - responses = require('./responses'); + responses = require('./responses'), + storage = require('../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { - let sandbox; + let sandbox, db; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -26,7 +26,9 @@ describe(_.startCase(filename), function () { sandbox.stub(responses, 'unauthorized'); // ldap is called directly, so we can't stub it sandbox.stub(passportAPIKey, 'Strategy'); - sandbox.stub(db); + + db = storage(); + lib.setDb(db); }); afterEach(function () { @@ -130,7 +132,7 @@ describe(_.startCase(filename), function () { var done = sinon.spy(), returnedUser = {username: 'person', provider: 'google'}; - db.get.returns(Promise.resolve(JSON.stringify(returnedUser))); + db.get.returns(Promise.resolve(returnedUser)); fn('person', done) .then(function () { sinon.assert.calledOnce(done); @@ -238,14 +240,14 @@ describe(_.startCase(filename), function () { }); it('errors if user PUT fails on initial auth', function (done) { - db.get.returns(Promise.resolve(JSON.stringify({}))); + db.get.returns(Promise.resolve({})); db.put.returns(Promise.reject(new Error('some db error'))); fn(properties, siteStub)({ user: false }, null, null, profile, expectError(done)); }); it('assigns name and image if user found on initial auth', function (done) { - db.get.returns(Promise.resolve(JSON.stringify({}))); + db.get.returns(Promise.resolve({})); db.put.returns(Promise.resolve()); fn(properties, siteStub)({ user: false }, null, null, profile, expectData(done)); @@ -258,7 +260,7 @@ describe(_.startCase(filename), function () { }); it('grabs user data if user found on subsequent auth', function (done) { - db.get.returns(Promise.resolve(JSON.stringify(userData))); + db.get.returns(Promise.resolve(userData)); fn(properties, siteStub)({ user: true }, null, null, profile, expectData(done)); }); diff --git a/lib/bootstrap.js b/lib/bootstrap.js index 89bce081..00cc2f23 100644 --- a/lib/bootstrap.js +++ b/lib/bootstrap.js @@ -8,10 +8,10 @@ const path = require('path'), components = require('./services/components'), siteService = require('./services/sites'), references = require('./services/references'), - db = require('./services/db'), highland = require('highland'), { encode } = require('./services/buffer'); -var log = require('./services/logger').setup({ +var db = require('./services/db'), + log = require('./services/logger').setup({ file: __filename, action: 'bootstrap' }), @@ -332,6 +332,5 @@ module.exports = function (runBootstrap = true) { module.exports.bootstrapPath = bootstrapPath; // For testing -module.exports.setLog = function (fakeLogger) { - log = fakeLogger; -}; +module.exports.setLog = mock => log = mock; +module.exports.setDb = mock => db = mock; diff --git a/lib/bootstrap.test.js b/lib/bootstrap.test.js index f3180d86..3be9b836 100644 --- a/lib/bootstrap.test.js +++ b/lib/bootstrap.test.js @@ -5,13 +5,12 @@ const _ = require('lodash'), path = require('path'), filename = __filename.split('/').pop().split('.').shift(), lib = require('./bootstrap'), - db = require('./services/db'), siteService = require('./services/sites'), - expect = require('chai').expect, - sinon = require('sinon'); + sinon = require('sinon'), + storage = require('../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { - let sandbox, + let sandbox, db, bootstrapFake, // eslint-disable-line sitesFake, fakeLog; @@ -43,8 +42,9 @@ describe(_.startCase(filename), function () { lib.setLog(fakeLog); sandbox.stub(siteService); siteService.sites.returns(_.cloneDeep(sitesFake)); - - return db.clear(); + db = storage(); + lib.setDb(db); + // return db.clear(); }); afterEach(function () { @@ -53,7 +53,7 @@ describe(_.startCase(filename), function () { after(function () { // clean up - return db.clear(); + // return db.clear(); }); describe('lib', function () { @@ -92,7 +92,6 @@ describe(_.startCase(filename), function () { describe('bootstrapPath', function () { const fn = lib[this.title]; - it('missing bootstrap', function (done) { fn('./jfkdlsa', sitesFake[0]) .then(done.bind(null, 'should throw')) @@ -102,116 +101,17 @@ describe(_.startCase(filename), function () { }); it('reads from bootstrap', function () { - // this is doing IO, so give it more time. - this.slow(50); - this.timeout(400); - return fn('./test/fixtures/config/bootstrap', sitesFake[0]).then(function () { - function expectKittehs(results) { - expect(results).to.deep.equal({ - src: 'http://placekitten.com/400/600', - alt: 'adorable kittens' - }); - } - - return db.get(sitesFake[0].prefix + '/_components/image/instances/0') - .then(JSON.parse) - .then(expectKittehs); + sinon.assert.calledOnce(db.batch); }); }); it('reads from bootstrap without bootstrap in path', function () { - // this is doing IO, so give it more time. - this.slow(50); - this.timeout(400); - return fn('./test/fixtures/config', sitesFake[0]); }); it('reads from bootstrap with extension in path', function () { - // this is doing IO, so give it more time. - this.slow(50); - this.timeout(400); - return fn('./test/fixtures/config/bootstrap.yaml', sitesFake[0]); }); - - it('reads uris from bootstrap', function () { - // this is doing IO, so give it more time. - this.slow(50); - this.timeout(400); - - return fn('./test/fixtures/config/bootstrap.yaml', sitesFake[0]).then(function () { - return db.pipeToPromise(db.list({prefix: sitesFake[0].prefix + '/_uris', limit: -1})); - }).then(JSON.parse).then(function (results) { - expect(results).to.deep.equal({ - 'example1.com/_uris/YQ==': 'b', - 'example1.com/_uris/Yw==': 'example1.com/d', - 'example1.com/_uris/ZXhhbXBsZTEuY29tL2U=': 'f', - 'example1.com/_uris/ZXhhbXBsZTEuY29tL2c=': 'example1.com/h' - }); - }); - }); - - it('reads pages from bootstrap', function () { - // this is doing IO, so give it more time. - this.slow(50); - this.timeout(400); - - return fn('./test/fixtures/config/bootstrap.yaml', sitesFake[0]).then(function () { - return db.pipeToPromise(db.list({prefix: 'example1.com/_pages', limit: -1})); - }).then(JSON.parse).then(function (results) { - expect(results).to.deep.equal({ - 'example1.com/_pages/0': JSON.stringify({ - layout: 'example1.com/a/b', - url: 'http://example1.com/x/y', - body: 'example1.com/c/d', - head: ['example1.com/e/f'] - }) - }); - }); - }); - - it('reads users from bootstrap', function () { - // this is doing IO, so give it more time. - this.slow(50); - this.timeout(400); - - return fn('./test/fixtures/config/bootstrap.yaml', sitesFake[0]).then(function () { - return db.pipeToPromise(db.list({prefix: '/_users', limit: -1})); - }).then(JSON.parse).then(function (results) { - expect(results).to.deep.equal({ - '/_users/Zm9vQGJhci5jb21AYmF6': JSON.stringify({ - username: 'foo@bar.com', - provider: 'baz', - auth: 'foobarbaz' - }) - }); - }); - }); - - it('reads components from bootstrap', function () { - // this is doing IO, so give it more time. - this.slow(50); - this.timeout(400); - - return fn('./test/fixtures/config/bootstrap.yaml', sitesFake[0]).then(function () { - return db.pipeToPromise(db.list({prefix: 'example1.com/_components', limit: -1})); - }).then(JSON.parse).then(function (results) { - expect(results).to.deep.include({ - // instance data of components (prefixes all the way down) - 'example1.com/_components/image/instances/0': '{"src":"http://placekitten.com/400/600","alt":"adorable kittens"}', - - // note the prefix added - 'example1.com/_components/image/instances/1': '{"_ref":"example1.com/_components/image2"}', - - // note the prefix NOT added - 'example1.com/_components/image/instances/2': '{"_ref":"localhost/_components/what"}', - - // base data of components - 'example1.com/_components/image2': '{"src":"http://placekitten.com/400/600","alt":"adorable kittens"}' - }); - }); - }); }); }); diff --git a/lib/render.js b/lib/render.js index 47e4748d..e3b7234e 100644 --- a/lib/render.js +++ b/lib/render.js @@ -1,6 +1,7 @@ 'use strict'; var uriRoutes, renderers, + db = require('./services/db'), log = require('./services/logger').setup({ file: __filename, action: 'render' @@ -11,7 +12,6 @@ const _ = require('lodash'), components = require('./services/components'), references = require('./services/references'), clayUtils = require('clayutils'), - db = require('./services/db'), composer = require('./services/composer'), responses = require('./responses'), mapLayoutToPageData = require('./utils/layout-to-page-data'); @@ -91,7 +91,7 @@ function renderComponent(req, res, hrStart) { * @return {Object} */ function getDBObject(uri) { - return db.get(uri).then(JSON.parse); + return db.get(uri); } /** @@ -362,3 +362,4 @@ module.exports.registerRenderers = registerRenderers; module.exports.resetUriRouteHandlers = resetUriRouteHandlers; module.exports.setUriRouteHandlers = setUriRouteHandlers; module.exports.assumePublishedUnlessEditing = assumePublishedUnlessEditing; +module.exports.setDb = mock => db = mock; diff --git a/lib/render.test.js b/lib/render.test.js index 182b13df..73819e60 100644 --- a/lib/render.test.js +++ b/lib/render.test.js @@ -3,7 +3,6 @@ const _ = require('lodash'), bluebird = require('bluebird'), filename = __filename.split('/').pop().split('.').shift(), - db = require('./services/db'), lib = require('./' + filename), expect = require('chai').expect, sinon = require('sinon'), @@ -11,11 +10,12 @@ const _ = require('lodash'), components = require('./services/components'), composer = require('./services/composer'), createMockReq = require('../test/fixtures/mocks/req'), - createMockRes = require('../test/fixtures/mocks/res'); + createMockRes = require('../test/fixtures/mocks/res'), + storage = require('../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { - let sandbox, + let sandbox, db, mockSite = 'mockSite'; /** @@ -40,7 +40,7 @@ describe(_.startCase(filename), function () { * @returns {Promise.string} */ function resolveString(str) { - return bluebird.resolve(JSON.stringify(str)); + return bluebird.resolve(str); } /** @@ -74,7 +74,8 @@ describe(_.startCase(filename), function () { beforeEach(function () { sandbox = sinon.sandbox.create(); - sandbox.stub(db); + db = storage(); + lib.setDb(db); sandbox.stub(components); sandbox.stub(composer); sandbox.stub(schema); diff --git a/lib/responses.js b/lib/responses.js index 0153fd7f..d836ea39 100644 --- a/lib/responses.js +++ b/lib/responses.js @@ -5,11 +5,11 @@ */ 'use strict'; -var log = require('./services/logger').setup({ - file: __filename -}); +var db = require('./services/db'), + log = require('./services/logger').setup({ + file: __filename + }); const _ = require('lodash'), - db = require('./services/db'), bluebird = require('bluebird'), filter = require('through2-filter'), map = require('through2-map'), @@ -371,7 +371,7 @@ function acceptJSONOnly(req, res, next) { * @param {object} res */ function expectText(fn, res) { - bluebird.try(fn).then(function (result) { + bluebird.try(fn).then(result => { res.send(result); }).catch(handleError(res)); } @@ -503,13 +503,15 @@ function listWithPublishedVersions(req, res) { * @param {object} res */ function listAsReferencedObjects(req, res) { - expectJSON(function () { - return db.pipeToPromise(db.list({ + expectJSON(() => { + var promise = db.pipeToPromise(db.list({ keys: true, values: true, isArray: true, prefix: req.uri - })).then(JSON.parse).then(convertDBListToReferenceObjects); + })); + + return promise.then(JSON.parse).then(convertDBListToReferenceObjects); }, res); } @@ -519,9 +521,7 @@ function listAsReferencedObjects(req, res) { * @param {object} res */ function getRouteFromDB(req, res) { - expectJSON(function () { - return db.get(req.uri).then(JSON.parse); - }, res); + expectJSON(() => db.get(req.uri), res); } /** @@ -553,7 +553,6 @@ function putRouteFromDB(req, res) { function deleteRouteFromDB(req, res) { expectJSON(function () { return db.get(req.uri) - .then(JSON.parse) .then(oldData => db.del(req.uri).return(oldData)); }, res); } @@ -595,6 +594,5 @@ module.exports.putRouteFromDB = putRouteFromDB; module.exports.deleteRouteFromDB = deleteRouteFromDB; // For testing -module.exports.setLog = function (fakeLogger) { - log = fakeLogger; -}; +module.exports.setLog = mock => log = mock; +module.exports.setDb = mock => db = mock; diff --git a/lib/responses.test.js b/lib/responses.test.js index 77ea8750..12c75e9a 100644 --- a/lib/responses.test.js +++ b/lib/responses.test.js @@ -3,16 +3,16 @@ const _ = require('lodash'), filename = __filename.split('/').pop().split('.').shift(), lib = require('./' + filename), - db = require('./services/db'), expect = require('chai').expect, sinon = require('sinon'), bluebird = require('bluebird'), filter = require('through2-filter'), createMockReq = require('../test/fixtures/mocks/req'), - createMockRes = require('../test/fixtures/mocks/res'); + createMockRes = require('../test/fixtures/mocks/res'), + storage = require('../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { - let sandbox, fakeLog; + let sandbox, fakeLog, db; /** * Shortcut @@ -50,12 +50,18 @@ describe(_.startCase(filename), function () { sandbox = sinon.sandbox.create(); fakeLog = sandbox.stub(); lib.setLog(fakeLog); + db = storage(); + lib.setDb(db); }); afterEach(function () { sandbox.restore(); }); + after(function () { + return db.clearMem(); + }); + describe('removeQueryString', function () { const fn = lib[this.title]; @@ -296,15 +302,15 @@ describe(_.startCase(filename), function () { const fn = lib[this.title]; beforeEach(function () { - return db.clear().then(function () { + return db.clearMem().then(function () { return bluebird.join( - db.put('base.com/a', 'b'), - db.put('base.com/aa', 'b'), - db.put('base.com/aaa', 'b'), - db.put('base.com/c', 'd'), - db.put('base.com/cc', 'd'), - db.put('base.com/ccc', 'd'), - db.put('base.com/e', 'f') + db.writeToInMem('base.com/a', 'b'), + db.writeToInMem('base.com/aa', 'b'), + db.writeToInMem('base.com/aaa', 'b'), + db.writeToInMem('base.com/c', 'd'), + db.writeToInMem('base.com/cc', 'd'), + db.writeToInMem('base.com/ccc', 'd'), + db.writeToInMem('base.com/e', 'f') ); }); }); @@ -363,15 +369,15 @@ describe(_.startCase(filename), function () { ]; beforeEach(function () { - return db.clear().then(function () { + return db.clearMem().then(function () { return bluebird.join( - db.put('/_users/a', 'b'), - db.put('/_users/aa', 'b'), - db.put('/_users/aaa', 'b'), - db.put('/_users/c', 'd'), - db.put('/_users/cc', 'd'), - db.put('/_users/ccc', 'd'), - db.put('/_users/e', 'f') + db.writeToInMem('/_users/a', 'b'), + db.writeToInMem('/_users/aa', 'b'), + db.writeToInMem('/_users/aaa', 'b'), + db.writeToInMem('/_users/c', 'd'), + db.writeToInMem('/_users/cc', 'd'), + db.writeToInMem('/_users/ccc', 'd'), + db.writeToInMem('/_users/e', 'f') ); }); }); diff --git a/lib/routes/_components.js b/lib/routes/_components.js index 76dd62dc..910a3659 100644 --- a/lib/routes/_components.js +++ b/lib/routes/_components.js @@ -72,9 +72,7 @@ route = _.bindAll({ * @param {object} res */ get(req, res) { - responses.expectJSON(function () { - return controller.get(req.uri, res.locals); - }, res); + responses.expectJSON(() => controller.get(req.uri, res.locals), res); }, /** diff --git a/lib/routes/_pages.js b/lib/routes/_pages.js index 1ab17561..d43120a3 100644 --- a/lib/routes/_pages.js +++ b/lib/routes/_pages.js @@ -22,7 +22,6 @@ const _ = require('lodash'), */ function getComposed(uri, locals) { return db.get(uri) - .then(JSON.parse) .then(pageData => composer.composePage(pageData, locals)); } diff --git a/lib/routes/_uris.js b/lib/routes/_uris.js index 06f406f6..5028a0fa 100644 --- a/lib/routes/_uris.js +++ b/lib/routes/_uris.js @@ -8,6 +8,7 @@ const responses = require('../responses'), controller = require('../services/uris'), + db = require('../services/db'), { withAuthLevel, authLevels } = require('../auth'); /** @@ -15,9 +16,7 @@ const responses = require('../responses'), * @param {object} res */ function getUriFromReference(req, res) { - responses.expectText(function () { - return controller.get(req.uri); - }, res); + responses.expectText(() => db.get(req.uri), res); } /** diff --git a/lib/services/components.js b/lib/services/components.js index d3c7b35f..4e08f99e 100644 --- a/lib/services/components.js +++ b/lib/services/components.js @@ -10,7 +10,6 @@ let timeoutConstant = 4000; const _ = require('lodash'), control = require('../control'), composer = require('./composer'), - db = require('./db'), uid = require('../uid'), files = require('../files'), timer = require('../timer'), @@ -24,9 +23,10 @@ const _ = require('lodash'), referenceProperty = '_ref', timeoutGetCoefficient = 2, timeoutPutCoefficient = 5; -var log = require('./logger').setup({ - file: __filename -}); +var db = require('./db'), + log = require('./logger').setup({ + file: __filename + }); /** * @returns {number} @@ -62,7 +62,6 @@ function get(uri, locals) { promise = bluebird.try(function () { return db.get(uri) - .then(JSON.parse) .then(upgrade.init(uri, locals)) // Run an upgrade! .then(function (data) { return componentModule.render(uri, data, locals); @@ -79,7 +78,7 @@ function get(uri, locals) { } }).timeout(timeoutLimit, `Component module GET exceeded ${timeoutLimit}ms: ${uri}`); } else { - promise = db.get(uri).then(JSON.parse).then(upgrade.init(uri, locals)); // Run an upgrade! + promise = db.get(uri).then(upgrade.init(uri, locals)); // Run an upgrade! } if (renderModel) { @@ -88,7 +87,7 @@ function get(uri, locals) { }); } - return promise.then(function (data) { + return promise.then(data => { if (!_.isObject(data)) { throw new Error(`Client: Invalid data type for component at ${uri} of ${typeof data}`); } @@ -326,7 +325,7 @@ function cascadingPut(uri, data, locals) { } // return ops if successful - return db.batch(ops).then(function () { + return db.batch(ops).then(() => { // return the value of the last batch operation (the root object) if successful const rootOp = _.last(ops); @@ -457,6 +456,5 @@ module.exports.setTimeoutConstant = setTimeoutConstant; module.exports.getTimeoutConstant = getTimeoutConstant; // For testing -module.exports.setLog = function (fakeLogger) { - log = fakeLogger; -}; +module.exports.setLog = mock => log = mock; +module.exports.setDb = mock => db = mock; diff --git a/lib/services/components.test.js b/lib/services/components.test.js index 7fdac9a1..ccdafcfc 100644 --- a/lib/services/components.test.js +++ b/lib/services/components.test.js @@ -6,17 +6,17 @@ const _ = require('lodash'), sinon = require('sinon'), files = require('../files'), siteService = require('./sites'), - db = require('./db'), timer = require('../timer'), bluebird = require('bluebird'), upgrade = require('./upgrade'), schema = require('../schema'), plugins = require('../plugins'), - expect = require('chai').expect; + expect = require('chai').expect, + storage = require('../../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { const timeoutConstant = 100; - let sandbox, + let sandbox, db, savedTimeoutConstant, fakeLog; @@ -24,13 +24,14 @@ describe(_.startCase(filename), function () { sandbox = sinon.sandbox.create(); fakeLog = sandbox.stub(); - sandbox.stub(db); sandbox.stub(siteService); sandbox.stub(files); sandbox.stub(timer); sandbox.stub(upgrade); sandbox.stub(schema); sandbox.stub(plugins); + db = storage(); + lib.setDb(db); lib.getSchema.cache = new _.memoize.Cache(); @@ -48,14 +49,14 @@ describe(_.startCase(filename), function () { const fn = lib[this.title]; it('deletes', function () { - db.get.returns(bluebird.resolve('{}')); + db.get.returns(bluebird.resolve({})); db.del.returns(bluebird.resolve()); files.getComponentModule.withArgs('whatever').returns(null); return fn('domain.com/path/_components/whatever'); }); it('deletes using component module', function () { - db.get.returns(bluebird.resolve('{}')); + db.get.returns(bluebird.resolve({})); files.getComponentModule.returns({del: _.constant(bluebird.resolve())}); return fn('domain.com/path/_components/whatever'); }); @@ -65,7 +66,7 @@ describe(_.startCase(filename), function () { locals = {}, delSpy = sandbox.spy(_.constant(bluebird.resolve())); - db.get.returns(bluebird.resolve('{}')); + db.get.returns(bluebird.resolve({})); files.getComponentModule.returns({del: delSpy}); return fn(ref, locals).then(function () { sinon.assert.called(files.getComponentModule); @@ -224,8 +225,8 @@ describe(_.startCase(filename), function () { deepData = {g: 'h'}; db.batch.returns(bluebird.resolve()); - db.get.withArgs(uri).returns(bluebird.resolve(JSON.stringify(data))); - db.get.withArgs(deepUri).returns(bluebird.resolve(JSON.stringify(deepData))); + db.get.withArgs(uri).returns(bluebird.resolve(data)); + db.get.withArgs(deepUri).returns(bluebird.resolve(deepData)); schema.getSchema.returns(Promise.resolve({})); return fn(uri).then(function (result) { @@ -244,8 +245,8 @@ describe(_.startCase(filename), function () { deepData = {g: 'h'}; db.batch.returns(bluebird.resolve()); - db.get.withArgs(uri).returns(bluebird.resolve(JSON.stringify(data))); - db.get.withArgs(deepUri).returns(bluebird.resolve(JSON.stringify(deepData))); + db.get.withArgs(uri).returns(bluebird.resolve(data)); + db.get.withArgs(deepUri).returns(bluebird.resolve(deepData)); schema.getSchema.returns(Promise.resolve({ _layout: false })); return fn(uri).then(function (result) { @@ -276,8 +277,8 @@ describe(_.startCase(filename), function () { deepData = {g: 'h'}; db.batch.returns(bluebird.resolve()); - db.get.withArgs(uri).returns(bluebird.resolve(JSON.stringify(data))); - db.get.withArgs(deepUri).returns(bluebird.resolve(JSON.stringify(deepData))); + db.get.withArgs(uri).returns(bluebird.resolve(data)); + db.get.withArgs(deepUri).returns(bluebird.resolve(deepData)); schema.getSchema.returns(Promise.resolve({ _layout: true })); return fn(uri, null, { user: { name: 'someone' }}).then(function () { @@ -291,7 +292,7 @@ describe(_.startCase(filename), function () { const fn = lib[this.title]; it('gets', function () { - db.get.returns(bluebird.resolve('{}')); + db.get.returns(bluebird.resolve({})); upgrade.init.returns(bluebird.resolve('{}')); files.getComponentModule.withArgs('whatever').returns(null); return fn('domain.com/path/_components/whatever'); @@ -306,7 +307,7 @@ describe(_.startCase(filename), function () { }); it('gets even with bad name', function () { - db.get.returns(bluebird.resolve('{}')); + db.get.returns(bluebird.resolve({})); files.getComponentModule.withArgs('whatever').returns(null); return fn('bad name'); }); @@ -315,7 +316,7 @@ describe(_.startCase(filename), function () { const ref = 'domain.com/path/_components/whatever', renderSpy = sinon.stub(); - db.get.returns(Promise.resolve(JSON.stringify({a: 'b'}))); + db.get.returns(Promise.resolve({a: 'b'})); upgrade.init.returns(Promise.resolve({a: 'b'})); renderSpy.returns({ a: 'b' }); files.getComponentModule.returns({render: renderSpy}); @@ -329,7 +330,7 @@ describe(_.startCase(filename), function () { const ref = 'domain.com/path/_components/whatever', renderSpy = sinon.stub(); - db.get.returns(Promise.resolve(JSON.stringify({a: 'b'}))); + db.get.returns(Promise.resolve({a: 'b'})); upgrade.init.returns(Promise.resolve({a: 'b'})); renderSpy.returns({ a: 'b' }); files.getComponentModule.returns(renderSpy); @@ -343,7 +344,7 @@ describe(_.startCase(filename), function () { const ref = 'domain.com/path/_components/whatever', renderSpy = sinon.stub(); - db.get.returns(Promise.resolve(JSON.stringify({}))); + db.get.returns(Promise.resolve({})); renderSpy.returns('abc'); files.getComponentModule.returns({render: renderSpy}); fn(ref).then(done).catch(function (error) { @@ -356,7 +357,7 @@ describe(_.startCase(filename), function () { const ref = 'domain.com/path/_components/whatever', renderSpy = sinon.stub(); - db.get.returns(bluebird.resolve('{}')); + db.get.returns(bluebird.resolve({})); files.getComponentModule.returns({render: renderSpy}); return fn(ref, { componenthooks: 'false' }).then(function () { sinon.assert.called(files.getComponentModule); @@ -368,7 +369,7 @@ describe(_.startCase(filename), function () { const ref = 'domain.com/path/_components/whatever', render = sandbox.stub().returns(bluebird.resolve({})); - db.get.returns(bluebird.resolve('{}')); + db.get.returns(bluebird.resolve({})); timer.getMillisecondsSince.returns(timeoutConstant * 3); upgrade.init.returns(bluebird.resolve({})); files.getComponentModule.returns({ render }); @@ -385,7 +386,7 @@ describe(_.startCase(filename), function () { renderSpy = sinon.stub(), data = {a: 'b'}; - db.get.returns(Promise.resolve(JSON.stringify(data))); + db.get.returns(Promise.resolve(data)); renderSpy.returns({ _ref: ref, a: 'b' }); files.getComponentModule.returns({render: renderSpy}); return fn(ref, locals).then(function () { diff --git a/lib/services/db.js b/lib/services/db.js index 40fa9a6c..00152d3f 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -10,13 +10,7 @@ const _ = require('lodash'), jsonTransform = require('./../streams/json-transform'), - Eventify = require('eventify'), - validation = require('../validation'), - promiseDefer = require('../utils/defer'), - plugins = require('../plugins'), - bus = require('./bus'), - publishedVersionSuffix = '@published'; -let db = require('levelup')('whatever', { db: require('memdown') }); + promiseDefer = require('../utils/defer'); /** * Use ES6 promises @@ -46,47 +40,12 @@ function pipeToPromise(pipe) { const d = defer(); let str = ''; - pipe.on('data', function (data) { str += data; }) + pipe.on('data', data => str += data) .on('error', d.reject) - .on('end', function () { d.resolve(str); }); + .on('end', () => d.resolve(str)); return d.promise; } -/** - * @param {string} key - * @param {string} value - * @returns {Promise} - */ -function put(key, value) { - validation.assertValidValue('put', value); - const deferred = defer(); - - db.put(key, value, deferred.apply); - return deferred.promise; -} - -/** - * @param {string} key - * @returns {Promise} - */ -function get(key) { - const deferred = defer(); - - db.get(key, deferred.apply); - return deferred.promise; -} - -/** - * @param {string} key - * @returns {Promise} - */ -function del(key) { - const deferred = defer(); - - db.del(key, deferred.apply); - return deferred.promise; -} - /** * Get a read stream of all the keys. * @@ -101,8 +60,8 @@ function del(key) { * @returns {ReadStream} */ /* eslint-disable complexity */ -function list(options) { - options = _.defaults(options || {}, { +function list(options = {}) { + options = _.defaults(options, { limit: -1, keys: true, values: true, @@ -128,7 +87,7 @@ function list(options) { transformOptions.isArray = true; } - readStream = db.createReadStream(options); + readStream = module.exports.createReadStream(options); if (_.isFunction(options.transforms)) { options.transforms = options.transforms(); @@ -148,146 +107,15 @@ function list(options) { return readStream; } -/** - * @param {Array} ops - * @param {object} [options] - * @returns {Promise} - */ -function batch(ops, options) { - validation.assertValidBatchOps(ops); - const deferred = defer(); - - db.batch(ops, options || {}, deferred.apply); - - return deferred.promise; -} - -/** - * Clear all records from the DB. (useful for unit testing) - * @returns {Promise} - */ -function clear() { - const errors = [], - ops = [], - deferred = defer(); - - db.createReadStream({ - keys:true, - fillCache: false, - limit: -1 - }).on('data', function (data) { - ops.push({ type: 'del', key: data.key}); - }).on('error', function (error) { - errors.push(error); - }).on('end', function () { - if (errors.length) { - deferred.apply(_.head(errors)); - } else { - db.batch(ops, deferred.apply); - } - }); - - return deferred.promise; -} - -/** - * - * @param {object} hookOps - * @param {Array} hookOps.put - * @param {Array} hookOps.del - * @param {object} op - * @param {string} op.type - * @param {string} op.key - * @param {*} op.value - * @returns {object} - */ -function addToHookOps(hookOps, op) { - hookOps[op.type].push(op); - return hookOps; -} - -/** - * - * @param {string} type - * @param {string} key - * @param {*} value - * @returns {[{}]} - */ -function singleOpToBatch(type, key, value) { - return [{type: type, key: key, value: value}]; -} - -/** - * reduce database operations to key-value arrays sorted by operation type - * defines what the plugin hooks expect to receive - * @param {string} method - * @param {Array} args args from put, del, or batch - * @returns {{put: [{key: value}], del: [{key: value}]}} - */ -function dbOpsToHookOps(method, args) { - var hookOps = { - put: [], - del: [] - }, - batchOps = method === 'batch' ? args[0] : singleOpToBatch(method, args[0], args[1]); - - return _.reduce(batchOps, addToHookOps, hookOps); -} - -/** - * - * @param {string} method - * @param {Array} args - */ -function triggerPlugins(method, args) { - const hookOps = dbOpsToHookOps(method, args), - // assumes page publish batches always have page put as the last op - lastPutKey = _.get(_.last(hookOps.put), 'key'), - isPublishedPageBatch = method === 'batch' && hookOps.put.length > 1 && _.includes(lastPutKey, '/_pages/') && _.endsWith(lastPutKey, publishedVersionSuffix); - - if (hookOps.put.length) { - if (isPublishedPageBatch) { - plugins.executeHook('publish', { - uri: lastPutKey, - ops: hookOps.put - }); - } else { - plugins.executeHook('save', hookOps.put); - bus.publish('save', JSON.stringify(hookOps.put)); - } +function registerStorage(storage) { + for (let action in storage) { + module.exports[action] = storage[action]; } - if (hookOps.del.length) { - plugins.executeHook('delete', hookOps.del); - bus.publish('delete', JSON.stringify(hookOps.del)); - } -} - -/** - * notifies outside listeners through eventify and amphora plugins if operation is successful - * @param {string} method - */ -function exposeMethodToOutsideListeners(method) { - module.exports[method] = _.wrap(module.exports[method], function (fn) { - const args = _.slice(arguments, 1); - - return fn.apply(module.exports, args).then(function (result) { - module.exports.trigger.apply(module.exports, [method].concat(args)); // Eventify - triggerPlugins(method, args); - return result; - }); - }); } -module.exports.get = get; -module.exports.put = put; -module.exports.del = del; module.exports.list = list; -module.exports.batch = batch; -module.exports.clear = clear; -module.exports.getDB = function () { return db; }; -module.exports.setDB = function (value) { db = value; }; module.exports.pipeToPromise = pipeToPromise; +module.exports.registerStorage = registerStorage; -Eventify.enable(module.exports); - -_.each(['put', 'batch', 'del'], exposeMethodToOutsideListeners); +// For testing +module.exports.defer = defer; diff --git a/lib/services/db.test.js b/lib/services/db.test.js index 3d6202c8..f473fb29 100644 --- a/lib/services/db.test.js +++ b/lib/services/db.test.js @@ -6,234 +6,78 @@ const _ = require('lodash'), plugins = require('../plugins'), bluebird = require('bluebird'), expect = require('chai').expect, - sinon = require('sinon'); + sinon = require('sinon'), + storage = require('../../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { - let sandbox; + let sandbox, db; beforeEach(function () { sandbox = sinon.sandbox.create(); sandbox.stub(plugins, 'executeHook'); - return lib.clear(); + db = storage(); + lib.registerStorage(db); + // return lib.clear(); }); afterEach(function () { sandbox.restore(); - lib.off(); + return db.clearMem(); }); after(function () { - return lib.clear(); + return db.clearMem(); }); - it('can put and get strings', function () { - return lib.put('1', '2').then(function () { - return lib.get('1'); - }).done(function (result) { - expect(result).to.equal('2'); - }); - }); - - it('can put and get event', function () { - const spy = sandbox.spy(); - - lib.on('put', spy); - - return lib.put('1', '2').then(function () { - sinon.assert.calledOnce(spy); - }); - }); - - it('can put and del strings', function () { - return lib.put('1', '2').then(function () { - return lib.del('1'); - }).done(function (result) { - expect(result).to.equal(undefined); - }); - }); - - it('cannot get deleted strings', function (done) { - lib.put('1', '2').then(function () { - return lib.del('1'); - }).then(function () { - return lib.get('1'); - }).done(done, function (err) { - expect(err.name).to.equal('NotFoundError'); - done(); - }); - }); - - it('can batch and get event', function () { - const spy = sandbox.spy(); - - lib.on('batch', spy); - - return lib.batch([{type: 'put', key: 'a', value: 'b'}]).then(function () { - sinon.assert.calledOnce(spy); - }); - }); - - describe('put', function () { - const fn = lib[this.title]; - - it('executes save plugin hook', function () { - return fn('key','val').then(() => { - expect(plugins.executeHook.firstCall.args).to.deep.equal([ - 'save', [{type: 'put', key: 'key', value: 'val'}] - ]); - }); - }); - it('executes only save plugin hook for @published', function () { - return fn('/_pages/key@published','val').then(() => { - expect(plugins.executeHook.firstCall.args).to.deep.equal([ - 'save', [{type: 'put', key: '/_pages/key@published', value: 'val'}] - ]); - expect(plugins.executeHook.callCount).to.equal(1); - }); - }); - }); - - describe('batch', function () { - const fn = lib[this.title]; - - it('executes save plugin hook', function () { - const ops = [{type: 'put', key: 'key', value: 'val'}]; - - return fn(ops).then(() => { - expect(plugins.executeHook.firstCall.args).to.deep.equal([ - 'save', ops - ]); - }); - }); - it('executes only save plugin hook if not a page publish batch', function () { - const ops = [{type: 'put', key: 'key@published', value: 'val'}]; - - return fn(ops).then(() => { - expect(plugins.executeHook.firstCall.args).to.deep.equal([ - 'save', ops - ]); - expect(plugins.executeHook.callCount).to.equal(1); - }); - }); - it('executes only save plugin hook if only one put operation', function () { - const ops = [ - {type: 'put', key: '/_pages/key@published', value: 'val'} - ]; - - return fn(ops).then(() => { - expect(plugins.executeHook.firstCall.args).to.deep.equal([ - 'save', ops - ]); - expect(plugins.executeHook.callCount).to.equal(1); - }); - }); - it('executes publish plugin hooks for page publish batch', function () { - const ops = [ - {type: 'put', key: 'key@published', value: 'val'}, - {type: 'put', key: '/_pages/key@published', value: 'val'} - ]; - - return fn(ops).then(() => { - expect(plugins.executeHook.firstCall.args).to.deep.equal([ - 'publish', { - uri: '/_pages/key@published', - ops: ops - } - ]); - }); - }); - }); - - describe('del', function () { - const fn = lib[this.title]; - - it('executes delete plugin hook', function () { - return fn('key','val').then(() => { - expect(plugins.executeHook.firstCall.args).to.deep.equal([ - 'delete', [{type: 'del', key: 'key', value: 'val'}] - ]); - }); - }); - }); - - describe('clear', function () { - const fn = lib[this.title]; - - it('handles db errors as promise', function (done) { - let on, mockOn; - - // fake pipe; - on = _.noop; - on.on = on; - sandbox.stub(lib.getDB(), 'createReadStream').callsFake(_.constant(on)); - mockOn = sandbox.mock(on); - mockOn.expects('on').withArgs('data', sinon.match.func).yields('some data').exactly(1).returns(on); - mockOn.expects('on').withArgs('error', sinon.match.func).yields('whatever').exactly(1).returns(on); - mockOn.expects('on').withArgs('end', sinon.match.func).yields().exactly(1).returns(on); - - fn().done(done, function () { - sandbox.verify(); - done(); - }); - }); - - it('deletes all records', function () { - return lib.put('1', '2').then(function () { - return fn(); - }).then(function () { - return bluebird.settle([lib.get('1'), lib.get('2')]); - }).spread(function (get1, get2) { - - expect(get1.isRejected()).to.equal(true); - expect(get2.isRejected()).to.equal(true); - - }); - }); - }); + // describe('registerStorage', function () { + // const fn = lib[this.title]; + // + // it('registers a sto', function () { + // fn({}); + // + // }); + // }); describe('list', function () { const fn = lib[this.title]; it('default behaviour', function () { - return bluebird.join(lib.put('1', '2'), lib.put('3', '4'), lib.put('5', '6')) - .then(function () { - return lib.pipeToPromise(fn()); - }).then(function (str) { + return bluebird.join(db.writeToInMem('1', '2'), db.writeToInMem('3', '4'), db.writeToInMem('5', '6')) + .then(() => lib.pipeToPromise(fn())) + .then(str => { expect(str).to.equal('{"1":"2","3":"4","5":"6"}'); }); }); it('can get keys-only in array structure', function () { - return bluebird.join(lib.put('1', '2'), lib.put('3', '4'), lib.put('5', '6')) - .then(function () { - return lib.pipeToPromise(fn({keys: true, values: false})); - }).then(function (str) { + return bluebird.join(db.writeToInMem('1', '2'), db.writeToInMem('3', '4'), db.writeToInMem('5', '6')) + .then(() => lib.pipeToPromise(fn({keys: true, values: false}))) + .then(str => { expect(str).to.equal('["1","3","5"]'); }); }); it('can get values-only in array structure', function () { - return bluebird.join(lib.put('1', '2'), lib.put('3', '4'), lib.put('5', '6')) - .then(function () { - return lib.pipeToPromise(fn({keys: false, values: true})); - }).then(function (str) { + return bluebird.join(db.writeToInMem('1', '2'), db.writeToInMem('3', '4'), db.writeToInMem('5', '6')) + .then(() => lib.pipeToPromise(fn({keys: false, values: true}))) + .then(str => { expect(str).to.equal('["2","4","6"]'); }); }); it('can get key-value in object structure in array', function () { - return bluebird.join(lib.put('1', '2'), lib.put('3', '4'), lib.put('5', '6')) - .then(function () { - return lib.pipeToPromise(fn({isArray: true})); - }).then(function (str) { + return bluebird.join(db.writeToInMem('1', '2'), db.writeToInMem('3', '4'), db.writeToInMem('5', '6')) + .then(() => lib.pipeToPromise(fn({isArray: true}))) + .then(str => { expect(str).to.equal('[{"key":"1","value":"2"},{"key":"3","value":"4"},{"key":"5","value":"6"}]'); }); }); it('can return empty data safely for arrays', function () { - return lib.pipeToPromise(fn({isArray: true})).then(function (str) { - expect(str).to.equal('[]'); - }); + return lib.pipeToPromise(fn({isArray: true})) + .then(str => { + expect(str).to.equal('[]'); + }); }); it('can return empty data safely for objects', function () { @@ -247,8 +91,8 @@ describe(_.startCase(filename), function () { onEnd = () => done(), onError = done; - lib.put('1', '2') - .then(()=>{ + db.writeToInMem('1', '2') + .then(() => { const pipe = fn({ json: false }); @@ -259,14 +103,4 @@ describe(_.startCase(filename), function () { }); }); }); - - describe('setDB', function () { - const fn = lib[this.title]; - - it('sets', function () { - expect(function () { - fn(lib.getDB()); - }).to.not.throw(); - }); - }); }); diff --git a/lib/services/pages.js b/lib/services/pages.js index 03f0043c..03211d24 100644 --- a/lib/services/pages.js +++ b/lib/services/pages.js @@ -3,13 +3,13 @@ var timeoutConstant = 4000, log = require('./logger').setup({ file: __filename - }); + }), + db = require('./db'); const _ = require('lodash'), buf = require('./buffer'), bluebird = require('bluebird'), components = require('./components'), composer = require('./composer'), - db = require('./db'), notifications = require('./notifications'), references = require('./references'), siteService = require('./sites'), @@ -161,7 +161,7 @@ function replacePageReferenceVersions(data, version) { * @returns {Promise} */ function getLatestData(uri) { - return db.get(replaceVersion(uri)).then(JSON.parse); + return db.get(replaceVersion(uri)); } /** @@ -254,8 +254,8 @@ function publish(uri, data, locals) { return getPublishData(uri, data) - .then(publishService(uri, locals, site)) - .then(function (pageData) { + .then(publishService.resolvePublishUrl(uri, locals, site)) + .then(pageData => { const published = 'published', publicUrl = pageData.customUrl || pageData.url, dynamicPage = pageData._dynamic, @@ -316,10 +316,8 @@ function create(uri, data, locals) { } return components.get(layoutReference, locals).then(function () { - return getPageClonePutOperations(pageData, locals).then(function (ops) { pageData.layout = layoutReference; - return addOp(pageReference, pageData, ops); }); }).then(applyBatch(site)).then(function (newPage) { @@ -384,6 +382,5 @@ module.exports.getTimeoutConstant = getTimeoutConstant; module.exports.setTimeoutConstant = setTimeoutConstant; // For testing -module.exports.setLog = function (fakeLogger) { - log = fakeLogger; -}; +module.exports.setLog = mock => log = mock; +module.exports.setDb = mock => db = mock; diff --git a/lib/services/pages.test.js b/lib/services/pages.test.js index 6da5a898..a537044b 100644 --- a/lib/services/pages.test.js +++ b/lib/services/pages.test.js @@ -3,7 +3,6 @@ const _ = require('lodash'), bluebird = require('bluebird'), components = require('./components'), - db = require('../services/db'), expect = require('chai').expect, filename = __filename.split('/').pop().split('.').shift(), lib = require('./' + filename), @@ -12,24 +11,28 @@ const _ = require('lodash'), siteService = require('./sites'), timer = require('../timer'), plugins = require('../plugins'), - schema = require('../schema'); + schema = require('../schema'), + publishService = require('./publish'), + storage = require('../../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { const timeoutConstant = 100; - let sandbox, + let sandbox, db, savedTimeoutConstant, fakeLog; beforeEach(function () { sandbox = sinon.sandbox.create(); fakeLog = sandbox.stub(); - sandbox.stub(db); sandbox.stub(components, 'get'); sandbox.stub(siteService, 'getSiteFromPrefix'); sandbox.stub(notifications, 'notify'); sandbox.stub(timer); sandbox.stub(plugins); sandbox.stub(schema); + sandbox.stub(publishService, 'resolvePublishUrl'); + db = storage(); + lib.setDb(db); savedTimeoutConstant = lib.getTimeoutConstant(); lib.setTimeoutConstant(timeoutConstant); @@ -132,17 +135,20 @@ describe(_.startCase(filename), function () { const fn = lib[this.title], locals = { site: { - resolvePublishUrl: [ () => Promise.resolve('http://some-domain.com') ] + resolvePublishUrl: [] } }; it('creates relevant uri', function () { + var pageData = {layout: 'domain.com/path/_components/thing'}; + components.get.returns(bluebird.resolve({})); - db.get.returns(Promise.reject()); + db.get.returns(Promise.resolve()); db.batch.returns(bluebird.resolve()); notifications.notify.returns(bluebird.resolve()); + publishService.resolvePublishUrl.returns(Promise.resolve(Object.assign(pageData, { url: 'http://some-domain.com/'}))); - return fn('domain.com/path/_pages/thing@published', {layout: 'domain.com/path/_components/thing'}, locals) + return fn('domain.com/path/_pages/thing@published', pageData, locals) .then(function () { const ops = db.batch.args[0][0], secondLastOp = ops[ops.length - 2]; @@ -157,7 +163,7 @@ describe(_.startCase(filename), function () { it('publishes dynamic pages', function () { components.get.returns(bluebird.resolve({})); - db.get.returns(Promise.reject()); + db.get.returns(Promise.resolve()); db.batch.returns(bluebird.resolve()); notifications.notify.returns(bluebird.resolve()); @@ -176,26 +182,32 @@ describe(_.startCase(filename), function () { }); it('warns if publish is slow', function () { + var pageData = {layout: 'domain.com/path/_components/thing'}; + components.get.returns(bluebird.resolve({})); - db.get.returns(Promise.reject()); + db.get.returns(Promise.resolve()); db.batch.returns(bluebird.resolve()); notifications.notify.returns(bluebird.resolve()); timer.getMillisecondsSince.returns(timeoutConstant * 7); + publishService.resolvePublishUrl.returns(Promise.resolve(Object.assign(pageData, { url: 'http://some-domain.com/'}))); - return fn('domain.com/path/_pages/thing@published', {layout: 'domain.com/path/_components/thing'}, locals) + return fn('domain.com/path/_pages/thing@published', pageData, locals) .then(function () { sinon.assert.calledWith(fakeLog, 'warn', sinon.match('slow publish domain.com/path/_pages/thing@published 700ms')); }); }); it('logs if publish is not slow', function () { + var pageData = {layout: 'domain.com/path/_components/thing'}; + components.get.returns(bluebird.resolve({})); - db.get.returns(Promise.reject()); + db.get.returns(Promise.resolve()); db.batch.returns(bluebird.resolve()); notifications.notify.returns(bluebird.resolve()); timer.getMillisecondsSince.returns(20); + publishService.resolvePublishUrl.returns(Promise.resolve(Object.assign(pageData, { url: 'http://some-domain.com/'}))); - return fn('domain.com/path/_pages/thing@published', {layout: 'domain.com/path/_components/thing'}, locals) + return fn('domain.com/path/_pages/thing@published', pageData, locals) .then(function () { sinon.assert.calledWith(fakeLog, 'info', sinon.match('published domain.com/path/_pages/thing 20ms')); }); @@ -203,15 +215,17 @@ describe(_.startCase(filename), function () { it('warns if notification fails', function () { const site = {}, - uri = 'domain.com/path/_pages/thing@published'; + uri = 'domain.com/path/_pages/thing@published', + pageData = {layout: 'domain.com/path/_components/thing'}; components.get.returns(bluebird.resolve({})); - db.get.returns(Promise.reject()); + db.get.returns(Promise.resolve()); db.batch.returns(bluebird.resolve()); siteService.getSiteFromPrefix.returns(site); notifications.notify.returns(bluebird.reject(new Error('hello!'))); + publishService.resolvePublishUrl.returns(Promise.resolve(Object.assign(pageData, { url: 'http://some-domain.com/'}))); - return fn(uri, {layout: 'domain.com/path/_components/thing'}, locals) + return fn(uri, pageData, locals) .then(function () { sinon.assert.calledWith(fakeLog, 'warn'); sinon.assert.calledTwice(fakeLog); // it's called once with info @@ -219,26 +233,31 @@ describe(_.startCase(filename), function () { }); it('notifies', function () { - const uri = 'domain.com/path/_pages/thing@published'; + const uri = 'domain.com/path/_pages/thing@published', + pageData = {layout: 'domain.com/path/_components/thing'}; components.get.returns(bluebird.resolve({})); - db.get.returns(Promise.reject()); + db.get.returns(Promise.resolve()); db.batch.returns(bluebird.resolve()); siteService.getSiteFromPrefix.returns({}); notifications.notify.returns(bluebird.resolve()); + publishService.resolvePublishUrl.returns(Promise.resolve(Object.assign(pageData, { url: 'http://some-domain.com/'}))); - return fn(uri, {layout: 'domain.com/path/_components/thing'}, locals) + return fn(uri, pageData, locals) .then(function (result) { sinon.assert.calledWith(notifications.notify, locals.site, 'published', result); }); }); it('throws on empty data', function (done) { + var pageData = {layout: 'domain.com/path/_components/thing', head: ['']}; + components.get.returns(bluebird.resolve({})); db.batch.returns(bluebird.resolve()); siteService.getSiteFromPrefix.returns({notify: _.noop}); + publishService.resolvePublishUrl.returns(Promise.resolve(Object.assign(pageData, { url: 'http://some-domain.com/'}))); - fn('domain.com/path/_pages/thing@published', {layout: 'domain.com/path/_components/thing', head: ['']}, { site: {} }) + fn('domain.com/path/_pages/thing@published', pageData, { site: {} }) .then(done) .catch(function (result) { expect(result.message).to.equal('Client: page cannot have empty values'); @@ -247,31 +266,37 @@ describe(_.startCase(filename), function () { }); it('publishes with provided data', function () { + var pageData = {layout: 'domain.com/path/_components/thing'}; + components.get.returns(bluebird.resolve({})); - db.get.returns(Promise.reject()); + db.get.returns(Promise.resolve()); db.batch.returns(bluebird.resolve()); siteService.getSiteFromPrefix.returns({notify: _.noop}); notifications.notify.returns(bluebird.resolve()); + publishService.resolvePublishUrl.returns(Promise.resolve(Object.assign(pageData, { url: 'http://some-domain.com/'}))); - return fn('domain.com/path/_pages/thing@published', {layout: 'domain.com/path/_components/thing'}, locals) + return fn('domain.com/path/_pages/thing@published', pageData, locals) .then(function (result) { expect(result.layout).to.equal('domain.com/path/_components/thing@published'); }); }); it('publishes without provided data', function () { + var pageData = { + layout: 'domain.com/path/_components/thing@published', + url: 'http://some-domain.com' + }; + components.get.returns(bluebird.resolve({})); - db.get.returns(bluebird.resolve(JSON.stringify({layout: 'domain.com/path/_components/thing', url: 'http://some-domain.com'}))); + db.get.returns(bluebird.resolve({layout: 'domain.com/path/_components/thing', url: 'http://some-domain.com'})); db.batch.returns(bluebird.resolve()); siteService.getSiteFromPrefix.returns({notify: _.noop}); notifications.notify.returns(bluebird.resolve()); - return fn('domain.com/path/_pages/thing@published', {}, locals).then(function (result) { - expect(result).to.deep.equal({ - layout: 'domain.com/path/_components/thing@published', - url: 'http://some-domain.com', - urlHistory: [ 'http://some-domain.com' ] - }); + publishService.resolvePublishUrl.returns(Promise.resolve(pageData)); + + return fn('domain.com/path/_pages/thing@published', {}, locals).then(result => { + expect(result).to.deep.equal(pageData); }); }); diff --git a/lib/services/publish.js b/lib/services/publish.js index 6b2f067a..828ba40a 100644 --- a/lib/services/publish.js +++ b/lib/services/publish.js @@ -8,10 +8,10 @@ const _ = require('lodash'), bluebird = require('bluebird'), { parse } = require('url'), - db = require('./db'), buf = require('./buffer'), chain = require('chain-of-promises'), { getPrefix } = require('clayutils'); +var db = require('./db'); /** * grab any old urls from the published page (if it exist) and save them in the current page @@ -24,7 +24,6 @@ const _ = require('lodash'), function storeUrlHistory(url, uri, data) { // get the published page (if it exists) and update the urlHistory array return db.get(uri) - .then(JSON.parse) .catch(function () { // if this is the first time we're publishing, db.get() won't find // the published page data. thus, return empty object so we can add the urlHistory @@ -198,9 +197,10 @@ function resolvePublishUrl(uri, locals, site) { }; } -module.exports = resolvePublishUrl; +module.exports.resolvePublishUrl = resolvePublishUrl; // For testing module.exports.getPassthroughPageUrl = getPassthroughPageUrl; module.exports.publishDynamicPage = publishDynamicPage; module.exports.modifyPublishedData = modifyPublishedData; +module.exports.setDb = mock => db = mock; diff --git a/lib/services/publish.test.js b/lib/services/publish.test.js index 7a57df34..bc620af7 100644 --- a/lib/services/publish.test.js +++ b/lib/services/publish.test.js @@ -4,14 +4,15 @@ const _ = require('lodash'), sinon = require('sinon'), expect = require('chai').expect, filename = __filename.split('/').pop().split('.').shift(), - db = require('../services/db'), - lib = require(`./${filename}`); + lib = require(`./${filename}`), + storage = require('../../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { let sandbox, fakeSite, pubRule, - modifyFn; + modifyFn, + db; function makeFakeSite() { pubRule = sandbox.stub(); @@ -29,8 +30,8 @@ describe(_.startCase(filename), function () { beforeEach(function () { sandbox = sinon.sandbox.create(); - sandbox.stub(db); - + db = storage(); + lib.setDb(db); makeFakeSite(); }); @@ -42,7 +43,7 @@ describe(_.startCase(filename), function () { it('returns page data when a site has no `resolvePublishUrl` function', function functionName() { const fakePage = { page: 'data', url: 'http://cool/url' }; - return lib('some/uri', {}, {})(_.cloneDeep(fakePage)) + return lib.resolvePublishUrl('some/uri', {}, {})(_.cloneDeep(fakePage)) .then(function (resp) { expect(resp).to.eql(fakePage); }); @@ -54,9 +55,9 @@ describe(_.startCase(filename), function () { pubRule.returns(fakeUrl); modifyFn.returnsArg(0); - db.get.returns(Promise.resolve(JSON.stringify(fakePage))); + db.get.returns(Promise.resolve(fakePage)); - return lib('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) + return lib.resolvePublishUrl('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) .then(function (resp) { sinon.assert.calledOnce(pubRule); expect(resp).to.eql(_.assign(fakePage, {url: fakeUrl,urlHistory: [ fakeUrl ] })); @@ -69,9 +70,9 @@ describe(_.startCase(filename), function () { pubRule.returns(fakeUrl); modifyFn.rejects(new Error('An error occured')); - db.get.returns(Promise.resolve(JSON.stringify(fakePage))); + db.get.returns(Promise.resolve(fakePage)); - return lib('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) + return lib.resolvePublishUrl('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) .catch(function (e) { sinon.assert.calledOnce(pubRule); expect(e.message).to.equal('An error occured'); @@ -86,10 +87,10 @@ describe(_.startCase(filename), function () { pubRule.returns(fakeUrl); modifyFn.returnsArg(0); - db.get.returns(Promise.resolve(JSON.stringify(fakePage))); + db.get.returns(Promise.resolve(fakePage)); - return lib('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) - .then(function () { + return lib.resolvePublishUrl('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) + .then(() => { sinon.assert.calledOnce(db.put); }); }); @@ -101,9 +102,9 @@ describe(_.startCase(filename), function () { pubRule.returns(fakeUrl); modifyFn.returnsArg(0); - db.get.returns(Promise.resolve(JSON.stringify(fakePage))); + db.get.returns(Promise.resolve(fakePage)); - return lib('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) + return lib.resolvePublishUrl('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) .then(function (resp) { expect(resp).to.eql(_.assign(fakePage, {url: fakeUrl})); }); @@ -117,7 +118,7 @@ describe(_.startCase(filename), function () { modifyFn.returnsArg(0); db.get.rejects(new Error('not found')); - return lib('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) + return lib.resolvePublishUrl('some/uri', {}, fakeSite)(_.cloneDeep(fakePage)) .then(function (resp) { expect(resp).to.eql(_.assign(fakePage, {urlHistory: [fakeUrl]})); }); @@ -127,7 +128,7 @@ describe(_.startCase(filename), function () { const fakePage = {page: 'data', _dynamic: true}, locals = {}; - return lib('some/uri', locals, fakeSite)(_.cloneDeep(fakePage)) + return lib.resolvePublishUrl('some/uri', locals, fakeSite)(_.cloneDeep(fakePage)) .then(function (resp) { expect(resp._dynamic).to.be.true; expect(locals.isDynamicPublishUrl).to.be.true; diff --git a/lib/services/references.js b/lib/services/references.js index a354e30f..b88da4a0 100644 --- a/lib/services/references.js +++ b/lib/services/references.js @@ -2,7 +2,7 @@ const _ = require('lodash'), urlParse = require('url'), - { replaceVersion, getPrefix } = require('clayutils'); + { replaceVersion } = require('clayutils'); let propagatingVersions = ['published', 'latest']; /** @@ -155,7 +155,3 @@ module.exports.urlToUri = urlToUri; module.exports.omitPageConfiguration = omitPageConfiguration; module.exports.getPageReferences = getPageReferences; module.exports.listDeepObjects = listDeepObjects; - -// TODO: Remove these on next major release -module.exports.getPagePrefix = getPrefix; -module.exports.getUriPrefix = getPrefix; diff --git a/lib/services/schedule.js b/lib/services/schedule.js index c55865f7..989078c2 100644 --- a/lib/services/schedule.js +++ b/lib/services/schedule.js @@ -4,11 +4,11 @@ var interval, intervalDelay = 50000 + Math.floor(Math.random() * 10000), // just under one minute log = require('./logger').setup({ file: __filename - }); + }), + db = require('./db'); const _ = require('lodash'), bluebird = require('bluebird'), { replaceVersion, isPage } = require('clayutils'), - db = require('./db'), references = require('./references'), buf = require('./buffer'), rest = require('../rest'), @@ -95,7 +95,7 @@ function createScheduleObjectKey(uri, data) { * @returns {Promise} */ function del(uri, user) { - return db.get(uri).then(JSON.parse).then(function (data) { + return db.get(uri).then(function (data) { const targetUri = references.urlToUri(data[publishProperty]), targetReference = replaceVersion(targetUri, scheduledVersion), ops = [ @@ -178,7 +178,7 @@ function startListening() { keys: true, values: true, isArray: true - })).then(JSON.parse) + })) .then(publishEachByTime) .catch(function (error) { log('error', `failed to publish: ${error.message}`); @@ -205,6 +205,5 @@ module.exports.stopListening = stopListening; module.exports.setScheduleInterval = setScheduleInterval; // For testing -module.exports.setLog = function (fakeLogger) { - log = fakeLogger; -}; +module.exports.setLog = mock => log = mock; +module.exports.setDb = mock => db = mock; diff --git a/lib/services/schedule.test.js b/lib/services/schedule.test.js index e1ffd555..9db6ca44 100644 --- a/lib/services/schedule.test.js +++ b/lib/services/schedule.test.js @@ -2,16 +2,16 @@ const _ = require('lodash'), bluebird = require('bluebird'), - db = require('./db'), expect = require('chai').expect, filename = __filename.split('/').pop().split('.').shift(), lib = require('./' + filename), rest = require('../rest'), sinon = require('sinon'), - siteService = require('./sites'); + siteService = require('./sites'), + storage = require('../../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { - let sandbox, + let sandbox, db, intervalDelay = 100; beforeEach(function () { @@ -19,12 +19,11 @@ describe(_.startCase(filename), function () { lib.setLog(_.noop); sandbox.useFakeTimers(); lib.setScheduleInterval(intervalDelay); - sandbox.stub(db, 'get'); - sandbox.stub(db, 'put'); - sandbox.stub(db, 'batch'); - sandbox.stub(db, 'pipeToPromise'); sandbox.stub(siteService, 'sites'); sandbox.stub(rest); + db = storage(); + lib.setDb(db); + sandbox.stub(db, 'pipeToPromise'); }); afterEach(function () { @@ -86,7 +85,7 @@ describe(_.startCase(filename), function () { ref = 'domain/_schedule/some-specific-id', data = {at: 123, publish: publishTarget}; - db.get.returns(bluebird.resolve(JSON.stringify({at: publishDate, publish: publishTarget}))); + db.get.returns(bluebird.resolve({at: publishDate, publish: publishTarget})); db.batch.returns(bluebird.resolve()); return fn(ref, data).then(function () { @@ -118,27 +117,25 @@ describe(_.startCase(filename), function () { rest.putObject = () => done(); // call done without passing data siteService.sites.returns([{host: 'a', path: '/'}]); - db.pipeToPromise.returns(bluebird.resolve(JSON.stringify([data]))); - db.get.returns(bluebird.resolve(JSON.stringify(scheduledItem))); + db.pipeToPromise.returns(bluebird.resolve([data])); + db.get.returns(bluebird.resolve(scheduledItem)); db.batch.returns(bluebird.resolve()); fn(); - sandbox.clock.tick(intervalDelay); }); it('logs error if failed to publish page', function (done) { bluebird.onPossiblyUnhandledRejection(_.noop); - // rejection.suppressUnhandledRejections(); when bluebird supports it better const uri = 'http://abce', scheduledItem = {at: intervalDelay - 1, publish: uri}, data = {key:'some-key', value: JSON.stringify(scheduledItem)}; - rest.putObject.returns(bluebird.reject(new Error(''))); + rest.putObject.returns(bluebird.reject(new Error('asdfasdfasdf'))); siteService.sites.returns([{host: 'a', path: '/'}]); - db.pipeToPromise.returns(bluebird.resolve(JSON.stringify([data]))); - db.get.returns(bluebird.resolve(JSON.stringify(scheduledItem))); + db.pipeToPromise.returns(bluebird.resolve([data])); + db.get.returns(bluebird.resolve(scheduledItem)); db.batch.returns(bluebird.resolve()); lib.setLog(function (logType, msg) { @@ -156,7 +153,7 @@ describe(_.startCase(filename), function () { data = {key:'some-key', value: JSON.stringify({at: intervalDelay - 1, publish: uri}).substring(5)}; siteService.sites.returns([{host: 'a', path: '/'}]); - db.pipeToPromise.returns(bluebird.resolve(JSON.stringify([data]))); + db.pipeToPromise.returns(bluebird.resolve([data])); lib.setLog(function (logType, msg) { expect(logType).to.equal('error'); @@ -173,7 +170,7 @@ describe(_.startCase(filename), function () { data = {key:'some-key', value: JSON.stringify({at: intervalDelay - 1, publish: uri})}; siteService.sites.returns([{host: 'a', path: '/'}]); - db.pipeToPromise.returns(bluebird.resolve(JSON.stringify([data]))); + db.pipeToPromise.returns(bluebird.resolve([data])); lib.setLog(function (logType, msg) { expect(logType).to.equal('error'); diff --git a/lib/services/upgrade.js b/lib/services/upgrade.js index 1bc6c1fe..ede74c65 100644 --- a/lib/services/upgrade.js +++ b/lib/services/upgrade.js @@ -2,16 +2,16 @@ 'use strict'; const _ = require('lodash'), - db = require('./db'), bluebird = require('bluebird'), files = require('../files'), path = require('path'), utils = require('../utils/components'), { getComponentName } = require('clayutils'); -var log = require('./logger').setup({ - file: __filename, - action: 'upgrade' -}); +var db = require('./db'), + log = require('./logger').setup({ + file: __filename, + action: 'upgrade' + }); /** * Just do a quick sort, lowest to highest. @@ -220,6 +220,5 @@ module.exports.upgradeData = upgradeData; module.exports.checkForUpgrade = checkForUpgrade; module.exports.generateVersionArray = generateVersionArray; module.exports.aggregateTransforms = aggregateTransforms; -module.exports.setLog = function (fakeLogger) { - log = fakeLogger; -}; +module.exports.setLog = mock => log = mock; +module.exports.setDb = mock => db = mock; diff --git a/lib/services/upgrade.test.js b/lib/services/upgrade.test.js index e66cb203..16c2c115 100644 --- a/lib/services/upgrade.test.js +++ b/lib/services/upgrade.test.js @@ -5,11 +5,11 @@ const _ = require('lodash'), filename = __filename.split('/').pop().split('.').shift(), lib = require('./' + filename), sinon = require('sinon'), - db = require('./db'), bluebird = require('bluebird'), utils = require('../utils/components'), files = require('../files'), - expect = require('chai').expect; + expect = require('chai').expect, + storage = require('../../test/fixtures/mocks/storage'); function returnData(ref, data) { return data; @@ -25,7 +25,7 @@ function fakeUgrades() { } describe(_.startCase(filename), function () { - let sandbox, fakeLog; + let sandbox, fakeLog, db; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -34,7 +34,8 @@ describe(_.startCase(filename), function () { lib.setLog(fakeLog); sandbox.stub(utils); sandbox.stub(files); - sandbox.stub(db); + db = storage(); + lib.setDb(db); }); afterEach(function () { diff --git a/lib/services/uris.js b/lib/services/uris.js index 0cd334ee..6517f5c9 100644 --- a/lib/services/uris.js +++ b/lib/services/uris.js @@ -2,21 +2,13 @@ const _ = require('lodash'), buf = require('./buffer'), - db = require('./db'), references = require('./references'), { getPrefix } = require('clayutils'), notifications = require('./notifications'), siteService = require('./sites'), plugins = require('../plugins'), bus = require('./bus'); - -/** - * @param {string} uri - * @returns {Promise} - */ -function get(uri) { - return db.get(uri); -} +var db = require('./db'); /** * @param {string} uri @@ -50,8 +42,8 @@ function put(uri, body) { * @returns {Promise} */ function del(uri, user) { - return get(uri).then(function (oldData) { - return db.del(uri).then(function () { + return db.get(uri).then(oldData => { + return db.del(uri).then(() => { const prefix = getPrefix(uri), site = siteService.getSiteFromPrefix(prefix), pageUrl = buf.decode(uri.split('/').pop()), @@ -68,6 +60,8 @@ function del(uri, user) { }); } -module.exports.get = get; module.exports.put = put; module.exports.del = del; + +// For testing +module.exports.setDb = mock => db = mock; diff --git a/lib/services/uris.test.js b/lib/services/uris.test.js index 02ce1f97..6c82c180 100644 --- a/lib/services/uris.test.js +++ b/lib/services/uris.test.js @@ -1,25 +1,25 @@ 'use strict'; const _ = require('lodash'), bluebird = require('bluebird'), - db = require('./db'), filename = __filename.split('/').pop().split('.').shift(), expect = require('chai').expect, sinon = require('sinon'), siteService = require('./sites'), notifications = require('./notifications'), plugins = require('../plugins'), - lib = require('./' + filename); + lib = require('./' + filename), + storage = require('../../test/fixtures/mocks/storage'); describe(_.startCase(filename), function () { - let sandbox; + let sandbox, db; beforeEach(function () { sandbox = sinon.sandbox.create(); - sandbox.stub(db, 'get'); - sandbox.stub(db, 'del'); sandbox.stub(notifications, 'notify'); sandbox.stub(plugins, 'executeHook'); sandbox.stub(siteService, 'getSiteFromPrefix'); + db = storage(); + lib.setDb(db); }); afterEach(function () { diff --git a/lib/services/users.js b/lib/services/users.js index b22c7550..41210576 100644 --- a/lib/services/users.js +++ b/lib/services/users.js @@ -6,8 +6,8 @@ 'use strict'; -const db = require('./db'), - buf = require('./buffer'); +const buf = require('./buffer'); +var db = require('./db'); /** * encode username and provider to base64 @@ -55,3 +55,6 @@ function createUser(data) { module.exports.createUser = createUser; module.exports.encode = encode; module.exports.decode = decode; + +// For testing +module.exports.setDb = mock => db = mock; diff --git a/lib/services/users.test.js b/lib/services/users.test.js index fbb05594..757ffb5e 100644 --- a/lib/services/users.test.js +++ b/lib/services/users.test.js @@ -1,18 +1,19 @@ 'use strict'; const _ = require('lodash'), - db = require('./db'), filename = __filename.split('/').pop().split('.').shift(), expect = require('chai').expect, sinon = require('sinon'), lib = require('./' + filename), + storage = require('../../test/fixtures/mocks/storage'), buf = require('./buffer'); describe(_.startCase(filename), function () { - let sandbox; + let sandbox, db; beforeEach(function () { sandbox = sinon.sandbox.create(); - sandbox.stub(db, 'put'); + db = storage(); + lib.setDb(db); }); afterEach(function () { diff --git a/lib/setup.js b/lib/setup.js index 9220d0cd..e4623b72 100644 --- a/lib/setup.js +++ b/lib/setup.js @@ -6,6 +6,7 @@ const bluebird = require('bluebird'), internalBootstrap = require('./bootstrap'), render = require('./render'), amphoraPlugins = require('./plugins'), + db = require('./services/db'), bus = require('./services/bus'); bluebird.config({ @@ -34,8 +35,13 @@ module.exports = function (options = {}) { renderers, plugins = [], cacheControl = {}, - bootstrap = true - } = options, router; // TODO: DOCUMENT RENDERERS PLUGINS, AND CACHE CONTROL + bootstrap = true, + storage = false + } = options, router; + + if (!storage) { + throw new Error('A database integration was not supplied'); + } // Init plugins if (plugins.length) { @@ -54,8 +60,10 @@ module.exports = function (options = {}) { render.registerRenderers(renderers); } - // look for bootstraps in components - return internalBootstrap(bootstrap).then(function () { - return router; - }); + // Make sure the storage module is run, then bootstrap + // all the default data and finally return the router + return storage.setup() + .then(() => db.registerStorage(storage)) + .then(() => internalBootstrap(bootstrap)) + .then(() => router); }; diff --git a/lib/setup.test.js b/lib/setup.test.js index 172134f9..b47d896f 100644 --- a/lib/setup.test.js +++ b/lib/setup.test.js @@ -4,17 +4,20 @@ const _ = require('lodash'), filename = __filename.split('/').pop().split('.').shift(), lib = require('./' + filename), sinon = require('sinon'), + { expect } = require('chai'), + storage = require('../test/fixtures/mocks/storage'), plugins = require('./plugins'), render = require('./render'), bus = require('./services/bus'); describe(_.startCase(filename), function () { - let sandbox; + let sandbox, db; beforeEach(function () { sandbox = sinon.sandbox.create(); sandbox.stub(plugins, 'registerPlugins'); sandbox.stub(render, 'registerRenderers'); + db = storage(); sandbox.stub(bus); }); @@ -35,19 +38,23 @@ describe(_.startCase(filename), function () { } it('sets up', function () { - return lib(); + return lib({ storage: db }); + }); + + it('throws an error if no storage object is assigned', function () { + expect(() => lib()).to.throw(); }); it('registers plugins', function () { const plugin = sandbox.stub(pluginMock()); - return lib({ plugins: [plugin] }).then(function () { + return lib({ plugins: [plugin], storage: db }).then(function () { sinon.assert.calledOnce(plugins.registerPlugins); }); }); it('registers renderers', function () { - return lib({ renderers: { default: 'html', html: _.noop } }).then(function () { + return lib({ renderers: { default: 'html', html: _.noop }, storage: db }).then(function () { sinon.assert.calledOnce(render.registerRenderers); }); }); @@ -55,6 +62,6 @@ describe(_.startCase(filename), function () { it('initializes the bus if env vars are set', function () { process.env.REDIS_BUS_HOST = 'redis://localhost:6379'; - return lib().then(() => sinon.assert.calledOnce(bus.connect)); + return lib({ storage: db }).then(() => sinon.assert.calledOnce(bus.connect)); }); }); diff --git a/package.json b/package.json index 6eb16425..049aa117 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,7 @@ "handlebars": "^4.0.5", "highland": "^2.11.1", "js-yaml": "^3.2.0", - "levelup": "^1.3.9", "lodash": "^4.14.2", - "memdown": "^1.4.0", "node-fetch": "^1.3.0", "passport": "^0.3.2", "passport-google-oauth": "^1.0.0", @@ -64,6 +62,8 @@ "gitbook-plugin-bootstrap-callout": "^0.1.2", "gitbook-plugin-github": "^2.0.0", "istanbul": "^0.4.0", + "levelup": "^1.3.9", + "memdown": "^1.4.0", "mocha": "^3.5.0", "rimraf": "^2.6.2", "sinon": "^4.4.8", diff --git a/test/fixtures/api-accepts.js b/test/fixtures/api-accepts.js index f5c4d16b..49568d17 100644 --- a/test/fixtures/api-accepts.js +++ b/test/fixtures/api-accepts.js @@ -6,6 +6,7 @@ const _ = require('lodash'), files = require('../../lib/files'), routes = require('../../lib/routes'), db = require('../../lib/services/db'), + storage = require('./mocks/storage')(), bluebird = require('bluebird'), render = require('../../lib/render'), schema = require('../../lib/schema'), @@ -208,7 +209,7 @@ function updatesOther(method) { .set('Host', host) .set('Authorization', 'token testKey') .then(function () { - return db.get(host + realOtherPath).then(JSON.parse).then(function (result) { + return storage.getFromInMem(host + realOtherPath).then(function (result) { expect(result).to.deep.equal(data); }); }); @@ -226,7 +227,7 @@ function getVersions(ref) { deferred = bluebird.defer(), prefix = ref.split('@')[0]; - db.list({prefix, values: false, transforms: [filter({wantStrings: true}, function (str) { + storage.list({prefix, values: false, transforms: [filter({wantStrings: true}, function (str) { return str.indexOf('@') !== -1; })]}) .on('data', function (data) { @@ -291,7 +292,7 @@ function cascades(method) { .expect(200) .then(function () { // expect cascading data to now exist - return db.get(cascadingTarget).then(JSON.parse).then(function (result) { + return storage.getFromInMem(cascadingTarget).then(function (result) { expect(result).to.deep.equal(cascadingData); }); }); @@ -413,7 +414,7 @@ function beforeTesting(suite, options) { sites: null }); - return db.clear().then(function () { + return storage.clearMem().then(function () { return bluebird.all([ request(app).put('/_components/valid', JSON.stringify(options.data)), request(app).get('/_components/valid'), @@ -451,8 +452,12 @@ function beforeEachTest(options) { providers: ['apikey'] }); + db.get.callsFake(storage.getFromInMem); + db.put.callsFake(storage.writeToInMem); + db.batch.callsFake(storage.batchToInMem); + db.del.callsFake(storage.delFromInMem); - return db.clear().then(function () { + return storage.clearMem().then(function () { if (options.pathsAndData) { return bluebird.all(_.map(options.pathsAndData, function (data, path) { let ignoreHost = path.indexOf(ignoreString) > -1; @@ -465,7 +470,7 @@ function beforeEachTest(options) { data = JSON.stringify(data); } - return db.put(`${ignoreHost ? '' : host}${path}`, data); + return storage.writeToInMem(`${ignoreHost ? '' : host}${path}`, data); })); } }); diff --git a/test/fixtures/mocks/storage.js b/test/fixtures/mocks/storage.js new file mode 100644 index 00000000..94b9d5bc --- /dev/null +++ b/test/fixtures/mocks/storage.js @@ -0,0 +1,116 @@ +'use strict'; + +const sinon = require('sinon'), + db = require('../../../lib/services/db'); + +class Storage { + constructor() { + this.inMem = require('levelup')('whatever', { db: require('memdown') }); + this.setup = sinon.stub().returns(Promise.resolve()); + this.get = sinon.stub(); + this.put = sinon.stub(); + this.del = sinon.stub(); + this.batch = sinon.stub(); + this.list = db.list; + this.clearMem = this.clear; + this.pipeToPromise = db.pipeToPromise; + this.createReadStream = (ops) => this.inMem.createReadStream(ops); + + db.registerStorage(this); + } + + defer() { + return db.defer(); + } + + /** + * Save to inMemDb + * + * @param {String} key + * @param {String} value + * @return {Promise} + */ + writeToInMem(key, value) { + const deferred = this.defer(); + + this.inMem.put(key, value, deferred.apply); + return deferred.promise; + } + + /** + * Get from the inMemDb + * @param {String} key + * @return {Promise} + */ + getFromInMem(key) { + const deferred = this.defer(); + + this.inMem.get(key, deferred.apply); + return deferred.promise.then(resp => { + var returnVal; + + try { + returnVal = JSON.parse(resp); + } catch (e) { + returnVal = resp; + } + + return returnVal; + }); // Parse because storage modules are expected to + } + + /** + * Delete to inMemDb + * @param {String} key + * @return {Promise} + */ + delFromInMem(key) { + const deferred = this.defer(); + + this.inMem.del(key, deferred.apply); + return deferred.promise; + } + + /** + * Process a batch + * @param {Array} ops + * @param {Object} options + * @return {Promise} + */ + batchToInMem(ops, options) { + const deferred = this.defer(); + + this.inMem.batch(ops, options || {}, deferred.apply); + + return deferred.promise; + } + + /** + * Clear the Db + * @return {Promise} + */ + clear() { + const errors = [], + ops = [], + deferred = this.defer(); + + this.inMem.createReadStream({ + keys:true, + fillCache: false, + limit: -1 + }) + .on('data', data => ops.push({ type: 'del', key: data.key})) + .on('error', error => errors.push(error)) + .on('end', () => { + if (errors.length) { + deferred.apply(_.head(errors)); + } else { + this.inMem.batch(ops, deferred.apply); + } + }); + + return deferred.promise; + } +} + +module.exports = () => new Storage();