0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-25 02:31:59 -05:00

Merge pull request #6572 from ErisDS/data005-part1-fixpastfixtures

Data 005 - Part 1 - Fix Past Fixtures (refactor & test fixture migrations)
This commit is contained in:
Sebastian Gierlinger 2016-03-07 11:26:30 +01:00
commit 21770c53da
24 changed files with 1610 additions and 752 deletions

View file

@ -0,0 +1,46 @@
// Moves jQuery inclusion to code injection via ghost_foot
var _ = require('lodash'),
Promise = require('bluebird'),
serverPath = '../../../../',
config = require(serverPath + 'config'),
models = require(serverPath + 'models'),
notifications = require(serverPath + 'api/notifications'),
i18n = require(serverPath + 'i18n'),
// These messages are shown in the admin UI, not the console, and should therefore be translated
jquery = [
i18n.t('notices.data.fixtures.canSafelyDelete'),
'<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>\n\n'
],
privacyMessage = [
i18n.t('notices.data.fixtures.jQueryRemoved'),
i18n.t('notices.data.fixtures.canBeChanged')
];
module.exports = function moveJQuery(options, logInfo) {
var value;
return models.Settings.findOne('ghost_foot').then(function (setting) {
if (setting) {
value = setting.attributes.value;
// Only add jQuery if it's not already in there
if (value.indexOf(jquery.join('')) === -1) {
logInfo('Adding jQuery link to ghost_foot');
value = jquery.join('') + value;
return models.Settings.edit({key: 'ghost_foot', value: value}, options).then(function () {
if (_.isEmpty(config.privacy)) {
return Promise.resolve();
}
logInfo(privacyMessage.join(' ').replace(/<\/?strong>/g, ''));
return notifications.add({
notifications: [{
type: 'info',
message: privacyMessage.join(' ')
}]
}, options);
});
}
}
});
};

View file

@ -0,0 +1,13 @@
// Update the `isPrivate` setting, so that it has a type of `private` rather than `blog`
var models = require('../../../../models'),
Promise = require('bluebird');
module.exports = function updatePrivateSetting(options, logInfo) {
return models.Settings.findOne('isPrivate').then(function (setting) {
if (setting) {
logInfo('Update isPrivate setting');
return models.Settings.edit({key: 'isPrivate', type: 'private'}, options);
}
return Promise.resolve();
});
};

View file

@ -0,0 +1,13 @@
// Update the `password` setting, so that it has a type of `private` rather than `blog`
var models = require('../../../../models'),
Promise = require('bluebird');
module.exports = function updatePasswordSetting(options, logInfo) {
return models.Settings.findOne('password').then(function (setting) {
if (setting) {
logInfo('Update password setting');
return models.Settings.edit({key: 'password', type: 'private'}, options);
}
return Promise.resolve();
});
};

View file

@ -0,0 +1,21 @@
// Update the `ghost-admin` client so that it has a proper secret
var models = require('../../../../models'),
_ = require('lodash'),
Promise = require('bluebird'),
crypto = require('crypto'),
adminClient = require('../fixtures').models.Client[0];
module.exports = function updateGhostAdminClient(options, logInfo) {
// ghost-admin should already exist from 003 version
return models.Client.findOne({slug: adminClient.slug}).then(function (client) {
if (client) {
logInfo('Update ghost-admin client fixture');
return models.Client.edit(
_.extend({}, adminClient, {secret: crypto.randomBytes(6).toString('hex')}),
_.extend({}, options, {id: client.id})
);
}
return Promise.resolve();
});
};

View file

@ -0,0 +1,15 @@
// Create a new `ghost-frontend` client for use in themes
var models = require('../../../../models'),
Promise = require('bluebird'),
frontendClient = require('../fixtures').models.Client[1];
module.exports = function addGhostFrontendClient(options, logInfo) {
return models.Client.findOne({slug: frontendClient.slug}).then(function (client) {
if (!client) {
logInfo('Add ghost-frontend client fixture');
return models.Client.add(frontendClient, options);
}
return Promise.resolve();
});
};

View file

@ -0,0 +1,27 @@
// Clean tags which start with commas, the only illegal char in tags
var models = require('../../../../models'),
Promise = require('bluebird');
module.exports = function cleanBrokenTags(options, logInfo) {
return models.Tag.findAll(options).then(function (tags) {
var tagOps = [];
if (tags) {
tags.each(function (tag) {
var name = tag.get('name'),
updated = name.replace(/^(,+)/, '').trim();
// If we've ended up with an empty string, default to just 'tag'
updated = updated === '' ? 'tag' : updated;
if (name !== updated) {
tagOps.push(tag.save({name: updated}, options));
}
});
if (tagOps.length > 0) {
logInfo('Cleaning ' + tagOps.length + ' malformed tags');
return Promise.all(tagOps);
}
}
return Promise.resolve();
});
};

View file

@ -0,0 +1,39 @@
// Add a new order value to posts_tags based on the existing info
var models = require('../../../../models'),
_ = require('lodash'),
sequence = require('../../../../utils/sequence');
module.exports = function addPostTagOrder(options, logInfo) {
var tagOps = [];
logInfo('Collecting data on tag order for posts...');
return models.Post.findAll(_.extend({}, options)).then(function (posts) {
if (posts) {
return posts.mapThen(function (post) {
return post.load(['tags']);
});
}
return [];
}).then(function (posts) {
_.each(posts, function (post) {
var order = 0;
post.related('tags').each(function (tag) {
tagOps.push((function (order) {
var sortOrder = order;
return function () {
return post.tags().updatePivot(
{sort_order: sortOrder}, _.extend({}, options, {query: {where: {tag_id: tag.id}}})
);
};
}(order)));
order += 1;
});
});
if (tagOps.length > 0) {
logInfo('Updating order on ' + tagOps.length + ' tag relationships (could take a while)...');
return sequence(tagOps).then(function () {
logInfo('Tag order successfully updated');
});
}
});
};

View file

@ -0,0 +1,27 @@
// Adds a new draft post with information about the new design
var models = require('../../../../models'),
newPost = {
title: 'You\'ve been upgraded to the latest version of Ghost',
slug: 'ghost-0-7',
markdown: 'You\'ve just upgraded to the latest version of Ghost and we\'ve made a few changes that you should probably know about!\n\n## Woah, why does everything look different?\n\nAfter two years and hundreds of thousands of users, we learned a great deal about what was (and wasn\'t) working in the old Ghost admin user interface. What you\'re looking at is Ghost\'s first major UI refresh, with a strong focus on being more usable and robust all round.\n\n![New Design](https://ghost.org/images/zelda.png)\n\nThe main navigation menu, previously located at the top of your screen, has now moved over to the left. This makes it way easier to work with on mobile devices, and has the added benefit of providing ample space for upcoming features!\n\n## Lost and found: Your old posts\n\nFrom talking to many of you we understand that finding old posts in the admin area was a real pain; so we\'ve added a new magical search bar which lets you quickly find posts for editing, without having to scroll endlessly. Take it for a spin!\n\n![Search](https://ghost.org/images/search.gif)\n\nQuestions? Comments? Send us a tweet [@TryGhost](https://twitter.com/tryghost)\n\nOh, and yes you can safely delete this draft post!',
image: null,
featured: false,
page: false,
status: 'draft',
language: 'en_US',
meta_title: null,
meta_description: null
};
module.exports = function addNewPostFixture(options, logInfo) {
return models.Post.findOne({slug: newPost.slug, status: 'all'}, options).then(function (post) {
if (!post) {
logInfo('Adding 0.7 upgrade post fixture');
// Set the published_at timestamp, but keep the post as a draft so doesn't appear on the frontend
// This is a hack to ensure that this post appears at the very top of the drafts list, because
// unpublished posts always appear first
newPost.published_at = Date.now();
return models.Post.add(newPost, options);
}
});
};

View file

@ -0,0 +1,25 @@
module.exports = [
// add jquery setting and privacy info
require('./01-move-jquery-with-alert'),
// change `type` for protected blog `isPrivate` setting
require('./02-update-private-setting-type'),
// change `type` for protected blog `password` setting
require('./03-update-password-setting-type'),
// Update ghost-admin client fixture
require('./04-update-ghost-admin-client'),
// add ghost-frontend client if missing
require('./05-add-ghost-frontend-client'),
// clean up broken tags
require('./06-clean-broken-tags'),
// Add post_tag order
require('./07-add-post-tag-order'),
// Add a new draft post
require('./08-add-post-fixture')
];

View file

@ -1,81 +1,269 @@
{
"users": [
{
"name": "Ghost Owner",
"email": "ghost@ghost.org",
"status": "inactive"
}
],
"posts": [
{
"title": "Welcome to Ghost",
"slug": "welcome-to-ghost",
"markdown": "You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at `<your blog URL>/ghost/`. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!\n\n## Getting Started\n\nGhost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!\n\nWriting in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use *shortcuts* to **style** your content. For example, a list:\n\n* Item number one\n* Item number two\n * A nested item\n* A final item\n\nor with numbers!\n\n1. Remember to buy some milk\n2. Drink the milk\n3. Tweet that I remembered to buy the milk, and drank it\n\n### Links\n\nWant to link to a source? No problem. If you paste in a URL, like http://ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to [the Ghost website](http://ghost.org). Neat.\n\n### What about Images?\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:\n\n![The Ghost Logo](https://ghost.org/images/ghost.png)\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:\n\n![A bowl of bananas]\n\n\n### Quoting\n\nSometimes a link isn't enough, you want to quote someone on what they've said. Perhaps you've started using a new blogging platform and feel the sudden urge to share their slogan? A quote might be just the way to do it!\n\n> Ghost - Just a blogging platform\n\n### Working with Code\n\nGot a streak of geek? We've got you covered there, too. You can write inline `<code>` blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.\n\n .awesome-thing {\n display: block;\n width: 100%;\n }\n\n### Ready for a Break? \n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.\n\n---\n\n### Advanced Usage\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.\n\n<input type=\"text\" placeholder=\"I'm an input field!\" />\n\nThat should be enough to get you started. Have fun - and let us know what you think :)",
"image": null,
"featured": false,
"page": false,
"status": "published",
"language": "en_US",
"meta_title": null,
"meta_description": null
}
],
"models": {
"Post": [
{
"title": "Welcome to Ghost",
"slug": "welcome-to-ghost",
"markdown": "You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at `<your blog URL>/ghost/`. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!\n\n## Getting Started\n\nGhost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!\n\nWriting in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use *shortcuts* to **style** your content. For example, a list:\n\n* Item number one\n* Item number two\n * A nested item\n* A final item\n\nor with numbers!\n\n1. Remember to buy some milk\n2. Drink the milk\n3. Tweet that I remembered to buy the milk, and drank it\n\n### Links\n\nWant to link to a source? No problem. If you paste in a URL, like http://ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to [the Ghost website](http://ghost.org). Neat.\n\n### What about Images?\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:\n\n![The Ghost Logo](https://ghost.org/images/ghost.png)\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:\n\n![A bowl of bananas]\n\n\n### Quoting\n\nSometimes a link isn't enough, you want to quote someone on what they've said. Perhaps you've started using a new blogging platform and feel the sudden urge to share their slogan? A quote might be just the way to do it!\n\n> Ghost - Just a blogging platform\n\n### Working with Code\n\nGot a streak of geek? We've got you covered there, too. You can write inline `<code>` blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.\n\n .awesome-thing {\n display: block;\n width: 100%;\n }\n\n### Ready for a Break? \n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.\n\n---\n\n### Advanced Usage\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.\n\n<input type=\"text\" placeholder=\"I'm an input field!\" />\n\nThat should be enough to get you started. Have fun - and let us know what you think :)",
"image": null,
"featured": false,
"page": false,
"status": "published",
"language": "en_US",
"meta_title": null,
"meta_description": null
}
],
"posts_0_7": [
{
"title": "You've been upgraded to the latest version of Ghost",
"slug": "ghost-0-7",
"markdown": "You've just upgraded to the latest version of Ghost and we've made a few changes that you should probably know about!\n\n## Woah, why does everything look different?\n\nAfter two years and hundreds of thousands of users, we learned a great deal about what was (and wasn't) working in the old Ghost admin user interface. What you're looking at is Ghost's first major UI refresh, with a strong focus on being more usable and robust all round.\n\n![New Design](https://ghost.org/images/zelda.png)\n\nThe main navigation menu, previously located at the top of your screen, has now moved over to the left. This makes it way easier to work with on mobile devices, and has the added benefit of providing ample space for upcoming features!\n\n## Lost and found: Your old posts\n\nFrom talking to many of you we understand that finding old posts in the admin area was a real pain; so we've added a new magical search bar which lets you quickly find posts for editing, without having to scroll endlessly. Take it for a spin!\n\n![Search](https://ghost.org/images/search.gif)\n\nQuestions? Comments? Send us a tweet [@TryGhost](https://twitter.com/tryghost)\n\nOh, and yes you can safely delete this draft post!",
"image": null,
"featured": false,
"page": false,
"status": "draft",
"language": "en_US",
"meta_title": null,
"meta_description": null
}
],
"Tag": [
{
"name": "Getting Started",
"slug": "getting-started",
"description": null,
"parent_id": null,
"meta_title": null,
"meta_description": null
}
],
"tags": [
"Client": [
{
"name": "Ghost Admin",
"slug": "ghost-admin",
"status": "enabled"
},
{
"name": "Ghost Frontend",
"slug": "ghost-frontend",
"status": "enabled"
}
],
"Role": [
{
"name": "Administrator",
"description": "Administrators"
},
{
"name": "Editor",
"description": "Editors"
},
{
"name": "Author",
"description": "Authors"
},
{
"name": "Owner",
"description": "Blog Owner"
}
],
"Permission": [
{
"name": "Export database",
"action_type": "exportContent",
"object_type": "db"
},
{
"name": "Import database",
"action_type": "importContent",
"object_type": "db"
},
{
"name": "Delete all content",
"action_type": "deleteAllContent",
"object_type": "db"
},
{
"name": "Send mail",
"action_type": "send",
"object_type": "mail"
},
{
"name": "Browse notifications",
"action_type": "browse",
"object_type": "notification"
},
{
"name": "Add notifications",
"action_type": "add",
"object_type": "notification"
},
{
"name": "Delete notifications",
"action_type": "destroy",
"object_type": "notification"
},
{
"name": "Browse posts",
"action_type": "browse",
"object_type": "post"
},
{
"name": "Read posts",
"action_type": "read",
"object_type": "post"
},
{
"name": "Edit posts",
"action_type": "edit",
"object_type": "post"
},
{
"name": "Add posts",
"action_type": "add",
"object_type": "post"
},
{
"name": "Delete posts",
"action_type": "destroy",
"object_type": "post"
},
{
"name": "Browse settings",
"action_type": "browse",
"object_type": "setting"
},
{
"name": "Read settings",
"action_type": "read",
"object_type": "setting"
},
{
"name": "Edit settings",
"action_type": "edit",
"object_type": "setting"
},
{
"name": "Generate slugs",
"action_type": "generate",
"object_type": "slug"
},
{
"name": "Browse tags",
"action_type": "browse",
"object_type": "tag"
},
{
"name": "Read tags",
"action_type": "read",
"object_type": "tag"
},
{
"name": "Edit tags",
"action_type": "edit",
"object_type": "tag"
},
{
"name": "Add tags",
"action_type": "add",
"object_type": "tag"
},
{
"name": "Delete tags",
"action_type": "destroy",
"object_type": "tag"
},
{
"name": "Browse themes",
"action_type": "browse",
"object_type": "theme"
},
{
"name": "Edit themes",
"action_type": "edit",
"object_type": "theme"
},
{
"name": "Browse users",
"action_type": "browse",
"object_type": "user"
},
{
"name": "Read users",
"action_type": "read",
"object_type": "user"
},
{
"name": "Edit users",
"action_type": "edit",
"object_type": "user"
},
{
"name": "Add users",
"action_type": "add",
"object_type": "user"
},
{
"name": "Delete users",
"action_type": "destroy",
"object_type": "user"
},
{
"name": "Assign a role",
"action_type": "assign",
"object_type": "role"
},
{
"name": "Browse roles",
"action_type": "browse",
"object_type": "role"
}
]
},
"relations": [
{
"name": "Getting Started",
"slug": "getting-started",
"description": null,
"parent_id": null,
"meta_title": null,
"meta_description": null
}
],
"roles": [
{
"name": "Administrator",
"description": "Administrators"
"from": {
"model": "Role",
"match": "name",
"relation": "permissions"
},
"to": {
"model": "Permission",
"match": ["object_type", "action_type"]
},
"entries": {
"Administrator": {
"db": "all",
"mail": "all",
"notification": "all",
"post": "all",
"setting": "all",
"slug": "all",
"tag": "all",
"theme": "all",
"user": "all",
"role": "all"
},
"Editor": {
"post": "all",
"setting": ["browse", "read"],
"slug": "all",
"tag": "all",
"user": "all",
"role": "all"
},
"Author": {
"post": ["browse", "read", "add"],
"setting": ["browse", "read"],
"slug": "all",
"tag": ["browse", "read", "add"],
"user": ["browse", "read"],
"role": ["browse"]
}
}
},
{
"name": "Editor",
"description": "Editors"
},
{
"name": "Author",
"description": "Authors"
},
{
"name": "Owner",
"description": "Blog Owner"
}
],
"clients": [
{
"name": "Ghost Admin",
"slug": "ghost-admin",
"status": "enabled"
},
{
"name": "Ghost Frontend",
"slug": "ghost-frontend",
"status": "enabled"
"from": {
"model": "Post",
"match": "title",
"relation": "tags"
},
"to": {
"model": "Tag",
"match": "name"
},
"entries": {
"Welcome to Ghost": ["Getting Started"]
}
}
]
}

View file

@ -1,361 +1,9 @@
// # Fixtures
// This module handles populating or updating fixtures.
//
// Currently fixtures only change between data version 002 and 003, therefore the update logic is hard coded
// rather than abstracted into a migration system. The upgrade function checks that its changes are safe before
// making them.
var Promise = require('bluebird'),
crypto = require('crypto'),
_ = require('lodash'),
fixtures = require('./fixtures'),
permissions = require('./permissions/index'),
notifications = require('../../../api/notifications'),
config = require('../../../config'),
errors = require('../../../errors'),
i18n = require('../../../i18n'),
models = require('../../../models'),
utils = require('../../../utils'),
sequence = require('../../../utils/sequence'),
// Private
logInfo,
to003,
to004,
convertAdminToOwner,
createOwner,
options = {context: {internal: true}},
// Public
populate,
update;
logInfo = function logInfo(message) {
errors.logInfo('Migrations', message);
};
/**
* Convert admin to Owner
* Changes an admin user to have the owner role
* @returns {Promise|*}
*/
convertAdminToOwner = function convertAdminToOwner() {
var adminUser;
return models.User.findOne({role: 'Administrator'}).then(function (user) {
adminUser = user;
return models.Role.findOne({name: 'Owner'});
}).then(function (ownerRole) {
if (adminUser) {
logInfo('Converting admin to owner');
return adminUser.roles().updatePivot({role_id: ownerRole.id});
}
});
};
/**
* Create Owner
* Creates the user fixture and gives it the owner role
* @returns {Promise|*}
*/
createOwner = function createOwner() {
var user = fixtures.users[0];
return models.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
user.roles = [ownerRole.id];
user.password = utils.uid(50);
logInfo('Creating owner');
return models.User.add(user, options);
});
};
populate = function populate() {
var ops = [],
relations = [],
Post = models.Post,
Tag = models.Tag,
Role = models.Role,
Client = models.Client;
logInfo('Populating fixtures');
_.each(fixtures.posts, function (post) {
ops.push(Post.add(post, options));
});
_.each(fixtures.tags, function (tag) {
ops.push(Tag.add(tag, options));
});
_.each(fixtures.roles, function (role) {
ops.push(Role.add(role, options));
});
_.each(fixtures.clients, function (client) {
ops.push(Client.add(client, options));
});
// add the tag to the post
relations.push(function () {
return Post.forge({slug: fixtures.posts[0].slug}).fetch().then(function (post) {
return Tag.forge({slug: fixtures.tags[0].slug}).fetch().then(function (tag) {
return post.related('tags').attach(tag.id);
});
});
});
return Promise.all(ops).then(function () {
return sequence(relations);
}).then(function () {
return permissions.populate(options);
}).then(function () {
return createOwner();
}).catch(function (errs) {
errors.logError(errs);
});
};
/**
* ### Update fixtures to 003
* Need to add client & owner role, then update permissions to 003 as well
* By doing this in a way that checks before adding, we can ensure that it's possible to force a migration safely.
*
* Note: At the moment this is pretty adhoc & untestable, in future it would be better to have a config based system.
* @returns {Promise|*}
*/
to003 = function to003() {
var ops = [],
upgradeOp,
Role = models.Role,
Client = models.Client;
logInfo('Upgrading fixtures to 003');
// Add the client fixture if missing
upgradeOp = Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) {
if (!client) {
logInfo('Adding ghost-admin client fixture');
return Client.add(fixtures.clients[0], options);
}
});
ops.push(upgradeOp);
// Add the owner role if missing
upgradeOp = Role.findOne({name: fixtures.roles[3].name}).then(function (owner) {
if (!owner) {
logInfo('Adding owner role fixture');
_.each(fixtures.roles.slice(3), function (role) {
return Role.add(role, options);
});
}
});
ops.push(upgradeOp);
return Promise.all(ops).then(function () {
return permissions.to003(options);
}).then(function () {
return convertAdminToOwner();
});
};
/**
* Update ghost_foot to include a CDN of jquery if the DB is migrating from
* @return {Promise}
*/
to004 = function to004() {
var value,
ops = [],
upgradeOp,
// These messages are shown in the admin UI, not the console, and should therefore be translated
jquery = [
i18n.t('notices.data.fixtures.canSafelyDelete'),
'<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>\n\n'
],
privacyMessage = [
i18n.t('notices.data.fixtures.jQueryRemoved'),
i18n.t('notices.data.fixtures.canBeChanged')
];
logInfo('Upgrading fixtures to 004');
// add jquery setting and privacy info
upgradeOp = function () {
return models.Settings.findOne('ghost_foot').then(function (setting) {
if (setting) {
value = setting.attributes.value;
// Only add jQuery if it's not already in there
if (value.indexOf(jquery.join('')) === -1) {
logInfo('Adding jQuery link to ghost_foot');
value = jquery.join('') + value;
return models.Settings.edit({key: 'ghost_foot', value: value}, options).then(function () {
if (_.isEmpty(config.privacy)) {
return Promise.resolve();
}
logInfo(privacyMessage.join(' ').replace(/<\/?strong>/g, ''));
return notifications.add({notifications: [{
type: 'info',
message: privacyMessage.join(' ')
}]}, options);
});
}
}
});
};
ops.push(upgradeOp);
// change `type` for protected blog `isPrivate` setting
upgradeOp = function () {
return models.Settings.findOne('isPrivate').then(function (setting) {
if (setting) {
logInfo('Update isPrivate setting');
return models.Settings.edit({key: 'isPrivate', type: 'private'}, options);
}
return Promise.resolve();
});
};
ops.push(upgradeOp);
// change `type` for protected blog `password` setting
upgradeOp = function () {
return models.Settings.findOne('password').then(function (setting) {
if (setting) {
logInfo('Update password setting');
return models.Settings.edit({key: 'password', type: 'private'}, options);
}
return Promise.resolve();
});
};
ops.push(upgradeOp);
// Update ghost-admin client fixture
// ghost-admin should exist from 003 version
upgradeOp = function () {
return models.Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) {
if (client) {
logInfo('Update ghost-admin client fixture');
var adminClient = fixtures.clients[0];
adminClient.secret = crypto.randomBytes(6).toString('hex');
return models.Client.edit(adminClient, _.extend({}, options, {id: client.id}));
}
return Promise.resolve();
});
};
ops.push(upgradeOp);
// add ghost-frontend client if missing
upgradeOp = function () {
return models.Client.findOne({slug: fixtures.clients[1].slug}).then(function (client) {
if (!client) {
logInfo('Add ghost-frontend client fixture');
var frontendClient = fixtures.clients[1];
return models.Client.add(frontendClient, options);
}
return Promise.resolve();
});
};
ops.push(upgradeOp);
// clean up broken tags
upgradeOp = function () {
return models.Tag.findAll(options).then(function (tags) {
var tagOps = [];
if (tags) {
tags.each(function (tag) {
var name = tag.get('name'),
updated = name.replace(/^(,+)/, '').trim();
// If we've ended up with an empty string, default to just 'tag'
updated = updated === '' ? 'tag' : updated;
if (name !== updated) {
tagOps.push(tag.save({name: updated}, options));
}
});
if (tagOps.length > 0) {
logInfo('Cleaning ' + tagOps.length + ' malformed tags');
return Promise.all(tagOps);
}
}
return Promise.resolve();
});
};
ops.push(upgradeOp);
// Add post_tag order
upgradeOp = function () {
var tagOps = [];
logInfo('Collecting data on tag order for posts...');
return models.Post.findAll(_.extend({}, options)).then(function (posts) {
if (posts) {
return posts.mapThen(function (post) {
return post.load(['tags']);
});
}
return [];
}).then(function (posts) {
_.each(posts, function (post) {
var order = 0;
post.related('tags').each(function (tag) {
tagOps.push((function (order) {
var sortOrder = order;
return function () {
return post.tags().updatePivot(
{sort_order: sortOrder}, _.extend({}, options, {query: {where: {tag_id: tag.id}}})
);
};
}(order)));
order += 1;
});
});
if (tagOps.length > 0) {
logInfo('Updating order on ' + tagOps.length + ' tag relationships (could take a while)...');
return sequence(tagOps).then(function () {
logInfo('Tag order successfully updated');
});
}
});
};
ops.push(upgradeOp);
// Add a new draft post
upgradeOp = function () {
return models.Post.findOne({slug: fixtures.posts_0_7[0].slug, status: 'all'}, options).then(function (post) {
if (!post) {
logInfo('Adding 0.7 upgrade post fixture');
// Set the published_at timestamp, but keep the post as a draft so doesn't appear on the frontend
// This is a hack to ensure that this post appears at the very top of the drafts list, because
// unpublished posts always appear first
fixtures.posts_0_7[0].published_at = Date.now();
return models.Post.add(fixtures.posts_0_7[0], options);
}
});
};
ops.push(upgradeOp);
return sequence(ops);
};
update = function update(fromVersion, toVersion) {
var ops = [];
logInfo('Updating fixtures');
// Are we migrating to, or past 003?
if ((fromVersion < '003' && toVersion >= '003') ||
fromVersion === '003' && toVersion === '003' && process.env.FORCE_MIGRATION) {
ops.push(to003);
}
if (fromVersion < '004' && toVersion === '004' ||
fromVersion === '004' && toVersion === '004' && process.env.FORCE_MIGRATION) {
ops.push(to004);
}
return sequence(ops);
};
var populate = require('./populate'),
update = require('./update'),
fixtures = require('./fixtures');
module.exports = {
populate: populate,
update: update
update: update,
fixtures: fixtures
};

View file

@ -1,110 +0,0 @@
// # Permissions Fixtures
// Sets up the permissions, and the default permissions_roles relationships
var Promise = require('bluebird'),
_ = require('lodash'),
errors = require('../../../../errors'),
models = require('../../../../models'),
sequence = require('../../../../utils/sequence'),
fixtures = require('./permissions'),
// private
logInfo,
addAllPermissions,
addAllRolesPermissions,
addRolesPermissionsForRole,
// public
populate,
to003;
logInfo = function logInfo(message) {
errors.logInfo('Migrations', message);
};
addRolesPermissionsForRole = function (roleName) {
var fixturesForRole = fixtures.permissions_roles[roleName],
permissionsToAdd;
return models.Role.forge({name: roleName}).fetch({withRelated: ['permissions']}).then(function (role) {
return models.Permissions.forge().fetch().then(function (permissions) {
if (_.isObject(fixturesForRole)) {
permissionsToAdd = _.map(permissions.toJSON(), function (permission) {
var objectPermissions = fixturesForRole[permission.object_type];
if (objectPermissions === 'all') {
return permission.id;
} else if (_.isArray(objectPermissions) && _.contains(objectPermissions, permission.action_type)) {
return permission.id;
}
return null;
});
}
return role.permissions().attach(_.compact(permissionsToAdd));
});
});
};
addAllRolesPermissions = function () {
var roleNames = _.keys(fixtures.permissions_roles),
ops = [];
_.each(roleNames, function (roleName) {
ops.push(addRolesPermissionsForRole(roleName));
});
return Promise.all(ops);
};
addAllPermissions = function (options) {
var ops = [];
_.each(fixtures.permissions, function (permissions, objectType) {
_.each(permissions, function (permission) {
ops.push(function () {
permission.object_type = objectType;
return models.Permission.add(permission, options);
});
});
});
return sequence(ops);
};
// ## Populate
populate = function (options) {
logInfo('Populating permissions');
// ### Ensure all permissions are added
return addAllPermissions(options).then(function () {
// ### Ensure all roles_permissions are added
return addAllRolesPermissions();
});
};
// ## Update
// Update permissions to 003
// Need to rename old permissions, and then add all of the missing ones
to003 = function (options) {
var ops = [];
logInfo('Upgrading permissions');
// To safely upgrade, we need to clear up the existing permissions and permissions_roles before recreating the new
// full set of permissions defined as of version 003
return models.Permissions.forge().fetch().then(function (permissions) {
logInfo('Removing old permissions');
permissions.each(function (permission) {
ops.push(permission.related('roles').detach().then(function () {
return permission.destroy();
}));
});
// Now we can perform the normal populate
return Promise.all(ops).then(function () {
return populate(options);
});
});
};
module.exports = {
populate: populate,
to003: to003
};

View file

@ -1,174 +0,0 @@
{
"permissions": {
"db": [
{
"name": "Export database",
"action_type": "exportContent"
},
{
"name": "Import database",
"action_type": "importContent"
},
{
"name": "Delete all content",
"action_type": "deleteAllContent"
}
],
"mail": [
{
"name": "Send mail",
"action_type": "send"
}
],
"notification": [
{
"name": "Browse notifications",
"action_type": "browse"
},
{
"name": "Add notifications",
"action_type": "add"
},
{
"name": "Delete notifications",
"action_type": "destroy"
}
],
"post": [
{
"name": "Browse posts",
"action_type": "browse"
},
{
"name": "Read posts",
"action_type": "read"
},
{
"name": "Edit posts",
"action_type": "edit"
},
{
"name": "Add posts",
"action_type": "add"
},
{
"name": "Delete posts",
"action_type": "destroy"
}
],
"setting": [
{
"name": "Browse settings",
"action_type": "browse"
},
{
"name": "Read settings",
"action_type": "read"
},
{
"name": "Edit settings",
"action_type": "edit"
}
],
"slug": [
{
"name": "Generate slugs",
"action_type": "generate"
}
],
"tag": [
{
"name": "Browse tags",
"action_type": "browse"
},
{
"name": "Read tags",
"action_type": "read"
},
{
"name": "Edit tags",
"action_type": "edit"
},
{
"name": "Add tags",
"action_type": "add"
},
{
"name": "Delete tags",
"action_type": "destroy"
}
],
"theme": [
{
"name": "Browse themes",
"action_type": "browse"
},
{
"name": "Edit themes",
"action_type": "edit"
}
],
"user": [
{
"name": "Browse users",
"action_type": "browse"
},
{
"name": "Read users",
"action_type": "read"
},
{
"name": "Edit users",
"action_type": "edit"
},
{
"name": "Add users",
"action_type": "add"
},
{
"name": "Delete users",
"action_type": "destroy"
}
],
"role": [
{
"name": "Assign a role",
"action_type": "assign"
},
{
"name": "Browse roles",
"action_type": "browse"
}
]
},
"permissions_roles": {
"Administrator": {
"db": "all",
"mail": "all",
"notification": "all",
"post": "all",
"setting": "all",
"slug": "all",
"tag": "all",
"theme": "all",
"user": "all",
"role": "all"
},
"Editor": {
"post": "all",
"setting": ["browse", "read"],
"slug": "all",
"tag": "all",
"user": "all",
"role": "all"
},
"Author": {
"post": ["browse", "read", "add"],
"setting": ["browse", "read"],
"slug": "all",
"tag": ["browse", "read", "add"],
"user": ["browse", "read"],
"role": ["browse"]
}
}
}

View file

@ -0,0 +1,173 @@
// # Populate Fixtures
// This module handles populating fixtures on a fresh install.
// This is done automatically, by reading the fixtures.json file
// All models, and relationships inside the file are then setup.
var Promise = require('bluebird'),
_ = require('lodash'),
models = require('../../../models'),
utils = require('../../../utils'),
sequence = require('../../../utils/sequence'),
fixtures = require('./fixtures'),
// private
addAllModels,
addAllRelations,
fetchRelationData,
matchFunc,
createOwner,
// public
populate;
/**
* ### Add All Models
* Sequentially calls add on all the models specified in fixtures.json
*
* @param {Object} modelOptions
* @returns {Promise<*>}
*/
addAllModels = function addAllModels(modelOptions) {
var ops = [];
_.each(fixtures.models, function (items, modelName) {
_.each(items, function (item) {
ops.push(function () {
return models[modelName].add(item, modelOptions);
});
});
});
return sequence(ops);
};
/**
* ### Fetch Relation Data
* Before we build relations we need to fetch all of the models from both sides so that we can
* use filter and find to quickly locate the correct models.
*
* @param {Object} relation
* @param {Object} modelOptions
* @returns {Promise<*>}
*/
fetchRelationData = function fetchRelationData(relation, modelOptions) {
var props = {
from: models[relation.from.model].findAll(modelOptions),
to: models[relation.to.model].findAll(modelOptions)
};
return Promise.props(props);
};
/**
* ### Match Func
* Figures out how to match across various combinations of keys and values.
* Match can be a string or an array containing 2 strings
* Key and Value are the values to be found
* Value can also be an array, in which case we look for a match in the array.
*
* @param {String|Array} match
* @param {String} key
* @param {String|Array} [value]
* @returns {Function}
*/
matchFunc = function matchFunc(match, key, value) {
if (_.isArray(match)) {
return function (item) {
var valueTest = true;
if (_.isArray(value)) {
valueTest = value.indexOf(item.get(match[1])) > -1;
} else if (value !== 'all') {
valueTest = item.get(match[1]) === value;
}
return item.get(match[0]) === key && valueTest;
};
}
return function (item) {
key = key === 0 && value ? value : key;
return item.get(match) === key;
};
};
/**
* ### Add All Relations
* Sequentially calls add on all the relations specified in fixtures.json
*
* @param {Object} modelOptions
* @returns {Promise|Array}
*/
addAllRelations = function addAllRelations(modelOptions) {
return Promise.map(fixtures.relations, function (relation) {
return fetchRelationData(relation, modelOptions).then(function (data) {
var ops = [];
_.each(relation.entries, function (entry, key) {
var fromItem = data.from.find(matchFunc(relation.from.match, key));
_.each(entry, function (value, key) {
var toItem = data.to.filter(matchFunc(relation.to.match, key, value));
if (toItem) {
ops.push(function () {
return fromItem[relation.from.relation]().attach(toItem);
});
}
});
});
return sequence(ops);
});
});
};
/**
* ### Create Owner
* Creates the user fixture and gives it the owner role.
* By default, users are given the Author role, making it hard to do this using the fixture system
*
* @param {Object} modelOptions
* @param {Function} logInfo
* @returns {Promise<*>}
*/
createOwner = function createOwner(modelOptions, logInfo) {
var user = {
name: 'Ghost Owner',
email: 'ghost@ghost.org',
status: 'inactive',
password: utils.uid(50)
};
return models.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
if (ownerRole) {
user.roles = [ownerRole.id];
logInfo('Creating owner');
return models.User.add(user, modelOptions);
}
});
};
/**
* ## Populate
* Sequentially creates all models, in the order they are specified, and then
* creates all the relationships, also maintaining order.
*
* @param {Object} modelOptions
* @param {Function} logInfo
* @returns {Promise<*>}
*/
populate = function populate(modelOptions, logInfo) {
logInfo('Populating fixtures');
// ### Ensure all models are added
return addAllModels(modelOptions).then(function () {
// ### Ensure all relations are added
return addAllRelations(modelOptions);
}).then(function () {
return createOwner(modelOptions, logInfo);
});
};
module.exports = populate;

View file

@ -0,0 +1,64 @@
// # Update Fixtures
// This module handles updating fixtures.
// This is done manually, through a series of files stored in an adjacent folder
// E.g. if we update to version 004, all the tasks in /004/ are executed
var sequence = require('../../../utils/sequence'),
// Private
getVersionTasks,
// Public
update;
/**
* ### Get Version Tasks
* Tries to require a directory matching the version number
*
* This was split from update to make testing easier
*
* @param {String} version
* @param {Function} logInfo
* @returns {Array}
*/
getVersionTasks = function getVersionTasks(version, logInfo) {
var tasks = [];
try {
tasks = require('./' + version);
} catch (e) {
logInfo('No fixture updates found for version', version);
}
return tasks;
};
/**
* ## Update
* Handles doing subsequent updates for versions
*
* @param {Array} versions
* @param {Object} modelOptions
* @param {Function} logInfo
* @returns {Promise<*>}
*/
update = function update(versions, modelOptions, logInfo) {
var ops = [];
logInfo('Updating fixtures');
versions.forEach(function (version) {
var tasks = getVersionTasks(version, logInfo);
if (tasks && tasks.length > 0) {
ops.push(function () {
logInfo('Updating fixtures to', version);
return sequence(require('./' + version), modelOptions, logInfo);
});
}
});
return sequence(ops, modelOptions, logInfo);
};
module.exports = update;

View file

@ -17,6 +17,7 @@ var _ = require('lodash'),
schemaTables = _.keys(schema),
// private
modelOptions,
logInfo,
populateDefaultSettings,
fixClientSecret,
@ -28,6 +29,9 @@ var _ = require('lodash'),
migrateUpFreshDb,
backupDatabase;
// modelOptions & logInfo are passed through to migration/fixture actions
modelOptions = {context: {internal: true}};
logInfo = function logInfo(message) {
errors.logInfo('Migrations', message);
};
@ -147,7 +151,7 @@ migrateUpFreshDb = function (tablesOnly) {
}
return tableSequence.then(function () {
// Load the fixtures
return fixtures.populate();
return fixtures.populate(modelOptions, logInfo);
}).then(function () {
return populateDefaultSettings();
});
@ -159,6 +163,12 @@ migrateUp = function (fromVersion, toVersion) {
modifyUniCommands = [],
migrateOps = [];
// Is the current version lower than the version we can migrate from?
// E.g. is this blog's DB older than 003?
if (fromVersion < versioning.canMigrateFromVersion) {
return versioning.showCannotMigrateError();
}
return backupDatabase().then(function () {
return commands.getTables();
}).then(function (tables) {
@ -198,8 +208,11 @@ migrateUp = function (fromVersion, toVersion) {
// Ensure all of the current default settings are created (these are fixtures, so should be inserted first)
return populateDefaultSettings();
}).then(function () {
// Finally, run any updates to the fixtures, including default settings
return fixtures.update(fromVersion, toVersion);
fromVersion = process.env.FORCE_MIGRATION ? versioning.canMigrateFromVersion : fromVersion;
var versions = versioning.getMigrationVersions(fromVersion, toVersion);
// Finally, run any updates to the fixtures, including default settings, that are required
// for anything other than the from/current version (which we're already on)
return fixtures.update(versions.slice(1), modelOptions, logInfo);
});
};

View file

@ -57,8 +57,33 @@ function setDatabaseVersion() {
.update({value: defaultDatabaseVersion});
}
function pad(num, width) {
return Array(Math.max(width - String(num).length + 1, 0)).join(0) + num;
}
function getMigrationVersions(fromVersion, toVersion) {
var versions = [],
i;
for (i = parseInt(fromVersion, 10); i <= toVersion; i += 1) {
versions.push(pad(i, 3));
}
return versions;
}
function showCannotMigrateError() {
return errors.logAndRejectError(
i18n.t('errors.data.versioning.index.cannotMigrate.error'),
i18n.t('errors.data.versioning.index.cannotMigrate.context'),
i18n.t('common.seeLinkForInstructions', {link: 'http://support.ghost.org/how-to-upgrade/'})
);
}
module.exports = {
canMigrateFromVersion: '003',
showCannotMigrateError: showCannotMigrateError,
getDefaultDatabaseVersion: getDefaultDatabaseVersion,
getDatabaseVersion: getDatabaseVersion,
setDatabaseVersion: setDatabaseVersion
setDatabaseVersion: setDatabaseVersion,
getMigrationVersions: getMigrationVersions
};

View file

@ -402,7 +402,11 @@
"versioning": {
"index": {
"dbVersionNotRecognized": "Database version is not recognized",
"settingsTableDoesNotExist": "Settings table does not exist"
"settingsTableDoesNotExist": "Settings table does not exist",
"cannotMigrate": {
"error": "Unable to upgrade from version 0.4.2 or earlier",
"context": "Please upgrade to 0.7.1 first"
}
}
},
"xml": {

View file

@ -1,8 +1,9 @@
var Promise = require('bluebird');
function sequence(tasks) {
function sequence(tasks /* Any Arguments */) {
var args = Array.prototype.slice.call(arguments, 1);
return Promise.reduce(tasks, function (results, task) {
return task().then(function (result) {
return task.apply(this, args).then(function (result) {
results.push(result);
return results;

View file

@ -1,39 +1,185 @@
/*globals describe, before, beforeEach, afterEach, it */
var testUtils = require('../utils'),
should = require('should'),
sinon = require('sinon'),
_ = require('lodash'),
Promise = require('bluebird'),
migration = require('../../server/data/migration/index'),
Models = require('../../server/models');
fixtures = require('../../server/data/migration/fixtures'),
Models = require('../../server/models'),
sandbox = sinon.sandbox.create();
describe('Database Migration (special functions)', function () {
before(testUtils.teardown);
afterEach(testUtils.teardown);
afterEach(function () {
sandbox.restore();
});
describe('004', function () {
beforeEach(testUtils.setup('settings'));
describe('Fixtures', function () {
// Custom assertion for detection that a permissions is assigned to the correct roles
should.Assertion.add('AssignedToRoles', function (roles) {
var roleNames;
this.params = {operator: 'to have role'};
it('should add jQuery to ghost_foot injection setting', function (done) {
Models.Settings.findOne('ghost_foot').then(function (setting) {
should.exist(setting);
should.exist(setting.attributes);
setting.attributes.value.should.equal('');
should.exist(this.obj);
process.env.FORCE_MIGRATION = true; // force a migration
migration.init().then(function () {
Models.Settings.findOne('ghost_foot').then(function (result) {
var jquery = [
'<!-- You can safely delete this line if your theme does not require jQuery -->\n',
'<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>\n\n'
];
this.obj.should.be.an.Object().with.property(['roles']);
this.obj.roles.should.be.an.Array();
roleNames = _.pluck(this.obj.roles, 'name');
roleNames.should.eql(roles);
});
should.exist(result);
should.exist(result.attributes);
result.attributes.value.should.equal(jquery.join(''));
// Custom assertion to wrap all permissions
should.Assertion.add('CompletePermissions', function () {
this.params = {operator: 'to have a complete set of permissions'};
var permissions = this.obj;
done();
});
// DB
permissions[0].name.should.eql('Export database');
permissions[0].should.be.AssignedToRoles(['Administrator']);
permissions[1].name.should.eql('Import database');
permissions[1].should.be.AssignedToRoles(['Administrator']);
permissions[2].name.should.eql('Delete all content');
permissions[2].should.be.AssignedToRoles(['Administrator']);
// Mail
permissions[3].name.should.eql('Send mail');
permissions[3].should.be.AssignedToRoles(['Administrator']);
// Notifications
permissions[4].name.should.eql('Browse notifications');
permissions[4].should.be.AssignedToRoles(['Administrator']);
permissions[5].name.should.eql('Add notifications');
permissions[5].should.be.AssignedToRoles(['Administrator']);
permissions[6].name.should.eql('Delete notifications');
permissions[6].should.be.AssignedToRoles(['Administrator']);
// Posts
permissions[7].name.should.eql('Browse posts');
permissions[7].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[8].name.should.eql('Read posts');
permissions[8].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[9].name.should.eql('Edit posts');
permissions[9].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[10].name.should.eql('Add posts');
permissions[10].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[11].name.should.eql('Delete posts');
permissions[11].should.be.AssignedToRoles(['Administrator', 'Editor']);
// Settings
permissions[12].name.should.eql('Browse settings');
permissions[12].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[13].name.should.eql('Read settings');
permissions[13].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[14].name.should.eql('Edit settings');
permissions[14].should.be.AssignedToRoles(['Administrator']);
// Slugs
permissions[15].name.should.eql('Generate slugs');
permissions[15].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
// Tags
permissions[16].name.should.eql('Browse tags');
permissions[16].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[17].name.should.eql('Read tags');
permissions[17].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[18].name.should.eql('Edit tags');
permissions[18].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[19].name.should.eql('Add tags');
permissions[19].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[20].name.should.eql('Delete tags');
permissions[20].should.be.AssignedToRoles(['Administrator', 'Editor']);
// Themes
permissions[21].name.should.eql('Browse themes');
permissions[21].should.be.AssignedToRoles(['Administrator']);
permissions[22].name.should.eql('Edit themes');
permissions[22].should.be.AssignedToRoles(['Administrator']);
// Users
permissions[23].name.should.eql('Browse users');
permissions[23].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[24].name.should.eql('Read users');
permissions[24].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
permissions[25].name.should.eql('Edit users');
permissions[25].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[26].name.should.eql('Add users');
permissions[26].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[27].name.should.eql('Delete users');
permissions[27].should.be.AssignedToRoles(['Administrator', 'Editor']);
// Roles
permissions[28].name.should.eql('Assign a role');
permissions[28].should.be.AssignedToRoles(['Administrator', 'Editor']);
permissions[29].name.should.eql('Browse roles');
permissions[29].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']);
});
beforeEach(testUtils.setup());
it('should populate all fixtures correctly', function (done) {
var logStub = sandbox.stub();
fixtures.populate({context: {internal: true}}, logStub).then(function () {
var props = {
posts: Models.Post.findAll({include: ['tags']}),
tags: Models.Tag.findAll(),
users: Models.User.findAll({include: ['roles']}),
clients: Models.Client.findAll(),
roles: Models.Role.findAll(),
permissions: Models.Permission.findAll({include: ['roles']})
};
logStub.called.should.be.true();
return Promise.props(props).then(function (result) {
should.exist(result);
// Post
should.exist(result.posts);
result.posts.length.should.eql(1);
result.posts.at(0).get('title').should.eql('Welcome to Ghost');
// Tag
should.exist(result.tags);
result.tags.length.should.eql(1);
result.tags.at(0).get('name').should.eql('Getting Started');
// Post Tag relation
result.posts.at(0).related('tags').length.should.eql(1);
result.posts.at(0).related('tags').at(0).get('name').should.eql('Getting Started');
// Clients
should.exist(result.clients);
result.clients.length.should.eql(2);
result.clients.at(0).get('name').should.eql('Ghost Admin');
result.clients.at(1).get('name').should.eql('Ghost Frontend');
// User (Owner)
should.exist(result.users);
result.users.length.should.eql(1);
result.users.at(0).get('name').should.eql('Ghost Owner');
result.users.at(0).related('roles').length.should.eql(1);
result.users.at(0).related('roles').at(0).get('name').should.eql('Owner');
// Roles
should.exist(result.roles);
result.roles.length.should.eql(4);
result.roles.at(0).get('name').should.eql('Administrator');
result.roles.at(1).get('name').should.eql('Editor');
result.roles.at(2).get('name').should.eql('Author');
result.roles.at(3).get('name').should.eql('Owner');
// Permissions
result.permissions.length.should.eql(30);
result.permissions.toJSON().should.be.CompletePermissions();
done();
});
});
}).catch(done);
});
});
});

View file

@ -0,0 +1,586 @@
/*global describe, it, beforeEach, afterEach */
var should = require('should'),
sinon = require('sinon'),
rewire = require('rewire'),
Promise = require('bluebird'),
// Stuff we are testing
configUtils = require('../utils/configUtils'),
models = require('../../server/models'),
notifications = require('../../server/api/notifications'),
update = rewire('../../server/data/migration/fixtures/update'),
populate = rewire('../../server/data/migration/fixtures/populate'),
fixtures004 = require('../../server/data/migration/fixtures/004'),
sandbox = sinon.sandbox.create();
describe('Fixtures', function () {
beforeEach(function (done) {
models.init().then(function () {
done();
});
});
afterEach(function () {
sandbox.restore();
configUtils.restore();
});
describe('Update fixtures', function () {
it('should call `getVersionTasks` when upgrading from 003 -> 004', function (done) {
var logStub = sandbox.stub(),
getVersionTasksStub = sandbox.stub().returns([]),
reset = update.__set__('getVersionTasks', getVersionTasksStub);
update(['004'], {}, logStub).then(function () {
logStub.calledOnce.should.be.true();
getVersionTasksStub.calledOnce.should.be.true();
reset();
done();
}).catch(done);
});
it('should NOT call `getVersionTasks` when upgrading from 004 -> 004', function (done) {
var logStub = sandbox.stub(),
getVersionTasksStub = sandbox.stub().returns(Promise.resolve()),
reset = update.__set__('getVersionTasks', getVersionTasksStub);
update([], {}, logStub).then(function () {
logStub.calledOnce.should.be.true();
getVersionTasksStub.calledOnce.should.be.false();
reset();
done();
}).catch(done);
});
it('`getVersionTasks` returns empty array if no tasks are found', function () {
var logStub = sandbox.stub();
update.__get__('getVersionTasks')('999', logStub).should.eql([]);
logStub.calledOnce.should.be.true();
});
describe('Update to 004', function () {
it('should call all the 004 fixture upgrades', function (done) {
// Stub all the model methods so that nothing happens
var logStub = sandbox.stub(),
settingsOneStub = sandbox.stub(models.Settings, 'findOne').returns(Promise.resolve()),
settingsEditStub = sandbox.stub(models.Settings, 'edit').returns(Promise.resolve()),
clientOneStub = sandbox.stub(models.Client, 'findOne'),
clientEditStub = sandbox.stub(models.Client, 'edit').returns(Promise.resolve()),
clientAddStub = sandbox.stub(models.Client, 'add').returns(Promise.resolve()),
tagAllStub = sandbox.stub(models.Tag, 'findAll').returns(Promise.resolve()),
postAllStub = sandbox.stub(models.Post, 'findAll').returns(Promise.resolve()),
postOneStub = sandbox.stub(models.Post, 'findOne').returns(Promise.resolve({})),
postAddStub = sandbox.stub(models.Post, 'add').returns(Promise.resolve());
clientOneStub.withArgs({slug: 'ghost-admin'}).returns(Promise.resolve());
clientOneStub.withArgs({slug: 'ghost-frontend'}).returns(Promise.resolve({}));
update(['004'], {}, logStub).then(function (result) {
should.exist(result);
logStub.called.should.be.true();
settingsOneStub.calledThrice.should.be.true();
settingsEditStub.called.should.be.false();
clientOneStub.calledTwice.should.be.true();
clientEditStub.called.should.be.false();
clientAddStub.called.should.be.false();
tagAllStub.calledOnce.should.be.true();
postAllStub.calledOnce.should.be.true();
postOneStub.calledOnce.should.be.true();
postAddStub.called.should.be.false();
sinon.assert.callOrder(
settingsOneStub, settingsOneStub, settingsOneStub, clientOneStub, clientOneStub, tagAllStub,
postAllStub, postOneStub
);
done();
}).catch(done);
});
describe('01-move-jquery-with-alert', function () {
it('tries to move jQuery to ghost_foot', function (done) {
var logStub = sandbox.stub(),
settingsOneStub = sandbox.stub(models.Settings, 'findOne').returns(Promise.resolve({
attributes: {value: ''}
})),
settingsEditStub = sandbox.stub(models.Settings, 'edit').returns(Promise.resolve());
fixtures004[0]({}, logStub).then(function () {
settingsOneStub.calledOnce.should.be.true();
settingsOneStub.calledWith('ghost_foot').should.be.true();
settingsEditStub.calledOnce.should.be.true();
logStub.calledOnce.should.be.true();
done();
});
});
it('does not move jQuery to ghost_foot if it is already there', function (done) {
var logStub = sandbox.stub(),
settingsOneStub = sandbox.stub(models.Settings, 'findOne').returns(Promise.resolve({
attributes: {
value: '<!-- You can safely delete this line if your theme does not require jQuery -->\n'
+ '<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>\n\n'
}
})),
settingsEditStub = sandbox.stub(models.Settings, 'edit').returns(Promise.resolve());
fixtures004[0]({}, logStub).then(function () {
settingsOneStub.calledOnce.should.be.true();
settingsOneStub.calledWith('ghost_foot').should.be.true();
settingsEditStub.calledOnce.should.be.false();
logStub.called.should.be.false();
done();
}).catch(done);
});
it('tried to move jQuery AND add a privacy message if any privacy settings are on', function (done) {
configUtils.set({privacy: {useGoogleFonts: false}});
var logStub = sandbox.stub(),
settingsOneStub = sandbox.stub(models.Settings, 'findOne').returns(Promise.resolve({
attributes: {value: ''}
})),
settingsEditStub = sandbox.stub(models.Settings, 'edit').returns(Promise.resolve()),
notificationsAddStub = sandbox.stub(notifications, 'add').returns(Promise.resolve());
fixtures004[0]({}, logStub).then(function () {
settingsOneStub.calledOnce.should.be.true();
settingsOneStub.calledWith('ghost_foot').should.be.true();
settingsEditStub.calledOnce.should.be.true();
notificationsAddStub.calledOnce.should.be.true();
logStub.calledTwice.should.be.true();
done();
}).catch(done);
});
});
describe('02-update-private-setting-type', function () {
it('tries to update setting type correctly', function (done) {
var logStub = sandbox.stub(),
settingsOneStub = sandbox.stub(models.Settings, 'findOne').returns(Promise.resolve({})),
settingsEditStub = sandbox.stub(models.Settings, 'edit').returns(Promise.resolve());
fixtures004[1]({}, logStub).then(function () {
settingsOneStub.calledOnce.should.be.true();
settingsOneStub.calledWith('isPrivate').should.be.true();
settingsEditStub.calledOnce.should.be.true();
settingsEditStub.calledWith({key: 'isPrivate', type: 'private'}).should.be.true();
logStub.calledOnce.should.be.true();
sinon.assert.callOrder(settingsOneStub, logStub, settingsEditStub);
done();
}).catch(done);
});
});
describe('03-update-password-setting-type', function () {
it('tries to update setting type correctly', function (done) {
var logStub = sandbox.stub(),
settingsOneStub = sandbox.stub(models.Settings, 'findOne').returns(Promise.resolve({})),
settingsEditStub = sandbox.stub(models.Settings, 'edit').returns(Promise.resolve());
fixtures004[2]({}, logStub).then(function () {
settingsOneStub.calledOnce.should.be.true();
settingsOneStub.calledWith('password').should.be.true();
settingsEditStub.calledOnce.should.be.true();
settingsEditStub.calledWith({key: 'password', type: 'private'}).should.be.true();
logStub.calledOnce.should.be.true();
sinon.assert.callOrder(settingsOneStub, logStub, settingsEditStub);
done();
}).catch(done);
});
});
describe('04-update-ghost-admin-client', function () {
it('tries to update client correctly', function (done) {
var logStub = sandbox.stub(),
clientOneStub = sandbox.stub(models.Client, 'findOne').returns(Promise.resolve({})),
clientEditStub = sandbox.stub(models.Client, 'edit').returns(Promise.resolve());
fixtures004[3]({}, logStub).then(function () {
clientOneStub.calledOnce.should.be.true();
clientOneStub.calledWith({slug: 'ghost-admin'}).should.be.true();
clientEditStub.calledOnce.should.be.true();
logStub.calledOnce.should.be.true();
sinon.assert.callOrder(clientOneStub, logStub, clientEditStub);
done();
}).catch(done);
});
});
describe('05-add-ghost-frontend-client', function () {
it('tries to add client correctly', function (done) {
var logStub = sandbox.stub(),
clientOneStub = sandbox.stub(models.Client, 'findOne').returns(Promise.resolve()),
clientAddStub = sandbox.stub(models.Client, 'add').returns(Promise.resolve());
fixtures004[4]({}, logStub).then(function () {
clientOneStub.calledOnce.should.be.true();
clientOneStub.calledWith({slug: 'ghost-frontend'}).should.be.true();
clientAddStub.calledOnce.should.be.true();
logStub.calledOnce.should.be.true();
sinon.assert.callOrder(clientOneStub, logStub, clientAddStub);
done();
}).catch(done);
});
});
describe('06-clean-broken-tags', function () {
it('tries to clean broken tags correctly', function (done) {
var logStub = sandbox.stub(),
tagObjStub = {
get: sandbox.stub().returns(',hello'),
save: sandbox.stub().returns(Promise.resolve)
},
tagCollStub = {each: sandbox.stub().callsArgWith(0, tagObjStub)},
tagAllStub = sandbox.stub(models.Tag, 'findAll').returns(Promise.resolve(tagCollStub));
fixtures004[5]({}, logStub).then(function () {
tagAllStub.calledOnce.should.be.true();
tagCollStub.each.calledOnce.should.be.true();
tagObjStub.get.calledOnce.should.be.true();
tagObjStub.get.calledWith('name').should.be.true();
tagObjStub.save.calledOnce.should.be.true();
tagObjStub.save.calledWith({name: 'hello'}).should.be.true();
logStub.calledOnce.should.be.true();
sinon.assert.callOrder(tagAllStub, tagCollStub.each, tagObjStub.get, tagObjStub.save, logStub);
done();
}).catch(done);
});
it('tries can handle tags which end up empty', function (done) {
var logStub = sandbox.stub(),
tagObjStub = {
get: sandbox.stub().returns(','),
save: sandbox.stub().returns(Promise.resolve)
},
tagCollStub = {each: sandbox.stub().callsArgWith(0, tagObjStub)},
tagAllStub = sandbox.stub(models.Tag, 'findAll').returns(Promise.resolve(tagCollStub));
fixtures004[5]({}, logStub).then(function () {
tagAllStub.calledOnce.should.be.true();
tagCollStub.each.calledOnce.should.be.true();
tagObjStub.get.calledOnce.should.be.true();
tagObjStub.get.calledWith('name').should.be.true();
tagObjStub.save.calledOnce.should.be.true();
tagObjStub.save.calledWith({name: 'tag'}).should.be.true();
logStub.calledOnce.should.be.true();
sinon.assert.callOrder(tagAllStub, tagCollStub.each, tagObjStub.get, tagObjStub.save, logStub);
done();
}).catch(done);
});
it('tries only changes a tag if necessary', function (done) {
var logStub = sandbox.stub(),
tagObjStub = {
get: sandbox.stub().returns('hello'),
save: sandbox.stub().returns(Promise.resolve)
},
tagCollStub = {each: sandbox.stub().callsArgWith(0, tagObjStub)},
tagAllStub = sandbox.stub(models.Tag, 'findAll').returns(Promise.resolve(tagCollStub));
fixtures004[5]({}, logStub).then(function () {
tagAllStub.calledOnce.should.be.true();
tagCollStub.each.calledOnce.should.be.true();
tagObjStub.get.calledOnce.should.be.true();
tagObjStub.get.calledWith('name').should.be.true();
tagObjStub.save.called.should.be.false();
logStub.calledOnce.should.be.false();
sinon.assert.callOrder(tagAllStub, tagCollStub.each, tagObjStub.get);
done();
}).catch(done);
});
});
describe('07-add-post-tag-order', function () {
it('calls load on each post', function (done) {
var logStub = sandbox.stub(),
postObjStub = {
load: sandbox.stub().returnsThis()
},
postCollStub = {mapThen: sandbox.stub().callsArgWith(0, postObjStub)},
postAllStub = sandbox.stub(models.Post, 'findAll').returns(Promise.resolve(postCollStub));
fixtures004[6]({}, logStub).then(function () {
postAllStub.calledOnce.should.be.true();
postCollStub.mapThen.calledOnce.should.be.true();
postObjStub.load.calledOnce.should.be.true();
postObjStub.load.calledWith(['tags']).should.be.true();
logStub.calledOnce.should.be.true();
sinon.assert.callOrder(logStub, postAllStub, postCollStub.mapThen, postObjStub.load);
done();
}).catch(done);
});
it('tries to add order to posts_tags', function (done) {
var logStub = sandbox.stub(),
postObjStub = {
load: sandbox.stub().returnsThis(),
related: sandbox.stub().returnsThis(),
tags: sandbox.stub().returnsThis(),
each: sandbox.stub().callsArgWith(0, {id: 5}),
updatePivot: sandbox.stub().returns(Promise.resolve())
},
postCollStub = {mapThen: sandbox.stub().returns([postObjStub])},
postAllStub = sandbox.stub(models.Post, 'findAll').returns(Promise.resolve(postCollStub));
fixtures004[6]({}, logStub).then(function () {
postAllStub.calledOnce.should.be.true();
postCollStub.mapThen.calledOnce.should.be.true();
postObjStub.load.called.should.be.false();
postObjStub.related.calledOnce.should.be.true();
postObjStub.each.calledOnce.should.be.true();
postObjStub.tags.calledOnce.should.be.true();
postObjStub.updatePivot.calledOnce.should.be.true();
logStub.calledThrice.should.be.true();
sinon.assert.callOrder(
logStub, postAllStub, postCollStub.mapThen, postObjStub.related, postObjStub.each,
logStub, postObjStub.tags, postObjStub.updatePivot, logStub
);
done();
}).catch(done);
});
});
describe('08-add-post-fixture', function () {
it('tries to add a new post fixture correctly', function (done) {
var logStub = sandbox.stub(),
postOneStub = sandbox.stub(models.Post, 'findOne').returns(Promise.resolve()),
postAddStub = sandbox.stub(models.Post, 'add').returns(Promise.resolve());
fixtures004[7]({}, logStub).then(function () {
postOneStub.calledOnce.should.be.true();
logStub.calledOnce.should.be.true();
postAddStub.calledOnce.should.be.true();
sinon.assert.callOrder(postOneStub, logStub, postAddStub);
done();
}).catch(done);
});
});
});
});
describe('Populate fixtures', function () {
// This tests that all the models & relations get called correctly
it('should call all the fixture populations', function (done) {
// Stub all the model methods so that nothing happens
var logStub = sandbox.stub(),
postAddStub = sandbox.stub(models.Post, 'add').returns(Promise.resolve()),
tagAddStub = sandbox.stub(models.Tag, 'add').returns(Promise.resolve()),
roleAddStub = sandbox.stub(models.Role, 'add').returns(Promise.resolve()),
clientAddStub = sandbox.stub(models.Client, 'add').returns(Promise.resolve()),
permsAddStub = sandbox.stub(models.Permission, 'add').returns(Promise.resolve()),
// Relations
modelMethodStub = {filter: sandbox.stub(), find: sandbox.stub()},
permsAllStub = sandbox.stub(models.Permission, 'findAll').returns(Promise.resolve(modelMethodStub)),
rolesAllStub = sandbox.stub(models.Role, 'findAll').returns(Promise.resolve(modelMethodStub)),
postsAllStub = sandbox.stub(models.Post, 'findAll').returns(Promise.resolve(modelMethodStub)),
tagsAllStub = sandbox.stub(models.Tag, 'findAll').returns(Promise.resolve(modelMethodStub)),
// Create Owner
roleOneStub = sandbox.stub(models.Role, 'findOne').returns(Promise.resolve({id: 1})),
userAddStub = sandbox.stub(models.User, 'add').returns(Promise.resolve({}));
populate({}, logStub).then(function () {
logStub.called.should.be.true();
postAddStub.calledOnce.should.be.true();
tagAddStub.calledOnce.should.be.true();
roleAddStub.callCount.should.eql(4);
clientAddStub.calledTwice.should.be.true();
permsAddStub.called.should.be.true();
permsAddStub.callCount.should.eql(30);
permsAllStub.calledOnce.should.be.true();
rolesAllStub.calledOnce.should.be.true();
postsAllStub.calledOnce.should.be.true();
tagsAllStub.calledOnce.should.be.true();
// Relations
modelMethodStub.filter.called.should.be.true();
// 22 permissions, 1 tag
modelMethodStub.filter.callCount.should.eql(22 + 1);
modelMethodStub.find.called.should.be.true();
// 3 roles, 1 post
modelMethodStub.find.callCount.should.eql(3 + 1);
// Create Owner
roleOneStub.calledOnce.should.be.true();
userAddStub.calledOnce.should.be.true();
done();
}).catch(done);
});
describe('Add All Relations', function () {
it('should call attach if relation models are found', function (done) {
var addAllRelations = populate.__get__('addAllRelations'),
emptyMethodStub = {filter: sandbox.stub(), find: sandbox.stub()},
// Setup a chain of methods
dataMethodStub = {
filter: sandbox.stub().returnsThis(),
find: sandbox.stub().returnsThis(),
tags: sandbox.stub().returnsThis(),
attach: sandbox.stub().returns(Promise.resolve())
},
permsAllStub = sandbox.stub(models.Permission, 'findAll').returns(Promise.resolve(emptyMethodStub)),
rolesAllStub = sandbox.stub(models.Role, 'findAll').returns(Promise.resolve(emptyMethodStub)),
postsAllStub = sandbox.stub(models.Post, 'findAll').returns(Promise.resolve(dataMethodStub)),
tagsAllStub = sandbox.stub(models.Tag, 'findAll').returns(Promise.resolve(dataMethodStub));
addAllRelations().then(function () {
permsAllStub.calledOnce.should.be.true();
rolesAllStub.calledOnce.should.be.true();
postsAllStub.calledOnce.should.be.true();
tagsAllStub.calledOnce.should.be.true();
// Permissions & Roles
emptyMethodStub.filter.called.should.be.true();
emptyMethodStub.filter.callCount.should.eql(22);
emptyMethodStub.find.called.should.be.true();
emptyMethodStub.find.callCount.should.eql(3);
// Posts & Tags
dataMethodStub.filter.calledOnce.should.be.true();
dataMethodStub.find.calledOnce.should.be.true();
dataMethodStub.tags.calledOnce.should.be.true();
dataMethodStub.attach.calledOnce.should.be.true();
dataMethodStub.attach.calledWith(dataMethodStub).should.be.true();
done();
}).catch(done);
});
});
describe('Create Owner', function () {
it('createOwner will add user if owner role is present', function (done) {
var createOwner = populate.__get__('createOwner'),
logStub = sandbox.stub(),
roleOneStub = sandbox.stub(models.Role, 'findOne').returns(Promise.resolve({id: 1})),
userAddStub = sandbox.stub(models.User, 'add').returns(Promise.resolve({}));
createOwner({}, logStub).then(function () {
logStub.called.should.be.true();
roleOneStub.calledOnce.should.be.true();
userAddStub.called.should.be.true();
done();
}).catch(done);
});
it('createOwner does not add user if owner role is not present', function (done) {
var createOwner = populate.__get__('createOwner'),
roleOneStub = sandbox.stub(models.Role, 'findOne').returns(Promise.resolve()),
userAddStub = sandbox.stub(models.User, 'add').returns(Promise.resolve({}));
createOwner().then(function () {
roleOneStub.calledOnce.should.be.true();
userAddStub.called.should.be.false();
done();
}).catch(done);
});
});
describe('Match Func', function () {
var matchFunc = populate.__get__('matchFunc');
it('should match undefined with no args', function () {
var getStub = sandbox.stub();
matchFunc()({get: getStub}).should.be.true();
getStub.calledOnce.should.be.true();
getStub.calledWith(undefined).should.be.true();
});
it('should match key with match string', function () {
var getStub = sandbox.stub();
getStub.withArgs('foo').returns('bar');
matchFunc('foo', 'bar')({get: getStub}).should.be.true();
getStub.calledOnce.should.be.true();
getStub.calledWith('foo').should.be.true();
matchFunc('foo', 'buz')({get: getStub}).should.be.false();
getStub.calledTwice.should.be.true();
getStub.secondCall.calledWith('foo').should.be.true();
});
it('should match value when key is 0', function () {
var getStub = sandbox.stub();
getStub.withArgs('foo').returns('bar');
matchFunc('foo', 0, 'bar')({get: getStub}).should.be.true();
getStub.calledOnce.should.be.true();
getStub.calledWith('foo').should.be.true();
matchFunc('foo', 0, 'buz')({get: getStub}).should.be.false();
getStub.calledTwice.should.be.true();
getStub.secondCall.calledWith('foo').should.be.true();
});
it('should match key & value when match is array', function () {
var getStub = sandbox.stub();
getStub.withArgs('foo').returns('bar');
getStub.withArgs('fun').returns('baz');
matchFunc(['foo', 'fun'], 'bar', 'baz')({get: getStub}).should.be.true();
getStub.calledTwice.should.be.true();
getStub.getCall(0).calledWith('fun').should.be.true();
getStub.getCall(1).calledWith('foo').should.be.true();
matchFunc(['foo', 'fun'], 'baz', 'bar')({get: getStub}).should.be.false();
getStub.callCount.should.eql(4);
getStub.getCall(2).calledWith('fun').should.be.true();
getStub.getCall(3).calledWith('foo').should.be.true();
});
it('should match key only when match is array, but value is all', function () {
var getStub = sandbox.stub();
getStub.withArgs('foo').returns('bar');
getStub.withArgs('fun').returns('baz');
matchFunc(['foo', 'fun'], 'bar', 'all')({get: getStub}).should.be.true();
getStub.calledOnce.should.be.true();
getStub.calledWith('foo').should.be.true();
matchFunc(['foo', 'fun'], 'all', 'bar')({get: getStub}).should.be.false();
getStub.callCount.should.eql(3);
getStub.getCall(1).calledWith('fun').should.be.true();
getStub.getCall(2).calledWith('foo').should.be.true();
});
it('should match key & value when match and value are arrays', function () {
var getStub = sandbox.stub();
getStub.withArgs('foo').returns('bar');
getStub.withArgs('fun').returns('baz');
matchFunc(['foo', 'fun'], 'bar', ['baz', 'buz'])({get: getStub}).should.be.true();
getStub.calledTwice.should.be.true();
getStub.getCall(0).calledWith('fun').should.be.true();
getStub.getCall(1).calledWith('foo').should.be.true();
matchFunc(['foo', 'fun'], 'bar', ['biz', 'buz'])({get: getStub}).should.be.false();
getStub.callCount.should.eql(4);
getStub.getCall(2).calledWith('fun').should.be.true();
getStub.getCall(3).calledWith('foo').should.be.true();
});
});
});
});

View file

@ -1,17 +1,24 @@
/*globals describe, it*/
/*globals describe, it, afterEach*/
var should = require('should'),
sinon = require('sinon'),
_ = require('lodash'),
crypto = require('crypto'),
// Stuff we are testing
schema = require('../../server/data/schema'),
permissions = require('../../server/data/migration/fixtures/permissions/permissions'),
defaultSettings = schema.defaultSettings;
fixtures = require('../../server/data/migration/fixtures'),
defaultSettings = schema.defaultSettings,
sandbox = sinon.sandbox.create();
// To stop jshint complaining
should.equal(true, true);
describe('Migrations', function () {
afterEach(function () {
sandbox.restore();
});
// Check version integrity
// These tests exist to ensure that developers are not able to modify the database schema, or permissions fixtures
// without knowing that they also need to update the default database version,
@ -20,14 +27,14 @@ describe('Migrations', function () {
// Only these variables should need updating
var currentDbVersion = '004',
currentSchemaHash = 'a195562bf4915e3f3f610f6d178aba01',
currentPermissionsHash = '42e486732270cda623fc5efc04808c0c';
currentFixturesHash = '17d6aa36a6ba904adca90279eb929381';
// If this test is failing, then it is likely a change has been made that requires a DB version bump,
// and the values above will need updating as confirmation
it('should not change without fixing this test', function () {
var tablesNoValidation = _.cloneDeep(schema.tables),
schemaHash,
permissionsHash;
fixturesHash;
_.each(tablesNoValidation, function (table) {
return _.each(table, function (column, name) {
@ -36,12 +43,15 @@ describe('Migrations', function () {
});
schemaHash = crypto.createHash('md5').update(JSON.stringify(tablesNoValidation)).digest('hex');
permissionsHash = crypto.createHash('md5').update(JSON.stringify(permissions)).digest('hex');
fixturesHash = crypto.createHash('md5').update(JSON.stringify(fixtures.fixtures)).digest('hex');
// Test!
defaultSettings.core.databaseVersion.defaultValue.should.eql(currentDbVersion);
schemaHash.should.eql(currentSchemaHash);
permissionsHash.should.eql(currentPermissionsHash);
fixturesHash.should.eql(currentFixturesHash);
schema.versioning.canMigrateFromVersion.should.eql('003');
});
});
describe('Builder', function () {});
});

View file

@ -0,0 +1,58 @@
/*globals describe, it, afterEach */
var should = require('should'),
sinon = require('sinon'),
// Stuff we are testing
versioning = require('../../server/data/schema').versioning,
errors = require('../../server/errors'),
sandbox = sinon.sandbox.create();
describe('Versioning', function () {
afterEach(function () {
sandbox.restore();
});
describe('getMigrationVersions', function () {
it('should output a single item if the from and to versions are the same', function () {
should.exist(versioning.getMigrationVersions);
versioning.getMigrationVersions('003', '003').should.eql(['003']);
versioning.getMigrationVersions('004', '004').should.eql(['004']);
});
it('should output an empty array if the toVersion is higher than the fromVersion', function () {
versioning.getMigrationVersions('003', '002').should.eql([]);
});
it('should output all the versions between two versions', function () {
versioning.getMigrationVersions('003', '004').should.eql(['003', '004']);
versioning.getMigrationVersions('003', '005').should.eql(['003', '004', '005']);
versioning.getMigrationVersions('003', '006').should.eql(['003', '004', '005', '006']);
versioning.getMigrationVersions('010', '011').should.eql(['010', '011']);
});
});
describe('getDefaultDatabaseVersion', function () {
it('should return the correct version', function () {
var currentVersion = require('../../server/data/schema').defaultSettings.core.databaseVersion.defaultValue;
// This function has an internal cache, so we call it twice.
// First, to check that it fetches the correct version from default-settings.json.
versioning.getDefaultDatabaseVersion().should.eql(currentVersion);
// Second, to check that it returns the same value from the cache.
versioning.getDefaultDatabaseVersion().should.eql(currentVersion);
});
});
describe('showCannotMigrateError', function () {
it('should output a detailed error message', function () {
var errorStub = sandbox.stub(errors, 'logAndRejectError');
versioning.showCannotMigrateError();
errorStub.calledOnce.should.be.true();
errorStub.calledWith(
'Unable to upgrade from version 0.4.2 or earlier',
'Please upgrade to 0.7.1 first',
'See http://support.ghost.org/how-to-upgrade/ for instructions.'
).should.be.true();
});
});
});

View file

@ -5,10 +5,10 @@ var Promise = require('bluebird'),
uuid = require('node-uuid'),
db = require('../../server/data/db'),
migration = require('../../server/data/migration/'),
mainFixtures = require('../../server/data/migration/fixtures').fixtures,
Models = require('../../server/models'),
SettingsAPI = require('../../server/api/settings'),
permissions = require('../../server/permissions'),
permsFixtures = require('../../server/data/migration/fixtures/permissions/permissions.json'),
sequence = require('../../server/utils/sequence'),
DataGenerator = require('./fixtures/data-generator'),
filterData = require('./fixtures/filter-param'),
@ -316,8 +316,8 @@ fixtures = {
},
permissionsFor: function permissionsFor(obj) {
var permsToInsert = permsFixtures.permissions[obj],
permsRolesToInsert = permsFixtures.permissions_roles,
var permsToInsert = _.filter(mainFixtures.models.Permission, function (perm) { return perm.object_type === obj; }),
permsRolesToInsert = mainFixtures.relations[0].entries,
actions = [],
permissionsRoles = [],
roles = {