diff --git a/test/regression/update-check/update_check_spec.js b/test/regression/update-check/update_check_spec.js index 6d841dd2ee..d895e67f8c 100644 --- a/test/regression/update-check/update_check_spec.js +++ b/test/regression/update-check/update_check_spec.js @@ -7,760 +7,142 @@ const moment = require('moment'); const uuid = require('uuid'); const testUtils = require('../../utils'); const configUtils = require('../../utils/configUtils'); +const urlUtils = require('../../utils/urlUtils'); const packageInfo = require('../../../package.json'); const api = require('../../../core/server/api').v2; const mailService = require('../../../core/server/services/mail/'); - -let updateCheck = rewire('../../../core/server/update-check'); let ghostVersion = rewire('../../../core/server/lib/ghost-version'); +const UpdateCheckService = rewire('../../../core/server/update-check-service'); + describe('Update Check', function () { - beforeEach(function () { - updateCheck = rewire('../../../core/server/update-check'); - ghostVersion = rewire('../../../core/server/lib/ghost-version'); - sinon.stub(mailService.GhostMailer.prototype, 'send').resolves('Stubed email response'); - }); - - afterEach(function () { - sinon.restore(); - configUtils.restore(); - }); - - after(testUtils.teardownDb); - - describe('fn: updateCheck', function () { - let updateCheckRequestSpy; - let updateCheckResponseSpy; - let updateCheckErrorSpy; - - beforeEach(testUtils.teardownDb); - beforeEach(testUtils.setup('roles', 'owner')); + describe('fn: check', function () { + const internal = {context: {internal: true}}; + let settingsStub; + let i18nStub; + let loggingStub; + let requestStub; + let urlUtilsStub; beforeEach(function () { - updateCheckRequestSpy = sinon.stub().returns(Promise.resolve()); - updateCheckResponseSpy = sinon.stub().returns(Promise.resolve()); - updateCheckErrorSpy = sinon.stub(); - - updateCheck.__set__('updateCheckRequest', updateCheckRequestSpy); - updateCheck.__set__('updateCheckResponse', updateCheckResponseSpy); - updateCheck.__set__('updateCheckError', updateCheckErrorSpy); - updateCheck.__set__('allowedCheckEnvironments', ['development', 'production', 'testing', 'testing-mysql']); - }); - - it('update check was never executed', function (done) { - const readStub = sinon.stub().resolves({ + settingsStub = sinon.stub().resolves({ + settings: [] + }); + settingsStub.withArgs(Object.assign({key: 'db_hash'}, internal)).resolves({ settings: [{ - value: null + value: 'dummy_db_hash' + }] + }); + settingsStub.withArgs(Object.assign({key: 'active_theme'}, internal)).resolves({ + settings: [{ + value: 'casperito' }] }); - sinon.stub(api, 'settings').get(() => ({ - read: readStub - })); - updateCheck() - .then(function () { - updateCheckRequestSpy.calledOnce.should.eql(true); - updateCheckResponseSpy.calledOnce.should.eql(true); - updateCheckErrorSpy.called.should.eql(false); - done(); - }) - .catch(done); + i18nStub = { + t: sinon.stub() + }; + + loggingStub = { + error: sinon.stub() + }; + + requestStub = sinon.stub(); + urlUtilsStub = urlUtils.stubUrlUtilsFromConfig(); }); - it('update check won\'t happen if it\'s too early', function (done) { - const readStub = sinon.stub().resolves({ + afterEach(function () { + sinon.restore(); + urlUtils.restore(); + configUtils.restore(); + }); + + it('update check was executed', async function () { + const updateCheckService = new UpdateCheckService({ + api: { + settings: { + read: settingsStub, + edit: settingsStub + }, + users: { + browse: sinon.stub().resolves() + }, + posts: { + browse: sinon.stub().resolves() + } + }, + config: configUtils.config, + i18n: i18nStub, + logging: loggingStub, + urlUtils: urlUtilsStub, + request: requestStub, + ghostVersion, + ghostMailer: mailService + }); + + await updateCheckService.check(); + + requestStub.calledOnce.should.equal(true); + + requestStub.args[0][0].should.equal('https://updates.ghost.org'); + requestStub.args[0][1].query.ghost_version.should.equal(ghostVersion.full); + }); + + it('update check won\'t happen if it\'s too early', async function () { + const lateSettingStub = sinon.stub().resolves({ settings: [{ value: moment().add('10', 'minutes').unix() }] }); - sinon.stub(api, 'settings').get(() => ({ - read: readStub - })); - updateCheck() - .then(function () { - updateCheckRequestSpy.calledOnce.should.eql(false); - updateCheckResponseSpy.calledOnce.should.eql(false); - updateCheckErrorSpy.called.should.eql(false); - done(); - }) - .catch(done); + const updateCheckService = new UpdateCheckService({ + api: { + settings: { + read: lateSettingStub + } + }, + config: configUtils.config + }); + + await updateCheckService.check(); + + requestStub.called.should.equal(false); }); - it('update check will happen if it\'s time to check', function (done) { - const readStub = sinon.stub().resolves({ + it('update check will happen if it\'s time to check', async function () { + const updateCheckDelayPassed = sinon.stub().resolves({ settings: [{ value: moment().subtract('10', 'minutes').unix() }] }); - sinon.stub(api, 'settings').get(() => ({ - read: readStub - })); - updateCheck() - .then(function () { - updateCheckRequestSpy.calledOnce.should.eql(true); - updateCheckResponseSpy.calledOnce.should.eql(true); - updateCheckErrorSpy.called.should.eql(false); - done(); - }) - .catch(done); - }); - }); - - describe('fn: updateCheckData', function () { - let environmentsOrig; - - before(function () { - configUtils.set('privacy:useUpdateCheck', true); - }); - - after(function () { - configUtils.restore(); - }); - - beforeEach(testUtils.teardownDb); - beforeEach(testUtils.setup('roles', 'owner', 'settings', 'posts', 'perms:setting', 'perms:user', 'perms:init')); - - it('should report the correct data', function (done) { - const updateCheckData = updateCheck.__get__('updateCheckData'); - - updateCheckData().then(function (data) { - should.exist(data); - data.ghost_version.should.equal(packageInfo.version); - data.node_version.should.equal(process.versions.node); - data.env.should.equal(process.env.NODE_ENV); - data.database_type.should.match(/sqlite3|mysql/); - data.blog_id.should.be.a.String(); - data.blog_id.should.not.be.empty(); - data.theme.should.be.equal('casper'); - data.blog_created_at.should.be.a.Number(); - data.user_count.should.be.above(0); - data.post_count.should.be.above(0); - data.npm_version.should.be.a.String(); - data.npm_version.should.not.be.empty(); - - done(); - }).catch(done); - }); - }); - - describe('fn: createCustomNotification', function () { - let currentVersionOrig; - let currentVersionFull; - - before(function () { - currentVersionOrig = updateCheck.__get__('ghostVersion.original'); - currentVersionFull = updateCheck.__get__('ghostVersion.original'); - - ghostVersion.__set__('original', '0.9.0'); - ghostVersion.__set__('full', '0.9.0'); - }); - - after(function () { - ghostVersion.__set__('original', currentVersionOrig); - ghostVersion.__set__('full', currentVersionFull); - }); - - beforeEach(testUtils.teardownDb); - beforeEach(testUtils.setup('settings', 'roles', 'owner', 'perms:setting', 'perms:notification', 'perms:user', 'perms:init')); - - beforeEach(function () { - return api.notifications.destroyAll(testUtils.context.internal); - }); - - it('should create a release notification for target version', function (done) { - const createCustomNotification = updateCheck.__get__('createCustomNotification'); - - const notification = { - id: 1, - custom: 0, - messages: [{ - id: uuid.v4(), - version: '999.9.x', - content: '

Hey there! This is for 999.9.0 version

', - dismissible: true, - top: true - }] - }; - - createCustomNotification(notification).then(function () { - return api.notifications.browse(testUtils.context.internal); - }).then(function (results) { - should.exist(results); - should.exist(results.notifications); - results.notifications.length.should.eql(1); - - const targetNotification = _.find(results.notifications, {id: notification.messages[0].id}); - should.exist(targetNotification); - - targetNotification.dismissible.should.eql(notification.messages[0].dismissible); - targetNotification.id.should.eql(notification.messages[0].id); - targetNotification.top.should.eql(notification.messages[0].top); - targetNotification.type.should.eql('info'); - targetNotification.message.should.eql(notification.messages[0].content); - done(); - }).catch(done); - }); - - it('release notification version format is wrong', function (done) { - const createCustomNotification = updateCheck.__get__('createCustomNotification'); - - const notification = { - id: 1, - custom: 0, - messages: [{ - id: uuid.v4(), - version: '0.9.x', - content: '

Hey there! This is for 0.9 version

', - dismissible: true, - top: true - }] - }; - - createCustomNotification(notification).then(function () { - return api.notifications.browse(testUtils.context.internal); - }).then(function (results) { - should.exist(results); - should.exist(results.notifications); - results.notifications.length.should.eql(0); - done(); - }).catch(done); - }); - - it('blog version format is wrong', function (done) { - const createCustomNotification = updateCheck.__get__('createCustomNotification'); - - const notification = { - id: 1, - custom: 0, - messages: [{ - id: uuid.v4(), - version: '0.9.x', - content: '

Hey there! This is for 0.9.0 version

', - dismissible: true, - top: true - }] - }; - - createCustomNotification(notification).then(function () { - return api.notifications.browse(testUtils.context.internal); - }).then(function (results) { - should.exist(results); - should.exist(results.notifications); - results.notifications.length.should.eql(0); - done(); - }).catch(done); - }); - - it('should create a custom notification', function (done) { - const createCustomNotification = updateCheck.__get__('createCustomNotification'); - - const notification = { - id: 1, - custom: 1, - messages: [{ - id: uuid.v4(), - version: 'custom1', - content: '

How about migrating your blog?

', - dismissible: false, - top: true, - type: 'warn' - }] - }; - - createCustomNotification(notification).then(function () { - return api.notifications.browse(testUtils.context.internal); - }).then(function (results) { - should.exist(results); - should.exist(results.notifications); - results.notifications.length.should.eql(1); - - const targetNotification = _.find(results.notifications, {id: notification.messages[0].id}); - should.exist(targetNotification); - targetNotification.dismissible.should.eql(notification.messages[0].dismissible); - targetNotification.top.should.eql(notification.messages[0].top); - targetNotification.type.should.eql(notification.messages[0].type); - done(); - }).catch(done); - }); - - it('should not add duplicates', function (done) { - const createCustomNotification = updateCheck.__get__('createCustomNotification'); - - const notification = { - id: 1, - custom: 1, - messages: [{ - id: uuid.v4(), - version: 'custom1', - content: '

How about migrating your blog?

', - dismissible: false, - top: true, - type: 'warn' - }] - }; - - createCustomNotification(notification) - .then(function () { - return api.notifications.browse(testUtils.context.internal); - }) - .then(function (results) { - should.exist(results); - should.exist(results.notifications); - results.notifications.length.should.eql(1); - }) - .then(function () { - return createCustomNotification(notification); - }) - .then(function () { - return api.notifications.browse(testUtils.context.internal); - }) - .then(function (results) { - should.exist(results); - should.exist(results.notifications); - results.notifications.length.should.eql(1); - done(); - }) - .catch(done); - }); - - it('should send an email for critical notification', async function () { - const createCustomNotification = updateCheck.__get__('createCustomNotification'); - - const notification = { - id: 1, - custom: 1, - messages: [{ - id: uuid.v4(), - version: 'custom1', - content: '

Critical message. Upgrade your site!

', - dismissible: false, - top: true, - type: 'alert' - }] - }; - - await createCustomNotification(notification); - - mailService.GhostMailer.prototype.send.called.should.be.true(); - mailService.GhostMailer.prototype.send.args[0][0].to.should.equal('jbloggs@example.com'); - mailService.GhostMailer.prototype.send.args[0][0].subject.should.equal('Action required: Critical alert from Ghost instance http://127.0.0.1:2369'); - mailService.GhostMailer.prototype.send.args[0][0].html.should.equal('

Critical message. Upgrade your site!

'); - mailService.GhostMailer.prototype.send.args[0][0].forceTextContent.should.equal(true); - - const results = await api.notifications.browse(testUtils.context.internal); - - should.exist(results); - should.exist(results.notifications); - results.notifications.length.should.eql(1); - - const targetNotification = _.find(results.notifications, {id: notification.messages[0].id}); - should.exist(targetNotification); - targetNotification.dismissible.should.eql(notification.messages[0].dismissible); - targetNotification.top.should.eql(notification.messages[0].top); - targetNotification.type.should.eql(notification.messages[0].type); - }); - }); - - describe('fn: updateCheckResponse', function () { - beforeEach(testUtils.teardownDb); - beforeEach(testUtils.setup('roles', 'settings', 'perms:setting', 'perms:init')); - - it('receives a notifications with messages', function (done) { - const updateCheckResponse = updateCheck.__get__('updateCheckResponse'); - const createNotificationSpy = sinon.spy(); - - const message = { - id: uuid.v4(), - version: '^0.11.11', - content: 'Test', - dismissible: true, - top: true - }; - - updateCheck.__set__('createCustomNotification', createNotificationSpy); - - updateCheckResponse({version: '0.11.12', messages: [message]}) - .then(function () { - createNotificationSpy.callCount.should.eql(1); - done(); - }) - .catch(done); - }); - - it('receives multiple notifications', function (done) { - const updateCheckResponse = updateCheck.__get__('updateCheckResponse'); - const createNotificationSpy = sinon.spy(); - - const message1 = { - id: uuid.v4(), - version: '^0.11.11', - content: 'Test1', - dismissible: true, - top: true - }; - - const message2 = { - id: uuid.v4(), - version: '^0', - content: 'Test2', - dismissible: true, - top: false - }; - - const notifications = [ - {version: '0.11.12', messages: [message1]}, - {version: 'custom1', messages: [message2]} - ]; - - updateCheck.__set__('createCustomNotification', createNotificationSpy); - - updateCheckResponse(notifications) - .then(function () { - createNotificationSpy.callCount.should.eql(2); - done(); - }) - .catch(done); - }); - - it('ignores some custom notifications which are not marked as group', function (done) { - const updateCheckResponse = updateCheck.__get__('updateCheckResponse'); - const createNotificationSpy = sinon.spy(); - - const message1 = { - id: uuid.v4(), - version: '^0.11.11', - content: 'Test1', - dismissible: true, - top: true - }; - - const message2 = { - id: uuid.v4(), - version: '^0', - content: 'Test2', - dismissible: true, - top: false - }; - - const message3 = { - id: uuid.v4(), - version: '^0', - content: 'Test2', - dismissible: true, - top: false - }; - - const notifications = [ - {version: '0.11.12', messages: [message1]}, - {version: 'all1', messages: [message2], custom: 1}, - {version: 'migration1', messages: [message3], custom: 1} - ]; - - updateCheck.__set__('createCustomNotification', createNotificationSpy); - - updateCheckResponse(notifications) - .then(function () { - createNotificationSpy.callCount.should.eql(2); - done(); - }) - .catch(done); - }); - - it('group matches', function (done) { - const updateCheckResponse = updateCheck.__get__('updateCheckResponse'); - const createNotificationSpy = sinon.spy(); - - const message1 = { - id: uuid.v4(), - version: '^0.11.11', - content: 'Test1', - dismissible: true, - top: true - }; - - const message2 = { - id: uuid.v4(), - version: '^0', - content: 'Test2', - dismissible: true, - top: false - }; - - const message3 = { - id: uuid.v4(), - version: '^0', - content: 'Test2', - dismissible: true, - top: false - }; - - const notifications = [ - {version: '0.11.12', messages: [message1], custom: 0}, - {version: 'all1', messages: [message2], custom: 1}, - {version: 'migration1', messages: [message3], custom: 1} - ]; - - updateCheck.__set__('createCustomNotification', createNotificationSpy); - - configUtils.set({notificationGroups: ['migration']}); - - updateCheckResponse(notifications) - .then(function () { - createNotificationSpy.callCount.should.eql(3); - done(); - }) - .catch(done); - }); - - it('single custom notification received, group matches', function (done) { - const updateCheckResponse = updateCheck.__get__('updateCheckResponse'); - const createNotificationSpy = sinon.spy(); - - const message1 = { - id: uuid.v4(), - version: '^0.11.11', - content: 'Custom', - dismissible: true, - top: true - }; - - const notifications = [ - {version: 'something', messages: [message1], custom: 1} - ]; - - updateCheck.__set__('createCustomNotification', createNotificationSpy); - - configUtils.set({notificationGroups: ['something']}); - - updateCheckResponse(notifications) - .then(function () { - createNotificationSpy.callCount.should.eql(1); - done(); - }) - .catch(done); - }); - - it('single custom notification received, group does not match', function (done) { - const updateCheckResponse = updateCheck.__get__('updateCheckResponse'); - const createNotificationSpy = sinon.spy(); - - const message1 = { - id: uuid.v4(), - version: '^0.11.11', - content: 'Custom', - dismissible: true, - top: true - }; - - const notifications = [ - {version: 'something', messages: [message1], custom: 1} - ]; - - updateCheck.__set__('createCustomNotification', createNotificationSpy); - - configUtils.set({notificationGroups: ['migration']}); - - updateCheckResponse(notifications) - .then(function () { - createNotificationSpy.callCount.should.eql(0); - done(); - }) - .catch(done); - }); - }); - - describe('fn: updateCheckRequest', function () { - beforeEach(function () { - configUtils.set('privacy:useUpdateCheck', true); - }); - - afterEach(function () { - configUtils.restore(); - }); - - it('[default]', function () { - const updateCheckRequest = updateCheck.__get__('updateCheckRequest'); - const updateCheckDataSpy = sinon.stub(); - let hostname; - let reqObj; - - const data = { - ghost_version: '0.11.11', - blog_id: 'something', - npm_version: 'something' - }; - - updateCheck.__set__('request', function (_hostname, _reqObj) { - hostname = _hostname; - reqObj = _reqObj; - - return Promise.resolve({ - statusCode: 200, - body: {version: 'something'} - }); - }); - - updateCheck.__set__('updateCheckData', updateCheckDataSpy); - - updateCheckDataSpy.returns(Promise.resolve(data)); - - return updateCheckRequest() - .then(function () { - hostname.should.eql('https://updates.ghost.org'); - should.exist(reqObj.headers['Content-Length']); - reqObj.body.should.eql(data); - reqObj.json.should.eql(true); - }); - }); - - it('privacy flag is used', function () { - const updateCheckRequest = updateCheck.__get__('updateCheckRequest'); - const updateCheckDataSpy = sinon.stub(); - let reqObj; - let hostname; - - configUtils.set({ - privacy: { - useUpdateCheck: false - } - }); - - updateCheck.__set__('request', function (_hostname, _reqObj) { - hostname = _hostname; - reqObj = _reqObj; - - return Promise.resolve({ - statusCode: 200, - body: {version: 'something'} - }); - }); - - updateCheck.__set__('updateCheckData', updateCheckDataSpy); - - updateCheckDataSpy.returns(Promise.resolve({ - ghost_version: '0.11.11', - blog_id: 'something', - npm_version: 'something' - })); - - return updateCheckRequest() - .then(function () { - hostname.should.eql('https://updates.ghost.org'); - reqObj.query.should.eql({ - ghost_version: '0.11.11' - }); - - should.not.exist(reqObj.body); - reqObj.json.should.eql(true); - should.not.exist(reqObj.headers['Content-Length']); - }); - }); - - it('received 500 from the service', function () { - const updateCheckRequest = updateCheck.__get__('updateCheckRequest'); - const updateCheckDataSpy = sinon.stub(); - let reqObj; - let hostname; - - updateCheck.__set__('request', function (_hostname, _reqObj) { - hostname = _hostname; - reqObj = _reqObj; - - return Promise.reject({ - statusCode: 500, - message: 'something went wrong' - }); - }); - - updateCheck.__set__('updateCheckData', updateCheckDataSpy); - - updateCheckDataSpy.returns(Promise.resolve({ - ghost_version: '0.11.11', - blog_id: 'something', - npm_version: 'something' - })); - - return updateCheckRequest() - .then(function () { - throw new Error('Should fail.'); - }) - .catch(function (err) { - err.message.should.eql('something went wrong'); - }); - }); - - it('received 404 from the service', function () { - const updateCheckRequest = updateCheck.__get__('updateCheckRequest'); - const updateCheckDataSpy = sinon.stub(); - let reqObj; - let hostname; - - updateCheck.__set__('request', function (_hostname, _reqObj) { - hostname = _hostname; - reqObj = _reqObj; - - return Promise.reject({ - statusCode: 404, - response: { - body: { - errors: [{detail: 'No Notifications available.'}] - } + const updateCheckService = new UpdateCheckService({ + api: { + settings: { + read: updateCheckDelayPassed, + edit: settingsStub + }, + users: { + browse: sinon.stub().resolves() + }, + posts: { + browse: sinon.stub().resolves() } - }); + }, + config: configUtils.config, + i18n: i18nStub, + logging: loggingStub, + urlUtils: urlUtilsStub, + request: requestStub, + ghostVersion, + ghostMailer: mailService }); - updateCheck.__set__('updateCheckData', updateCheckDataSpy); + await updateCheckService.check(); - updateCheckDataSpy.returns(Promise.resolve({ - ghost_version: '0.11.11', - blog_id: 'something', - npm_version: 'something' - })); + requestStub.calledOnce.should.equal(true); - return updateCheckRequest() - .then(function () { - hostname.should.eql('https://updates.ghost.org'); - }); - }); - - it('custom url', function () { - const updateCheckRequest = updateCheck.__get__('updateCheckRequest'); - const updateCheckDataSpy = sinon.stub(); - let reqObj; - let hostname; - - configUtils.set({ - updateCheck: { - url: 'http://localhost:3000' - } - }); - - updateCheck.__set__('request', function (_hostname, _reqObj) { - hostname = _hostname; - reqObj = _reqObj; - - return Promise.resolve({ - statusCode: 200, - body: { - version: 'something' - } - }); - }); - - updateCheck.__set__('updateCheckData', updateCheckDataSpy); - - updateCheckDataSpy.returns(Promise.resolve({ - ghost_version: '0.11.11', - blog_id: 'something', - npm_version: 'something' - })); - - return updateCheckRequest() - .then(function () { - hostname.should.eql('http://localhost:3000'); - }); - }); + requestStub.args[0][0].should.equal('https://updates.ghost.org'); + requestStub.args[0][1].query.ghost_version.should.equal(ghostVersion.full); }); }); });