mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-08-17 00:30:34 +03:00
Update Notification improvements (#9123)
closes #5071 - Remove hardcoded notification in admin controller - NOTE: update check notifications are no longer blocking the admin rendering - this is one of the most import changes - we remove the hardcoded release message - we also remove adding a notification manually in here, because this will work differently from now on -> you receive a notification (release or custom) in the update check module and this module adds the notification as is to our database - Change default core settings keys - remove displayUpdateNotification -> this was used to store the release version number send from the UCS -> based on this value, Ghost creates a notification container with self defined values -> not needed anymore - rename seenNotifications to notifications -> the new notifications key will hold both 1. the notification from the USC 2. the information about if a notification was seen or not - this key hold only one release notification - and n custom notifications - Update Check Module: Request to the USC depends on the privacy configuration - useUpdateCheck: true -> does a checkin in the USC (exposes data) - useUpdateCheck: false -> does only a GET query to the USC (does not expose any data) - make the request handling dynamic, so it depends on the flag - add an extra logic to be able to define a custom USC endpoint (helpful for testing) - add an extra logic to be able to force the request to the service (helpful for testing) - Update check module: re-work condition when a check should happen - only if the env is not correct - remove deprecated config.updateCheck - remove isPrivacyDisabled check (handled differently now, explained in last commit) - Update check module: remove `showUpdateNotification` and readability - showUpdateNotification was used in the admin controller to fetch the latest release version number from the db - no need to check against semver in general, the USC takes care of that (no need to double check) - improve readability of `nextUpdateCheck` condition - Update check module: refactor `updateCheckResponse` - remove db call to displayUpdateNotification, not used anymore - support receiving multiple custom notifications - support custom notification groups - the default group is `all` - this will always be consumed - groups can be extended via config e.g. `notificationGroups: ['migration']` - Update check module: refactor createCustomNotification helper - get rid of taking over notification duplication handling (this is not the task of the update check module) - ensure we have good fallback values for non present attributes in a notification - get rid of semver check (happens in the USC) - could be reconsidered later if LTS is gone - Refactor notification API - reason: get rid of in process notification store -> this was an object hold in process -> everything get's lost after restart -> not helpful anymore, because imagine the following case -> you get a notification -> you store it in process -> you mark this notification as seen -> you restart Ghost, you will receive the same notification on the next check again -> because we are no longer have a separate seen notifications object - use database settings key `notification` instead - refactor all api endpoints to support reading and storing into the `notifications` object - most important: notification deletion happens via a `seen` property (the notification get's physically deleted 3 month automatically) -> we have to remember a seen property, because otherwise you don't know which notification was already received/seen - Add listener to remove seen notifications automatically after 3 month - i just decided for 3 month (we can decrease?) - at the end it doesn't really matter, as long as the windows is not tooooo short - listen on updates for the notifications settings - check if notification was seen and is older than 3 month - ignore release notification - Updated our privacy document - Updated docs.ghost.org for privacy config behaviour - contains a migration script to remove old settings keys
This commit is contained in:
parent
f671f9d2c9
commit
5b77f052d9
@ -11,9 +11,11 @@ Some official services for Ghost are enabled by default. These services connect
|
||||
|
||||
### Automatic Update Checks
|
||||
|
||||
When a new session is started, Ghost pings a Ghost.org endpoint to check if the current version of Ghost is the latest version of Ghost. If an update is available, a notification appears inside Ghost to let you know. Ghost.org collects basic anonymised usage statistics from update check requests.
|
||||
When a new session is started, Ghost pings a Ghost.org service to check if the current version of Ghost is the latest version of Ghost. If an update is available, a notification on the About Page appears to let you know.
|
||||
|
||||
This service can be disabled at any time. All of the information and code related to this service is available in the [update-check.js](https://github.com/TryGhost/Ghost/blob/master/core/server/update-check.js) file.
|
||||
Ghost will collect basic anonymised usage statistics from your blog before sending the request to the service. You can disable collecting statistics using the [privacy configuration](https://docs.ghost.org/v1/docs/config#section-update-check). You will still receive notifications from the service.
|
||||
|
||||
All of the information and code related to this service is available in the [update-check.js](https://github.com/TryGhost/Ghost/blob/master/core/server/update-check.js) file.
|
||||
|
||||
|
||||
## Third Party Services
|
||||
|
@ -1,17 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
// # Notifications API
|
||||
// RESTful API for creating notifications
|
||||
var Promise = require('bluebird'),
|
||||
|
||||
const Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
moment = require('moment'),
|
||||
ObjectId = require('bson-objectid'),
|
||||
pipeline = require('../lib/promise/pipeline'),
|
||||
permissions = require('../services/permissions'),
|
||||
canThis = permissions.canThis,
|
||||
localUtils = require('./utils'),
|
||||
common = require('../lib/common'),
|
||||
settingsAPI = require('./settings'),
|
||||
// Holds the persistent notifications
|
||||
notificationsStore = [],
|
||||
notifications;
|
||||
SettingsAPI = require('./settings'),
|
||||
internalContext = {context: {internal: true}},
|
||||
canThis = permissions.canThis;
|
||||
|
||||
let notifications,
|
||||
_private = {};
|
||||
|
||||
_private.fetchAllNotifications = function fetchAllNotifications() {
|
||||
let allNotifications;
|
||||
|
||||
return SettingsAPI.read(_.merge({key: 'notifications'}, internalContext))
|
||||
.then(function (response) {
|
||||
allNotifications = JSON.parse(response.settings[0].value || []);
|
||||
|
||||
_.each(allNotifications, function (notification) {
|
||||
notification.addedAt = moment(notification.addedAt).toDate();
|
||||
});
|
||||
|
||||
return allNotifications;
|
||||
});
|
||||
};
|
||||
|
||||
_private.publicResponse = function publicResponse(notificationsToReturn) {
|
||||
_.each(notificationsToReturn, function (notification) {
|
||||
delete notification.seen;
|
||||
delete notification.addedAt;
|
||||
});
|
||||
|
||||
return {
|
||||
notifications: notificationsToReturn
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* ## Notification API Methods
|
||||
@ -27,9 +58,20 @@ notifications = {
|
||||
*/
|
||||
browse: function browse(options) {
|
||||
return canThis(options.context).browse.notification().then(function () {
|
||||
return {notifications: notificationsStore};
|
||||
return _private.fetchAllNotifications()
|
||||
.then(function (allNotifications) {
|
||||
allNotifications = _.orderBy(allNotifications, 'addedAt', 'desc');
|
||||
|
||||
allNotifications = allNotifications.filter(function (notification) {
|
||||
return notification.seen !== true;
|
||||
});
|
||||
|
||||
return _private.publicResponse(allNotifications);
|
||||
});
|
||||
}, function () {
|
||||
return Promise.reject(new common.errors.NoPermissionError({message: common.i18n.t('errors.api.notifications.noPermissionToBrowseNotif')}));
|
||||
return Promise.reject(new common.errors.NoPermissionError({
|
||||
message: common.i18n.t('errors.api.notifications.noPermissionToBrowseNotif')
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
@ -69,7 +111,9 @@ notifications = {
|
||||
return canThis(options.context).add.notification().then(function () {
|
||||
return options;
|
||||
}, function () {
|
||||
return Promise.reject(new common.errors.NoPermissionError({message: common.i18n.t('errors.api.notifications.noPermissionToAddNotif')}));
|
||||
return Promise.reject(new common.errors.NoPermissionError({
|
||||
message: common.i18n.t('errors.api.notifications.noPermissionToAddNotif')
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -80,31 +124,61 @@ notifications = {
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function saveNotifications(options) {
|
||||
var defaults = {
|
||||
let defaults = {
|
||||
dismissible: true,
|
||||
location: 'bottom',
|
||||
status: 'alert'
|
||||
},
|
||||
addedNotifications = [], existingNotification;
|
||||
|
||||
_.each(options.data.notifications, function (notification) {
|
||||
notification = _.assign(defaults, notification, {
|
||||
status: 'alert',
|
||||
id: ObjectId.generate()
|
||||
},
|
||||
overrides = {
|
||||
seen: false,
|
||||
addedAt: moment().toDate()
|
||||
},
|
||||
notificationsToCheck = options.data.notifications,
|
||||
addedNotifications = [];
|
||||
|
||||
return _private.fetchAllNotifications()
|
||||
.then(function (allNotifications) {
|
||||
_.each(notificationsToCheck, function (notification) {
|
||||
let isDuplicate = _.find(allNotifications, {id: notification.id});
|
||||
|
||||
if (!isDuplicate) {
|
||||
addedNotifications.push(_.merge({}, defaults, notification, overrides));
|
||||
}
|
||||
});
|
||||
|
||||
let hasReleaseNotification = _.find(notificationsToCheck, {custom: false});
|
||||
|
||||
// CASE: remove any existing release notifications if a new release notification comes in
|
||||
if (hasReleaseNotification) {
|
||||
_.remove(allNotifications, function (el) {
|
||||
return !el.custom;
|
||||
});
|
||||
}
|
||||
|
||||
// CASE: nothing to add, skip
|
||||
if (!addedNotifications.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let addedReleaseNotifications = _.filter(addedNotifications, {custom: false});
|
||||
|
||||
// CASE: only latest release notification
|
||||
if (addedReleaseNotifications.length > 1) {
|
||||
addedNotifications = _.filter(addedNotifications, {custom: true});
|
||||
addedNotifications.push(_.orderBy(addedReleaseNotifications, 'created_at', 'desc')[0]);
|
||||
}
|
||||
|
||||
return SettingsAPI.edit({
|
||||
settings: [{
|
||||
key: 'notifications',
|
||||
value: allNotifications.concat(addedNotifications)
|
||||
}]
|
||||
}, internalContext);
|
||||
})
|
||||
.then(function () {
|
||||
return _private.publicResponse(addedNotifications);
|
||||
});
|
||||
|
||||
existingNotification = _.find(notificationsStore, {message: notification.message});
|
||||
|
||||
if (!existingNotification) {
|
||||
notificationsStore.push(notification);
|
||||
addedNotifications.push(notification);
|
||||
} else {
|
||||
addedNotifications.push(existingNotification);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
notifications: addedNotifications
|
||||
};
|
||||
}
|
||||
|
||||
tasks = [
|
||||
@ -124,26 +198,7 @@ notifications = {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
destroy: function destroy(options) {
|
||||
var tasks;
|
||||
|
||||
/**
|
||||
* Adds the id of notification to "seen_notifications" array.
|
||||
* @param {Object} notification
|
||||
* @return {*|Promise}
|
||||
*/
|
||||
function markAsSeen(notification) {
|
||||
var context = {internal: true};
|
||||
return settingsAPI.read({key: 'seen_notifications', context: context}).then(function then(response) {
|
||||
var seenNotifications = JSON.parse(response.settings[0].value);
|
||||
seenNotifications = _.uniqBy(seenNotifications.concat([notification.id]));
|
||||
return settingsAPI.edit({
|
||||
settings: [{
|
||||
key: 'seen_notifications',
|
||||
value: seenNotifications
|
||||
}]
|
||||
}, {context: context});
|
||||
});
|
||||
}
|
||||
let tasks;
|
||||
|
||||
/**
|
||||
* ### Handle Permissions
|
||||
@ -155,36 +210,47 @@ notifications = {
|
||||
return canThis(options.context).destroy.notification().then(function () {
|
||||
return options;
|
||||
}, function () {
|
||||
return Promise.reject(new common.errors.NoPermissionError({message: common.i18n.t('errors.api.notifications.noPermissionToDestroyNotif')}));
|
||||
return Promise.reject(new common.errors.NoPermissionError({
|
||||
message: common.i18n.t('errors.api.notifications.noPermissionToDestroyNotif')
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function destroyNotification(options) {
|
||||
var notification = _.find(notificationsStore, function (element) {
|
||||
return element.id === options.id;
|
||||
});
|
||||
return _private.fetchAllNotifications()
|
||||
.then(function (allNotifications) {
|
||||
let notificationToMarkAsSeen = _.find(allNotifications, {id: options.id}),
|
||||
notificationToMarkAsSeenIndex = _.findIndex(allNotifications, {id: options.id});
|
||||
|
||||
if (notification && !notification.dismissible) {
|
||||
return Promise.reject(
|
||||
new common.errors.NoPermissionError({message: common.i18n.t('errors.api.notifications.noPermissionToDismissNotif')})
|
||||
);
|
||||
}
|
||||
if (notificationToMarkAsSeenIndex > -1 && !notificationToMarkAsSeen.dismissible) {
|
||||
return Promise.reject(new common.errors.NoPermissionError({
|
||||
message: common.i18n.t('errors.api.notifications.noPermissionToDismissNotif')
|
||||
}));
|
||||
}
|
||||
|
||||
if (!notification) {
|
||||
return Promise.reject(new common.errors.NotFoundError({message: common.i18n.t('errors.api.notifications.notificationDoesNotExist')}));
|
||||
}
|
||||
if (notificationToMarkAsSeenIndex < 0) {
|
||||
return Promise.reject(new common.errors.NotFoundError({
|
||||
message: common.i18n.t('errors.api.notifications.notificationDoesNotExist')
|
||||
}));
|
||||
}
|
||||
|
||||
notificationsStore = _.reject(notificationsStore, function (element) {
|
||||
return element.id === options.id;
|
||||
});
|
||||
if (notificationToMarkAsSeen.seen) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (notification.custom) {
|
||||
return markAsSeen(notification);
|
||||
}
|
||||
allNotifications[notificationToMarkAsSeenIndex].seen = true;
|
||||
|
||||
return SettingsAPI.edit({
|
||||
settings: [{
|
||||
key: 'notifications',
|
||||
value: allNotifications
|
||||
}]
|
||||
}, internalContext);
|
||||
})
|
||||
.return();
|
||||
}
|
||||
|
||||
tasks = [
|
||||
localUtils.validate('notifications', {opts: localUtils.idDefaultOptions}),
|
||||
handlePermissions,
|
||||
destroyNotification
|
||||
];
|
||||
@ -200,15 +266,28 @@ notifications = {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
destroyAll: function destroyAll(options) {
|
||||
return canThis(options.context).destroy.notification().then(function () {
|
||||
notificationsStore = [];
|
||||
return notificationsStore;
|
||||
}, function (err) {
|
||||
return Promise.reject(new common.errors.NoPermissionError({
|
||||
err: err,
|
||||
context: common.i18n.t('errors.api.notifications.noPermissionToDestroyNotif')
|
||||
}));
|
||||
});
|
||||
return canThis(options.context).destroy.notification()
|
||||
.then(function () {
|
||||
return _private.fetchAllNotifications()
|
||||
.then(function (allNotifications) {
|
||||
_.each(allNotifications, function (notification) {
|
||||
notification.seen = true;
|
||||
});
|
||||
|
||||
return SettingsAPI.edit({
|
||||
settings: [{
|
||||
key: 'notifications',
|
||||
value: allNotifications
|
||||
}]
|
||||
}, internalContext);
|
||||
})
|
||||
.return();
|
||||
}, function (err) {
|
||||
return Promise.reject(new common.errors.NoPermissionError({
|
||||
err: err,
|
||||
context: common.i18n.t('errors.api.notifications.noPermissionToDestroyNotif')
|
||||
}));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,10 @@
|
||||
"host": "127.0.0.1",
|
||||
"port": 2368
|
||||
},
|
||||
"updateCheck": {
|
||||
"url": "https://updates.ghost.org",
|
||||
"forceUpdate": false
|
||||
},
|
||||
"privacy": false,
|
||||
"useMinFiles": true,
|
||||
"paths": {
|
||||
|
@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash'),
|
||||
models = require('../../../../models'),
|
||||
common = require('../../../../lib/common');
|
||||
|
||||
module.exports.config = {
|
||||
transaction: true
|
||||
};
|
||||
|
||||
module.exports.up = function removeSettingKeys(options) {
|
||||
let localOptions = _.merge({
|
||||
context: {internal: true}
|
||||
}, options);
|
||||
|
||||
return models.Settings.findOne({key: 'display_update_notification'}, localOptions)
|
||||
.then(function (settingsModel) {
|
||||
if (!settingsModel) {
|
||||
common.logging.warn('Deleted Settings Key `display_update_notification`.');
|
||||
return;
|
||||
}
|
||||
|
||||
common.logging.info('Deleted Settings Key `display_update_notification`.');
|
||||
return models.Settings.destroy({id: settingsModel.id}, localOptions);
|
||||
})
|
||||
.then(function () {
|
||||
return models.Settings.findOne({key: 'seen_notifications'}, localOptions);
|
||||
})
|
||||
.then(function (settingsModel) {
|
||||
if (!settingsModel) {
|
||||
common.logging.warn('Deleted Settings Key `seen_notifications`.');
|
||||
return;
|
||||
}
|
||||
|
||||
common.logging.info('Deleted Settings Key `seen_notifications`.');
|
||||
return models.Settings.destroy({id: settingsModel.id}, localOptions);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.down = function addSettingsKeys(options) {
|
||||
let localOptions = _.merge({
|
||||
context: {internal: true}
|
||||
}, options);
|
||||
|
||||
return models.Settings.findOne({key: 'display_update_notification'}, localOptions)
|
||||
.then(function (settingsModel) {
|
||||
if (settingsModel) {
|
||||
common.logging.warn('Added Settings Key `display_update_notification`.');
|
||||
return;
|
||||
}
|
||||
|
||||
common.logging.info('Added Settings Key `display_update_notification`.');
|
||||
return models.Settings.forge({key: 'display_update_notification'}).save(null, localOptions);
|
||||
})
|
||||
.then(function () {
|
||||
return models.Settings.findOne({key: 'seen_notifications'}, localOptions);
|
||||
})
|
||||
.then(function (settingsModel) {
|
||||
if (settingsModel) {
|
||||
common.logging.warn('Added Settings Key `seen_notifications`.');
|
||||
return;
|
||||
}
|
||||
|
||||
common.logging.info('Added Settings Key `seen_notifications`.');
|
||||
return models.Settings.forge({key: 'seen_notifications', value: '[]'}).save([], localOptions);
|
||||
});
|
||||
};
|
@ -6,10 +6,7 @@
|
||||
"next_update_check": {
|
||||
"defaultValue": null
|
||||
},
|
||||
"display_update_notification": {
|
||||
"defaultValue": null
|
||||
},
|
||||
"seen_notifications": {
|
||||
"notifications": {
|
||||
"defaultValue": "[]"
|
||||
}
|
||||
},
|
||||
|
@ -120,3 +120,39 @@ common.events.on('settings.active_timezone.edited', function (settingModel, opti
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove all notifications, which are seen, older than 3 months.
|
||||
* No transaction, because notifications are not sensitive and we would have to add `forUpdate`
|
||||
* to the settings model to create real lock.
|
||||
*/
|
||||
common.events.on('settings.notifications.edited', function (settingModel) {
|
||||
var allNotifications = JSON.parse(settingModel.attributes.value || []),
|
||||
options = {context: {internal: true}},
|
||||
skip = true;
|
||||
|
||||
allNotifications = allNotifications.filter(function (notification) {
|
||||
// Do not delete the release notification
|
||||
if (notification.hasOwnProperty('custom') && !notification.custom) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (notification.seen && moment().diff(moment(notification.addedAt), 'month') > 2) {
|
||||
skip = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
return models.Settings.edit({
|
||||
key: 'notifications',
|
||||
value: JSON.stringify(allNotifications)
|
||||
}, options).catch(function (err) {
|
||||
common.errors.logError(err);
|
||||
});
|
||||
});
|
||||
|
@ -544,9 +544,6 @@
|
||||
}
|
||||
},
|
||||
"notices": {
|
||||
"controllers": {
|
||||
"newVersionAvailable": "Ghost {version} is available! Hot Damn. {link} to upgrade."
|
||||
},
|
||||
"index": {
|
||||
"welcomeToGhost": "Welcome to Ghost.",
|
||||
"youAreRunningUnderEnvironment": "You're running under the <strong> {environment} </strong> environment.",
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
// # Update Checking Service
|
||||
//
|
||||
// Makes a request to Ghost.org to check if there is a new version of Ghost available.
|
||||
@ -20,31 +22,34 @@
|
||||
// - theme - name of the currently active theme
|
||||
// - apps - names of any active apps
|
||||
|
||||
var crypto = require('crypto'),
|
||||
const crypto = require('crypto'),
|
||||
exec = require('child_process').exec,
|
||||
moment = require('moment'),
|
||||
semver = require('semver'),
|
||||
Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
url = require('url'),
|
||||
debug = require('ghost-ignition').debug('update-check'),
|
||||
api = require('./api'),
|
||||
config = require('./config'),
|
||||
urlService = require('./services/url'),
|
||||
common = require('./lib/common'),
|
||||
request = require('./lib/request'),
|
||||
currentVersion = require('./lib/ghost-version').full,
|
||||
ghostVersion = require('./lib/ghost-version'),
|
||||
internal = {context: {internal: true}},
|
||||
checkEndpoint = config.get('updateCheckUrl') || 'https://updates.ghost.org';
|
||||
allowedCheckEnvironments = ['development', 'production'];
|
||||
|
||||
function nextCheckTimestamp() {
|
||||
var now = Math.round(new Date().getTime() / 1000);
|
||||
return now + (24 * 3600);
|
||||
}
|
||||
|
||||
function updateCheckError(err) {
|
||||
if (err.response && err.response.body && typeof err.response.body === 'object') {
|
||||
err = common.errors.utils.deserialize(err.response.body);
|
||||
}
|
||||
|
||||
api.settings.edit(
|
||||
{settings: [{key: 'next_update_check', value: Math.round(Date.now() / 1000 + 24 * 3600)}]},
|
||||
internal
|
||||
);
|
||||
api.settings.edit({
|
||||
settings: [{
|
||||
key: 'next_update_check',
|
||||
value: nextCheckTimestamp()
|
||||
}]
|
||||
}, internal);
|
||||
|
||||
err.context = common.i18n.t('errors.updateCheck.checkingForUpdatesFailed.error');
|
||||
err.help = common.i18n.t('errors.updateCheck.checkingForUpdatesFailed.help', {url: 'https://docs.ghost.org/v1'});
|
||||
@ -53,40 +58,36 @@ function updateCheckError(err) {
|
||||
|
||||
/**
|
||||
* If the custom message is intended for current version, create and store a custom notification.
|
||||
* @param {Object} message {id: uuid, version: '0.9.x', content: '' }
|
||||
* @param {Object} notification
|
||||
* @return {*|Promise}
|
||||
*/
|
||||
function createCustomNotification(message) {
|
||||
if (!semver.satisfies(currentVersion, message.version)) {
|
||||
function createCustomNotification(notification) {
|
||||
if (!notification) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var notification = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
custom: true,
|
||||
uuid: message.id,
|
||||
dismissible: true,
|
||||
return Promise.each(notification.messages, function (message) {
|
||||
let toAdd = {
|
||||
custom: !!notification.custom,
|
||||
createdAt: moment(notification.created_at).toDate(),
|
||||
status: message.status || 'alert',
|
||||
type: message.type || 'info',
|
||||
id: message.id,
|
||||
dismissible: message.hasOwnProperty('dismissible') ? message.dismissible : true,
|
||||
top: !!message.top,
|
||||
message: message.content
|
||||
},
|
||||
getAllNotifications = api.notifications.browse({context: {internal: true}}),
|
||||
getSeenNotifications = api.settings.read(_.extend({key: 'seen_notifications'}, internal));
|
||||
};
|
||||
|
||||
return Promise.join(getAllNotifications, getSeenNotifications, function joined(all, seen) {
|
||||
var isSeen = _.includes(JSON.parse(seen.settings[0].value || []), notification.id),
|
||||
isDuplicate = _.some(all.notifications, {message: notification.message});
|
||||
|
||||
if (!isSeen && !isDuplicate) {
|
||||
return api.notifications.add({notifications: [notification]}, {context: {internal: true}});
|
||||
}
|
||||
debug('Add Custom Notification', toAdd);
|
||||
return api.notifications.add({notifications: [toAdd]}, {context: {internal: true}});
|
||||
});
|
||||
}
|
||||
|
||||
function updateCheckData() {
|
||||
var data = {},
|
||||
let data = {},
|
||||
mailConfig = config.get('mail');
|
||||
|
||||
data.ghost_version = currentVersion;
|
||||
data.ghost_version = ghostVersion.original;
|
||||
data.node_version = process.versions.node;
|
||||
data.env = config.get('env');
|
||||
data.database_type = config.get('database').client;
|
||||
@ -134,19 +135,53 @@ function updateCheckData() {
|
||||
}).catch(updateCheckError);
|
||||
}
|
||||
|
||||
/**
|
||||
* With the privacy setting `useUpdateCheck` you can control if you want to expose data from your blog to the
|
||||
* Update Check Service. Enabled or disabled, you will receive the latest notification available from the service.
|
||||
*/
|
||||
function updateCheckRequest() {
|
||||
return updateCheckData()
|
||||
.then(function then(reqData) {
|
||||
return request(checkEndpoint, {
|
||||
json: true,
|
||||
body: reqData,
|
||||
headers: {
|
||||
'Content-Length': Buffer.byteLength(JSON.stringify(reqData))
|
||||
let reqObj = {
|
||||
timeout: 1000,
|
||||
headers: {}
|
||||
},
|
||||
timeout: 1000
|
||||
}).then(function (response) {
|
||||
return response.body;
|
||||
});
|
||||
checkEndpoint = config.get('updateCheck:url'),
|
||||
checkMethod = config.isPrivacyDisabled('useUpdateCheck') ? 'GET' : 'POST';
|
||||
|
||||
if (checkMethod === 'POST') {
|
||||
reqObj.json = true;
|
||||
reqObj.body = reqData;
|
||||
reqObj.headers['Content-Length'] = Buffer.byteLength(JSON.stringify(reqData));
|
||||
reqObj.headers['Content-Type'] = 'application/json';
|
||||
} else {
|
||||
reqObj.json = true;
|
||||
reqObj.query = {
|
||||
ghost_version: reqData.ghost_version
|
||||
};
|
||||
}
|
||||
|
||||
debug('Request Update Check Service', checkEndpoint);
|
||||
|
||||
return request(checkEndpoint, reqObj)
|
||||
.then(function (response) {
|
||||
return response.body;
|
||||
})
|
||||
.catch(function (err) {
|
||||
// CASE: no notifications available, ignore
|
||||
if (err.statusCode === 404) {
|
||||
return {
|
||||
next_check: nextCheckTimestamp(),
|
||||
notifications: []
|
||||
};
|
||||
}
|
||||
|
||||
if (err.response && err.response.body && typeof err.response.body === 'object') {
|
||||
err = common.errors.utils.deserialize(err.response.body);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -154,65 +189,84 @@ function updateCheckRequest() {
|
||||
* Handles the response from the update check
|
||||
* Does three things with the information received:
|
||||
* 1. Updates the time we can next make a check
|
||||
* 2. Checks if the version in the response is new, and updates the notification setting
|
||||
* 3. Create custom notifications is response from UpdateCheck as "messages" array which has the following structure:
|
||||
* 2. Create custom notifications is response from UpdateCheck as "messages" array which has the following structure:
|
||||
*
|
||||
* "messages": [{
|
||||
* "id": ed9dc38c-73e5-4d72-a741-22b11f6e151a,
|
||||
* "version": "0.5.x",
|
||||
* "content": "<p>Hey there! 0.6 is available, visit <a href=\"https://ghost.org/download\">Ghost.org</a> to grab your copy now<!/p>"
|
||||
* "content": "<p>Hey there! 0.6 is available, visit <a href=\"https://ghost.org/download\">Ghost.org</a> to grab your copy now<!/p>",
|
||||
* "dismissible": true | false,
|
||||
* "top": true | false
|
||||
* ]}
|
||||
*
|
||||
* Example for grouped custom notifications in config:
|
||||
*
|
||||
* notificationGroups: ['migration', 'something']
|
||||
*
|
||||
* 'all' is a reserved name for general custom notifications.
|
||||
*
|
||||
* @param {Object} response
|
||||
* @return {Promise}
|
||||
*/
|
||||
function updateCheckResponse(response) {
|
||||
return Promise.all([
|
||||
api.settings.edit({settings: [{key: 'next_update_check', value: response.next_check}]}, internal),
|
||||
api.settings.edit({settings: [{key: 'display_update_notification', value: response.version}]}, internal)
|
||||
]).then(function () {
|
||||
var messages = response.messages || [];
|
||||
let notifications = [],
|
||||
notificationGroups = (config.get('notificationGroups') || []).concat(['all']);
|
||||
|
||||
/**
|
||||
* by default the update check service returns messages: []
|
||||
* but the latest release version get's stored anyway, because we adding the `display_update_notification` ^
|
||||
*/
|
||||
return Promise.map(messages, createCustomNotification);
|
||||
});
|
||||
debug('Notification Groups', notificationGroups);
|
||||
debug('Response Update Check Service', response);
|
||||
|
||||
return api.settings.edit({settings: [{key: 'next_update_check', value: response.next_check}]}, internal)
|
||||
.then(function () {
|
||||
// CASE: Update Check Service returns multiple notifications.
|
||||
if (_.isArray(response)) {
|
||||
notifications = response;
|
||||
} else if ((response.hasOwnProperty('notifications') && _.isArray(response.notifications))) {
|
||||
notifications = response.notifications;
|
||||
} else {
|
||||
notifications = [response];
|
||||
}
|
||||
|
||||
// CASE: Hook into received notifications and decide whether you are allowed to receive custom group messages.
|
||||
if (notificationGroups.length) {
|
||||
notifications = notifications.filter(function (notification) {
|
||||
if (!notification.custom) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _.includes(notificationGroups.map(function (groupIdentifier) {
|
||||
if (notification.version.match(new RegExp(groupIdentifier))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}), true) === true;
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.each(notifications, createCustomNotification);
|
||||
});
|
||||
}
|
||||
|
||||
function updateCheck() {
|
||||
if (config.isPrivacyDisabled('useUpdateCheck')) {
|
||||
// CASE: The check will not happen if your NODE_ENV is not in the allowed defined environments.
|
||||
if (_.indexOf(allowedCheckEnvironments, process.env.NODE_ENV) === -1) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return api.settings.read(_.extend({key: 'next_update_check'}, internal)).then(function then(result) {
|
||||
}
|
||||
|
||||
return api.settings.read(_.extend({key: 'next_update_check'}, internal))
|
||||
.then(function then(result) {
|
||||
var nextUpdateCheck = result.settings[0];
|
||||
|
||||
if (nextUpdateCheck && nextUpdateCheck.value && nextUpdateCheck.value > moment().unix()) {
|
||||
// It's not time to check yet
|
||||
return; // eslint-disable-line no-useless-return
|
||||
} else {
|
||||
// We need to do a check
|
||||
return updateCheckRequest()
|
||||
.then(updateCheckResponse)
|
||||
.catch(updateCheckError);
|
||||
// CASE: Next update check should happen now?
|
||||
if (!config.get('updateCheck:forceUpdate') && nextUpdateCheck && nextUpdateCheck.value && nextUpdateCheck.value > moment().unix()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}).catch(updateCheckError);
|
||||
}
|
||||
}
|
||||
|
||||
function showUpdateNotification() {
|
||||
return api.settings.read(_.extend({key: 'display_update_notification'}, internal)).then(function then(response) {
|
||||
var display = response.settings[0];
|
||||
|
||||
// @TODO: We only show minor/major releases. This is a temporary fix. #5071 is coming soon.
|
||||
if (display && display.value && currentVersion && semver.gt(display.value, currentVersion) && semver.patch(display.value) === 0) {
|
||||
return display.value;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return updateCheckRequest()
|
||||
.then(updateCheckResponse)
|
||||
.catch(updateCheckError);
|
||||
})
|
||||
.catch(updateCheckError);
|
||||
}
|
||||
|
||||
module.exports = updateCheck;
|
||||
module.exports.showUpdateNotification = showUpdateNotification;
|
||||
|
@ -1,8 +1,8 @@
|
||||
var debug = require('ghost-ignition').debug('admin:controller'),
|
||||
_ = require('lodash'),
|
||||
'use strict';
|
||||
|
||||
const debug = require('ghost-ignition').debug('admin:controller'),
|
||||
path = require('path'),
|
||||
config = require('../../config'),
|
||||
api = require('../../api'),
|
||||
updateCheck = require('../../update-check'),
|
||||
common = require('../../lib/common');
|
||||
|
||||
@ -12,36 +12,14 @@ var debug = require('ghost-ignition').debug('admin:controller'),
|
||||
module.exports = function adminController(req, res) {
|
||||
debug('index called');
|
||||
|
||||
updateCheck().then(function then() {
|
||||
return updateCheck.showUpdateNotification();
|
||||
}).then(function then(updateVersion) {
|
||||
if (!updateVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
location: 'upgrade.new-version-available',
|
||||
dismissible: false,
|
||||
message: common.i18n.t('notices.controllers.newVersionAvailable',
|
||||
{
|
||||
version: updateVersion,
|
||||
link: '<a href="https://docs.ghost.org/docs/upgrade" target="_blank">Click here</a>'
|
||||
})
|
||||
};
|
||||
|
||||
return api.notifications.browse({context: {internal: true}}).then(function then(results) {
|
||||
if (!_.some(results.notifications, {message: notification.message})) {
|
||||
return api.notifications.add({notifications: [notification]}, {context: {internal: true}});
|
||||
}
|
||||
// run in background, don't block the admin rendering
|
||||
updateCheck()
|
||||
.catch(function onError(err) {
|
||||
common.logging.error(err);
|
||||
});
|
||||
}).finally(function noMatterWhat() {
|
||||
var defaultTemplate = config.get('env') === 'production' ? 'default-prod.html' : 'default.html',
|
||||
templatePath = path.resolve(config.get('paths').adminViews, defaultTemplate);
|
||||
|
||||
res.sendFile(templatePath);
|
||||
}).catch(function (err) {
|
||||
common.logging.error(err);
|
||||
});
|
||||
let defaultTemplate = config.get('env') === 'production' ? 'default-prod.html' : 'default.html',
|
||||
templatePath = path.resolve(config.get('paths').adminViews, defaultTemplate);
|
||||
|
||||
res.sendFile(templatePath);
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
var should = require('should'),
|
||||
testUtils = require('../../utils'),
|
||||
_ = require('lodash'),
|
||||
uuid = require('uuid'),
|
||||
ObjectId = require('bson-objectid'),
|
||||
NotificationsAPI = require('../../../server/api/notifications'),
|
||||
SettingsAPI = require('../../../server/api/settings');
|
||||
testUtils = require('../../utils'),
|
||||
NotificationsAPI = require('../../../server/api/notifications');
|
||||
|
||||
describe('Notifications API', function () {
|
||||
// Keep the DB clean
|
||||
@ -13,10 +13,6 @@ describe('Notifications API', function () {
|
||||
|
||||
should.exist(NotificationsAPI);
|
||||
|
||||
after(function () {
|
||||
return NotificationsAPI.destroyAll(testUtils.context.internal);
|
||||
});
|
||||
|
||||
it('can add, adds defaults (internal)', function (done) {
|
||||
var msg = {
|
||||
type: 'info',
|
||||
@ -54,6 +50,7 @@ describe('Notifications API', function () {
|
||||
notification.dismissible.should.be.true();
|
||||
should.exist(notification.location);
|
||||
notification.location.should.equal('bottom');
|
||||
notification.id.should.be.a.String();
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
@ -74,7 +71,7 @@ describe('Notifications API', function () {
|
||||
should.exist(result.notifications);
|
||||
|
||||
notification = result.notifications[0];
|
||||
notification.id.should.not.equal(msg.id);
|
||||
notification.id.should.be.a.String();
|
||||
should.exist(notification.status);
|
||||
notification.status.should.equal('alert');
|
||||
|
||||
@ -82,6 +79,32 @@ describe('Notifications API', function () {
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('duplicates', function (done) {
|
||||
var customNotification1 = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
location: 'test.to-be-deleted1',
|
||||
custom: true,
|
||||
id: uuid.v1(),
|
||||
dismissible: true,
|
||||
message: 'Hello, this is dog number 1'
|
||||
};
|
||||
|
||||
NotificationsAPI
|
||||
.add({notifications: [customNotification1]}, testUtils.context.internal)
|
||||
.then(function () {
|
||||
return NotificationsAPI.add({notifications: [customNotification1]}, testUtils.context.internal);
|
||||
})
|
||||
.then(function () {
|
||||
return NotificationsAPI.browse(testUtils.context.internal);
|
||||
})
|
||||
.then(function (response) {
|
||||
response.notifications.length.should.eql(1);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('can browse (internal)', function (done) {
|
||||
var msg = {
|
||||
type: 'error', // this can be 'error', 'success', 'warn' and 'info'
|
||||
@ -114,6 +137,40 @@ describe('Notifications API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('receive correct order', function (done) {
|
||||
var customNotification1 = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
custom: true,
|
||||
id: uuid.v1(),
|
||||
dismissible: true,
|
||||
message: '1'
|
||||
}, customNotification2 = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
custom: true,
|
||||
id: uuid.v1(),
|
||||
dismissible: true,
|
||||
message: '2'
|
||||
};
|
||||
|
||||
NotificationsAPI
|
||||
.add({notifications: [customNotification1]}, testUtils.context.internal)
|
||||
.then(function () {
|
||||
return NotificationsAPI.add({notifications: [customNotification2]}, testUtils.context.internal);
|
||||
})
|
||||
.then(function () {
|
||||
return NotificationsAPI.browse(testUtils.context.internal);
|
||||
})
|
||||
.then(function (response) {
|
||||
response.notifications.length.should.eql(2);
|
||||
response.notifications[0].message.should.eql('2');
|
||||
response.notifications[1].message.should.eql('1');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('can destroy (internal)', function (done) {
|
||||
var msg = {
|
||||
type: 'error',
|
||||
@ -123,13 +180,13 @@ describe('Notifications API', function () {
|
||||
NotificationsAPI.add({notifications: [msg]}, testUtils.context.internal).then(function (result) {
|
||||
var notification = result.notifications[0];
|
||||
|
||||
NotificationsAPI.destroy(
|
||||
_.extend({}, testUtils.context.internal, {id: notification.id})
|
||||
).then(function (result) {
|
||||
should.not.exist(result);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
NotificationsAPI
|
||||
.destroy(_.extend({}, testUtils.context.internal, {id: notification.id}))
|
||||
.then(function (result) {
|
||||
should.not.exist(result);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
@ -142,22 +199,23 @@ describe('Notifications API', function () {
|
||||
NotificationsAPI.add({notifications: [msg]}, testUtils.context.internal).then(function (result) {
|
||||
var notification = result.notifications[0];
|
||||
|
||||
NotificationsAPI.destroy(
|
||||
_.extend({}, testUtils.context.owner, {id: notification.id})
|
||||
).then(function (result) {
|
||||
should.not.exist(result);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
NotificationsAPI
|
||||
.destroy(_.extend({}, testUtils.context.owner, {id: notification.id}))
|
||||
.then(function (result) {
|
||||
should.not.exist(result);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('can destroy a custom notification and add its uuid to seen_notifications (owner)', function (done) {
|
||||
it('ensure notification get\'s removed', function (done) {
|
||||
var customNotification = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
location: 'test.to-be-deleted',
|
||||
custom: true,
|
||||
id: uuid.v1(),
|
||||
dismissible: true,
|
||||
message: 'Hello, this is dog number 4'
|
||||
};
|
||||
@ -165,16 +223,68 @@ describe('Notifications API', function () {
|
||||
NotificationsAPI.add({notifications: [customNotification]}, testUtils.context.internal).then(function (result) {
|
||||
var notification = result.notifications[0];
|
||||
|
||||
NotificationsAPI.destroy(
|
||||
_.extend({}, testUtils.context.internal, {id: notification.id})
|
||||
).then(function () {
|
||||
return SettingsAPI.read(_.extend({key: 'seen_notifications'}, testUtils.context.internal));
|
||||
}).then(function (response) {
|
||||
should.exist(response);
|
||||
response.settings[0].value.should.containEql(notification.id);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
return NotificationsAPI.browse(testUtils.context.internal)
|
||||
.then(function (response) {
|
||||
response.notifications.length.should.eql(1);
|
||||
return NotificationsAPI.destroy(_.extend({}, testUtils.context.internal, {id: notification.id}));
|
||||
})
|
||||
.then(function () {
|
||||
return NotificationsAPI.browse(testUtils.context.internal);
|
||||
})
|
||||
.then(function (response) {
|
||||
response.notifications.length.should.eql(0);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('destroy unknown id', function (done) {
|
||||
NotificationsAPI
|
||||
.destroy(_.extend({}, testUtils.context.internal, {id: 1}))
|
||||
.then(function () {
|
||||
done(new Error('Expected notification error.'));
|
||||
})
|
||||
.catch(function (err) {
|
||||
err.statusCode.should.eql(404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('destroy all', function (done) {
|
||||
var customNotification1 = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
location: 'test.to-be-deleted1',
|
||||
custom: true,
|
||||
id: uuid.v1(),
|
||||
dismissible: true,
|
||||
message: 'Hello, this is dog number 1'
|
||||
}, customNotification2 = {
|
||||
status: 'alert',
|
||||
type: 'info',
|
||||
location: 'test.to-be-deleted2',
|
||||
custom: true,
|
||||
id: uuid.v1(),
|
||||
dismissible: true,
|
||||
message: 'Hello, this is dog number 2'
|
||||
};
|
||||
|
||||
NotificationsAPI
|
||||
.add({notifications: [customNotification1]}, testUtils.context.internal)
|
||||
.then(function () {
|
||||
return NotificationsAPI.add({notifications: [customNotification2]}, testUtils.context.internal);
|
||||
})
|
||||
.then(function () {
|
||||
return NotificationsAPI.destroyAll(testUtils.context.internal);
|
||||
})
|
||||
.then(function () {
|
||||
return NotificationsAPI.browse(testUtils.context.internal);
|
||||
})
|
||||
.then(function (response) {
|
||||
response.notifications.length.should.eql(0);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
@ -1517,19 +1517,21 @@ describe('Import (new test structure)', function () {
|
||||
users[1].profile_image.should.eql(exportData.data.users[0].image);
|
||||
// Check feature image is correctly mapped for a tag
|
||||
tags[0].feature_image.should.eql(exportData.data.tags[0].image);
|
||||
|
||||
// Check logo image is correctly mapped for a blog
|
||||
settings[6].key.should.eql('logo');
|
||||
settings[6].value.should.eql('/content/images/2017/05/bloglogo.jpeg');
|
||||
settings[5].key.should.eql('logo');
|
||||
settings[5].value.should.eql('/content/images/2017/05/bloglogo.jpeg');
|
||||
|
||||
// Check cover image is correctly mapped for a blog
|
||||
settings[7].key.should.eql('cover_image');
|
||||
settings[7].value.should.eql('/content/images/2017/05/blogcover.jpeg');
|
||||
settings[6].key.should.eql('cover_image');
|
||||
settings[6].value.should.eql('/content/images/2017/05/blogcover.jpeg');
|
||||
|
||||
// Check default settings locale is not overwritten by defaultLang
|
||||
settings[9].key.should.eql('default_locale');
|
||||
settings[9].value.should.eql('en');
|
||||
settings[8].key.should.eql('default_locale');
|
||||
settings[8].value.should.eql('en');
|
||||
|
||||
settings[18].key.should.eql('labs');
|
||||
settings[18].value.should.eql('{"publicAPI":true}');
|
||||
settings[17].key.should.eql('labs');
|
||||
settings[17].value.should.eql('{"publicAPI":true}');
|
||||
|
||||
// Check post language is null
|
||||
should(firstPost.locale).equal(null);
|
||||
@ -1600,15 +1602,15 @@ describe('Import (new test structure)', function () {
|
||||
// Check feature image is correctly mapped for a tag
|
||||
tags[0].feature_image.should.eql(exportData.data.tags[0].image);
|
||||
// Check logo image is correctly mapped for a blog
|
||||
settings[6].key.should.eql('logo');
|
||||
settings[6].value.should.eql('/content/images/2017/05/bloglogo.jpeg');
|
||||
settings[5].key.should.eql('logo');
|
||||
settings[5].value.should.eql('/content/images/2017/05/bloglogo.jpeg');
|
||||
// Check cover image is correctly mapped for a blog
|
||||
settings[7].key.should.eql('cover_image');
|
||||
settings[7].value.should.eql('/content/images/2017/05/blogcover.jpeg');
|
||||
settings[6].key.should.eql('cover_image');
|
||||
settings[6].value.should.eql('/content/images/2017/05/blogcover.jpeg');
|
||||
|
||||
// Check default settings locale is not overwritten by defaultLang
|
||||
settings[9].key.should.eql('default_locale');
|
||||
settings[9].value.should.eql('en');
|
||||
settings[8].key.should.eql('default_locale');
|
||||
settings[8].value.should.eql('en');
|
||||
|
||||
// Check post language is set to null
|
||||
should(firstPost.locale).equal(null);
|
||||
|
@ -22,7 +22,7 @@ describe('Models: listeners', function () {
|
||||
};
|
||||
|
||||
before(testUtils.teardown);
|
||||
beforeEach(testUtils.setup('owner', 'user-token:0'));
|
||||
beforeEach(testUtils.setup('owner', 'user-token:0', 'settings'));
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox.stub(common.events, 'on').callsFake(function (eventName, callback) {
|
||||
@ -361,4 +361,70 @@ describe('Models: listeners', function () {
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
describe('on notifications changed', function () {
|
||||
it('nothing to delete', function (done) {
|
||||
var notifications = JSON.stringify([
|
||||
{
|
||||
addedAt: moment().subtract(1, 'week').format(),
|
||||
seen: true
|
||||
},
|
||||
{
|
||||
addedAt: moment().subtract(2, 'month').format(),
|
||||
seen: true
|
||||
},
|
||||
{
|
||||
addedAt: moment().subtract(1, 'day').format(),
|
||||
seen: false
|
||||
}
|
||||
]);
|
||||
|
||||
models.Settings.edit({key: 'notifications', value: notifications}, testUtils.context.internal)
|
||||
.then(function () {
|
||||
eventsToRemember['settings.notifications.edited']({
|
||||
attributes: {
|
||||
value: notifications
|
||||
}
|
||||
});
|
||||
|
||||
return models.Settings.findOne({key: 'notifications'}, testUtils.context.internal);
|
||||
}).then(function (model) {
|
||||
JSON.parse(model.get('value')).length.should.eql(3);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('expect deletion', function (done) {
|
||||
var notifications = JSON.stringify([
|
||||
{
|
||||
content: 'keep-1',
|
||||
addedAt: moment().subtract(1, 'week').toDate(),
|
||||
seen: true
|
||||
},
|
||||
{
|
||||
content: 'delete-me',
|
||||
addedAt: moment().subtract(3, 'month').toDate(),
|
||||
seen: true
|
||||
},
|
||||
{
|
||||
content: 'keep-2',
|
||||
addedAt: moment().subtract(1, 'day').toDate(),
|
||||
seen: false
|
||||
}
|
||||
]);
|
||||
|
||||
models.Settings.edit({key: 'notifications', value: notifications}, testUtils.context.internal)
|
||||
.then(function () {
|
||||
setTimeout(function () {
|
||||
return models.Settings.findOne({key: 'notifications'}, testUtils.context.internal)
|
||||
.then(function (model) {
|
||||
JSON.parse(model.get('value')).length.should.eql(2);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
}, 1000);
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,20 +1,102 @@
|
||||
var should = require('should'),
|
||||
_ = require('lodash'),
|
||||
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'),
|
||||
settingsCache = require('../../server/services/settings/cache'),
|
||||
NotificationsAPI = require('../../server/api/notifications');
|
||||
SettingsAPI = require('../../server/api/settings'),
|
||||
NotificationsAPI = require('../../server/api/notifications'),
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('Update Check', function () {
|
||||
after(function () {
|
||||
return NotificationsAPI.destroyAll(testUtils.context.internal);
|
||||
beforeEach(function () {
|
||||
updateCheck = rewire('../../server/update-check');
|
||||
});
|
||||
|
||||
describe('Reporting to UpdateCheck', function () {
|
||||
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);
|
||||
});
|
||||
@ -52,156 +134,512 @@ describe('Update Check', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom Notifications', function () {
|
||||
describe('fn: createCustomNotification', function () {
|
||||
var currentVersionOrig;
|
||||
|
||||
before(function () {
|
||||
currentVersionOrig = updateCheck.__get__('currentVersion');
|
||||
updateCheck.__set__('currentVersion', '0.9.0');
|
||||
currentVersionOrig = updateCheck.__get__('ghostVersion.original');
|
||||
updateCheck.__set__('ghostVersion.original', '0.9.0');
|
||||
});
|
||||
|
||||
after(function () {
|
||||
updateCheck.__set__('currentVersion', currentVersionOrig);
|
||||
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 custom notification for target version', function (done) {
|
||||
it('should create a release notification for target version', function (done) {
|
||||
var createCustomNotification = updateCheck.__get__('createCustomNotification'),
|
||||
message = {
|
||||
id: uuid.v4(),
|
||||
version: '0.9.x',
|
||||
content: '<p>Hey there! This is for 0.9.0 version</p>'
|
||||
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(message).then(function () {
|
||||
createCustomNotification(notification).then(function () {
|
||||
return NotificationsAPI.browse(testUtils.context.internal);
|
||||
}).then(function (results) {
|
||||
should.exist(results);
|
||||
should.exist(results.notifications);
|
||||
results.notifications.length.should.be.above(0);
|
||||
should.exist(_.find(results.notifications, {uuid: message.id}));
|
||||
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('should not create notifications meant for other versions', function (done) {
|
||||
it('should create a custom notification', function (done) {
|
||||
var createCustomNotification = updateCheck.__get__('createCustomNotification'),
|
||||
message = {
|
||||
id: uuid.v4(),
|
||||
version: '0.5.x',
|
||||
content: '<p>Hey there! This is for 0.5.0 version</p>'
|
||||
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(message).then(function () {
|
||||
createCustomNotification(notification).then(function () {
|
||||
return NotificationsAPI.browse(testUtils.context.internal);
|
||||
}).then(function (results) {
|
||||
should.not.exist(_.find(results.notifications, {uuid: message.id}));
|
||||
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('Show notification', function () {
|
||||
var currentVersionOrig;
|
||||
|
||||
before(function () {
|
||||
currentVersionOrig = updateCheck.__get__('currentVersion');
|
||||
});
|
||||
|
||||
after(function () {
|
||||
updateCheck.__set__('currentVersion', currentVersionOrig);
|
||||
});
|
||||
|
||||
beforeEach(testUtils.setup('settings', 'perms:setting', 'perms:notification', 'perms:init'));
|
||||
|
||||
describe('fn: updateCheckResponse', function () {
|
||||
beforeEach(testUtils.setup('settings', 'perms:setting', 'perms:init'));
|
||||
afterEach(testUtils.teardown);
|
||||
|
||||
it('should show update notification', function (done) {
|
||||
var showUpdateNotification = updateCheck.__get__('showUpdateNotification');
|
||||
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__('currentVersion', '1.7.1');
|
||||
settingsCache.set('display_update_notification', {value: '1.9.0'});
|
||||
updateCheck.__set__('createCustomNotification', createNotificationSpy);
|
||||
|
||||
showUpdateNotification()
|
||||
.then(function (result) {
|
||||
result.should.eql('1.9.0');
|
||||
updateCheckResponse({version: '0.11.12', messages: [message]})
|
||||
.then(function () {
|
||||
createNotificationSpy.callCount.should.eql(1);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should show update notification', function (done) {
|
||||
var showUpdateNotification = updateCheck.__get__('showUpdateNotification');
|
||||
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__('currentVersion', '1.7.1');
|
||||
settingsCache.set('display_update_notification', {value: '2.0.0'});
|
||||
updateCheck.__set__('createCustomNotification', createNotificationSpy);
|
||||
|
||||
showUpdateNotification()
|
||||
.then(function (result) {
|
||||
result.should.eql('2.0.0');
|
||||
updateCheckResponse(notifications)
|
||||
.then(function () {
|
||||
createNotificationSpy.callCount.should.eql(2);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should not show update notification: latest minor release is not greater than your Ghost version', function (done) {
|
||||
var showUpdateNotification = updateCheck.__get__('showUpdateNotification');
|
||||
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__('currentVersion', '1.9.0');
|
||||
settingsCache.set('display_update_notification', {value: '1.9.0'});
|
||||
updateCheck.__set__('createCustomNotification', createNotificationSpy);
|
||||
|
||||
showUpdateNotification()
|
||||
.then(function (result) {
|
||||
result.should.eql(false);
|
||||
updateCheckResponse(notifications)
|
||||
.then(function () {
|
||||
createNotificationSpy.callCount.should.eql(2);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should not show update notification: latest minor release is not greater than your Ghost version', function (done) {
|
||||
var showUpdateNotification = updateCheck.__get__('showUpdateNotification');
|
||||
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__('currentVersion', '1.9.1');
|
||||
settingsCache.set('display_update_notification', {value: '1.9.1'});
|
||||
updateCheck.__set__('createCustomNotification', createNotificationSpy);
|
||||
|
||||
showUpdateNotification()
|
||||
.then(function (result) {
|
||||
result.should.eql(false);
|
||||
configUtils.set({notificationGroups: ['migration']});
|
||||
|
||||
updateCheckResponse(notifications)
|
||||
.then(function () {
|
||||
createNotificationSpy.callCount.should.eql(3);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should not show update notification: latest release is a patch', function (done) {
|
||||
var showUpdateNotification = updateCheck.__get__('showUpdateNotification');
|
||||
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__('currentVersion', '1.9.0');
|
||||
settingsCache.set('display_update_notification', {value: '1.9.1'});
|
||||
updateCheck.__set__('createCustomNotification', createNotificationSpy);
|
||||
|
||||
showUpdateNotification()
|
||||
.then(function (result) {
|
||||
result.should.eql(false);
|
||||
configUtils.set({notificationGroups: ['something']});
|
||||
|
||||
updateCheckResponse(notifications)
|
||||
.then(function () {
|
||||
createNotificationSpy.callCount.should.eql(1);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should not show update notification: latest release is a patch', function (done) {
|
||||
var showUpdateNotification = updateCheck.__get__('showUpdateNotification');
|
||||
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__('currentVersion', '1.9.1');
|
||||
settingsCache.set('display_update_notification', {value: '1.9.0'});
|
||||
updateCheck.__set__('createCustomNotification', createNotificationSpy);
|
||||
|
||||
showUpdateNotification()
|
||||
.then(function (result) {
|
||||
result.should.eql(false);
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,35 +0,0 @@
|
||||
var should = require('should'), // jshint ignore:line
|
||||
rewire = require('rewire'),
|
||||
NotificationAPI = rewire('../../../server/api/notifications');
|
||||
|
||||
describe('UNIT: Notification API', function () {
|
||||
it('ensure non duplicates', function (done) {
|
||||
var options = {context: {internal: true}},
|
||||
notifications = [{
|
||||
type: 'info',
|
||||
message: 'Hello, this is dog'
|
||||
}],
|
||||
notificationStore = NotificationAPI.__get__('notificationsStore');
|
||||
|
||||
NotificationAPI.add({notifications: notifications}, options)
|
||||
.then(function () {
|
||||
notificationStore.length.should.eql(1);
|
||||
return NotificationAPI.add({notifications: notifications}, options);
|
||||
})
|
||||
.then(function () {
|
||||
notificationStore.length.should.eql(1);
|
||||
|
||||
notifications.push({
|
||||
type: 'info',
|
||||
message: 'Hello, this is cat'
|
||||
});
|
||||
|
||||
return NotificationAPI.add({notifications: notifications}, options);
|
||||
})
|
||||
.then(function () {
|
||||
notificationStore.length.should.eql(2);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user