0
Fork 0
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:
Hannah Wolfe 2021-10-20 19:10:21 +01:00
parent 458f5b894b
commit 426c8bf918
No known key found for this signature in database
GPG key ID: 9F8C7532D0A6BA55
14 changed files with 1277 additions and 392 deletions

View file

@ -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);
};

View file

@ -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: {

View file

@ -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));

View file

@ -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: {

View file

@ -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);
};

View file

@ -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);
};

View file

@ -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) => {

View file

@ -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));

View 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;

View file

@ -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);

View file

@ -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
};

View file

@ -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'});

View file

@ -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) {

File diff suppressed because one or more lines are too long