2020-11-10 14:15:31 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// eslint-disable-next-line node/no-extraneous-require
|
|
|
|
const _ = require('lodash');
|
2020-11-13 16:48:17 +01:00
|
|
|
// eslint-disable-next-line node/no-extraneous-require
|
|
|
|
const { map, prop } = require('lodash/fp');
|
2020-11-10 14:15:31 +01:00
|
|
|
const modelsUtils = require('../models');
|
|
|
|
const { createAction } = require('./action');
|
|
|
|
const { createEventsManager } = require('./event');
|
2020-11-13 16:48:17 +01:00
|
|
|
const { sanitizeEntity } = require('../../../packages/strapi-utils');
|
2020-11-10 14:15:31 +01:00
|
|
|
|
|
|
|
const events = {
|
|
|
|
MODEL_CREATED: 'model.created',
|
|
|
|
MODEL_DELETED: 'model.deleted',
|
|
|
|
FIXTURES_CREATED: 'fixture.created',
|
|
|
|
};
|
|
|
|
|
2020-11-13 16:48:17 +01:00
|
|
|
const stringifyDates = object =>
|
|
|
|
JSON.parse(
|
|
|
|
JSON.stringify(object, (key, value) => {
|
|
|
|
if (this[key] instanceof Date) {
|
|
|
|
return this[key].toUTCString();
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
const formatFixtures = map(stringifyDates);
|
|
|
|
|
2020-11-10 14:15:31 +01:00
|
|
|
/**
|
|
|
|
* Create a test builder from the given args.
|
|
|
|
*
|
|
|
|
* A builder allows to create resources (content-types, components, fixtures) that can be cleaned-up easily.
|
|
|
|
*
|
|
|
|
* @param options The builder's constructor options
|
|
|
|
* @param options.initialState {State} The builder's initial state
|
|
|
|
*
|
|
|
|
* @example ```
|
|
|
|
* // Setup the builder
|
|
|
|
* const builder = createTestBuilder()
|
|
|
|
* .addContentType(articleModel)
|
|
|
|
* .addComponent(myComponent);
|
|
|
|
*
|
|
|
|
* // Run the build operations
|
|
|
|
* await builder.build();
|
|
|
|
*
|
|
|
|
* // ...
|
|
|
|
*
|
|
|
|
* // Cleanup created resources
|
|
|
|
* await builder.cleanup();
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
const createTestBuilder = (options = {}) => {
|
|
|
|
const { initialState } = options;
|
|
|
|
|
|
|
|
const addAction = (code, ...params) => {
|
2020-11-13 16:48:17 +01:00
|
|
|
const action = createAction(code, ...params).setEmitter(_eventsManager);
|
2020-11-10 14:15:31 +01:00
|
|
|
_state.actions.push(action);
|
2020-11-13 16:48:17 +01:00
|
|
|
return action;
|
2020-11-10 14:15:31 +01:00
|
|
|
};
|
2020-11-13 16:48:17 +01:00
|
|
|
|
|
|
|
const getDefaultState = () => ({
|
|
|
|
actions: [],
|
|
|
|
deleted: [],
|
|
|
|
created: [],
|
|
|
|
fixtures: {},
|
|
|
|
...initialState,
|
|
|
|
});
|
|
|
|
|
|
|
|
const getModelsMap = (type = null) =>
|
|
|
|
_.difference(_state.created, _state.deleted)
|
|
|
|
// Keep the models with the wanted type (all, content-types, or components)
|
|
|
|
.filter(event => _.isNull(type) || _.get(event, 'metadata.type') === type)
|
|
|
|
// Flatten the data property to obtain an array of model
|
|
|
|
.flatMap(_.property('result'))
|
|
|
|
// Transform the array into a map where the key is the model's name
|
|
|
|
.reduce((acc, model) => _.merge(acc, { [model.modelName]: model }), {});
|
|
|
|
|
2020-11-10 14:15:31 +01:00
|
|
|
const _state = getDefaultState();
|
|
|
|
|
2020-11-13 16:48:17 +01:00
|
|
|
const _eventsManager = createEventsManager({ allowedEvents: Object.values(events) });
|
2020-11-10 14:15:31 +01:00
|
|
|
|
2020-11-13 16:48:17 +01:00
|
|
|
_eventsManager
|
2020-11-10 14:15:31 +01:00
|
|
|
.register(events.MODEL_CREATED, event => _state.created.push(event))
|
2020-11-13 16:48:17 +01:00
|
|
|
.register(events.MODEL_DELETED, event => _state.deleted.push(event))
|
|
|
|
.register(events.FIXTURES_CREATED, ({ result }) => {
|
|
|
|
const existingFixtures = _.get(_state.fixtures, result.model, []);
|
|
|
|
_state.fixtures[result.model] = existingFixtures.concat(formatFixtures(result.entries));
|
|
|
|
});
|
2020-11-10 14:15:31 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
get models() {
|
|
|
|
return getModelsMap();
|
|
|
|
},
|
|
|
|
|
|
|
|
get contentTypes() {
|
|
|
|
return getModelsMap('ct');
|
|
|
|
},
|
|
|
|
|
|
|
|
get components() {
|
|
|
|
return getModelsMap('comp');
|
|
|
|
},
|
|
|
|
|
2020-11-13 16:48:17 +01:00
|
|
|
get fixtures() {
|
|
|
|
return _state.fixtures;
|
|
|
|
},
|
|
|
|
|
|
|
|
sanitizedFixtures(strapi) {
|
|
|
|
return _.mapValues(this.fixtures, (value, key) => this.sanitizedFixturesFor(key, strapi));
|
|
|
|
},
|
|
|
|
|
|
|
|
sanitizedFixturesFor(modelName, strapi) {
|
|
|
|
const model = strapi.getModel(modelName);
|
|
|
|
const fixtures = this.fixturesFor(modelName);
|
|
|
|
|
|
|
|
return sanitizeEntity(fixtures, { model });
|
|
|
|
},
|
|
|
|
|
|
|
|
fixturesFor(modelName) {
|
|
|
|
return this.fixtures[modelName];
|
2020-11-10 14:15:31 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
addContentType(contentType) {
|
2020-11-13 16:48:17 +01:00
|
|
|
addAction('ct.create', contentType);
|
|
|
|
return this;
|
2020-11-10 14:15:31 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
addContentTypes(contentTypes, { batch = true } = {}) {
|
|
|
|
addAction(batch ? 'ct.createBatch' : 'ct.createMany', contentTypes);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
addComponent(component) {
|
2020-11-13 16:48:17 +01:00
|
|
|
addAction('comp.create', component);
|
|
|
|
return this;
|
2020-11-10 14:15:31 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
addFixtures(model, entries, { onCreated = null } = {}) {
|
2020-11-13 16:48:17 +01:00
|
|
|
addAction('fixture.create', model, entries, () => this.fixtures);
|
2020-11-10 14:15:31 +01:00
|
|
|
|
|
|
|
if (_.isFunction(onCreated)) {
|
2020-11-13 16:48:17 +01:00
|
|
|
_eventsManager.register(events.FIXTURES_CREATED, e => {
|
2020-11-10 14:15:31 +01:00
|
|
|
const { result } = e;
|
|
|
|
|
|
|
|
// Filters fixture.created events triggered for other models
|
|
|
|
if (result.model === model) {
|
2020-11-13 16:48:17 +01:00
|
|
|
onCreated(formatFixtures(result.entries));
|
2020-11-10 14:15:31 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute every registered step
|
|
|
|
*/
|
|
|
|
async build() {
|
|
|
|
for (const action of _state.actions) {
|
|
|
|
await action.execute();
|
|
|
|
}
|
2020-11-13 16:48:17 +01:00
|
|
|
return this;
|
2020-11-10 14:15:31 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleanup and delete every model (content-types / components) created by the builder
|
|
|
|
*/
|
|
|
|
async cleanup() {
|
|
|
|
// The first model to be created should be the last one deleted
|
|
|
|
const createdEvents = _state.created.reverse();
|
|
|
|
|
|
|
|
for (const event of createdEvents) {
|
2020-11-13 16:48:17 +01:00
|
|
|
const {
|
|
|
|
result,
|
|
|
|
metadata: { type },
|
|
|
|
} = event;
|
2020-11-10 14:15:31 +01:00
|
|
|
|
|
|
|
// Helpers for the given model
|
|
|
|
const isBatchOperation = _.isArray(result);
|
|
|
|
const modelType = type === 'ct' ? 'ContentType' : 'Component';
|
|
|
|
const pluralSuffix = isBatchOperation ? 's' : '';
|
|
|
|
|
|
|
|
// Pluralized & Type related methods name
|
|
|
|
const cleanupMethod = `cleanupModel${pluralSuffix}`;
|
2020-11-13 16:48:17 +01:00
|
|
|
const deleteMethod = `delete${modelType}${pluralSuffix}`;
|
2020-11-10 14:15:31 +01:00
|
|
|
|
2020-11-13 16:48:17 +01:00
|
|
|
// Format params
|
|
|
|
const uidAttribute = type === 'ct' ? 'modelName' : 'uid';
|
|
|
|
const params = isBatchOperation
|
|
|
|
? result.map(prop(uidAttribute))
|
|
|
|
: prop(uidAttribute, result);
|
2020-11-10 14:15:31 +01:00
|
|
|
|
2020-11-13 16:48:17 +01:00
|
|
|
// Execute cleanup & delete operations
|
2020-11-10 14:15:31 +01:00
|
|
|
await modelsUtils[cleanupMethod](params);
|
|
|
|
await modelsUtils[deleteMethod](params);
|
|
|
|
|
|
|
|
// Notify the builder that the created model has been deleted
|
2020-11-13 16:48:17 +01:00
|
|
|
_eventsManager.emit(events.MODEL_DELETED, event);
|
2020-11-10 14:15:31 +01:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
};
|
2020-11-13 16:48:17 +01:00
|
|
|
};
|
2020-11-10 14:15:31 +01:00
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
createTestBuilder,
|
|
|
|
};
|