0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Added test coverage from the "check" method

refs https://github.com/TryGhost/Team/issues/728

- This approach is moving away from stubbing and "rewiring" massive internal modules and instead only stubs needed dependencies for the UpdateCheckService class
- Also eliminates the need to depend on the database fixtures which should speed up test execution and move it to proper "unit test" class when the test rewrite is complete
This commit is contained in:
Naz 2021-05-31 20:55:51 +04:00
parent d5e6dbb0fb
commit 63c7d5aad1

View file

@ -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: '<p>Hey there! This is for 999.9.0 version</p>',
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: '<p>Hey there! This is for 0.9 version</p>',
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: '<p>Hey there! This is for 0.9.0 version</p>',
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: '<p>How about migrating your blog?</p>',
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: '<p>How about migrating your blog?</p>',
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: '<p>Critical message. Upgrade your site!</p>',
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('<p>Critical message. Upgrade your site!</p>');
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); });
});
});