mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
Refactored fixtures to be loaded by fixture manager
refs: https://github.com/TryGhost/Toolbox/issues/133 - instead of just a collection of utils, we now have a class that manages fixtures - this should allow us to change the path to fixtures, e.g. between prod/dev and test, so that different fixtures can be loaded by default - also makes it easier to test the fixture manager code itself
This commit is contained in:
parent
458f5b894b
commit
426c8bf918
14 changed files with 1277 additions and 392 deletions
|
@ -1,26 +1,9 @@
|
|||
const Promise = require('bluebird');
|
||||
const _ = require('lodash');
|
||||
const fixtures = require('../../schema/fixtures');
|
||||
const logging = require('@tryghost/logging');
|
||||
const {fixtureManager} = require('../../schema/fixtures');
|
||||
|
||||
module.exports.config = {
|
||||
transaction: true
|
||||
};
|
||||
|
||||
module.exports.up = async (options) => {
|
||||
const localOptions = _.merge({
|
||||
context: {internal: true},
|
||||
migrating: true
|
||||
}, options);
|
||||
|
||||
await Promise.mapSeries(fixtures.models, async (model) => {
|
||||
logging.info('Model: ' + model.name);
|
||||
|
||||
await fixtures.utils.addFixturesForModel(model, localOptions);
|
||||
});
|
||||
|
||||
await Promise.mapSeries(fixtures.relations, async (relation) => {
|
||||
logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
|
||||
await fixtures.utils.addFixturesForRelation(relation, localOptions);
|
||||
});
|
||||
module.exports.up = async function insertFixtures(options) {
|
||||
return await fixtureManager.addAllFixtures(options);
|
||||
};
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
const merge = require('lodash/merge');
|
||||
const utils = require('../../../schema/fixtures/utils');
|
||||
const {fixtureManager} = require('../../../schema/fixtures');
|
||||
const models = require('../../../../models');
|
||||
const permissions = require('../../../../services/permissions');
|
||||
const logging = require('@tryghost/logging');
|
||||
const _private = {};
|
||||
|
||||
_private.addRole = function addRole(options) {
|
||||
const contributorRole = utils.findModelFixtureEntry('Role', {name: 'Contributor'});
|
||||
const contributorRole = fixtureManager.findModelFixtureEntry('Role', {name: 'Contributor'});
|
||||
const message = 'Adding "Contributor" role to roles table';
|
||||
|
||||
return models.Role.findOne({name: contributorRole.name}, options)
|
||||
.then((role) => {
|
||||
if (!role) {
|
||||
logging.info(message);
|
||||
return utils.addFixturesForModel({name: 'Role', entries: [contributorRole]}, options);
|
||||
return fixtureManager.addFixturesForModel({name: 'Role', entries: [contributorRole]}, options);
|
||||
}
|
||||
|
||||
logging.warn(message);
|
||||
|
@ -22,10 +22,10 @@ _private.addRole = function addRole(options) {
|
|||
};
|
||||
|
||||
_private.addContributorPermissions = function getPermissions(options) {
|
||||
const relations = utils.findRelationFixture('Role', 'Permission');
|
||||
const relations = fixtureManager.findRelationFixture('Role', 'Permission');
|
||||
const message = 'Adding permissions_roles fixtures for the contributor role';
|
||||
|
||||
return utils.addFixturesForRelation({
|
||||
return fixtureManager.addFixturesForRelation({
|
||||
from: relations.from,
|
||||
to: relations.to,
|
||||
entries: {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const logging = require('@tryghost/logging');
|
||||
const merge = require('lodash/merge');
|
||||
const models = require('../../../../models');
|
||||
const utils = require('../../../schema/fixtures/utils');
|
||||
const {fixtureManager} = require('../../../schema/fixtures');
|
||||
|
||||
const _private = {};
|
||||
|
||||
|
@ -15,12 +15,12 @@ _private.printResult = function printResult(result, message) {
|
|||
|
||||
_private.addZapierIntegration = (options) => {
|
||||
const message = 'Adding "Zapier" integration';
|
||||
const fixtureIntegration = utils.findModelFixtureEntry('Integration', {slug: 'zapier'});
|
||||
const fixtureIntegration = fixtureManager.findModelFixtureEntry('Integration', {slug: 'zapier'});
|
||||
|
||||
return models.Integration.findOne({slug: fixtureIntegration.slug}, options)
|
||||
.then((integration) => {
|
||||
if (!integration) {
|
||||
return utils.addFixturesForModel({
|
||||
return fixtureManager.addFixturesForModel({
|
||||
name: 'Integration',
|
||||
entries: [fixtureIntegration]
|
||||
}, options).then(result => _private.printResult(result, message));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const logging = require('@tryghost/logging');
|
||||
const merge = require('lodash/merge');
|
||||
const models = require('../../../../models');
|
||||
const utils = require('../../../schema/fixtures/utils');
|
||||
const {fixtureManager} = require('../../../schema/fixtures');
|
||||
|
||||
const _private = {};
|
||||
|
||||
|
@ -15,12 +15,12 @@ _private.printResult = function printResult(result, message) {
|
|||
|
||||
_private.addApiKeyRole = (options) => {
|
||||
const message = 'Adding "Admin Integration" role to roles table';
|
||||
const apiKeyRole = utils.findModelFixtureEntry('Role', {name: 'Admin Integration'});
|
||||
const apiKeyRole = fixtureManager.findModelFixtureEntry('Role', {name: 'Admin Integration'});
|
||||
|
||||
return models.Role.findOne({name: apiKeyRole.name}, options)
|
||||
.then((role) => {
|
||||
if (!role) {
|
||||
return utils.addFixturesForModel({
|
||||
return fixtureManager.addFixturesForModel({
|
||||
name: 'Role',
|
||||
entries: [apiKeyRole]
|
||||
}, options).then(result => _private.printResult(result, message));
|
||||
|
@ -32,9 +32,9 @@ _private.addApiKeyRole = (options) => {
|
|||
|
||||
_private.addApiKeyPermissions = (options) => {
|
||||
const message = 'Adding permissions for the "Admin Integration" role';
|
||||
const relations = utils.findRelationFixture('Role', 'Permission');
|
||||
const relations = fixtureManager.findRelationFixture('Role', 'Permission');
|
||||
|
||||
return utils.addFixturesForRelation({
|
||||
return fixtureManager.addFixturesForRelation({
|
||||
from: relations.from,
|
||||
to: relations.to,
|
||||
entries: {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const logging = require('@tryghost/logging');
|
||||
const merge = require('lodash/merge');
|
||||
const models = require('../../../../models');
|
||||
const utils = require('../../../schema/fixtures/utils');
|
||||
const {fixtureManager} = require('../../../schema/fixtures');
|
||||
|
||||
const _private = {};
|
||||
|
||||
|
@ -15,12 +15,12 @@ _private.printResult = function printResult(result, message) {
|
|||
|
||||
_private.addApiKeyRole = (options) => {
|
||||
const message = 'Adding "DB Backup Integration" role to roles table';
|
||||
const apiKeyRole = utils.findModelFixtureEntry('Role', {name: 'DB Backup Integration'});
|
||||
const apiKeyRole = fixtureManager.findModelFixtureEntry('Role', {name: 'DB Backup Integration'});
|
||||
|
||||
return models.Role.findOne({name: apiKeyRole.name}, options)
|
||||
.then((role) => {
|
||||
if (!role) {
|
||||
return utils.addFixturesForModel({
|
||||
return fixtureManager.addFixturesForModel({
|
||||
name: 'Role',
|
||||
entries: [apiKeyRole]
|
||||
}, options).then(result => _private.printResult(result, message));
|
||||
|
@ -32,9 +32,9 @@ _private.addApiKeyRole = (options) => {
|
|||
|
||||
_private.addApiKeyPermissions = (options) => {
|
||||
const message = 'Adding permissions for the "DB Backup Integration" role';
|
||||
const relations = utils.findRelationFixture('Role', 'Permission');
|
||||
const relations = fixtureManager.findRelationFixture('Role', 'Permission');
|
||||
|
||||
return utils.addFixturesForRelation({
|
||||
return fixtureManager.addFixturesForRelation({
|
||||
from: relations.from,
|
||||
to: relations.to,
|
||||
entries: {
|
||||
|
@ -81,4 +81,3 @@ module.exports.down = (options) => {
|
|||
|
||||
return _private.removeApiKeyPermissionsAndRole(localOptions);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const logging = require('@tryghost/logging');
|
||||
const merge = require('lodash/merge');
|
||||
const models = require('../../../../models');
|
||||
const utils = require('../../../schema/fixtures/utils');
|
||||
const {fixtureManager} = require('../../../schema/fixtures');
|
||||
|
||||
const _private = {};
|
||||
|
||||
|
@ -15,12 +15,12 @@ _private.printResult = function printResult(result, message) {
|
|||
|
||||
_private.addGhostBackupIntegration = (options) => {
|
||||
const message = 'Adding "Ghost Backup DB" integration';
|
||||
const fixtureIntegration = utils.findModelFixtureEntry('Integration', {slug: 'ghost-backup'});
|
||||
const fixtureIntegration = fixtureManager.findModelFixtureEntry('Integration', {slug: 'ghost-backup'});
|
||||
|
||||
return models.Integration.findOne({slug: fixtureIntegration.slug}, options)
|
||||
.then((integration) => {
|
||||
if (!integration) {
|
||||
return utils.addFixturesForModel({
|
||||
return fixtureManager.addFixturesForModel({
|
||||
name: 'Integration',
|
||||
entries: [fixtureIntegration]
|
||||
}, options).then(result => _private.printResult(result, message));
|
||||
|
@ -67,4 +67,3 @@ module.exports.down = (options) => {
|
|||
|
||||
return _private.removeGhostBackupIntegration(localOptions);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const logging = require('@tryghost/logging');
|
||||
const merge = require('lodash/merge');
|
||||
const models = require('../../../../models');
|
||||
const utils = require('../../../schema/fixtures/utils');
|
||||
const {fixtureManager} = require('../../../schema/fixtures');
|
||||
|
||||
const resource = 'post';
|
||||
const _private = {};
|
||||
|
@ -16,12 +16,12 @@ _private.printResult = function printResult(result, message) {
|
|||
|
||||
_private.addSchedulerRole = (options) => {
|
||||
const message = 'Adding "Scheduler Integration" role to roles table';
|
||||
const apiKeyRole = utils.findModelFixtureEntry('Role', {name: 'Scheduler Integration'});
|
||||
const apiKeyRole = fixtureManager.findModelFixtureEntry('Role', {name: 'Scheduler Integration'});
|
||||
|
||||
return models.Role.findOne({name: apiKeyRole.name}, options)
|
||||
.then((role) => {
|
||||
if (!role) {
|
||||
return utils.addFixturesForModel({
|
||||
return fixtureManager.addFixturesForModel({
|
||||
name: 'Role',
|
||||
entries: [apiKeyRole]
|
||||
}, options).then(result => _private.printResult(result, message));
|
||||
|
@ -32,19 +32,19 @@ _private.addSchedulerRole = (options) => {
|
|||
};
|
||||
|
||||
_private.addPublishPermission = (options) => {
|
||||
const modelToAdd = utils.findModelFixtures('Permission', {object_type: resource, action_type: 'publish'});
|
||||
const modelToAdd = fixtureManager.findModelFixtures('Permission', {object_type: resource, action_type: 'publish'});
|
||||
|
||||
return utils.addFixturesForModel(modelToAdd, options)
|
||||
return fixtureManager.addFixturesForModel(modelToAdd, options)
|
||||
.then(result => _private.printResult(result, `Adding "publish" permissions fixtures for ${resource}s`));
|
||||
};
|
||||
|
||||
_private.removeApiKeyPermissionsAndRole = (options) => {
|
||||
const message = 'Rollback: Removing "Scheduler Integration" role and permissions';
|
||||
|
||||
const modelToRemove = utils.findModelFixtures('Permission', {object_type: resource, action_type: 'publish'});
|
||||
const modelToRemove = fixtureManager.findModelFixtures('Permission', {object_type: resource, action_type: 'publish'});
|
||||
|
||||
// permission model automatically cleans up permissions_roles on .destroy()
|
||||
return utils.removeFixturesForModel(modelToRemove, options)
|
||||
return fixtureManager.removeFixturesForModel(modelToRemove, options)
|
||||
.then(result => _private.printResult(result, `Removing "publish" permissions fixtures for ${resource}s`))
|
||||
.then(() => models.Role.findOne({name: 'Scheduler Integration'}, options))
|
||||
.then((role) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const logging = require('@tryghost/logging');
|
||||
const merge = require('lodash/merge');
|
||||
const models = require('../../../../models');
|
||||
const utils = require('../../../schema/fixtures/utils');
|
||||
const {fixtureManager} = require('../../../schema/fixtures');
|
||||
|
||||
const _private = {};
|
||||
|
||||
|
@ -15,12 +15,12 @@ _private.printResult = function printResult(result, message) {
|
|||
|
||||
_private.addGhostSchedulerIntegration = (options) => {
|
||||
const message = 'Adding "Ghost Scheduler" integration';
|
||||
const fixtureIntegration = utils.findModelFixtureEntry('Integration', {slug: 'ghost-scheduler'});
|
||||
const fixtureIntegration = fixtureManager.findModelFixtureEntry('Integration', {slug: 'ghost-scheduler'});
|
||||
|
||||
return models.Integration.findOne({slug: fixtureIntegration.slug}, options)
|
||||
.then((integration) => {
|
||||
if (!integration) {
|
||||
return utils.addFixturesForModel({
|
||||
return fixtureManager.addFixturesForModel({
|
||||
name: 'Integration',
|
||||
entries: [fixtureIntegration]
|
||||
}, options).then(result => _private.printResult(result, message));
|
||||
|
|
340
core/server/data/schema/fixtures/fixture-manager.js
Normal file
340
core/server/data/schema/fixtures/fixture-manager.js
Normal file
|
@ -0,0 +1,340 @@
|
|||
const _ = require('lodash');
|
||||
const Promise = require('bluebird');
|
||||
const logging = require('@tryghost/logging');
|
||||
const {sequence} = require('@tryghost/promise');
|
||||
|
||||
const models = require('../../../models');
|
||||
const baseUtils = require('../../../models/base/utils');
|
||||
|
||||
const moment = require('moment');
|
||||
|
||||
class FixtureManager {
|
||||
constructor(fixtures) {
|
||||
this.fixtures = fixtures;
|
||||
}
|
||||
|
||||
/**
|
||||
* ### 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
|
||||
*/
|
||||
static matchFunc(match, key, value) {
|
||||
if (_.isArray(match)) {
|
||||
return function (item) {
|
||||
let 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;
|
||||
};
|
||||
}
|
||||
|
||||
static matchObj(match, item) {
|
||||
const matchedObj = {};
|
||||
|
||||
if (_.isArray(match)) {
|
||||
_.each(match, (matchProp) => {
|
||||
matchedObj[matchProp] = item.get(matchProp);
|
||||
});
|
||||
} else {
|
||||
matchedObj[match] = item.get(match);
|
||||
}
|
||||
|
||||
return matchedObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add All Fixtures
|
||||
*
|
||||
* Helper method to handle adding all fixtures
|
||||
*
|
||||
* @param {object} options
|
||||
* @returns
|
||||
*/
|
||||
async addAllFixtures(options) {
|
||||
const localOptions = _.merge({
|
||||
context: {internal: true},
|
||||
migrating: true
|
||||
}, options);
|
||||
|
||||
await Promise.mapSeries(this.fixtures.models, (model) => {
|
||||
logging.info('Model: ' + model.name);
|
||||
|
||||
return this.addFixturesForModel(model, localOptions);
|
||||
});
|
||||
|
||||
await Promise.mapSeries(this.fixtures.relations, (relation) => {
|
||||
logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
|
||||
return this.addFixturesForRelation(relation, localOptions);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Find methods - use the local fixtures
|
||||
*/
|
||||
|
||||
/**
|
||||
* ### Find Model Fixture
|
||||
* Finds a model fixture based on model name
|
||||
* @api private
|
||||
* @param {String} modelName
|
||||
* @returns {Object} model fixture
|
||||
*/
|
||||
findModelFixture(modelName) {
|
||||
return _.find(this.fixtures.models, (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(modelName, matchExpr) {
|
||||
return _.find(this.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(modelName, matchExpr) {
|
||||
const foundModel = _.cloneDeep(this.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(from, to) {
|
||||
return _.find(this.fixtures.relations, (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(objName, role) {
|
||||
// Make a copy and delete any entries we don't want
|
||||
const foundRelation = _.cloneDeep(this.findRelationFixture('Role', 'Permission'));
|
||||
|
||||
_.each(foundRelation.entries, (entry, key) => {
|
||||
_.each(entry, (perm, obj) => {
|
||||
if (obj !== objName) {
|
||||
delete entry[obj];
|
||||
}
|
||||
});
|
||||
|
||||
if (_.isEmpty(entry) || (role && role !== key)) {
|
||||
delete foundRelation.entries[key];
|
||||
}
|
||||
});
|
||||
|
||||
return foundRelation;
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* From here down, the methods require access to models
|
||||
* But aren't dependent on this.fixtures
|
||||
******************************************************/
|
||||
|
||||
/**
|
||||
* ### 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(relation, options) {
|
||||
const fromOptions = _.extend({}, options, {withRelated: [relation.from.relation]});
|
||||
|
||||
const 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<any>}
|
||||
*/
|
||||
async addFixturesForModel(modelFixture, options = {}) {
|
||||
// Clone the fixtures as they get changed in this function.
|
||||
// The initial blog posts will be added a `published_at` property, which
|
||||
// would change the fixturesHash.
|
||||
modelFixture = _.cloneDeep(modelFixture);
|
||||
// The Post model fixtures need a `published_at` date, where at least the seconds
|
||||
// are different, otherwise `prev_post` and `next_post` helpers won't workd with
|
||||
// them.
|
||||
if (modelFixture.name === 'Post') {
|
||||
_.forEach(modelFixture.entries, (post, index) => {
|
||||
if (!post.published_at) {
|
||||
post.published_at = moment().add(index, 'seconds');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
||||
let data = {};
|
||||
|
||||
// CASE: if id is specified, only query by id
|
||||
if (entry.id) {
|
||||
data.id = entry.id;
|
||||
} else if (entry.slug) {
|
||||
data.slug = entry.slug;
|
||||
} else {
|
||||
data = _.cloneDeep(entry);
|
||||
}
|
||||
|
||||
if (modelFixture.name === 'Post') {
|
||||
data.status = 'all';
|
||||
}
|
||||
|
||||
const found = await models[modelFixture.name].findOne(data, options);
|
||||
if (!found) {
|
||||
return models[modelFixture.name].add(entry, options);
|
||||
}
|
||||
});
|
||||
|
||||
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<any>}
|
||||
*/
|
||||
async addFixturesForRelation(relationFixture, options) {
|
||||
const ops = [];
|
||||
let max = 0;
|
||||
|
||||
const data = await this.fetchRelationData(relationFixture, options);
|
||||
|
||||
_.each(relationFixture.entries, (entry, key) => {
|
||||
const fromItem = data.from.find(FixtureManager.matchFunc(relationFixture.from.match, key));
|
||||
|
||||
// CASE: You add new fixtures e.g. a new role in a new release.
|
||||
// As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
|
||||
// permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
|
||||
if (!fromItem) {
|
||||
logging.warn('Skip: Target database entry not found for key: ' + key);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
_.each(entry, (value, entryKey) => {
|
||||
let toItems = data.to.filter(FixtureManager.matchFunc(relationFixture.to.match, entryKey, value));
|
||||
max += toItems.length;
|
||||
|
||||
// Remove any duplicates that already exist in the collection
|
||||
toItems = _.reject(toItems, (item) => {
|
||||
return fromItem
|
||||
.related(relationFixture.from.relation)
|
||||
.find((model) => {
|
||||
const objectToMatch = FixtureManager.matchObj(relationFixture.to.match, item);
|
||||
return Object.keys(objectToMatch).every((keyToCheck) => {
|
||||
return model.get(keyToCheck) === objectToMatch[keyToCheck];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const result = await sequence(ops);
|
||||
return {expected: max, done: _(result).map('length').sum()};
|
||||
}
|
||||
|
||||
async removeFixturesForModel(modelFixture, options) {
|
||||
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
||||
const found = models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options);
|
||||
if (found) {
|
||||
return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
|
||||
}
|
||||
});
|
||||
|
||||
return {expected: modelFixture.entries.length, done: results.length};
|
||||
}
|
||||
|
||||
async removeFixturesForRelation(relationFixture, options) {
|
||||
const data = await this.fetchRelationData(relationFixture, options);
|
||||
const ops = [];
|
||||
|
||||
_.each(relationFixture.entries, (entry, key) => {
|
||||
const fromItem = data.from.find(FixtureManager.matchFunc(relationFixture.from.match, key));
|
||||
|
||||
_.each(entry, (value, entryKey) => {
|
||||
const toItems = data.to.filter(FixtureManager.matchFunc(relationFixture.to.match, entryKey, value));
|
||||
|
||||
if (toItems && toItems.length > 0) {
|
||||
ops.push(function detachRelation() {
|
||||
return baseUtils.detach(
|
||||
models[relationFixture.from.Model || relationFixture.from.model],
|
||||
fromItem.id,
|
||||
relationFixture.from.relation,
|
||||
toItems,
|
||||
options
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return await sequence(ops);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FixtureManager;
|
|
@ -1,2 +1,5 @@
|
|||
module.exports = require('./fixtures');
|
||||
module.exports.utils = require('./utils');
|
||||
const FixtureManager = require('./fixture-manager');
|
||||
const fixtures = require('./fixtures');
|
||||
|
||||
module.exports.FixtureManager = FixtureManager;
|
||||
module.exports.fixtureManager = new FixtureManager(fixtures);
|
||||
|
|
|
@ -1,321 +0,0 @@
|
|||
// # Fixture Utils
|
||||
// Standalone file which can be required to help with advanced operations on the fixtures.json file
|
||||
const _ = require('lodash');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
const logging = require('@tryghost/logging');
|
||||
const models = require('../../../models');
|
||||
const baseUtils = require('../../../models/base/utils');
|
||||
const {sequence} = require('@tryghost/promise');
|
||||
const moment = require('moment');
|
||||
const fixtures = require('./fixtures');
|
||||
|
||||
/**
|
||||
* ### 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
|
||||
*/
|
||||
const matchFunc = function matchFunc(match, key, value) {
|
||||
if (_.isArray(match)) {
|
||||
return function (item) {
|
||||
let 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;
|
||||
};
|
||||
};
|
||||
|
||||
const matchObj = function matchObj(match, item) {
|
||||
const matchedObj = {};
|
||||
|
||||
if (_.isArray(match)) {
|
||||
_.each(match, (matchProp) => {
|
||||
matchedObj[matchProp] = item.get(matchProp);
|
||||
});
|
||||
} else {
|
||||
matchedObj[match] = item.get(match);
|
||||
}
|
||||
|
||||
return matchedObj;
|
||||
};
|
||||
|
||||
/**
|
||||
* ### 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<*>}
|
||||
*/
|
||||
const fetchRelationData = function fetchRelationData(relation, options) {
|
||||
const fromOptions = _.extend({}, options, {withRelated: [relation.from.relation]});
|
||||
|
||||
const props = {
|
||||
from: models[relation.from.model].findAll(fromOptions),
|
||||
to: models[relation.to.model].findAll(options)
|
||||
};
|
||||
|
||||
return Promise.props(props);
|
||||
};
|
||||
|
||||
/*
|
||||
* Find methods - use the local fixtures
|
||||
*/
|
||||
|
||||
/**
|
||||
* ### Find Model Fixture
|
||||
* Finds a model fixture based on model name
|
||||
* @api private
|
||||
* @param {String} modelName
|
||||
* @returns {Object} model fixture
|
||||
*/
|
||||
const findModelFixture = function findModelFixture(modelName) {
|
||||
return _.find(fixtures.models, (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
|
||||
*/
|
||||
const 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
|
||||
*/
|
||||
const findModelFixtures = function findModelFixtures(modelName, matchExpr) {
|
||||
const 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
|
||||
*/
|
||||
const findRelationFixture = function findRelationFixture(from, to) {
|
||||
return _.find(fixtures.relations, (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
|
||||
*/
|
||||
const findPermissionRelationsForObject = function findPermissionRelationsForObject(objName, role) {
|
||||
// Make a copy and delete any entries we don't want
|
||||
const foundRelation = _.cloneDeep(findRelationFixture('Role', 'Permission'));
|
||||
|
||||
_.each(foundRelation.entries, (entry, key) => {
|
||||
_.each(entry, (perm, obj) => {
|
||||
if (obj !== objName) {
|
||||
delete entry[obj];
|
||||
}
|
||||
});
|
||||
|
||||
if (_.isEmpty(entry) || (role && role !== key)) {
|
||||
delete foundRelation.entries[key];
|
||||
}
|
||||
});
|
||||
|
||||
return foundRelation;
|
||||
};
|
||||
|
||||
/*
|
||||
* Add and Remove Functions, require access to models
|
||||
*/
|
||||
|
||||
/**
|
||||
* ### 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<any>}
|
||||
*/
|
||||
const addFixturesForModel = async function addFixturesForModel(modelFixture, options = {}) {
|
||||
// Clone the fixtures as they get changed in this function.
|
||||
// The initial blog posts will be added a `published_at` property, which
|
||||
// would change the fixturesHash.
|
||||
modelFixture = _.cloneDeep(modelFixture);
|
||||
// The Post model fixtures need a `published_at` date, where at least the seconds
|
||||
// are different, otherwise `prev_post` and `next_post` helpers won't workd with
|
||||
// them.
|
||||
if (modelFixture.name === 'Post') {
|
||||
_.forEach(modelFixture.entries, (post, index) => {
|
||||
if (!post.published_at) {
|
||||
post.published_at = moment().add(index, 'seconds');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
||||
let data = {};
|
||||
|
||||
// CASE: if id is specified, only query by id
|
||||
if (entry.id) {
|
||||
data.id = entry.id;
|
||||
} else if (entry.slug) {
|
||||
data.slug = entry.slug;
|
||||
} else {
|
||||
data = _.cloneDeep(entry);
|
||||
}
|
||||
|
||||
if (modelFixture.name === 'Post') {
|
||||
data.status = 'all';
|
||||
}
|
||||
|
||||
const found = await models[modelFixture.name].findOne(data, options);
|
||||
if (!found) {
|
||||
return models[modelFixture.name].add(entry, options);
|
||||
}
|
||||
});
|
||||
|
||||
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<any>}
|
||||
*/
|
||||
const addFixturesForRelation = async function addFixturesForRelation(relationFixture, options) {
|
||||
const ops = [];
|
||||
let max = 0;
|
||||
|
||||
const data = await fetchRelationData(relationFixture, options);
|
||||
|
||||
_.each(relationFixture.entries, (entry, key) => {
|
||||
const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
|
||||
|
||||
// CASE: You add new fixtures e.g. a new role in a new release.
|
||||
// As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
|
||||
// permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
|
||||
if (!fromItem) {
|
||||
logging.warn('Skip: Target database entry not found for key: ' + key);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
_.each(entry, (value, entryKey) => {
|
||||
let toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
|
||||
max += toItems.length;
|
||||
|
||||
// Remove any duplicates that already exist in the collection
|
||||
toItems = _.reject(toItems, (item) => {
|
||||
return fromItem
|
||||
.related(relationFixture.from.relation)
|
||||
.find((model) => {
|
||||
const objectToMatch = matchObj(relationFixture.to.match, item);
|
||||
return Object.keys(objectToMatch).every((keyToCheck) => {
|
||||
return model.get(keyToCheck) === objectToMatch[keyToCheck];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const result = await sequence(ops);
|
||||
return {expected: max, done: _(result).map('length').sum()};
|
||||
};
|
||||
|
||||
const removeFixturesForModel = async function removeFixturesForModel(modelFixture, options) {
|
||||
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
||||
const found = models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options);
|
||||
if (found) {
|
||||
return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
|
||||
}
|
||||
});
|
||||
|
||||
return {expected: modelFixture.entries.length, done: results.length};
|
||||
};
|
||||
|
||||
const removeFixturesForRelation = async function removeFixturesForRelation(relationFixture, options) {
|
||||
const data = await fetchRelationData(relationFixture, options);
|
||||
const ops = [];
|
||||
|
||||
_.each(relationFixture.entries, (entry, key) => {
|
||||
const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
|
||||
|
||||
_.each(entry, (value, entryKey) => {
|
||||
const toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
|
||||
|
||||
if (toItems && toItems.length > 0) {
|
||||
ops.push(function detachRelation() {
|
||||
return baseUtils.detach(
|
||||
models[relationFixture.from.Model || relationFixture.from.model],
|
||||
fromItem.id,
|
||||
relationFixture.from.relation,
|
||||
toItems,
|
||||
options
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return await sequence(ops);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// find
|
||||
findModelFixtureEntry: findModelFixtureEntry,
|
||||
findModelFixtures: findModelFixtures,
|
||||
findRelationFixture: findRelationFixture,
|
||||
findPermissionRelationsForObject: findPermissionRelationsForObject,
|
||||
|
||||
// add / remove
|
||||
addFixturesForModel: addFixturesForModel,
|
||||
addFixturesForRelation: addFixturesForRelation,
|
||||
removeFixturesForModel: removeFixturesForModel,
|
||||
removeFixturesForRelation: removeFixturesForRelation
|
||||
};
|
|
@ -1,11 +1,13 @@
|
|||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const Promise = require('bluebird');
|
||||
const rewire = require('rewire');
|
||||
|
||||
const models = require('../../../../../../core/server/models');
|
||||
const baseUtils = require('../../../../../../core/server/models/base/utils');
|
||||
const fixtureUtils = rewire('../../../../../../core/server/data/schema/fixtures/utils');
|
||||
const fixtures = require('../../../../../../core/server/data/schema/fixtures/fixtures');
|
||||
const {FixtureManager} = require('../../../../../../core/server/data/schema/fixtures');
|
||||
const fixtures = require('../../../../../utils/fixtures/fixtures.json');
|
||||
|
||||
const fixtureManager = new FixtureManager(fixtures);
|
||||
|
||||
describe('Migration Fixture Utils', function () {
|
||||
let loggerStub;
|
||||
|
@ -24,7 +26,7 @@ describe('Migration Fixture Utils', function () {
|
|||
});
|
||||
|
||||
describe('Match Func', function () {
|
||||
const matchFunc = fixtureUtils.__get__('matchFunc');
|
||||
const matchFunc = FixtureManager.matchFunc;
|
||||
let getStub;
|
||||
|
||||
beforeEach(function () {
|
||||
|
@ -104,7 +106,7 @@ describe('Migration Fixture Utils', function () {
|
|||
return modelFixture.name === 'Post';
|
||||
});
|
||||
|
||||
fixtureUtils.addFixturesForModel(postFixtures).then(function (result) {
|
||||
fixtureManager.addFixturesForModel(postFixtures).then(function (result) {
|
||||
should.exist(result);
|
||||
result.should.be.an.Object();
|
||||
result.should.have.property('expected', 11);
|
||||
|
@ -125,7 +127,7 @@ describe('Migration Fixture Utils', function () {
|
|||
return modelFixture.name === 'Post';
|
||||
});
|
||||
|
||||
fixtureUtils.addFixturesForModel(postFixtures).then(function (result) {
|
||||
fixtureManager.addFixturesForModel(postFixtures).then(function (result) {
|
||||
should.exist(result);
|
||||
result.should.be.an.Object();
|
||||
result.should.have.property('expected', 11);
|
||||
|
@ -157,7 +159,7 @@ describe('Migration Fixture Utils', function () {
|
|||
const permsAllStub = sinon.stub(models.Permission, 'findAll').returns(Promise.resolve(dataMethodStub));
|
||||
const rolesAllStub = sinon.stub(models.Role, 'findAll').returns(Promise.resolve(dataMethodStub));
|
||||
|
||||
fixtureUtils.addFixturesForRelation(fixtures.relations[0]).then(function (result) {
|
||||
fixtureManager.addFixturesForRelation(fixtures.relations[0]).then(function (result) {
|
||||
should.exist(result);
|
||||
result.should.be.an.Object();
|
||||
result.should.have.property('expected', 82);
|
||||
|
@ -194,7 +196,7 @@ describe('Migration Fixture Utils', function () {
|
|||
const postsAllStub = sinon.stub(models.Post, 'findAll').returns(Promise.resolve(dataMethodStub));
|
||||
const tagsAllStub = sinon.stub(models.Tag, 'findAll').returns(Promise.resolve(dataMethodStub));
|
||||
|
||||
fixtureUtils.addFixturesForRelation(fixtures.relations[1]).then(function (result) {
|
||||
fixtureManager.addFixturesForRelation(fixtures.relations[1]).then(function (result) {
|
||||
should.exist(result);
|
||||
result.should.be.an.Object();
|
||||
result.should.have.property('expected', 7);
|
||||
|
@ -231,7 +233,7 @@ describe('Migration Fixture Utils', function () {
|
|||
const postsAllStub = sinon.stub(models.Post, 'findAll').returns(Promise.resolve(dataMethodStub));
|
||||
const tagsAllStub = sinon.stub(models.Tag, 'findAll').returns(Promise.resolve(dataMethodStub));
|
||||
|
||||
fixtureUtils.addFixturesForRelation(fixtures.relations[1]).then(function (result) {
|
||||
fixtureManager.addFixturesForRelation(fixtures.relations[1]).then(function (result) {
|
||||
should.exist(result);
|
||||
result.should.be.an.Object();
|
||||
result.should.have.property('expected', 7);
|
||||
|
@ -256,7 +258,7 @@ describe('Migration Fixture Utils', function () {
|
|||
|
||||
describe('findModelFixtureEntry', function () {
|
||||
it('should fetch a single fixture entry', function () {
|
||||
const foundFixture = fixtureUtils.findModelFixtureEntry('Integration', {slug: 'zapier'});
|
||||
const foundFixture = fixtureManager.findModelFixtureEntry('Integration', {slug: 'zapier'});
|
||||
foundFixture.should.be.an.Object();
|
||||
foundFixture.should.eql({
|
||||
slug: 'zapier',
|
||||
|
@ -270,7 +272,7 @@ describe('Migration Fixture Utils', function () {
|
|||
|
||||
describe('findModelFixtures', function () {
|
||||
it('should fetch a fixture with multiple entries', function () {
|
||||
const foundFixture = fixtureUtils.findModelFixtures('Permission', {object_type: 'db'});
|
||||
const foundFixture = fixtureManager.findModelFixtures('Permission', {object_type: 'db'});
|
||||
foundFixture.should.be.an.Object();
|
||||
foundFixture.entries.should.be.an.Array().with.lengthOf(4);
|
||||
foundFixture.entries[0].should.eql({
|
||||
|
@ -288,7 +290,7 @@ describe('Migration Fixture Utils', function () {
|
|||
|
||||
describe('findPermissionRelationsForObject', function () {
|
||||
it('should fetch a fixture with multiple entries', function () {
|
||||
const foundFixture = fixtureUtils.findPermissionRelationsForObject('db');
|
||||
const foundFixture = fixtureManager.findPermissionRelationsForObject('db');
|
||||
foundFixture.should.be.an.Object();
|
||||
foundFixture.entries.should.be.an.Object();
|
||||
foundFixture.entries.should.have.property('Administrator', {db: 'all'});
|
|
@ -10,7 +10,7 @@ const knexMigrator = new KnexMigrator();
|
|||
|
||||
// Ghost Internals
|
||||
const models = require('../../core/server/models');
|
||||
const fixtureUtils = require('../../core/server/data/schema/fixtures/utils');
|
||||
const {fixtureManager} = require('../../core/server/data/schema/fixtures');
|
||||
const emailAnalyticsService = require('../../core/server/services/email-analytics');
|
||||
const permissions = require('../../core/server/services/permissions');
|
||||
const settingsService = require('../../core/server/services/settings');
|
||||
|
@ -370,8 +370,8 @@ const fixtures = {
|
|||
},
|
||||
|
||||
permissionsFor: function permissionsFor(obj) {
|
||||
let permsToInsert = _.cloneDeep(fixtureUtils.findModelFixtures('Permission', {object_type: obj}).entries);
|
||||
const permsRolesToInsert = fixtureUtils.findPermissionRelationsForObject(obj).entries;
|
||||
let permsToInsert = _.cloneDeep(fixtureManager.findModelFixtures('Permission', {object_type: obj}).entries);
|
||||
const permsRolesToInsert = fixtureManager.findPermissionRelationsForObject(obj).entries;
|
||||
const actions = [];
|
||||
const permissionsRoles = {};
|
||||
|
||||
|
@ -461,7 +461,7 @@ const fixtures = {
|
|||
return Promise.map(DataGenerator.forKnex.labels, function (label) {
|
||||
return models.Label.add(label, context.internal);
|
||||
}).then(function () {
|
||||
let productsToInsert = fixtureUtils.findModelFixtures('Product').entries;
|
||||
let productsToInsert = fixtureManager.findModelFixtures('Product').entries;
|
||||
return Promise.map(productsToInsert, async (product) => {
|
||||
const found = await models.Product.findOne(product, context.internal);
|
||||
if (!found) {
|
||||
|
|
880
test/utils/fixtures/fixtures.json
Normal file
880
test/utils/fixtures/fixtures.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue