mirror of
https://github.com/strapi/strapi.git
synced 2025-12-28 15:44:59 +00:00
Merge pull request #15363 from strapi/fix/webhook-with-count-relations
This commit is contained in:
commit
786e476209
@ -13,4 +13,10 @@ module.exports = ({ env }) => ({
|
||||
app: {
|
||||
keys: env.array('APP_KEYS', ['toBeModified1', 'toBeModified2']),
|
||||
},
|
||||
webhooks: {
|
||||
// TODO: V5, set to false by default
|
||||
// Receive populated relations in webhook and db lifecycle payloads
|
||||
// This only populates relations in all content-manager endpoints
|
||||
populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', true),
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const entityManagerLoader = require('../entity-manager');
|
||||
|
||||
let entityManager;
|
||||
@ -11,16 +12,20 @@ describe('Content-Manager', () => {
|
||||
};
|
||||
|
||||
describe('Publish', () => {
|
||||
const defaultConfig = {};
|
||||
beforeEach(() => {
|
||||
global.strapi = {
|
||||
entityService: {
|
||||
update: jest.fn(),
|
||||
update: jest.fn().mockReturnValue({ id: 1, publishedAt: new Date() }),
|
||||
},
|
||||
entityValidator: {
|
||||
validateEntityCreation() {},
|
||||
},
|
||||
eventHub: { emit: jest.fn(), sanitizeEntity: (entity) => entity },
|
||||
getModel: jest.fn(() => fakeModel),
|
||||
config: {
|
||||
get: (path, defaultValue) => _.get(defaultConfig, path, defaultValue),
|
||||
},
|
||||
};
|
||||
entityManager = entityManagerLoader({ strapi });
|
||||
});
|
||||
@ -42,13 +47,17 @@ describe('Content-Manager', () => {
|
||||
});
|
||||
|
||||
describe('Unpublish', () => {
|
||||
const defaultConfig = {};
|
||||
beforeEach(() => {
|
||||
global.strapi = {
|
||||
entityService: {
|
||||
update: jest.fn(),
|
||||
update: jest.fn().mockReturnValue({ id: 1, publishedAt: null }),
|
||||
},
|
||||
eventHub: { emit: jest.fn(), sanitizeEntity: (entity) => entity },
|
||||
getModel: jest.fn(() => fakeModel),
|
||||
config: {
|
||||
get: (path, defaultValue) => _.get(defaultConfig, path, defaultValue),
|
||||
},
|
||||
};
|
||||
entityManager = entityManagerLoader({ strapi });
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ const { assoc, has, prop, omit } = require('lodash/fp');
|
||||
const strapiUtils = require('@strapi/utils');
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
const { getDeepPopulate, getDeepPopulateDraftCount } = require('./utils/populate');
|
||||
const { getDeepRelationsCount } = require('./utils/count');
|
||||
const { sumDraftCounts } = require('./utils/draft');
|
||||
|
||||
const { hasDraftAndPublish } = strapiUtils.contentTypes;
|
||||
@ -12,10 +13,8 @@ const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = strapiUtils.webhook.webhookEvents;
|
||||
|
||||
const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
|
||||
|
||||
const wrapWithEmitEvent = (event, fn) => async (entity, body, model) => {
|
||||
const result = await fn(entity, body, model);
|
||||
|
||||
const modelDef = strapi.getModel(model);
|
||||
const emitEvent = async (event, entity, modelUid) => {
|
||||
const modelDef = strapi.getModel(modelUid);
|
||||
const sanitizedEntity = await strapiUtils.sanitize.sanitizers.defaultSanitizeOutput(
|
||||
modelDef,
|
||||
entity
|
||||
@ -25,8 +24,6 @@ const wrapWithEmitEvent = (event, fn) => async (entity, body, model) => {
|
||||
model: modelDef.modelName,
|
||||
entry: sanitizedEntity,
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const findCreatorRoles = (entity) => {
|
||||
@ -49,6 +46,19 @@ const addCreatedByRolesPopulate = (populate) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* When webhooks.populateRelations is set to true, populated relations
|
||||
* will be passed to any webhook event. The entity-manager
|
||||
* response will not have the populated relations though.
|
||||
* For performance reasons, it is recommended to set it to false,
|
||||
*
|
||||
* TODO V5: Set to false by default.
|
||||
* TODO V5: Make webhooks always send the same entity data.
|
||||
*/
|
||||
const isRelationsPopulateEnabled = () => {
|
||||
return strapi.config.get('server.webhooks.populateRelations', true);
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {import('./entity-manager').default}
|
||||
*/
|
||||
@ -107,6 +117,7 @@ module.exports = ({ strapi }) => ({
|
||||
async create(body, uid) {
|
||||
const modelDef = strapi.getModel(uid);
|
||||
const publishData = { ...body };
|
||||
const populateRelations = isRelationsPopulateEnabled(uid);
|
||||
|
||||
if (hasDraftAndPublish(modelDef)) {
|
||||
publishData[PUBLISHED_AT_ATTRIBUTE] = null;
|
||||
@ -114,27 +125,59 @@ module.exports = ({ strapi }) => ({
|
||||
|
||||
const params = {
|
||||
data: publishData,
|
||||
populate: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
populate: populateRelations
|
||||
? getDeepPopulate(uid, {})
|
||||
: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
return strapi.entityService.create(uid, params);
|
||||
const entity = await strapi.entityService.create(uid, params);
|
||||
|
||||
// If relations were populated, relations count will be returned instead of the array of relations.
|
||||
if (populateRelations) {
|
||||
return getDeepRelationsCount(entity, uid);
|
||||
}
|
||||
|
||||
return entity;
|
||||
},
|
||||
|
||||
update(entity, body, uid) {
|
||||
async update(entity, body, uid) {
|
||||
const publishData = omitPublishedAtField(body);
|
||||
const populateRelations = isRelationsPopulateEnabled(uid);
|
||||
|
||||
const params = {
|
||||
data: publishData,
|
||||
populate: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
populate: populateRelations
|
||||
? getDeepPopulate(uid, {})
|
||||
: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
return strapi.entityService.update(uid, entity.id, params);
|
||||
const updatedEntity = await strapi.entityService.update(uid, entity.id, params);
|
||||
|
||||
// If relations were populated, relations count will be returned instead of the array of relations.
|
||||
if (populateRelations) {
|
||||
return getDeepRelationsCount(updatedEntity, uid);
|
||||
}
|
||||
|
||||
return updatedEntity;
|
||||
},
|
||||
|
||||
delete(entity, uid) {
|
||||
const params = { populate: getDeepPopulate(uid, { countMany: true, countOne: true }) };
|
||||
async delete(entity, uid) {
|
||||
const populateRelations = isRelationsPopulateEnabled(uid);
|
||||
|
||||
return strapi.entityService.delete(uid, entity.id, params);
|
||||
const params = {
|
||||
populate: populateRelations
|
||||
? getDeepPopulate(uid, {})
|
||||
: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
const deletedEntity = await strapi.entityService.delete(uid, entity.id, params);
|
||||
|
||||
// If relations were populated, relations count will be returned instead of the array of relations.
|
||||
if (populateRelations) {
|
||||
return getDeepRelationsCount(deletedEntity, uid);
|
||||
}
|
||||
|
||||
return deletedEntity;
|
||||
},
|
||||
|
||||
// FIXME: handle relations
|
||||
@ -144,7 +187,7 @@ module.exports = ({ strapi }) => ({
|
||||
return strapi.entityService.deleteMany(uid, params);
|
||||
},
|
||||
|
||||
publish: wrapWithEmitEvent(ENTRY_PUBLISH, async (entity, body = {}, uid) => {
|
||||
async publish(entity, body = {}, uid) {
|
||||
if (entity[PUBLISHED_AT_ATTRIBUTE]) {
|
||||
throw new ApplicationError('already.published');
|
||||
}
|
||||
@ -158,29 +201,53 @@ module.exports = ({ strapi }) => ({
|
||||
);
|
||||
|
||||
const data = { ...body, [PUBLISHED_AT_ATTRIBUTE]: new Date() };
|
||||
const populateRelations = isRelationsPopulateEnabled(uid);
|
||||
|
||||
const params = {
|
||||
data,
|
||||
populate: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
populate: populateRelations
|
||||
? getDeepPopulate(uid, {})
|
||||
: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
return strapi.entityService.update(uid, entity.id, params);
|
||||
}),
|
||||
const updatedEntity = await strapi.entityService.update(uid, entity.id, params);
|
||||
|
||||
unpublish: wrapWithEmitEvent(ENTRY_UNPUBLISH, (entity, body = {}, uid) => {
|
||||
await emitEvent(ENTRY_PUBLISH, entity, uid);
|
||||
|
||||
// If relations were populated, relations count will be returned instead of the array of relations.
|
||||
if (isRelationsPopulateEnabled(uid)) {
|
||||
return getDeepRelationsCount(updatedEntity, uid);
|
||||
}
|
||||
|
||||
return updatedEntity;
|
||||
},
|
||||
|
||||
async unpublish(entity, body = {}, uid) {
|
||||
if (!entity[PUBLISHED_AT_ATTRIBUTE]) {
|
||||
throw new ApplicationError('already.draft');
|
||||
}
|
||||
|
||||
const data = { ...body, [PUBLISHED_AT_ATTRIBUTE]: null };
|
||||
const populateRelations = isRelationsPopulateEnabled(uid);
|
||||
|
||||
const params = {
|
||||
data,
|
||||
populate: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
populate: populateRelations
|
||||
? getDeepPopulate(uid, {})
|
||||
: getDeepPopulate(uid, { countMany: true, countOne: true }),
|
||||
};
|
||||
|
||||
return strapi.entityService.update(uid, entity.id, params);
|
||||
}),
|
||||
const updatedEntity = await strapi.entityService.update(uid, entity.id, params);
|
||||
|
||||
await emitEvent(ENTRY_UNPUBLISH, entity, uid);
|
||||
|
||||
// If relations were populated, relations count will be returned instead of the array of relations.
|
||||
if (isRelationsPopulateEnabled(uid)) {
|
||||
return getDeepRelationsCount(updatedEntity, uid);
|
||||
}
|
||||
|
||||
return updatedEntity;
|
||||
},
|
||||
|
||||
async getNumberOfDraftRelations(id, uid) {
|
||||
const { populate, hasRelations } = getDeepPopulateDraftCount(uid);
|
||||
|
||||
@ -0,0 +1,242 @@
|
||||
'use strict';
|
||||
|
||||
const { getDeepRelationsCount } = require('../count');
|
||||
|
||||
const fakeModels = {
|
||||
component: {
|
||||
modelName: 'Fake component model',
|
||||
attributes: {
|
||||
componentAttrName: {
|
||||
type: 'component',
|
||||
component: 'relationMTM',
|
||||
},
|
||||
},
|
||||
},
|
||||
repeatableComponent: {
|
||||
modelName: 'Fake repeatable component model',
|
||||
attributes: {
|
||||
repeatableComponentAttrName: {
|
||||
type: 'component',
|
||||
repeatable: true,
|
||||
component: 'relationMTM',
|
||||
},
|
||||
},
|
||||
},
|
||||
dynZone: {
|
||||
modelName: 'Fake dynamic zone model',
|
||||
attributes: {
|
||||
dynZoneAttrName: {
|
||||
type: 'dynamiczone',
|
||||
components: ['component'],
|
||||
},
|
||||
},
|
||||
},
|
||||
relationMTM: {
|
||||
modelName: 'Fake relation manyToMany model',
|
||||
attributes: {
|
||||
relationAttrName: {
|
||||
type: 'relation',
|
||||
relation: 'oneToMany',
|
||||
},
|
||||
},
|
||||
},
|
||||
relationOTO: {
|
||||
modelName: 'Fake relation oneToOne model',
|
||||
attributes: {
|
||||
relationAttrName: {
|
||||
type: 'relation',
|
||||
relation: 'oneToOne',
|
||||
},
|
||||
},
|
||||
},
|
||||
media: {
|
||||
modelName: 'Fake media model',
|
||||
attributes: {
|
||||
mediaAttrName: {
|
||||
type: 'media',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('getDeepRelationsCount', () => {
|
||||
beforeEach(() => {
|
||||
global.strapi = {
|
||||
getModel: jest.fn((uid) => fakeModels[uid]),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('relation fields', () => {
|
||||
test('with many to many', () => {
|
||||
const count = getDeepRelationsCount(
|
||||
{
|
||||
relationAttrName: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'rel1',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'rel2',
|
||||
},
|
||||
],
|
||||
},
|
||||
'relationMTM'
|
||||
);
|
||||
|
||||
expect(count).toEqual({
|
||||
relationAttrName: {
|
||||
count: 2,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('with one to one', () => {
|
||||
const count = getDeepRelationsCount(
|
||||
{
|
||||
relationAttrName: {
|
||||
id: 2,
|
||||
name: 'rel1',
|
||||
},
|
||||
},
|
||||
'relationOTO'
|
||||
);
|
||||
|
||||
expect(count).toEqual({
|
||||
relationAttrName: {
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('media fields', () => {
|
||||
test('with media', () => {
|
||||
const mediaEntity = {
|
||||
mediaAttrName: { id: 1, name: 'img1' },
|
||||
};
|
||||
const count = getDeepRelationsCount(mediaEntity, 'media');
|
||||
|
||||
expect(count).toEqual(mediaEntity);
|
||||
});
|
||||
});
|
||||
|
||||
describe('component fields', () => {
|
||||
test('with component', () => {
|
||||
const count = getDeepRelationsCount(
|
||||
{
|
||||
componentAttrName: {
|
||||
relationAttrName: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'rel1',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'rel2',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
'component'
|
||||
);
|
||||
|
||||
expect(count).toEqual({
|
||||
componentAttrName: {
|
||||
relationAttrName: {
|
||||
count: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('with empty component', () => {
|
||||
const count = getDeepRelationsCount(
|
||||
{
|
||||
componentAttrName: null,
|
||||
},
|
||||
'component'
|
||||
);
|
||||
|
||||
expect(count).toEqual({
|
||||
componentAttrName: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('with repeatable component', () => {
|
||||
const count = getDeepRelationsCount(
|
||||
{
|
||||
repeatableComponentAttrName: [
|
||||
{
|
||||
relationAttrName: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'rel1',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'rel2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
'repeatableComponent'
|
||||
);
|
||||
|
||||
expect(count).toEqual({
|
||||
repeatableComponentAttrName: [
|
||||
{
|
||||
relationAttrName: {
|
||||
count: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dynamic zone fields', () => {
|
||||
test('with dynamic zone', () => {
|
||||
const count = getDeepRelationsCount(
|
||||
{
|
||||
dynZoneAttrName: [
|
||||
{
|
||||
__component: 'component',
|
||||
componentAttrName: {
|
||||
relationAttrName: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'rel1',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'rel2',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
'dynZone'
|
||||
);
|
||||
|
||||
expect(count).toEqual({
|
||||
dynZoneAttrName: [
|
||||
{
|
||||
__component: 'component',
|
||||
componentAttrName: {
|
||||
relationAttrName: {
|
||||
count: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
57
packages/core/content-manager/server/services/utils/count.js
Normal file
57
packages/core/content-manager/server/services/utils/count.js
Normal file
@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
const { isVisibleAttribute } = require('@strapi/utils').contentTypes;
|
||||
|
||||
function getCountForRelation(attributeName, entity, model) {
|
||||
// do not count createdBy, updatedBy, localizations etc.
|
||||
if (!isVisibleAttribute(model, attributeName)) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
if (Array.isArray(entity)) {
|
||||
return { count: entity.length };
|
||||
}
|
||||
|
||||
return entity ? { count: 1 } : { count: 0 };
|
||||
}
|
||||
|
||||
function getCountForDZ(entity) {
|
||||
return entity.map((component) => {
|
||||
return getDeepRelationsCount(component, component.__component);
|
||||
});
|
||||
}
|
||||
|
||||
function getCountFor(attributeName, entity, model) {
|
||||
const attribute = model.attributes[attributeName];
|
||||
|
||||
switch (attribute?.type) {
|
||||
case 'relation':
|
||||
return getCountForRelation(attributeName, entity, model);
|
||||
case 'component':
|
||||
if (!entity) return null;
|
||||
if (attribute.repeatable) {
|
||||
return entity.map((component) => getDeepRelationsCount(component, attribute.component));
|
||||
}
|
||||
return getDeepRelationsCount(entity, attribute.component);
|
||||
case 'dynamiczone':
|
||||
return getCountForDZ(entity);
|
||||
default:
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
const getDeepRelationsCount = (entity, uid) => {
|
||||
const model = strapi.getModel(uid);
|
||||
|
||||
return Object.keys(entity).reduce(
|
||||
(relationCountEntity, attributeName) =>
|
||||
Object.assign(relationCountEntity, {
|
||||
[attributeName]: getCountFor(attributeName, entity[attributeName], model),
|
||||
}),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getDeepRelationsCount,
|
||||
};
|
||||
@ -114,324 +114,331 @@ const getCollectorByName = (collectors, name) => collectors.find((c) => c.name =
|
||||
const getStampByName = (stamps, name) => stamps.find((s) => s.name === name);
|
||||
|
||||
describe('CM API', () => {
|
||||
beforeAll(async () => {
|
||||
await builder
|
||||
.addContentTypes([stamp, collector])
|
||||
.addFixtures(stamp.singularName, stampFixtures)
|
||||
.addFixtures(collector.singularName, collectorFixtures)
|
||||
.build();
|
||||
describe.each([{ populateRelations: false }, { populateRelations: true }])(
|
||||
`populate relations in webhooks ($populateRelations)`,
|
||||
({ populateRelations }) => {
|
||||
beforeAll(async () => {
|
||||
await builder
|
||||
.addContentTypes([stamp, collector])
|
||||
.addFixtures(stamp.singularName, stampFixtures)
|
||||
.addFixtures(collector.singularName, collectorFixtures)
|
||||
.build();
|
||||
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
|
||||
data.collectors = await builder.sanitizedFixturesFor(collector.singularName, strapi);
|
||||
data.stamps = await builder.sanitizedFixturesFor(stamp.singularName, strapi);
|
||||
});
|
||||
strapi.config.set('server.webhooks.populateRelations', populateRelations);
|
||||
|
||||
afterAll(async () => {
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('Automatic count and populate relations', () => {
|
||||
test('many-way', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'name:ASC',
|
||||
},
|
||||
data.collectors = await builder.sanitizedFixturesFor(collector.singularName, strapi);
|
||||
data.stamps = await builder.sanitizedFixturesFor(stamp.singularName, strapi);
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
// all relations are populated and xToMany relations are counted
|
||||
expect(res.body.results).toMatchObject([
|
||||
{
|
||||
age: 25,
|
||||
createdBy: null,
|
||||
id: 1,
|
||||
name: 'Bernard',
|
||||
stamps: { count: 2 },
|
||||
stamps_m2m: { count: 1 },
|
||||
stamps_one_many: { count: 0 },
|
||||
stamps_one_one: {
|
||||
afterAll(async () => {
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
describe('Automatic count and populate relations', () => {
|
||||
test('many-way', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'name:ASC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
// all relations are populated and xToMany relations are counted
|
||||
expect(res.body.results).toMatchObject([
|
||||
{
|
||||
age: 25,
|
||||
createdBy: null,
|
||||
id: 1,
|
||||
name: 'Bernard',
|
||||
stamps: { count: 2 },
|
||||
stamps_m2m: { count: 1 },
|
||||
stamps_one_many: { count: 0 },
|
||||
stamps_one_one: {
|
||||
id: 1,
|
||||
name: '1946',
|
||||
},
|
||||
stamps_one_way: {
|
||||
id: 1,
|
||||
name: '1946',
|
||||
},
|
||||
updatedBy: null,
|
||||
},
|
||||
{
|
||||
age: 23,
|
||||
createdBy: null,
|
||||
id: 3,
|
||||
name: 'Emma',
|
||||
stamps: { count: 0 },
|
||||
stamps_m2m: { count: 2 },
|
||||
stamps_one_many: { count: 1 },
|
||||
stamps_one_one: {
|
||||
id: 3,
|
||||
name: '1948',
|
||||
},
|
||||
stamps_one_way: {
|
||||
id: 3,
|
||||
name: '1948',
|
||||
},
|
||||
updatedBy: null,
|
||||
},
|
||||
{
|
||||
age: 55,
|
||||
createdBy: null,
|
||||
id: 2,
|
||||
name: 'Isabelle',
|
||||
stamps: { count: 1 },
|
||||
stamps_m2m: { count: 0 },
|
||||
stamps_one_many: { count: 2 },
|
||||
stamps_one_one: {
|
||||
id: 2,
|
||||
name: '1947',
|
||||
},
|
||||
stamps_one_way: {
|
||||
id: 2,
|
||||
name: '1947',
|
||||
},
|
||||
updatedBy: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('findOne', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: `/content-manager/collection-types/api::collector.collector/${data.collectors[0].id}`,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({
|
||||
age: 25,
|
||||
id: 1,
|
||||
name: '1946',
|
||||
},
|
||||
stamps_one_way: {
|
||||
id: 1,
|
||||
name: '1946',
|
||||
},
|
||||
updatedBy: null,
|
||||
},
|
||||
{
|
||||
age: 23,
|
||||
createdBy: null,
|
||||
id: 3,
|
||||
name: 'Emma',
|
||||
stamps: { count: 0 },
|
||||
stamps_m2m: { count: 2 },
|
||||
stamps_one_many: { count: 1 },
|
||||
stamps_one_one: {
|
||||
id: 3,
|
||||
name: '1948',
|
||||
},
|
||||
stamps_one_way: {
|
||||
id: 3,
|
||||
name: '1948',
|
||||
},
|
||||
updatedBy: null,
|
||||
},
|
||||
{
|
||||
age: 55,
|
||||
createdBy: null,
|
||||
id: 2,
|
||||
name: 'Isabelle',
|
||||
stamps: { count: 1 },
|
||||
stamps_m2m: { count: 0 },
|
||||
stamps_one_many: { count: 2 },
|
||||
stamps_one_one: {
|
||||
id: 2,
|
||||
name: '1947',
|
||||
},
|
||||
stamps_one_way: {
|
||||
id: 2,
|
||||
name: '1947',
|
||||
},
|
||||
updatedBy: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('findOne', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: `/content-manager/collection-types/api::collector.collector/${data.collectors[0].id}`,
|
||||
name: 'Bernard',
|
||||
stamps: { count: 2 },
|
||||
stamps_m2m: { count: 1 },
|
||||
stamps_one_many: { count: 0 },
|
||||
stamps_one_one: { count: 1 },
|
||||
stamps_one_way: { count: 1 },
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({
|
||||
age: 25,
|
||||
id: 1,
|
||||
name: 'Bernard',
|
||||
stamps: { count: 2 },
|
||||
stamps_m2m: { count: 1 },
|
||||
stamps_one_many: { count: 0 },
|
||||
stamps_one_one: { count: 1 },
|
||||
stamps_one_way: { count: 1 },
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Filter relations', () => {
|
||||
test('many-way', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps: { name: '1946' } },
|
||||
},
|
||||
});
|
||||
|
||||
describe('Filter relations', () => {
|
||||
test('many-way', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps: { name: '1946' } },
|
||||
},
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(res.body.results[0].name).toBe('Bernard');
|
||||
expect(res.body.results[1].name).toBe('Isabelle');
|
||||
});
|
||||
|
||||
test('many-to-many (collector -> stamps)', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps_m2m: { name: '1946' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(getCollectorByName(res.body.results, 'Bernard')).toBeDefined();
|
||||
expect(getCollectorByName(res.body.results, 'Emma')).toBeDefined();
|
||||
});
|
||||
|
||||
test('many-to-many (stamp -> collectors)', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::stamp.stamp',
|
||||
qs: {
|
||||
filters: { collectors: { name: 'Emma' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(getStampByName(res.body.results, '1946')).toBeDefined();
|
||||
expect(getStampByName(res.body.results, '1947')).toBeDefined();
|
||||
});
|
||||
|
||||
test('one-to-many', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps_one_many: { name: '1947' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(1);
|
||||
expect(res.body.results[0].name).toBe('Isabelle');
|
||||
});
|
||||
|
||||
test('many-to-one', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::stamp.stamp',
|
||||
qs: {
|
||||
filters: { collector: { name: 'Isabelle' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(getStampByName(res.body.results, '1947')).toBeDefined();
|
||||
expect(getStampByName(res.body.results, '1948')).toBeDefined();
|
||||
});
|
||||
|
||||
test('one-way', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps_one_way: { name: '1947' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(1);
|
||||
expect(getCollectorByName(res.body.results, 'Isabelle')).toBeDefined();
|
||||
});
|
||||
|
||||
test('one-one', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps_one_one: { name: '1947' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(1);
|
||||
expect(getCollectorByName(res.body.results, 'Isabelle')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(res.body.results[0].name).toBe('Bernard');
|
||||
expect(res.body.results[1].name).toBe('Isabelle');
|
||||
});
|
||||
describe('Sort relations', () => {
|
||||
test('many-to-one', async () => {
|
||||
let res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::stamp.stamp',
|
||||
qs: {
|
||||
sort: 'collector.name:ASC',
|
||||
},
|
||||
});
|
||||
|
||||
test('many-to-many (collector -> stamps)', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps_m2m: { name: '1946' } },
|
||||
},
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].collector.name).toBe('Emma');
|
||||
expect(res.body.results[1].collector.name).toBe('Isabelle');
|
||||
expect(res.body.results[2].collector.name).toBe('Isabelle');
|
||||
|
||||
res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::stamp.stamp',
|
||||
qs: {
|
||||
sort: 'collector.name:DESC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].collector.name).toBe('Isabelle');
|
||||
expect(res.body.results[1].collector.name).toBe('Isabelle');
|
||||
expect(res.body.results[2].collector.name).toBe('Emma');
|
||||
});
|
||||
|
||||
test('one-way', async () => {
|
||||
let res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'stamps_one_way.name:ASC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].stamps_one_way.name).toBe('1946');
|
||||
expect(res.body.results[1].stamps_one_way.name).toBe('1947');
|
||||
expect(res.body.results[2].stamps_one_way.name).toBe('1948');
|
||||
|
||||
res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'stamps_one_way.name:DESC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].stamps_one_way.name).toBe('1948');
|
||||
expect(res.body.results[1].stamps_one_way.name).toBe('1947');
|
||||
expect(res.body.results[2].stamps_one_way.name).toBe('1946');
|
||||
});
|
||||
|
||||
test('one-one', async () => {
|
||||
let res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'stamps_one_one.name:ASC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].stamps_one_one.name).toBe('1946');
|
||||
expect(res.body.results[1].stamps_one_one.name).toBe('1947');
|
||||
expect(res.body.results[2].stamps_one_one.name).toBe('1948');
|
||||
|
||||
res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'stamps_one_one.name:DESC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].stamps_one_one.name).toBe('1948');
|
||||
expect(res.body.results[1].stamps_one_one.name).toBe('1947');
|
||||
expect(res.body.results[2].stamps_one_one.name).toBe('1946');
|
||||
});
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(getCollectorByName(res.body.results, 'Bernard')).toBeDefined();
|
||||
expect(getCollectorByName(res.body.results, 'Emma')).toBeDefined();
|
||||
});
|
||||
|
||||
test('many-to-many (stamp -> collectors)', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::stamp.stamp',
|
||||
qs: {
|
||||
filters: { collectors: { name: 'Emma' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(getStampByName(res.body.results, '1946')).toBeDefined();
|
||||
expect(getStampByName(res.body.results, '1947')).toBeDefined();
|
||||
});
|
||||
|
||||
test('one-to-many', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps_one_many: { name: '1947' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(1);
|
||||
expect(res.body.results[0].name).toBe('Isabelle');
|
||||
});
|
||||
|
||||
test('many-to-one', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::stamp.stamp',
|
||||
qs: {
|
||||
filters: { collector: { name: 'Isabelle' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(getStampByName(res.body.results, '1947')).toBeDefined();
|
||||
expect(getStampByName(res.body.results, '1948')).toBeDefined();
|
||||
});
|
||||
|
||||
test('one-way', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps_one_way: { name: '1947' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(1);
|
||||
expect(getCollectorByName(res.body.results, 'Isabelle')).toBeDefined();
|
||||
});
|
||||
|
||||
test('one-one', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
filters: { stamps_one_one: { name: '1947' } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(1);
|
||||
expect(getCollectorByName(res.body.results, 'Isabelle')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sort relations', () => {
|
||||
test('many-to-one', async () => {
|
||||
let res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::stamp.stamp',
|
||||
qs: {
|
||||
sort: 'collector.name:ASC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].collector.name).toBe('Emma');
|
||||
expect(res.body.results[1].collector.name).toBe('Isabelle');
|
||||
expect(res.body.results[2].collector.name).toBe('Isabelle');
|
||||
|
||||
res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::stamp.stamp',
|
||||
qs: {
|
||||
sort: 'collector.name:DESC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].collector.name).toBe('Isabelle');
|
||||
expect(res.body.results[1].collector.name).toBe('Isabelle');
|
||||
expect(res.body.results[2].collector.name).toBe('Emma');
|
||||
});
|
||||
|
||||
test('one-way', async () => {
|
||||
let res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'stamps_one_way.name:ASC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].stamps_one_way.name).toBe('1946');
|
||||
expect(res.body.results[1].stamps_one_way.name).toBe('1947');
|
||||
expect(res.body.results[2].stamps_one_way.name).toBe('1948');
|
||||
|
||||
res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'stamps_one_way.name:DESC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].stamps_one_way.name).toBe('1948');
|
||||
expect(res.body.results[1].stamps_one_way.name).toBe('1947');
|
||||
expect(res.body.results[2].stamps_one_way.name).toBe('1946');
|
||||
});
|
||||
|
||||
test('one-one', async () => {
|
||||
let res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'stamps_one_one.name:ASC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].stamps_one_one.name).toBe('1946');
|
||||
expect(res.body.results[1].stamps_one_one.name).toBe('1947');
|
||||
expect(res.body.results[2].stamps_one_one.name).toBe('1948');
|
||||
|
||||
res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-manager/collection-types/api::collector.collector',
|
||||
qs: {
|
||||
sort: 'stamps_one_one.name:DESC',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(Array.isArray(res.body.results)).toBe(true);
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].stamps_one_one.name).toBe('1948');
|
||||
expect(res.body.results[1].stamps_one_one.name).toBe('1947');
|
||||
expect(res.body.results[2].stamps_one_one.name).toBe('1946');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@ -4,4 +4,7 @@ module.exports = ({ env }) => ({
|
||||
app: {
|
||||
keys: env.array('APP_KEYS'),
|
||||
},
|
||||
webhooks: {
|
||||
populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false),
|
||||
},
|
||||
});
|
||||
|
||||
@ -4,4 +4,7 @@ export default ({ env }) => ({
|
||||
app: {
|
||||
keys: env.array('APP_KEYS'),
|
||||
},
|
||||
webhooks: {
|
||||
populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false),
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user