Ghost/core/test/integration/update_check_spec.js
kirrg001 357ea3dffd 🐛 Fixed showing old release notifications in the about page
no issue

- reported in slack (https://ghost.slack.com/files/U8QV8DXQB/F8TSBQ532/image.png)
- do not expose old release notification
  - e.g. you are on 1.20.0
  - you receive a notification for 1.20.1 to update
  - you update to 1.20.1
- ensure we protect exposing the release notification (compare against blog version)
- protect against wrong formats
- @TODO: the notifications could store a `version` property
  - by that we could use `notification.version` and don't have to match the version in the message
2018-01-18 12:19:55 +01:00

702 lines
25 KiB
JavaScript

var _ = require('lodash'),
Promise = require('bluebird'),
should = require('should'),
rewire = require('rewire'),
sinon = require('sinon'),
moment = require('moment'),
uuid = require('uuid'),
testUtils = require('../utils'),
configUtils = require('../utils/configUtils'),
packageInfo = require('../../../package'),
updateCheck = rewire('../../server/update-check'),
ghostVersion = rewire('../../server/lib/ghost-version'),
SettingsAPI = require('../../server/api/settings'),
NotificationsAPI = rewire('../../server/api/notifications'),
sandbox = sinon.sandbox.create();
describe('Update Check', function () {
beforeEach(function () {
updateCheck = rewire('../../server/update-check');
NotificationsAPI = rewire('../../server/api/notifications');
});
afterEach(function () {
sandbox.restore();
configUtils.restore();
});
describe('fn: updateCheck', function () {
var updateCheckRequestSpy,
updateCheckResponseSpy,
updateCheckErrorSpy;
beforeEach(testUtils.setup('owner', 'posts', 'perms:setting', 'perms:user', 'perms:init'));
afterEach(testUtils.teardown);
beforeEach(function () {
updateCheckRequestSpy = sandbox.stub().returns(Promise.resolve());
updateCheckResponseSpy = sandbox.stub().returns(Promise.resolve());
updateCheckErrorSpy = sandbox.stub();
updateCheck.__set__('updateCheckRequest', updateCheckRequestSpy);
updateCheck.__set__('updateCheckResponse', updateCheckResponseSpy);
updateCheck.__set__('updateCheckError', updateCheckErrorSpy);
updateCheck.__set__('allowedCheckEnvironments', ['development', 'production', 'testing', 'testing-mysql', 'testing-pg']);
});
it('update check was never executed', function (done) {
sandbox.stub(SettingsAPI, 'read').returns(Promise.resolve({
settings: [{
value: null
}]
}));
updateCheck()
.then(function () {
updateCheckRequestSpy.calledOnce.should.eql(true);
updateCheckResponseSpy.calledOnce.should.eql(true);
updateCheckErrorSpy.called.should.eql(false);
done();
})
.catch(done);
});
it('update check won\'t happen if it\'s too early', function (done) {
sandbox.stub(SettingsAPI, 'read').returns(Promise.resolve({
settings: [{
value: moment().add('10', 'minutes').unix()
}]
}));
updateCheck()
.then(function () {
updateCheckRequestSpy.calledOnce.should.eql(false);
updateCheckResponseSpy.calledOnce.should.eql(false);
updateCheckErrorSpy.called.should.eql(false);
done();
})
.catch(done);
});
it('update check will happen if it\'s time to check', function (done) {
sandbox.stub(SettingsAPI, 'read').returns(Promise.resolve({
settings: [{
value: moment().subtract('10', 'minutes').unix()
}]
}));
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 () {
var environmentsOrig;
before(function () {
configUtils.set('privacy:useUpdateCheck', true);
});
after(function () {
configUtils.restore();
});
beforeEach(testUtils.setup('owner', 'settings', 'posts', 'perms:setting', 'perms:user', 'perms:init'));
afterEach(testUtils.teardown);
it('should report the correct data', function (done) {
var 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.apps.should.be.a.String();
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();
data.lts.should.eql(false);
done();
}).catch(done);
});
});
describe('fn: createCustomNotification', function () {
var currentVersionOrig;
before(function () {
currentVersionOrig = updateCheck.__get__('ghostVersion.original');
updateCheck.__set__('ghostVersion.original', '0.9.0');
});
after(function () {
updateCheck.__set__('ghostVersion.original', currentVersionOrig);
});
beforeEach(testUtils.setup('owner', 'posts', 'settings', 'perms:setting', 'perms:notification', 'perms:user', 'perms:init'));
beforeEach(function () {
return NotificationsAPI.destroyAll(testUtils.context.internal);
});
afterEach(testUtils.teardown);
it('should create a release notification for target version', function (done) {
var createCustomNotification = updateCheck.__get__('createCustomNotification'),
notification = {
id: 1,
custom: 0,
messages: [{
id: uuid.v4(),
version: '0.9.x',
content: '<p>Hey there! This is for 0.9.0 version</p>',
dismissible: true,
top: true
}]
};
NotificationsAPI.__set__('ghostVersion.full', '0.8.1');
createCustomNotification(notification).then(function () {
return NotificationsAPI.browse(testUtils.context.internal);
}).then(function (results) {
should.exist(results);
should.exist(results.notifications);
results.notifications.length.should.eql(1);
var 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) {
var createCustomNotification = updateCheck.__get__('createCustomNotification'),
notification = {
id: 1,
custom: 0,
messages: [{
id: uuid.v4(),
version: '0.9.x',
content: '<p>Hey there! This is for 0.9 version</p>',
dismissible: true,
top: true
}]
};
NotificationsAPI.__set__('ghostVersion.full', '0.8.1');
createCustomNotification(notification).then(function () {
return NotificationsAPI.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) {
var createCustomNotification = updateCheck.__get__('createCustomNotification'),
notification = {
id: 1,
custom: 0,
messages: [{
id: uuid.v4(),
version: '0.9.x',
content: '<p>Hey there! This is for 0.9.0 version</p>',
dismissible: true,
top: true
}]
};
NotificationsAPI.__set__('ghostVersion.full', '0.8');
createCustomNotification(notification).then(function () {
return NotificationsAPI.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) {
var createCustomNotification = updateCheck.__get__('createCustomNotification'),
notification = {
id: 1,
custom: 1,
messages: [{
id: uuid.v4(),
version: 'custom1',
content: '<p>How about migrating your blog?</p>',
dismissible: false,
top: true,
type: 'warn'
}]
};
createCustomNotification(notification).then(function () {
return NotificationsAPI.browse(testUtils.context.internal);
}).then(function (results) {
should.exist(results);
should.exist(results.notifications);
results.notifications.length.should.eql(1);
var 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) {
var createCustomNotification = updateCheck.__get__('createCustomNotification'),
notification = {
id: 1,
custom: 1,
messages: [{
id: uuid.v4(),
version: 'custom1',
content: '<p>How about migrating your blog?</p>',
dismissible: false,
top: true,
type: 'warn'
}]
};
createCustomNotification(notification)
.then(function () {
return NotificationsAPI.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 NotificationsAPI.browse(testUtils.context.internal);
})
.then(function (results) {
should.exist(results);
should.exist(results.notifications);
results.notifications.length.should.eql(1);
done();
})
.catch(done);
});
});
describe('fn: updateCheckResponse', function () {
beforeEach(testUtils.setup('settings', 'perms:setting', 'perms:init'));
afterEach(testUtils.teardown);
it('receives a notifications with messages', function (done) {
var updateCheckResponse = updateCheck.__get__('updateCheckResponse'),
createNotificationSpy = sandbox.spy(),
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) {
var updateCheckResponse = updateCheck.__get__('updateCheckResponse'),
createNotificationSpy = sandbox.spy(),
message1 = {
id: uuid.v4(),
version: '^0.11.11',
content: 'Test1',
dismissible: true,
top: true
},
message2 = {
id: uuid.v4(),
version: '^0',
content: 'Test2',
dismissible: true,
top: false
},
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) {
var updateCheckResponse = updateCheck.__get__('updateCheckResponse'),
createNotificationSpy = sandbox.spy(),
message1 = {
id: uuid.v4(),
version: '^0.11.11',
content: 'Test1',
dismissible: true,
top: true
},
message2 = {
id: uuid.v4(),
version: '^0',
content: 'Test2',
dismissible: true,
top: false
},
message3 = {
id: uuid.v4(),
version: '^0',
content: 'Test2',
dismissible: true,
top: false
},
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) {
var updateCheckResponse = updateCheck.__get__('updateCheckResponse'),
createNotificationSpy = sandbox.spy(),
message1 = {
id: uuid.v4(),
version: '^0.11.11',
content: 'Test1',
dismissible: true,
top: true
},
message2 = {
id: uuid.v4(),
version: '^0',
content: 'Test2',
dismissible: true,
top: false
},
message3 = {
id: uuid.v4(),
version: '^0',
content: 'Test2',
dismissible: true,
top: false
},
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) {
var updateCheckResponse = updateCheck.__get__('updateCheckResponse'),
createNotificationSpy = sandbox.spy(),
message1 = {
id: uuid.v4(),
version: '^0.11.11',
content: 'Custom',
dismissible: true,
top: true
},
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) {
var updateCheckResponse = updateCheck.__get__('updateCheckResponse'),
createNotificationSpy = sandbox.spy(),
message1 = {
id: uuid.v4(),
version: '^0.11.11',
content: 'Custom',
dismissible: true,
top: true
},
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 () {
var updateCheckRequest = updateCheck.__get__('updateCheckRequest'),
updateCheckDataSpy = sandbox.stub(),
hostname,
reqObj,
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 () {
var updateCheckRequest = updateCheck.__get__('updateCheckRequest'),
updateCheckDataSpy = sandbox.stub(),
reqObj,
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 () {
var updateCheckRequest = updateCheck.__get__('updateCheckRequest'),
updateCheckDataSpy = sandbox.stub(),
reqObj,
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 () {
var updateCheckRequest = updateCheck.__get__('updateCheckRequest'),
updateCheckDataSpy = sandbox.stub(),
reqObj,
hostname;
updateCheck.__set__('request', function (_hostname, _reqObj) {
hostname = _hostname;
reqObj = _reqObj;
return Promise.reject({
statusCode: 404,
response: {
body: {
errors: [{detail: 'No Notifications available.'}]
}
}
});
});
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');
});
});
it('custom url', function () {
var updateCheckRequest = updateCheck.__get__('updateCheckRequest'),
updateCheckDataSpy = sandbox.stub(),
reqObj,
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');
});
});
});
});