🔥 😎 remove old migrations (#7887)

refs #7489

- remove old migration code
- this logic was sourced out to knex-migrator
This commit is contained in:
Katharina Irrgang 2017-01-25 14:47:49 +01:00 committed by Kevin Ansfield
parent 042750f4cf
commit 2d19ae2c6c
23 changed files with 303 additions and 1671 deletions

View File

@ -405,24 +405,6 @@ var overrides = require('./core/server/overrides'),
cfg.express.test.options.node_env = process.env.NODE_ENV;
});
// #### Reset Database to "New" state *(Utility Task)*
// Drops all database tables and then runs the migration process to put the database
// in a "new" state.
grunt.registerTask('cleanDatabase', function () {
var done = this.async(),
models = require('./core/server/models'),
migration = require('./core/server/data/migration');
migration.reset().then(function () {
models.init();
return migration.init();
}).then(function () {
done();
}).catch(function (err) {
grunt.fail.fatal(err.stack);
});
});
// ### Test
// **Testing utility**
//

View File

@ -3,7 +3,7 @@
var Promise = require('bluebird'),
exporter = require('../data/export'),
importer = require('../data/importer'),
backupDatabase = require('../data/migration').backupDatabase,
backupDatabase = require('../data/db/backup'),
models = require('../models'),
errors = require('../errors'),
utils = require('./utils'),

View File

@ -1,386 +0,0 @@
{
"models": [
{
"name": "Post",
"entries": [
{
"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
}
]
},
{
"name": "Tag",
"entries": [
{
"name": "Getting Started",
"slug": "getting-started",
"description": null,
"parent_id": null,
"meta_title": null,
"meta_description": null
}
]
},
{
"name": "Client",
"entries": [
{
"name": "Ghost Admin",
"slug": "ghost-admin",
"status": "enabled"
},
{
"name": "Ghost Frontend",
"slug": "ghost-frontend",
"status": "enabled"
},
{
"name": "Ghost Scheduler",
"slug": "ghost-scheduler",
"status": "enabled",
"type": "web"
}
]
},
{
"name": "Role",
"entries": [
{
"name": "Administrator",
"description": "Administrators"
},
{
"name": "Editor",
"description": "Editors"
},
{
"name": "Author",
"description": "Authors"
},
{
"name": "Owner",
"description": "Blog Owner"
}
]
},
{
"name": "Permission",
"entries": [
{
"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": "Upload themes",
"action_type": "add",
"object_type": "theme"
},
{
"name": "Download themes",
"action_type": "read",
"object_type": "theme"
},
{
"name": "Delete themes",
"action_type": "destroy",
"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"
},
{
"name": "Browse clients",
"action_type": "browse",
"object_type": "client"
},
{
"name": "Read clients",
"action_type": "read",
"object_type": "client"
},
{
"name": "Edit clients",
"action_type": "edit",
"object_type": "client"
},
{
"name": "Add clients",
"action_type": "add",
"object_type": "client"
},
{
"name": "Delete clients",
"action_type": "destroy",
"object_type": "client"
},
{
"name": "Browse subscribers",
"action_type": "browse",
"object_type": "subscriber"
},
{
"name": "Read subscribers",
"action_type": "read",
"object_type": "subscriber"
},
{
"name": "Edit subscribers",
"action_type": "edit",
"object_type": "subscriber"
},
{
"name": "Add subscribers",
"action_type": "add",
"object_type": "subscriber"
},
{
"name": "Delete subscribers",
"action_type": "destroy",
"object_type": "subscriber"
},
{
"name": "Browse invites",
"action_type": "browse",
"object_type": "invite"
},
{
"name": "Read invites",
"action_type": "read",
"object_type": "invite"
},
{
"name": "Add invites",
"action_type": "add",
"object_type": "invite"
},
{
"name": "Edit invites",
"action_type": "edit",
"object_type": "invite"
},
{
"name": "Delete invites",
"action_type": "destroy",
"object_type": "invite"
}
]
}
],
"relations": [
{
"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",
"client": "all",
"subscriber": "all",
"invite": "all"
},
"Editor": {
"post": "all",
"setting": ["browse", "read"],
"slug": "all",
"tag": "all",
"user": "all",
"role": "all",
"client": "all",
"subscriber": ["add"]
},
"Author": {
"post": ["browse", "read", "add"],
"setting": ["browse", "read"],
"slug": "all",
"tag": ["browse", "read", "add"],
"user": ["browse", "read"],
"role": ["browse"],
"client": "all",
"subscriber": ["add"]
}
}
},
{
"from": {
"model": "Post",
"match": "title",
"relation": "tags"
},
"to": {
"model": "Tag",
"match": "name"
},
"entries": {
"Welcome to Ghost": ["Getting Started"]
}
}
]
}

View File

@ -1,9 +0,0 @@
var populate = require('./populate'),
update = require('./update'),
fixtures = require('./fixtures');
module.exports = {
populate: populate,
update: update,
fixtures: fixtures
};

View File

@ -1,98 +0,0 @@
// # 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'),
models = require('../../../models'),
coreUtils = require('../../../utils'),
fixtureUtils = require('./utils'),
fixtures = require('./fixtures'),
// private
addAllModels,
addAllRelations,
createOwner,
// public
populate;
/**
* ### Add All Models
* Sequentially calls add on all the models specified in fixtures.json
*
* @returns {Promise<*>}
*/
addAllModels = function addAllModels(modelOptions) {
return Promise.mapSeries(fixtures.models, function (model) {
return fixtureUtils.addFixturesForModel(model, modelOptions);
});
};
/**
* ### Add All Relations
* Sequentially calls add on all the relations specified in fixtures.json
*
* @returns {Promise|Array}
*/
addAllRelations = function addAllRelations(modelOptions) {
return Promise.mapSeries(fixtures.relations, function (model) {
return fixtureUtils.addFixturesForRelation(model, modelOptions);
});
};
/**
* ### 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 {{info: logger.info, warn: logger.warn}} logger
* @returns {Promise<*>}
*/
createOwner = function createOwner(logger, modelOptions) {
var user = {
name: 'Ghost Owner',
email: 'ghost@ghost.org',
status: 'inactive',
password: coreUtils.uid(50)
};
return models.Role.findOne({name: 'Owner'}, modelOptions).then(function (ownerRole) {
if (ownerRole) {
user.roles = [ownerRole.id];
return models.User
.findOne({name: 'Ghost Owner', status: 'all'}, modelOptions)
.then(function (exists) {
if (exists) {
logger.warn('Skipping: Creating owner');
return;
}
logger.info('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 {{info: logger.info, warn: logger.warn}} logger
* @returns {Promise<*>}
*/
populate = function populate(logger, modelOptions) {
logger.info('Running fixture populations');
return addAllModels(modelOptions)
.then(function () {
return addAllRelations(modelOptions);
})
.then(function () {
return createOwner(logger, modelOptions);
});
};
module.exports = populate;

View File

@ -1,33 +0,0 @@
// # 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 Promise = require('bluebird'),
_ = require('lodash'),
sequence = function sequence(tasks, modelOptions, logger) {
// utils/sequence.js does not offer an option to pass cloned arguments
return Promise.reduce(tasks, function (results, task) {
return task(_.cloneDeep(modelOptions), logger)
.then(function (result) {
results.push(result);
return results;
});
}, []);
},
update;
/**
* Handles doing subsequent update for one version
*/
update = function update(tasks, logger, modelOptions) {
logger.info('Running fixture updates');
if (!tasks.length) {
return Promise.resolve();
}
return sequence(tasks, modelOptions, logger);
};
module.exports = update;

View File

@ -1,237 +0,0 @@
// # Fixture Utils
// Standalone file which can be required to help with advanced operations on the fixtures.json file
var _ = require('lodash'),
Promise = require('bluebird'),
models = require('../../../models'),
baseUtils = require('../../../models/base/utils'),
sequence = require('../../../utils/sequence'),
fixtures = require('./fixtures'),
// Private
matchFunc,
matchObj,
fetchRelationData,
findRelationFixture,
findModelFixture,
addFixturesForModel,
addFixturesForRelation,
findModelFixtureEntry,
findModelFixtures,
findPermissionRelationsForObject;
/**
* ### 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.
* @api private
* @param {String|Array} match
* @param {String|Integer} key
* @param {String|Array} [value]
* @returns {Function} matching 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;
};
};
matchObj = function matchObj(match, item) {
var matchObj = {};
if (_.isArray(match)) {
_.each(match, function (matchProp) {
matchObj[matchProp] = item.get(matchProp);
});
} else {
matchObj[match] = item.get(match);
}
return matchObj;
};
/**
* ### 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.
* @api private
* @param {{from, to, entries}} relation
* @returns {Promise<*>}
*/
fetchRelationData = function fetchRelationData(relation, options) {
var fromOptions = _.extend({}, options, {withRelated: [relation.from.relation]}),
props = {
from: models[relation.from.model].findAll(fromOptions),
to: models[relation.to.model].findAll(options)
};
return Promise.props(props);
};
/**
* ### Add Fixtures for Model
* Takes a model fixture, with a name and some entries and processes these
* into a sequence of promises to get each fixture added.
*
* @param {{name, entries}} modelFixture
* @returns {Promise.<*>}
*/
addFixturesForModel = function addFixturesForModel(modelFixture, options) {
return Promise.mapSeries(modelFixture.entries, function (entry) {
return models[modelFixture.name].findOne(entry, options).then(function (found) {
if (!found) {
return models[modelFixture.name].add(entry, options);
}
});
}).then(function (results) {
return {expected: modelFixture.entries.length, done: _.compact(results).length};
});
};
/**
* ## Add Fixtures for Relation
* Takes a relation fixtures object, with a from, to and some entries and processes these
* into a sequence of promises, to get each fixture added.
*
* @param {{from, to, entries}} relationFixture
* @returns {Promise.<*>}
*/
addFixturesForRelation = function addFixturesForRelation(relationFixture, options) {
var ops = [], max = 0;
return fetchRelationData(relationFixture, options).then(function getRelationOps(data) {
_.each(relationFixture.entries, function processEntries(entry, key) {
var fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
_.each(entry, function processEntryValues(value, key) {
var toItems = data.to.filter(matchFunc(relationFixture.to.match, key, value));
max += toItems.length;
// Remove any duplicates that already exist in the collection
toItems = _.reject(toItems, function (item) {
return fromItem
.related(relationFixture.from.relation)
.findWhere(matchObj(relationFixture.to.match, item));
});
if (toItems && toItems.length > 0) {
ops.push(function addRelationItems() {
return baseUtils.attach(
models[relationFixture.from.Model || relationFixture.from.model],
fromItem.id,
relationFixture.from.relation,
toItems,
options
);
});
}
});
});
return sequence(ops);
}).then(function (result) {
return {expected: max, done: _(result).map('length').sum()};
});
};
/**
* ### Find Model Fixture
* Finds a model fixture based on model name
* @api private
* @param {String} modelName
* @returns {Object} model fixture
*/
findModelFixture = function findModelFixture(modelName) {
return _.find(fixtures.models, function (modelFixture) {
return modelFixture.name === modelName;
});
};
/**
* ### Find Model Fixture Entry
* Find a single model fixture entry by model name & a matching expression for the FIND function
* @param {String} modelName
* @param {String|Object|Function} matchExpr
* @returns {Object} model fixture entry
*/
findModelFixtureEntry = function findModelFixtureEntry(modelName, matchExpr) {
return _.find(findModelFixture(modelName).entries, matchExpr);
};
/**
* ### Find Model Fixtures
* Find a model fixture name & a matching expression for the FILTER function
* @param {String} modelName
* @param {String|Object|Function} matchExpr
* @returns {Object} model fixture
*/
findModelFixtures = function findModelFixtures(modelName, matchExpr) {
var foundModel = _.cloneDeep(findModelFixture(modelName));
foundModel.entries = _.filter(foundModel.entries, matchExpr);
return foundModel;
};
/**
* ### Find Relation Fixture
* Find a relation fixture by from & to models
* @api private
* @param {String} from
* @param {String} to
* @returns {Object} relation fixture
*/
findRelationFixture = function findRelationFixture(from, to) {
return _.find(fixtures.relations, function (relation) {
return relation.from.model === from && relation.to.model === to;
});
};
/**
* ### Find Permission Relations For Object
* Specialist function can return the permission relation fixture with only entries for a particular object.model
* @param {String} objName
* @returns {Object} fixture relation
*/
findPermissionRelationsForObject = function findPermissionRelationsForObject(objName) {
// Make a copy and delete any entries we don't want
var foundRelation = _.cloneDeep(findRelationFixture('Role', 'Permission'));
_.each(foundRelation.entries, function (entry, role) {
_.each(entry, function (perm, obj) {
if (obj !== objName) {
delete entry[obj];
}
});
if (_.isEmpty(entry)) {
delete foundRelation.entries[role];
}
});
return foundRelation;
};
module.exports = {
addFixturesForModel: addFixturesForModel,
addFixturesForRelation: addFixturesForRelation,
findModelFixtureEntry: findModelFixtureEntry,
findModelFixtures: findModelFixtures,
findPermissionRelationsForObject: findPermissionRelationsForObject
};

View File

@ -1,4 +0,0 @@
exports.update = require('./update');
exports.populate = require('./populate');
exports.reset = require('./reset');
exports.backupDatabase = require('./backup');

View File

@ -1,68 +0,0 @@
// # Populate
// Create a brand new database for a new install of ghost
var Promise = require('bluebird'),
_ = require('lodash'),
commands = require('../schema').commands,
versioning = require('../schema').versioning,
fixtures = require('./fixtures'),
db = require('../../data/db'),
logging = require('../../logging'),
models = require('../../models'),
errors = require('../../errors'),
schema = require('../schema').tables,
schemaTables = Object.keys(schema),
populate, logger;
logger = {
info: function info(message) {
logging.info('Migrations:' + message);
},
warn: function warn(message) {
logging.warn('Skipping Migrations:' + message);
}
};
/**
* ## Populate
* Uses the schema to determine table structures, and automatically creates each table in order
*/
populate = function populate(options) {
options = options || {};
var tablesOnly = options.tablesOnly,
modelOptions = {
context: {
internal: true
}
};
logger.info('Creating tables...');
return db.knex.transaction(function populateDatabaseInTransaction(transaction) {
return Promise.mapSeries(schemaTables, function createTable(table) {
logger.info('Creating table: ' + table);
return commands.createTable(table, transaction);
}).then(function () {
// @TODO:
// - key: migrations-kate
// - move to seed
return models.Settings.populateDefaults(_.merge({}, {transacting: transaction}, modelOptions));
}).then(function () {
// @TODO:
// - key: migrations-kate
// - move to seed
return versioning.setDatabaseVersion(transaction);
}).then(function populateFixtures() {
if (tablesOnly) {
return;
}
return fixtures.populate(logger, _.merge({}, {transacting: transaction}, modelOptions));
});
}).catch(function populateDatabaseError(err) {
logger.warn('rolling back...');
return Promise.reject(new errors.GhostError({err: err, context: 'Unable to populate database!'}));
});
};
module.exports = populate;

View File

@ -1,34 +0,0 @@
// ### Reset
// Delete all tables from the database in reverse order
var Promise = require('bluebird'),
commands = require('../schema').commands,
schema = require('../schema').tables,
schemaTables = Object.keys(schema).reverse(),
reset;
/**
* # Reset
* Deletes all the tables defined in the schema
* Uses reverse order, which ensures that foreign keys are removed before the parent table
*
* @TODO:
* - move to sephiroth
* - then deleting migrations table will make sense
*
* @returns {Promise<*>}
*/
reset = function reset() {
var result;
return Promise.mapSeries(schemaTables, function (table) {
return commands.deleteTable(table);
}).then(function (_result) {
result = _result;
return commands.deleteTable('migrations');
}).then(function () {
return result;
});
};
module.exports = reset;

View File

@ -1,150 +0,0 @@
// # Update Database
// Handles migrating a database between two different database versions
var Promise = require('bluebird'),
_ = require('lodash'),
backup = require('./backup'),
fixtures = require('./fixtures'),
errors = require('../../errors'),
logging = require('../../logging'),
i18n = require('../../i18n'),
db = require('../../data/db'),
versioning = require('../schema').versioning,
sequence = function sequence(tasks, modelOptions, logger) {
// utils/sequence.js does not offer an option to pass cloned arguments
return Promise.reduce(tasks, function (results, task) {
return task(_.cloneDeep(modelOptions), logger)
.then(function (result) {
results.push(result);
return results;
});
}, []);
},
updateDatabaseSchema,
migrateToDatabaseVersion,
execute, logger, isDatabaseOutOfDate;
logger = {
info: function info(message) {
logging.info('Migrations:' + message);
},
warn: function warn(message) {
logging.warn('Skipping Migrations:' + message);
}
};
/**
* update database schema for one single version
*/
updateDatabaseSchema = function (tasks, logger, modelOptions) {
if (!tasks.length) {
return Promise.resolve();
}
return sequence(tasks, modelOptions, logger);
};
/**
* update each database version as one transaction
* if a version fails, rollback
* if a version fails, stop updating more versions
*/
migrateToDatabaseVersion = function migrateToDatabaseVersion(version, logger, modelOptions) {
return new Promise(function (resolve, reject) {
db.knex.transaction(function (transaction) {
var migrationTasks = versioning.getUpdateDatabaseTasks(version, logger),
fixturesTasks = versioning.getUpdateFixturesTasks(version, logger);
logger.info('Updating database to ' + version);
modelOptions.transacting = transaction;
updateDatabaseSchema(migrationTasks, logger, modelOptions)
.then(function () {
return fixtures.update(fixturesTasks, logger, modelOptions);
})
.then(function () {
return versioning.setDatabaseVersion(transaction, version);
})
.then(function () {
transaction.commit();
resolve();
})
.catch(function (err) {
logger.warn('rolling back because of: ' + err.stack);
transaction.rollback();
});
}).catch(function () {
reject();
});
});
};
/**
* ## Update
* Does a backup, then updates the database and fixtures
*/
execute = function execute(options) {
options = options || {};
var fromVersion = options.fromVersion,
toVersion = options.toVersion,
forceMigration = options.forceMigration,
versionsToUpdate,
modelOptions = {
context: {
internal: true
}
};
fromVersion = forceMigration ? versioning.canMigrateFromVersion : fromVersion;
// Figure out which versions we're updating through.
// This shouldn't include the from/current version (which we're already on)
versionsToUpdate = versioning.getMigrationVersions(fromVersion, toVersion).slice(1);
return backup(logger)
.then(function () {
logger.info('Migration required from ' + fromVersion + ' to ' + toVersion);
return Promise.mapSeries(versionsToUpdate, function (versionToUpdate) {
return migrateToDatabaseVersion(versionToUpdate, logger, modelOptions);
});
})
.then(function () {
logger.info('Finished!');
});
};
isDatabaseOutOfDate = function isDatabaseOutOfDate(options) {
options = options || {};
var fromVersion = options.fromVersion,
toVersion = options.toVersion,
forceMigration = options.forceMigration;
// CASE: current database version is lower then we support
if (fromVersion < versioning.canMigrateFromVersion) {
return {
error: new errors.DatabaseVersionError({
message: i18n.t('errors.data.versioning.index.cannotMigrate.error'),
context: i18n.t('errors.data.versioning.index.cannotMigrate.context'),
help: i18n.t('common.seeLinkForInstructions', {link: 'http://support.ghost.org/how-to-upgrade/'})
})
};
}
// CASE: the database exists but is out of date
else if (fromVersion < toVersion || forceMigration) {
return {migrate: true};
}
// CASE: database is up-to-date
else if (fromVersion === toVersion) {
return {migrate: false};
}
// CASE: we don't understand the version
else {
return {error: new errors.DatabaseVersionError({message: i18n.t('errors.data.versioning.index.dbVersionNotRecognized')})};
}
};
exports.execute = execute;
exports.isDatabaseOutOfDate = isDatabaseOutOfDate;

View File

@ -1,5 +1,4 @@
var path = require('path'),
Promise = require('bluebird'),
var Promise = require('bluebird'),
db = require('../db'),
errors = require('../../errors'),
ghostVersion = require('../../utils/ghost-version');
@ -31,7 +30,7 @@ function validateDatabaseVersion(version) {
/**
* If the database version is null, the database was never seeded.
* The seed migration script will set your database to current Ghost Version.
* The init migration script will set your database to current Ghost Version.
*/
function getDatabaseVersion(options) {
options = options || {};
@ -71,58 +70,9 @@ function setDatabaseVersion(options) {
});
}
/**
* return the versions which need migration
* when on 1.1 and we update to 1.4, we expect [1.2, 1.3, 1.4]
*/
function getMigrationVersions(fromVersion, toVersion) {
var versions = [],
i;
for (i = (fromVersion * 10) + 1; i <= toVersion * 10; i += 1) {
versions.push((i / 10).toString());
}
return versions;
}
/**
* ### 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 {String} relPath
* @param {Function} logger
* @returns {Array}
*/
function getVersionTasks(version, relPath, logger) {
var tasks = [];
try {
tasks = require(path.join(relPath, version));
} catch (e) {
logger.info('No tasks found for version', version);
}
return tasks;
}
function getUpdateDatabaseTasks(version, logger) {
return getVersionTasks(version, '../migration/', logger);
}
function getUpdateFixturesTasks(version, logger) {
return getVersionTasks(version, '../migration/fixtures/', logger);
}
module.exports = {
canMigrateFromVersion: '1.0',
getNewestDatabaseVersion: getNewestDatabaseVersion,
getDatabaseVersion: getDatabaseVersion,
setDatabaseVersion: setDatabaseVersion,
getMigrationVersions: getMigrationVersions,
getUpdateDatabaseTasks: getUpdateDatabaseTasks,
getUpdateFixturesTasks: getUpdateFixturesTasks
setDatabaseVersion: setDatabaseVersion
};

View File

@ -28,6 +28,7 @@ Post = ghostBookshelf.Model.extend({
if (usePreviousResourceType) {
resourceType = this.updated('page') ? 'page' : 'post';
}
events.emit(resourceType + '.' + event, this);
},

View File

@ -491,7 +491,7 @@ User = ghostBookshelf.Model.extend({
}
if (action === 'edit') {
// Owner can only be editted by owner
// Owner can only be edited by owner
if (loadedPermissions.user && userModel.hasRole('Owner')) {
hasUserPermission = _.some(loadedPermissions.user.roles, {name: 'Owner'});
}

View File

@ -1,18 +1,12 @@
var testUtils = require('../utils'),
should = require('should'),
sinon = require('sinon'),
_ = require('lodash'),
Promise = require('bluebird'),
fixtures = require('../../server/data/migration/fixtures'),
Models = require('../../server/models'),
sandbox = sinon.sandbox.create();
var testUtils = require('../utils'),
should = require('should'),
sinon = require('sinon'),
_ = require('lodash'),
Promise = require('bluebird'),
Models = require('../../server/models'),
sandbox = sinon.sandbox.create();
describe('Database Migration (special functions)', function () {
var loggerStub = {
info: sandbox.stub(),
warn: sandbox.stub()
};
before(testUtils.teardown);
afterEach(testUtils.teardown);
afterEach(function () {
@ -152,69 +146,68 @@ describe('Database Migration (special functions)', function () {
});
describe('Populate', function () {
beforeEach(testUtils.setup());
beforeEach(testUtils.setup('default'));
it('should populate all fixtures correctly', function (done) {
fixtures.populate(loggerStub, {context:{internal:true}}).then(function () {
var props = {
posts: Models.Post.findAll({include: ['tags']}),
tags: Models.Tag.findAll(),
users: Models.User.findAll({filter: 'status:inactive', context: {internal:true}, include: ['roles']}),
clients: Models.Client.findAll(),
roles: Models.Role.findAll(),
permissions: Models.Permission.findAll({include: ['roles']})
};
var props = {
posts: Models.Post.findAll({include: ['tags']}),
tags: Models.Tag.findAll(),
users: Models.User.findAll({
filter: 'status:inactive',
context: {internal: true},
include: ['roles']
}),
clients: Models.Client.findAll(),
roles: Models.Role.findAll(),
permissions: Models.Permission.findAll({include: ['roles']})
};
loggerStub.info.called.should.be.true();
loggerStub.warn.called.should.be.false();
return Promise.props(props).then(function (result) {
should.exist(result);
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');
// 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');
// 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');
// 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(3);
result.clients.at(0).get('name').should.eql('Ghost Admin');
result.clients.at(1).get('name').should.eql('Ghost Frontend');
result.clients.at(2).get('name').should.eql('Ghost Scheduler');
// Clients
should.exist(result.clients);
result.clients.length.should.eql(3);
result.clients.at(0).get('name').should.eql('Ghost Admin');
result.clients.at(1).get('name').should.eql('Ghost Frontend');
result.clients.at(2).get('name').should.eql('Ghost Scheduler');
// 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).get('status').should.eql('inactive');
result.users.at(0).related('roles').length.should.eql(1);
result.users.at(0).related('roles').at(0).get('name').should.eql('Owner');
// 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).get('status').should.eql('inactive');
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');
// 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(48);
result.permissions.toJSON().should.be.CompletePermissions();
// Permissions
result.permissions.length.should.eql(48);
result.permissions.toJSON().should.be.CompletePermissions();
done();
});
}).catch(done);
done();
});
});
});
});

View File

@ -10,36 +10,26 @@ var testUtils = require('../../utils'),
ghostBookshelf = require('../../../server/models/base'),
PostModel = require('../../../server/models/post').Post,
TagModel = require('../../../server/models/tag').Tag,
models = require('../../../server/models'),
events = require('../../../server/events'),
errors = require('../../../server/errors'),
DataGenerator = testUtils.DataGenerator,
context = testUtils.context.owner,
configUtils = require('../../utils/configUtils'),
configUtils = require('../../utils/configUtils'),
sandbox = sinon.sandbox.create();
/**
* IMPORTANT:
* - do not spy the events unit, because when we only spy, all listeners get the event
* - this can cause unexpected behaviour as the listeners execute code
* - using rewire is not possible, because each model self registers it's model registry in bookshelf
* - rewire would add 1 registry, a file who requires the models, tries to register the model another time
*/
describe('Post Model', function () {
var eventSpy;
var eventsTriggered = {};
// Keep the DB clean
before(testUtils.teardown);
afterEach(testUtils.teardown);
beforeEach(function () {
eventSpy = sandbox.spy(events, 'emit');
/**
* @TODO:
* - key: migrations-kate
* - this is not pretty
* - eventSpy get's now more events then expected
* - because on migrations.populate we trigger populateDefaults
* - how to solve? eventSpy must be local and not global?
*/
models.init();
sandbox.stub(models.Settings, 'populateDefaults').returns(Promise.resolve());
});
afterEach(function () {
sandbox.restore();
});
@ -355,6 +345,17 @@ describe('Post Model', function () {
});
describe('edit', function () {
beforeEach(function () {
eventsTriggered = {};
sandbox.stub(events, 'emit', function (eventName, eventObj) {
if (!eventsTriggered[eventName]) {
eventsTriggered[eventName] = [];
}
eventsTriggered[eventName].push(eventObj);
});
});
it('can change title', function (done) {
var postId = testUtils.DataGenerator.Content.posts[0].id;
@ -369,9 +370,10 @@ describe('Post Model', function () {
}).then(function (edited) {
should.exist(edited);
edited.attributes.title.should.equal('new title');
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('post.published.edited').should.be.true();
eventSpy.secondCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.published.edited']);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -421,9 +423,10 @@ describe('Post Model', function () {
}).then(function (edited) {
should.exist(edited);
edited.attributes.status.should.equal('published');
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('post.published').should.be.true();
eventSpy.secondCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.published']);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -443,9 +446,10 @@ describe('Post Model', function () {
}).then(function (edited) {
should.exist(edited);
edited.attributes.status.should.equal('draft');
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('post.unpublished').should.be.true();
eventSpy.secondCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.unpublished']);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -508,9 +512,10 @@ describe('Post Model', function () {
// mysql does not store ms
moment(edited.attributes.published_at).startOf('seconds').diff(moment(newPublishedAt).startOf('seconds')).should.eql(0);
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('post.scheduled').should.be.true();
eventSpy.secondCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.scheduled']);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -530,9 +535,10 @@ describe('Post Model', function () {
}).then(function (edited) {
should.exist(edited);
edited.attributes.status.should.equal('draft');
eventSpy.callCount.should.eql(2);
eventSpy.firstCall.calledWith('post.unscheduled').should.be.true();
eventSpy.secondCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.unscheduled']);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -553,9 +559,10 @@ describe('Post Model', function () {
}).then(function (edited) {
should.exist(edited);
edited.attributes.status.should.equal('scheduled');
eventSpy.callCount.should.eql(2);
eventSpy.firstCall.calledWith('post.rescheduled').should.be.true();
eventSpy.secondCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.rescheduled']);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -575,8 +582,9 @@ describe('Post Model', function () {
}).then(function (edited) {
should.exist(edited);
edited.attributes.status.should.equal('scheduled');
eventSpy.callCount.should.eql(1);
eventSpy.firstCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -620,18 +628,23 @@ describe('Post Model', function () {
should.exist(edited);
edited.attributes.status.should.equal('draft');
edited.attributes.page.should.equal(true);
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('post.deleted').should.be.true();
eventSpy.secondCall.calledWith('page.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.deleted']);
should.exist(eventsTriggered['page.added']);
return PostModel.edit({page: 0}, _.extend({}, context, {id: postId}));
}).then(function (edited) {
should.exist(edited);
edited.attributes.status.should.equal('draft');
edited.attributes.page.should.equal(false);
eventSpy.callCount.should.equal(4);
eventSpy.thirdCall.calledWith('page.deleted').should.be.true();
eventSpy.lastCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(4);
should.exist(eventsTriggered['post.deleted']);
should.exist(eventsTriggered['page.added']);
should.exist(eventsTriggered['post.deleted']);
should.exist(eventsTriggered['post.added']);
done();
}).catch(done);
});
@ -652,21 +665,24 @@ describe('Post Model', function () {
should.exist(edited);
edited.attributes.status.should.equal('scheduled');
edited.attributes.page.should.equal(true);
eventSpy.callCount.should.be.eql(3);
eventSpy.firstCall.calledWith('post.deleted').should.be.true();
eventSpy.secondCall.calledWith('page.added').should.be.true();
eventSpy.thirdCall.calledWith('page.scheduled').should.be.true();
Object.keys(eventsTriggered).length.should.eql(3);
should.exist(eventsTriggered['post.deleted']);
should.exist(eventsTriggered['page.added']);
should.exist(eventsTriggered['page.scheduled']);
return PostModel.edit({page: 0}, _.extend({}, context, {id: edited.id}));
}).then(function (edited) {
should.exist(edited);
edited.attributes.status.should.equal('scheduled');
edited.attributes.page.should.equal(false);
eventSpy.callCount.should.equal(7);
eventSpy.getCall(3).calledWith('page.unscheduled').should.be.true();
eventSpy.getCall(4).calledWith('page.deleted').should.be.true();
eventSpy.getCall(5).calledWith('post.added').should.be.true();
eventSpy.getCall(6).calledWith('post.scheduled').should.be.true();
Object.keys(eventsTriggered).length.should.eql(7);
should.exist(eventsTriggered['page.unscheduled']);
should.exist(eventsTriggered['page.deleted']);
should.exist(eventsTriggered['post.added']);
should.exist(eventsTriggered['post.scheduled']);
done();
}).catch(done);
});
@ -687,11 +703,11 @@ describe('Post Model', function () {
edited.attributes.status.should.equal('published');
edited.attributes.page.should.equal(true);
eventSpy.callCount.should.equal(4);
eventSpy.firstCall.calledWith('post.unpublished').should.be.true();
eventSpy.secondCall.calledWith('post.deleted').should.be.true();
eventSpy.thirdCall.calledWith('page.added').should.be.true();
eventSpy.lastCall.calledWith('page.published').should.be.true();
Object.keys(eventsTriggered).length.should.eql(4);
should.exist(eventsTriggered['post.unpublished']);
should.exist(eventsTriggered['post.deleted']);
should.exist(eventsTriggered['page.added']);
should.exist(eventsTriggered['page.published']);
return PostModel.edit({page: 0}, _.extend({}, context, {id: postId}));
}).then(function (edited) {
@ -699,11 +715,12 @@ describe('Post Model', function () {
edited.attributes.status.should.equal('published');
edited.attributes.page.should.equal(false);
eventSpy.callCount.should.equal(8);
eventSpy.getCall(4).calledWith('page.unpublished').should.be.true();
eventSpy.getCall(5).calledWith('page.deleted').should.be.true();
eventSpy.getCall(6).calledWith('post.added').should.be.true();
eventSpy.getCall(7).calledWith('post.published').should.be.true();
Object.keys(eventsTriggered).length.should.eql(8);
should.exist(eventsTriggered['page.unpublished']);
should.exist(eventsTriggered['page.deleted']);
should.exist(eventsTriggered['post.added']);
should.exist(eventsTriggered['post.published']);
done();
}).catch(done);
});
@ -723,20 +740,23 @@ describe('Post Model', function () {
should.exist(edited);
edited.attributes.status.should.equal('published');
edited.attributes.page.should.equal(true);
eventSpy.calledThrice.should.be.true();
eventSpy.firstCall.calledWith('post.deleted').should.be.true();
eventSpy.secondCall.calledWith('page.added').should.be.true();
eventSpy.thirdCall.calledWith('page.published').should.be.true();
Object.keys(eventsTriggered).length.should.eql(3);
should.exist(eventsTriggered['post.deleted']);
should.exist(eventsTriggered['page.added']);
should.exist(eventsTriggered['page.published']);
return PostModel.edit({page: 0, status: 'draft'}, _.extend({}, context, {id: postId}));
}).then(function (edited) {
should.exist(edited);
edited.attributes.status.should.equal('draft');
edited.attributes.page.should.equal(false);
eventSpy.callCount.should.equal(6);
eventSpy.getCall(3).calledWith('page.unpublished').should.be.true();
eventSpy.getCall(4).calledWith('page.deleted').should.be.true();
eventSpy.getCall(5).calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(6);
should.exist(eventsTriggered['page.unpublished']);
should.exist(eventsTriggered['page.deleted']);
should.exist(eventsTriggered['post.added']);
done();
}).catch(done);
});
@ -805,6 +825,17 @@ describe('Post Model', function () {
});
describe('add', function () {
beforeEach(function () {
eventsTriggered = {};
sandbox.stub(events, 'emit', function (eventName, eventObj) {
if (!eventsTriggered[eventName]) {
eventsTriggered[eventName] = [];
}
eventsTriggered[eventName].push(eventObj);
});
});
it('can add, defaults are all correct', function (done) {
var createdPostUpdatedDate,
newPost = testUtils.DataGenerator.forModel.posts[2],
@ -841,8 +872,8 @@ describe('Post Model', function () {
createdPostUpdatedDate = createdPost.get('updated_at');
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
// Set the status to published to check that `published_at` is set.
return createdPost.save({status: 'published'}, context);
@ -853,9 +884,9 @@ describe('Post Model', function () {
publishedPost.get('updated_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
publishedPost.get('updated_at').should.not.equal(createdPostUpdatedDate);
eventSpy.calledThrice.should.be.true();
eventSpy.secondCall.calledWith('post.published').should.be.true();
eventSpy.thirdCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(3);
should.exist(eventsTriggered['post.published']);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -895,9 +926,9 @@ describe('Post Model', function () {
should.exist(newPost);
new Date(newPost.get('published_at')).getTime().should.equal(previousPublishedAtDate.getTime());
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
eventSpy.secondCall.calledWith('post.published').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.added']);
should.exist(eventsTriggered['post.published']);
done();
}).catch(done);
@ -911,8 +942,10 @@ describe('Post Model', function () {
}, context).then(function (newPost) {
should.exist(newPost);
should.not.exist(newPost.get('published_at'));
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
done();
}).catch(done);
});
@ -926,8 +959,10 @@ describe('Post Model', function () {
}, context).then(function (newPost) {
should.exist(newPost);
should.exist(newPost.get('published_at'));
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
done();
}).catch(done);
});
@ -940,7 +975,7 @@ describe('Post Model', function () {
}, context).catch(function (err) {
should.exist(err);
(err instanceof errors.ValidationError).should.eql(true);
eventSpy.called.should.be.false();
Object.keys(eventsTriggered).length.should.eql(0);
done();
});
});
@ -954,7 +989,7 @@ describe('Post Model', function () {
}, context).catch(function (err) {
should.exist(err);
(err instanceof errors.ValidationError).should.eql(true);
eventSpy.called.should.be.false();
Object.keys(eventsTriggered).length.should.eql(0);
done();
});
});
@ -967,7 +1002,7 @@ describe('Post Model', function () {
markdown: 'This is some content'
}, context).catch(function (err) {
(err instanceof errors.ValidationError).should.eql(true);
eventSpy.called.should.be.false();
Object.keys(eventsTriggered).length.should.eql(0);
done();
});
});
@ -980,9 +1015,11 @@ describe('Post Model', function () {
markdown: 'This is some content'
}, context).then(function (post) {
should.exist(post);
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
eventSpy.secondCall.calledWith('post.scheduled').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.added']);
should.exist(eventsTriggered['post.scheduled']);
done();
}).catch(done);
});
@ -996,9 +1033,11 @@ describe('Post Model', function () {
markdown: 'This is some content'
}, context).then(function (post) {
should.exist(post);
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('page.added').should.be.true();
eventSpy.secondCall.calledWith('page.scheduled').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['page.added']);
should.exist(eventsTriggered['page.scheduled']);
done();
}).catch(done);
});
@ -1028,15 +1067,15 @@ describe('Post Model', function () {
should.exist(createdPost);
createdPost.get('title').should.equal(untrimmedCreateTitle.trim());
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
return createdPost.save({title: untrimmedUpdateTitle}, context);
}).then(function (updatedPost) {
updatedPost.get('title').should.equal(untrimmedUpdateTitle.trim());
eventSpy.calledTwice.should.be.true();
eventSpy.secondCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.edited']);
done();
}).catch(done);
@ -1054,7 +1093,6 @@ describe('Post Model', function () {
})).then(function (createdPosts) {
// Should have created 12 posts
createdPosts.length.should.equal(12);
eventSpy.callCount.should.equal(12);
// Should have unique slugs and contents
_(createdPosts).each(function (post, i) {
@ -1068,7 +1106,10 @@ describe('Post Model', function () {
post.get('slug').should.equal('test-title-' + num);
post.get('markdown').should.equal('Test Content ' + num);
eventSpy.getCall(i).calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
eventsTriggered['post.added'].length.should.eql(12);
});
done();
@ -1083,8 +1124,9 @@ describe('Post Model', function () {
PostModel.add(newPost, context).then(function (createdPost) {
createdPost.get('slug').should.equal('apprehensive-titles-have-too-many-spaces-and-m-dashes-and-also-n-dashes');
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
done();
}).catch(done);
@ -1098,8 +1140,9 @@ describe('Post Model', function () {
PostModel.add(newPost, context).then(function (createdPost) {
createdPost.get('slug').should.not.equal('rss');
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
done();
});
@ -1132,16 +1175,18 @@ describe('Post Model', function () {
.then(function (createdFirstPost) {
// Store the slug for later
firstPost.slug = createdFirstPost.get('slug');
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
// Create the second post
return PostModel.add(secondPost, context);
}).then(function (createdSecondPost) {
// Store the slug for comparison later
secondPost.slug = createdSecondPost.get('slug');
eventSpy.calledTwice.should.be.true();
eventSpy.secondCall.calledWith('post.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.added']);
// Update with a conflicting slug from the first post
return createdSecondPost.save({
@ -1153,8 +1198,8 @@ describe('Post Model', function () {
// Should not have a conflicted slug from the first
updatedSecondPost.get('slug').should.not.equal(firstPost.slug);
eventSpy.calledThrice.should.be.true();
eventSpy.thirdCall.calledWith('post.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.edited']);
return PostModel.findOne({
id: updatedSecondPost.id,
@ -1172,6 +1217,17 @@ describe('Post Model', function () {
});
describe('destroy', function () {
beforeEach(function () {
eventsTriggered = {};
sandbox.stub(events, 'emit', function (eventName, eventObj) {
if (!eventsTriggered[eventName]) {
eventsTriggered[eventName] = [];
}
eventsTriggered[eventName].push(eventObj);
});
});
it('published post', function (done) {
// We're going to try deleting post id 1 which has tag id 1
var firstItemData = {id: testUtils.DataGenerator.Content.posts[0].id};
@ -1193,9 +1249,9 @@ describe('Post Model', function () {
should.equal(deleted.author, undefined);
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('post.unpublished').should.be.true();
eventSpy.secondCall.calledWith('post.deleted').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['post.unpublished']);
should.exist(eventsTriggered['post.deleted']);
// Double check we can't find the post again
return PostModel.findOne(firstItemData);
@ -1231,8 +1287,8 @@ describe('Post Model', function () {
should.equal(deleted.author, undefined);
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('post.deleted').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['post.deleted']);
// Double check we can't find the post again
return PostModel.findOne(firstItemData);
@ -1268,9 +1324,9 @@ describe('Post Model', function () {
should.equal(deleted.author, undefined);
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('page.unpublished').should.be.true();
eventSpy.secondCall.calledWith('page.deleted').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['page.unpublished']);
should.exist(eventsTriggered['page.deleted']);
// Double check we can't find the post again
return PostModel.findOne(firstItemData);
@ -1304,8 +1360,8 @@ describe('Post Model', function () {
should.equal(deleted.author, undefined);
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('page.deleted').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['page.deleted']);
// Double check we can't find the post again
return PostModel.findOne(firstItemData);

View File

@ -9,14 +9,13 @@ var testUtils = require('../../utils'),
gravatar = require('../../../server/utils/gravatar'),
UserModel = require('../../../server/models/user').User,
RoleModel = require('../../../server/models/role').Role,
models = require('../../../server/models'),
events = require('../../../server/events'),
context = testUtils.context.admin,
sandbox = sinon.sandbox.create();
describe('User Model', function run() {
var eventSpy;
// Keep the DB clean
var eventsTriggered = {};
before(testUtils.teardown);
afterEach(testUtils.teardown);
afterEach(function () {
@ -27,21 +26,6 @@ describe('User Model', function run() {
should.exist(UserModel);
});
beforeEach(function () {
eventSpy = sandbox.spy(events, 'emit');
/**
* @TODO:
* - key: migrations-kate
* - this is not pretty
* - eventSpy get's now more events then expected
* - because on migrations.populate we trigger populateDefaults
* - how to solve? eventSpy must be local and not global?
*/
models.init();
sandbox.stub(models.Settings, 'populateDefaults').returns(Promise.resolve());
});
describe('Registration', function runRegistration() {
beforeEach(testUtils.setup('roles'));
@ -185,6 +169,17 @@ describe('User Model', function run() {
describe('Basic Operations', function () {
beforeEach(testUtils.setup('users:roles'));
beforeEach(function () {
eventsTriggered = {};
sandbox.stub(events, 'emit', function (eventName, eventObj) {
if (!eventsTriggered[eventName]) {
eventsTriggered[eventName] = [];
}
eventsTriggered[eventName].push(eventObj);
});
});
it('sets last login time on successful login', function (done) {
var userData = testUtils.DataGenerator.forModel.users[0];
@ -341,8 +336,8 @@ describe('User Model', function run() {
createdUser.attributes.password.should.not.equal(userData.password, 'password was hashed');
createdUser.attributes.email.should.eql(userData.email, 'email address correct');
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('user.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['user.added']);
done();
}).catch(done);
@ -361,9 +356,9 @@ describe('User Model', function run() {
createdUser.get('email').should.eql(userData.email, 'email address correct');
createdUser.related('roles').toJSON()[0].name.should.eql('Administrator', 'role set correctly');
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('user.added').should.be.true();
eventSpy.secondCall.calledWith('user.activated').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['user.added']);
should.exist(eventsTriggered['user.activated']);
done();
}).catch(done);
@ -400,9 +395,9 @@ describe('User Model', function run() {
should.exist(edited);
edited.attributes.website.should.equal('http://some.newurl.com');
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('user.activated.edited').should.be.true();
eventSpy.secondCall.calledWith('user.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['user.activated.edited']);
should.exist(eventsTriggered['user.edited']);
done();
}).catch(done);
@ -432,15 +427,16 @@ describe('User Model', function run() {
userId = createdUser.attributes.id;
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('user.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['user.added']);
return UserModel.edit({website: 'http://some.newurl.com'}, {id: userId});
}).then(function (createdUser) {
createdUser.attributes.status.should.equal('invited');
eventSpy.calledTwice.should.be.true();
eventSpy.secondCall.calledWith('user.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['user.edited']);
done();
}).catch(done);
});
@ -457,16 +453,17 @@ describe('User Model', function run() {
userId = createdUser.attributes.id;
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('user.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['user.added']);
return UserModel.edit({status: 'active'}, {id: userId});
}).then(function (createdUser) {
createdUser.attributes.status.should.equal('active');
eventSpy.calledThrice.should.be.true();
eventSpy.secondCall.calledWith('user.activated').should.be.true();
eventSpy.thirdCall.calledWith('user.edited').should.be.true();
Object.keys(eventsTriggered).length.should.eql(3);
should.exist(eventsTriggered['user.activated']);
should.exist(eventsTriggered['user.edited']);
done();
}).catch(done);
});
@ -486,9 +483,9 @@ describe('User Model', function run() {
}).then(function (response) {
response.toJSON().should.be.empty();
eventSpy.calledTwice.should.be.true();
eventSpy.firstCall.calledWith('user.deactivated').should.be.true();
eventSpy.secondCall.calledWith('user.deleted').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['user.deactivated']);
should.exist(eventsTriggered['user.deleted']);
// Double check we can't find the user again
return UserModel.findOne(firstUser);
@ -511,16 +508,16 @@ describe('User Model', function run() {
userId = {id: createdUser.attributes.id};
eventSpy.calledOnce.should.be.true();
eventSpy.firstCall.calledWith('user.added').should.be.true();
Object.keys(eventsTriggered).length.should.eql(1);
should.exist(eventsTriggered['user.added']);
// Destroy the user
return UserModel.destroy(userId);
}).then(function (response) {
response.toJSON().should.be.empty();
eventSpy.calledTwice.should.be.true();
eventSpy.secondCall.calledWith('user.deleted').should.be.true();
Object.keys(eventsTriggered).length.should.eql(2);
should.exist(eventsTriggered['user.deleted']);
// Double check we can't find the user again
return UserModel.findOne(userId);

View File

@ -5,8 +5,8 @@ var should = require('should'),
models = require('../../server/models'),
baseUtils = require('../../server/models/base/utils'),
fixtureUtils = rewire('../../server/data/migration/fixtures/utils'),
fixtures = require('../../server/data/migration/fixtures/fixtures'),
fixtureUtils = rewire('../../server/data/schema/fixtures/utils'),
fixtures = require('../../server/data/schema/fixtures/fixtures'),
sandbox = sinon.sandbox.create();
describe('Migration Fixture Utils', function () {
@ -150,19 +150,19 @@ describe('Migration Fixture Utils', function () {
fixtureUtils.addFixturesForRelation(fixtures.relations[0]).then(function (result) {
should.exist(result);
result.should.be.an.Object();
result.should.have.property('expected', 29);
result.should.have.property('done', 29);
result.should.have.property('expected', 30);
result.should.have.property('done', 30);
// Permissions & Roles
permsAllStub.calledOnce.should.be.true();
rolesAllStub.calledOnce.should.be.true();
dataMethodStub.filter.callCount.should.eql(29);
dataMethodStub.filter.callCount.should.eql(30);
dataMethodStub.find.callCount.should.eql(3);
baseUtilAttachStub.callCount.should.eql(29);
baseUtilAttachStub.callCount.should.eql(30);
fromItem.related.callCount.should.eql(29);
fromItem.findWhere.callCount.should.eql(29);
toItem[0].get.callCount.should.eql(58);
fromItem.related.callCount.should.eql(30);
fromItem.findWhere.callCount.should.eql(30);
toItem[0].get.callCount.should.eql(60);
done();
}).catch(done);

View File

@ -5,27 +5,22 @@ var should = require('should'),
Promise = require('bluebird'),
crypto = require('crypto'),
fs = require('fs'),
errors = require('../../server/errors'),
models = require('../../server/models'),
exporter = require('../../server/data/export'),
schema = require('../../server/data/schema'),
migration = rewire('../../server/data/migration'),
fixtures = require('../../server/data/migration/fixtures'),
populate = require('../../server/data/migration/populate'),
update = rewire('../../server/data/migration/update'),
backupDatabase = rewire('../../server/data/db/backup'),
fixtures = require('../../server/data/schema/fixtures'),
defaultSettings = schema.defaultSettings,
schemaTables = Object.keys(schema.tables),
sandbox = sinon.sandbox.create();
// 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,
// both of which are required for migrations to work properly.
describe.skip('DB version integrity', function () {
describe('DB version integrity', function () {
// Only these variables should need updating
var currentDbVersion = 'alpha.1',
currentSchemaHash = 'b3bdae210526b2d4393359c3e45d7f83',
currentFixturesHash = '30b0a956b04e634e7f2cddcae8d2fd20';
var currentSchemaHash = '71d6b843f798352f804db09e5478eef5',
currentFixturesHash = 'c08c23c38a3732452a48915952861fc7';
// 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
@ -41,41 +36,23 @@ describe.skip('DB version integrity', function () {
});
schemaHash = crypto.createHash('md5').update(JSON.stringify(tablesNoValidation)).digest('hex');
fixturesHash = crypto.createHash('md5').update(JSON.stringify(fixtures.fixtures)).digest('hex');
fixturesHash = crypto.createHash('md5').update(JSON.stringify(fixtures)).digest('hex');
// Test!
defaultSettings.core.databaseVersion.defaultValue.should.eql(currentDbVersion);
// by default the database version is null
should.not.exist(defaultSettings.core.databaseVersion.defaultValue);
schemaHash.should.eql(currentSchemaHash);
fixturesHash.should.eql(currentFixturesHash);
schema.versioning.canMigrateFromVersion.should.eql('003');
schema.versioning.canMigrateFromVersion.should.eql('1.0');
});
});
/**
* we don't use our logic anymore
* so some tests are failing
*
* @TODO: kate-migrations
*/
describe.skip('Migrations', function () {
var loggerStub, resetLogger;
describe('Migrations', function () {
before(function () {
models.init();
});
afterEach(function () {
sandbox.restore();
resetLogger();
});
beforeEach(function () {
loggerStub = {
info: sandbox.stub(),
warn: sandbox.stub()
};
resetLogger = update.__set__('logger', loggerStub);
});
describe('Backup', function () {
@ -88,11 +65,10 @@ describe.skip('Migrations', function () {
});
it('should create a backup JSON file', function (done) {
migration.backupDatabase(loggerStub).then(function () {
backupDatabase().then(function () {
exportStub.calledOnce.should.be.true();
filenameStub.calledOnce.should.be.true();
fsStub.calledOnce.should.be.true();
loggerStub.info.calledTwice.should.be.true();
done();
}).catch(done);
@ -101,7 +77,7 @@ describe.skip('Migrations', function () {
it('should fall back to console.log if no logger provided', function (done) {
var noopStub = sandbox.stub(_, 'noop');
migration.backupDatabase().then(function () {
backupDatabase().then(function () {
exportStub.calledOnce.should.be.true();
filenameStub.calledOnce.should.be.true();
fsStub.calledOnce.should.be.true();
@ -113,147 +89,4 @@ describe.skip('Migrations', function () {
}).catch(done);
});
});
describe('Reset', function () {
var deleteStub;
beforeEach(function () {
deleteStub = sandbox.stub(schema.commands, 'deleteTable').returns(new Promise.resolve());
});
it('should delete all tables in reverse order', function (done) {
migration.reset().then(function (result) {
should.exist(result);
result.should.be.an.Array().with.lengthOf(schemaTables.length);
deleteStub.called.should.be.true();
deleteStub.callCount.should.be.eql(schemaTables.length + 1);
// First call should be called with the last table
deleteStub.firstCall.calledWith(schemaTables[schemaTables.length - 1]).should.be.true();
// Last call should be called with the first table
deleteStub.lastCall.calledWith('migrations').should.be.true();
done();
}).catch(done);
});
it('should delete all tables in reverse order when called twice in a row', function (done) {
migration.reset().then(function (result) {
should.exist(result);
result.should.be.an.Array().with.lengthOf(schemaTables.length);
deleteStub.called.should.be.true();
deleteStub.callCount.should.be.eql(schemaTables.length + 1);
// First call should be called with the last table
deleteStub.firstCall.calledWith(schemaTables[schemaTables.length - 1]).should.be.true();
// Last call should be called with the first table
deleteStub.lastCall.calledWith('migrations').should.be.true();
return migration.reset();
}).then(function (result) {
should.exist(result);
result.should.be.an.Array().with.lengthOf(schemaTables.length);
deleteStub.called.should.be.true();
deleteStub.callCount.should.be.eql(schemaTables.length * 2 + 2);
// First call (second set) should be called with the last table
deleteStub.getCall(schemaTables.length).calledWith('migrations').should.be.true();
// Last call (second Set) should be called with the first table
// deleteStub.getCall(schemaTables.length * 2 + 2).calledWith(schemaTables[0]).should.be.true();
done();
}).catch(done);
});
});
describe('Populate', function () {
var createStub, fixturesStub, setDatabaseVersionStub, populateDefaultsStub;
beforeEach(function () {
fixturesStub = sandbox.stub(fixtures, 'populate').returns(new Promise.resolve());
});
it('should create all tables, and populate fixtures', function (done) {
createStub = sandbox.stub(schema.commands, 'createTable').returns(new Promise.resolve());
setDatabaseVersionStub = sandbox.stub(schema.versioning, 'setDatabaseVersion').returns(new Promise.resolve());
populateDefaultsStub = sandbox.stub(models.Settings, 'populateDefaults').returns(new Promise.resolve());
populate().then(function (result) {
should.not.exist(result);
populateDefaultsStub.called.should.be.true();
setDatabaseVersionStub.called.should.be.true();
createStub.called.should.be.true();
createStub.callCount.should.be.eql(schemaTables.length);
createStub.firstCall.calledWith(schemaTables[0]).should.be.true();
createStub.lastCall.calledWith(schemaTables[schemaTables.length - 1]).should.be.true();
fixturesStub.calledOnce.should.be.true();
done();
}).catch(done);
});
it('should rollback if error occurs', function (done) {
var i = 0;
createStub = sandbox.stub(schema.commands, 'createTable', function () {
i = i + 1;
if (i > 10) {
return new Promise.reject(new Error('error on table creation :('));
}
return new Promise.resolve();
});
populate()
.then(function () {
done(new Error('should throw an error for database population'));
})
.catch(function (err) {
should.exist(err);
(err instanceof errors.GhostError).should.eql(true);
createStub.callCount.should.eql(11);
done();
});
});
});
describe('isDatabaseOutOfDate', function () {
var updateDatabaseSchemaStub, updateDatabaseSchemaReset, versionsSpy;
beforeEach(function () {
versionsSpy = sandbox.spy(schema.versioning, 'getMigrationVersions');
// For these tests, stub out the actual update task
updateDatabaseSchemaStub = sandbox.stub().returns(new Promise.resolve());
updateDatabaseSchemaReset = update.__set__('updateDatabaseSchema', updateDatabaseSchemaStub);
});
afterEach(function () {
updateDatabaseSchemaReset();
});
it('should throw error if versions are too old', function () {
var response = update.isDatabaseOutOfDate({fromVersion: '0.8', toVersion: '1.0'});
updateDatabaseSchemaStub.calledOnce.should.be.false();
(response.error instanceof errors.DatabaseVersionError).should.eql(true);
});
it('should just return if versions are the same', function () {
var migrateToDatabaseVersionStub = sandbox.stub().returns(new Promise.resolve()),
migrateToDatabaseVersionReset = update.__set__('migrateToDatabaseVersion', migrateToDatabaseVersionStub),
response = update.isDatabaseOutOfDate({fromVersion: '1.0', toVersion: '1.0'});
response.migrate.should.eql(false);
versionsSpy.calledOnce.should.be.false();
migrateToDatabaseVersionStub.callCount.should.eql(0);
migrateToDatabaseVersionReset();
});
it('should throw an error if the database version is higher than the default', function () {
var response = update.isDatabaseOutOfDate({fromVersion: '1.3', toVersion: '1.2'});
updateDatabaseSchemaStub.calledOnce.should.be.false();
(response.error instanceof errors.DatabaseVersionError).should.eql(true);
});
});
});

View File

@ -1,133 +0,0 @@
/*jshint unused:false*/
var should = require('should'),
sinon = require('sinon'),
Promise = require('bluebird'),
rewire = require('rewire'),
config = require('../../server/config'),
versioning = require(config.get('paths').corePath + '/server/data/schema/versioning'),
migration = require(config.get('paths').corePath + '/server/data/migration'),
models = require(config.get('paths').corePath + '/server/models'),
errors = require(config.get('paths').corePath + '/server/errors'),
permissions = require(config.get('paths').corePath + '/server/permissions'),
api = require(config.get('paths').corePath + '/server/api'),
apps = require(config.get('paths').corePath + '/server/apps'),
i18n = require(config.get('paths').corePath + '/server/i18n'),
xmlrpc = require(config.get('paths').corePath + '/server/data/xml/xmlrpc'),
slack = require(config.get('paths').corePath + '/server/data/slack'),
scheduling = require(config.get('paths').corePath + '/server/scheduling'),
bootstrap = rewire(config.get('paths').corePath + '/server'),
sandbox = sinon.sandbox.create();
describe('server bootstrap', function () {
var middlewareStub, resetMiddlewareStub, initDbHashAndFirstRunStub, resetInitDbHashAndFirstRunStub,
populateStub;
before(function () {
models.init();
});
beforeEach(function () {
middlewareStub = sandbox.stub();
initDbHashAndFirstRunStub = sandbox.stub();
populateStub = sandbox.stub(migration, 'populate').returns(Promise.resolve());
sandbox.stub(models.Settings, 'populateDefaults').returns(Promise.resolve());
sandbox.stub(permissions, 'init').returns(Promise.resolve());
sandbox.stub(api.settings, 'updateSettingsCache').returns(Promise.resolve());
sandbox.stub(apps, 'init').returns(Promise.resolve());
sandbox.stub(slack, 'listen').returns(Promise.resolve());
sandbox.stub(xmlrpc, 'listen').returns(Promise.resolve());
sandbox.stub(scheduling, 'init').returns(Promise.resolve());
resetMiddlewareStub = bootstrap.__set__('middleware', middlewareStub);
resetInitDbHashAndFirstRunStub = bootstrap.__set__('initDbHashAndFirstRun', initDbHashAndFirstRunStub);
});
afterEach(function () {
sandbox.restore();
resetMiddlewareStub();
resetInitDbHashAndFirstRunStub();
});
describe('migrations', function () {
it('database does not exist: expect database population error', function (done) {
sandbox.stub(migration.update, 'isDatabaseOutOfDate').returns({migrate:false});
sandbox.stub(versioning, 'getDatabaseVersion', function () {
return Promise.reject();
});
bootstrap()
.then(function () {
done(new Error('expect error: database population'));
})
.catch(function (err) {
migration.populate.calledOnce.should.eql(false);
config.get('maintenance').enabled.should.eql(false);
// checking the error code is tricky, because it depends on other tests running before
// it's fine just checking the type of the error
// @TODO: kate-migrations (export errors in knex-migrator to be able to check instanceof)
err.errorType.should.eql('DatabaseIsNotOkError');
done();
});
});
// @TODO fix these two tests once we've decided on a new migration
// @TODO kate-migrations
// versioning scheme
// the tests do not work right now because if the version isn't an
// alpha version, we error. I've added two temporary tests to show this.
it.skip('database does exist: expect no update', function (done) {
sandbox.stub(migration.update, 'isDatabaseOutOfDate').returns({migrate:false});
sandbox.spy(migration.update, 'execute');
sandbox.stub(versioning, 'getDatabaseVersion', function () {
return Promise.resolve('006');
});
bootstrap()
.then(function () {
migration.update.isDatabaseOutOfDate.calledOnce.should.eql(true);
migration.update.execute.called.should.eql(false);
models.Settings.populateDefaults.callCount.should.eql(1);
migration.populate.calledOnce.should.eql(false);
done();
})
.catch(function (err) {
done(err);
});
});
it.skip('database does exist: expect update', function (done) {
sandbox.stub(migration.update, 'isDatabaseOutOfDate').returns({migrate:true});
sandbox.stub(migration.update, 'execute').returns(Promise.resolve());
sandbox.stub(versioning, 'getDatabaseVersion', function () {
return Promise.resolve('006');
});
bootstrap()
.then(function () {
migration.update.isDatabaseOutOfDate.calledOnce.should.eql(true);
migration.update.execute.calledOnce.should.eql(true);
migration.update.execute.calledWith({
fromVersion: '006',
toVersion: '008',
forceMigration: undefined
}).should.eql(true);
models.Settings.populateDefaults.callCount.should.eql(1);
migration.populate.calledOnce.should.eql(false);
config.get('maintenance').enabled.should.eql(false);
done();
})
.catch(function (err) {
done(err);
});
});
});
});

View File

@ -14,25 +14,6 @@ describe('Versioning', 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('1.0', '1.0').should.eql([]);
versioning.getMigrationVersions('1.2', '1.2').should.eql([]);
});
it('should output an empty array if the toVersion is higher than the fromVersion', function () {
versioning.getMigrationVersions('1.2', '1.1').should.eql([]);
});
it('should output all the versions between two versions', function () {
versioning.getMigrationVersions('1.0', '1.1').should.eql(['1.1']);
versioning.getMigrationVersions('1.0', '1.2').should.eql(['1.1', '1.2']);
versioning.getMigrationVersions('1.2', '1.5').should.eql(['1.3', '1.4', '1.5']);
versioning.getMigrationVersions('2.1', '2.2').should.eql(['2.2']);
});
});
describe('getNewestDatabaseVersion', function () {
it('should return the correct version', function () {
var currentVersion = '1.0';
@ -111,11 +92,7 @@ describe('Versioning', function () {
}).catch(done);
});
// @TODO change this so we handle a non-existent version?
// There is an open bug in Ghost around this:
// https://github.com/TryGhost/Ghost/issues/7345
// I think it is a timing error
it.skip('should throw error if version does not exist', function (done) {
it('should throw error if version does not exist', function (done) {
// Setup
knexMock.schema.hasTable.returns(new Promise.resolve(true));
queryMock.first.returns(new Promise.resolve());
@ -126,6 +103,7 @@ describe('Versioning', function () {
}).catch(function (err) {
should.exist(err);
(err instanceof errors.DatabaseVersionError).should.eql(true);
err.code.should.eql('VERSION_DOES_NOT_EXIST');
knexStub.get.calledTwice.should.be.true();
knexMock.schema.hasTable.calledOnce.should.be.true();
@ -137,12 +115,10 @@ describe('Versioning', function () {
}).catch(done);
});
// @TODO decide on a new scheme for database versioning and update
// how we validate those versions
it.skip('should throw error if version is not a number', function (done) {
it('should throw error if version is not a number', function (done) {
// Setup
knexMock.schema.hasTable.returns(new Promise.resolve(true));
queryMock.first.returns(new Promise.resolve('Eyjafjallajökull'));
queryMock.first.returns(new Promise.resolve({value: 'Eyjafjallajökull'}));
// Execute
versioning.getDatabaseVersion().then(function () {

View File

@ -463,10 +463,6 @@ toDoList = {
* * `perms:obj` - initialise permissions for a particular object type
* * `users:roles` - create a full suite of users, one per role
* @param {Object} toDos
*
* @TODO:
* - key: migrations-kate
* - call migration-runner
*/
getFixtureOps = function getFixtureOps(toDos) {
// default = default fixtures, if it isn't present, init with tables only