208 lines
5.7 KiB
JavaScript
Raw Normal View History

'use strict';
// eslint-disable-next-line node/no-extraneous-require
const _ = require('lodash');
// eslint-disable-next-line node/no-extraneous-require
const { map, prop } = require('lodash/fp');
const modelsUtils = require('../models');
const { createAction } = require('./action');
const { createEventsManager } = require('./event');
const { sanitizeEntity } = require('../../../packages/strapi-utils');
const events = {
MODEL_CREATED: 'model.created',
MODEL_DELETED: 'model.deleted',
FIXTURES_CREATED: 'fixture.created',
};
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);
/**
* 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) => {
const action = createAction(code, ...params).setEmitter(_eventsManager);
_state.actions.push(action);
return action;
};
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 }), {});
const _state = getDefaultState();
const _eventsManager = createEventsManager({ allowedEvents: Object.values(events) });
_eventsManager
.register(events.MODEL_CREATED, event => _state.created.push(event))
.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));
});
return {
get models() {
return getModelsMap();
},
get contentTypes() {
return getModelsMap('ct');
},
get components() {
return getModelsMap('comp');
},
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];
},
addContentType(contentType) {
addAction('ct.create', contentType);
return this;
},
addContentTypes(contentTypes, { batch = true } = {}) {
addAction(batch ? 'ct.createBatch' : 'ct.createMany', contentTypes);
return this;
},
addComponent(component) {
addAction('comp.create', component);
return this;
},
addFixtures(model, entries, { onCreated = null } = {}) {
addAction('fixture.create', model, entries, () => this.fixtures);
if (_.isFunction(onCreated)) {
_eventsManager.register(events.FIXTURES_CREATED, e => {
const { result } = e;
// Filters fixture.created events triggered for other models
if (result.model === model) {
onCreated(formatFixtures(result.entries));
}
});
}
return this;
},
/**
* Execute every registered step
*/
async build() {
for (const action of _state.actions) {
await action.execute();
}
return this;
},
/**
* 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) {
const {
result,
metadata: { type },
} = event;
// 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}`;
const deleteMethod = `delete${modelType}${pluralSuffix}`;
// Format params
const uidAttribute = type === 'ct' ? 'modelName' : 'uid';
const params = isBatchOperation
? result.map(prop(uidAttribute))
: prop(uidAttribute, result);
// Execute cleanup & delete operations
await modelsUtils[cleanupMethod](params);
await modelsUtils[deleteMethod](params);
// Notify the builder that the created model has been deleted
_eventsManager.emit(events.MODEL_DELETED, event);
}
return this;
},
};
};
module.exports = {
createTestBuilder,
};