fd7ead6ce6
refs: https://github.com/TryGhost/Product/issues/3782 Latest version of request avoids using the OS-level name resolution system. This prevents long shutdowns from occuring where the OS is blocking the process from exiting cleanly. The new version uses `got` 13, which has many breaking changes. Some of these are resolved within @tryghost/request (like response errors), but input parameters need to be changed.
413 lines
15 KiB
JavaScript
413 lines
15 KiB
JavaScript
require('./utils');
|
|
|
|
const sinon = require('sinon');
|
|
const moment = require('moment');
|
|
const uuid = require('uuid');
|
|
const assert = require('assert/strict');
|
|
|
|
const logging = require('@tryghost/logging');
|
|
const UpdateCheckService = require('../lib/UpdateCheckService');
|
|
|
|
describe('Update Check', function () {
|
|
const internal = {context: {internal: true}};
|
|
let settingsStub;
|
|
let requestStub;
|
|
|
|
beforeEach(function () {
|
|
settingsStub = sinon.stub().resolves({
|
|
settings: []
|
|
});
|
|
settingsStub.withArgs(Object.assign({key: 'db_hash'}, internal)).resolves({
|
|
settings: [{
|
|
value: 'dummy_db_hash'
|
|
}]
|
|
});
|
|
settingsStub.withArgs(Object.assign({key: 'active_theme'}, internal)).resolves({
|
|
settings: [{
|
|
value: 'casperito'
|
|
}]
|
|
});
|
|
|
|
sinon.stub(logging, 'error');
|
|
|
|
requestStub = sinon.stub();
|
|
});
|
|
|
|
afterEach(function () {
|
|
sinon.restore();
|
|
});
|
|
|
|
describe('UpdateCheck execution', function () {
|
|
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: {
|
|
checkEndpoint: 'https://updates.ghost.org',
|
|
siteUrl: 'https://localhost:2368/test',
|
|
isPrivacyDisabled: true,
|
|
ghostVersion: '0.8.0'
|
|
},
|
|
request: requestStub
|
|
});
|
|
|
|
await updateCheckService.check();
|
|
|
|
requestStub.calledOnce.should.equal(true);
|
|
|
|
requestStub.args[0][0].should.equal('https://updates.ghost.org');
|
|
requestStub.args[0][1].searchParams.ghost_version.should.equal('0.8.0');
|
|
});
|
|
|
|
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()
|
|
}]
|
|
});
|
|
|
|
const updateCheckService = new UpdateCheckService({
|
|
api: {
|
|
settings: {
|
|
read: lateSettingStub
|
|
}
|
|
},
|
|
config: {},
|
|
request: requestStub
|
|
});
|
|
|
|
await updateCheckService.check();
|
|
|
|
requestStub.called.should.equal(false);
|
|
});
|
|
|
|
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()
|
|
}]
|
|
});
|
|
|
|
const updateCheckService = new UpdateCheckService({
|
|
api: {
|
|
settings: {
|
|
read: updateCheckDelayPassed,
|
|
edit: settingsStub
|
|
},
|
|
users: {
|
|
browse: sinon.stub().resolves()
|
|
},
|
|
posts: {
|
|
browse: sinon.stub().resolves()
|
|
}
|
|
},
|
|
config: {
|
|
checkEndpoint: 'https://updates.ghost.org',
|
|
siteUrl: 'https://example.com',
|
|
isPrivacyDisabled: true,
|
|
ghostVersion: '5.3.4'
|
|
},
|
|
request: requestStub
|
|
});
|
|
|
|
await updateCheckService.check();
|
|
|
|
requestStub.calledOnce.should.equal(true);
|
|
|
|
requestStub.args[0][0].should.equal('https://updates.ghost.org');
|
|
requestStub.args[0][1].searchParams.ghost_version.should.equal('5.3.4');
|
|
});
|
|
});
|
|
|
|
describe('Data sent with the POST request', function () {
|
|
it('should report the correct data', async function () {
|
|
const updateCheckService = new UpdateCheckService({
|
|
api: {
|
|
settings: {
|
|
read: settingsStub,
|
|
edit: settingsStub
|
|
},
|
|
users: {
|
|
browse: sinon.stub().resolves({
|
|
users: [{
|
|
created_at: '1995-12-24T23:15:00Z'
|
|
}, {}]
|
|
})
|
|
},
|
|
posts: {
|
|
browse: sinon.stub().resolves({
|
|
meta: {
|
|
pagination: {
|
|
total: 13
|
|
}
|
|
}
|
|
})
|
|
}
|
|
},
|
|
config: {
|
|
checkEndpoint: 'https://updates.ghost.org',
|
|
siteUrl: 'https://localhost:2368/test',
|
|
isPrivacyDisabled: false,
|
|
env: process.env.NODE_ENV,
|
|
databaseType: 'mysql',
|
|
ghostVersion: '4.0.0'
|
|
},
|
|
request: requestStub,
|
|
ghostMailer: {
|
|
send: sinon.stub().resolves()
|
|
}
|
|
});
|
|
|
|
await updateCheckService.check();
|
|
|
|
requestStub.calledOnce.should.equal(true);
|
|
|
|
requestStub.args[0][0].should.equal('https://updates.ghost.org');
|
|
|
|
const data = requestStub.args[0][1].json;
|
|
data.ghost_version.should.equal('4.0.0');
|
|
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('casperito');
|
|
data.blog_created_at.should.equal(819846900);
|
|
data.user_count.should.be.equal(2);
|
|
data.post_count.should.be.equal(13);
|
|
data.npm_version.should.be.a.String();
|
|
data.npm_version.should.not.be.empty();
|
|
});
|
|
});
|
|
|
|
describe('Notifications', function () {
|
|
it('should create a release notification for target version', async function () {
|
|
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
|
|
}]
|
|
};
|
|
|
|
const notificationsAPIAddStub = sinon.stub().resolves();
|
|
const usersBrowseStub = sinon.stub().resolves({
|
|
users: [{
|
|
roles: [{
|
|
name: 'Owner'
|
|
}]
|
|
}]
|
|
});
|
|
|
|
const updateCheckService = new UpdateCheckService({
|
|
api: {
|
|
settings: {
|
|
read: settingsStub,
|
|
edit: settingsStub
|
|
},
|
|
users: {
|
|
browse: usersBrowseStub
|
|
},
|
|
posts: {
|
|
browse: sinon.stub().resolves()
|
|
},
|
|
notifications: {
|
|
add: notificationsAPIAddStub
|
|
}
|
|
},
|
|
config: {
|
|
siteUrl: 'https://localhost:2368/test'
|
|
},
|
|
request: sinon.stub().resolves({
|
|
body: {
|
|
notifications: [notification]
|
|
}
|
|
})
|
|
});
|
|
|
|
await updateCheckService.check();
|
|
|
|
notificationsAPIAddStub.calledOnce.should.equal(true);
|
|
notificationsAPIAddStub.args[0][0].notifications.length.should.equal(1);
|
|
|
|
const targetNotification = notificationsAPIAddStub.args[0][0].notifications[0];
|
|
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);
|
|
|
|
usersBrowseStub.calledTwice.should.eql(true);
|
|
|
|
// Second (non statistical) call should be looking for admin users with an 'active' status only
|
|
usersBrowseStub.args[1][0].should.eql({
|
|
limit: 'all',
|
|
include: ['roles'],
|
|
filter: 'status:active',
|
|
context: {
|
|
internal: true
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should send an email for critical notification', async function () {
|
|
const notification = {
|
|
id: 1,
|
|
messages: [{
|
|
id: uuid.v4(),
|
|
version: 'custom1',
|
|
content: '<p>Critical message. Upgrade your site!</p>',
|
|
dismissible: false,
|
|
top: true,
|
|
type: 'alert'
|
|
}]
|
|
};
|
|
|
|
const notificationsAPIAddStub = sinon.stub().resolves();
|
|
const sendEmailStub = sinon.stub().resolves();
|
|
|
|
const updateCheckService = new UpdateCheckService({
|
|
api: {
|
|
settings: {
|
|
read: settingsStub,
|
|
edit: settingsStub
|
|
},
|
|
users: {
|
|
browse: sinon.stub().resolves({
|
|
users: [{
|
|
email: 'jbloggs@example.com',
|
|
roles: [{
|
|
name: 'Owner'
|
|
}]
|
|
}]
|
|
})
|
|
},
|
|
posts: {
|
|
browse: sinon.stub().resolves()
|
|
},
|
|
notifications: {
|
|
add: notificationsAPIAddStub
|
|
}
|
|
},
|
|
config: {
|
|
siteUrl: 'http://127.0.0.1:2369'
|
|
},
|
|
request: sinon.stub().resolves({
|
|
body: [notification]
|
|
}),
|
|
sendEmail: sendEmailStub
|
|
});
|
|
|
|
await updateCheckService.check();
|
|
|
|
sendEmailStub.called.should.be.true();
|
|
sendEmailStub.args[0][0].to.should.equal('jbloggs@example.com');
|
|
sendEmailStub.args[0][0].subject.should.equal('Action required: Critical alert from Ghost instance http://127.0.0.1:2369');
|
|
sendEmailStub.args[0][0].html.should.equal('<p>Critical message. Upgrade your site!</p>');
|
|
sendEmailStub.args[0][0].forceTextContent.should.equal(true);
|
|
|
|
notificationsAPIAddStub.calledOnce.should.equal(true);
|
|
notificationsAPIAddStub.args[0][0].notifications.length.should.equal(1);
|
|
});
|
|
|
|
it('not create a notification if the check response has no messages', async function () {
|
|
const notificationsAPIAddStub = sinon.stub().resolves();
|
|
|
|
const updateCheckService = new UpdateCheckService({
|
|
api: {
|
|
settings: {
|
|
read: settingsStub,
|
|
edit: settingsStub
|
|
},
|
|
users: {
|
|
browse: sinon.stub().resolves({
|
|
users: [{
|
|
roles: [{
|
|
name: 'Owner'
|
|
}]
|
|
}]
|
|
})
|
|
},
|
|
posts: {
|
|
browse: sinon.stub().resolves()
|
|
},
|
|
notifications: {
|
|
add: notificationsAPIAddStub
|
|
}
|
|
},
|
|
config: {
|
|
siteUrl: 'https://localhost:2368/test'
|
|
},
|
|
request: sinon.stub().resolves({
|
|
body: {
|
|
notifications: []
|
|
}
|
|
})
|
|
});
|
|
|
|
await updateCheckService.check();
|
|
|
|
notificationsAPIAddStub.calledOnce.should.equal(false);
|
|
});
|
|
});
|
|
|
|
describe('Error handling', function () {
|
|
it('logs an error when error', function () {
|
|
const updateCheckService = new UpdateCheckService({
|
|
api: {
|
|
settings: {
|
|
edit: settingsStub
|
|
}
|
|
},
|
|
config: {}
|
|
});
|
|
|
|
updateCheckService.updateCheckError({});
|
|
|
|
settingsStub.called.should.be.true();
|
|
logging.error.called.should.be.true();
|
|
logging.error.args[0][0].context.should.equal('Checking for updates failed, your site will continue to function.');
|
|
logging.error.args[0][0].help.should.equal('If you get this error repeatedly, please seek help from https://ghost.org/docs/');
|
|
});
|
|
|
|
it('logs and rethrows an error when error with rethrow configuration', function () {
|
|
const updateCheckService = new UpdateCheckService({
|
|
api: {
|
|
settings: {
|
|
edit: settingsStub
|
|
}
|
|
},
|
|
config: {
|
|
rethrowErrors: true
|
|
}
|
|
});
|
|
|
|
try {
|
|
updateCheckService.updateCheckError({});
|
|
assert.fail('should have thrown');
|
|
} catch (e) {
|
|
settingsStub.called.should.be.true();
|
|
logging.error.called.should.be.true();
|
|
logging.error.args[0][0].context.should.equal('Checking for updates failed, your site will continue to function.');
|
|
logging.error.args[0][0].help.should.equal('If you get this error repeatedly, please seek help from https://ghost.org/docs/');
|
|
|
|
e.context.should.equal('Checking for updates failed, your site will continue to function.');
|
|
}
|
|
});
|
|
});
|
|
});
|