add e2e tests

This commit is contained in:
Pierre Noël 2022-08-09 19:35:48 +02:00
parent 7f521c9348
commit 8dfa1c70d1
6 changed files with 521 additions and 443 deletions

View File

@ -1,238 +0,0 @@
'use strict';
const createContext = require('../../../../../../test/helpers/create-context');
const relations = require('../relations');
describe('Relations', () => {
describe('find', () => {
test('Fails on model not found', async () => {
const notFound = jest.fn();
const ctx = createContext(
{
params: { model: 'test', targetField: 'field' },
},
{
notFound,
}
);
const getModel = jest.fn();
global.strapi = {
getModel,
plugins: {
'content-manager': {
services: {},
},
},
};
await relations.find(ctx);
expect(notFound).toHaveBeenCalledWith('model.notFound');
});
test('Fails on invalid target field', async () => {
const badRequest = jest.fn();
const ctx = createContext(
{
params: { model: 'test', targetField: 'field' },
},
{
badRequest,
}
);
const getModel = jest.fn(() => ({
attributes: {},
}));
global.strapi = {
getModel,
plugins: {
'content-manager': {
services: {},
},
},
};
await relations.find(ctx);
expect(badRequest).toHaveBeenCalledWith('targetField.invalid');
});
test('Fails on model not found', async () => {
const notFound = jest.fn();
const ctx = createContext(
{
params: { model: 'test', targetField: 'target' },
},
{
notFound,
}
);
const getModel = jest
.fn()
.mockReturnValueOnce({
attributes: { target: { type: 'relation', target: 'test' } },
})
.mockReturnValueOnce(null);
global.strapi = {
getModel,
plugins: {
'content-manager': {
services: {},
},
},
};
await relations.find(ctx);
expect(notFound).toHaveBeenCalledWith('target.notFound');
});
test('Picks the mainField and id only', async () => {
const notFound = jest.fn();
const ctx = createContext(
{
params: { model: 'test', targetField: 'target' },
},
{
notFound,
}
);
const getModel = jest.fn(() => ({
attributes: { target: { type: 'relation', target: 'test' } },
}));
global.strapi = {
getModel,
plugins: {
'content-manager': {
services: {
'content-types': {
findConfiguration() {
return {
metadatas: {
target: {
edit: {
mainField: 'title',
},
},
},
};
},
},
'entity-manager': {
find() {
return [
{
id: 1,
title: 'title1',
secret: 'some secret',
},
{
id: 2,
title: 'title2',
secret: 'some secret 2',
},
];
},
},
},
},
},
};
await relations.find(ctx);
expect(ctx.body).toEqual([
{
id: 1,
title: 'title1',
},
{
id: 2,
title: 'title2',
},
]);
});
test('Omit somes ids', async () => {
const result = [
{
id: 1,
title: 'title1',
secret: 'some secret',
},
{
id: 2,
title: 'title2',
secret: 'some secret 2',
},
];
const configuration = {
metadatas: {
target: {
edit: {
mainField: 'title',
},
},
},
};
const assocModel = { uid: 'api::test.test', attributes: {} };
const notFound = jest.fn();
const find = jest.fn(() => Promise.resolve(result));
const findConfiguration = jest.fn(() => Promise.resolve(configuration));
const getModel = jest
.fn()
.mockImplementationOnce(() => ({
attributes: { target: { type: 'relation', target: 'test' } },
}))
.mockImplementationOnce(() => assocModel);
global.strapi = {
getModel,
plugins: {
'content-manager': {
services: {
'content-types': { findConfiguration },
'entity-manager': { find },
},
},
},
};
const ctx = createContext(
{
params: { model: 'test', targetField: 'target' },
body: { idsToOmit: [3, 4] },
},
{
notFound,
}
);
await relations.find(ctx);
expect(find).toHaveBeenCalledWith(
{ filters: { $and: [{ id: { $notIn: [3, 4] } }] } },
assocModel.uid,
[]
);
expect(ctx.body).toEqual([
{
id: 1,
title: 'title1',
},
{
id: 2,
title: 'title2',
},
]);
});
});
});

View File

@ -1,6 +1,7 @@
'use strict';
const { prop, isEmpty } = require('lodash/fp');
const { hasDraftAndPublish } = require('@strapi/utils').contentTypes;
const { PUBLISHED_AT_ATTRIBUTE } = require('@strapi/utils').contentTypes.constants;
const { getService } = require('../utils');
@ -14,14 +15,14 @@ module.exports = {
const { component, entityId, idsToOmit, page = 1, pageSize = 10, q } = ctx.request.query;
const sourceModel = component || model;
const sourceModelUid = component || model;
const modelDef = strapi.getModel(sourceModel);
if (!modelDef) {
const sourceModel = strapi.getModel(sourceModelUid);
if (!sourceModel) {
return ctx.badRequest("The model doesn't exist");
}
const attribute = modelDef.attributes[targetField];
const attribute = sourceModel.attributes[targetField];
if (!attribute || attribute.type !== 'relation') {
return ctx.badRequest("This relational field doesn't exist");
}
@ -32,8 +33,8 @@ module.exports = {
const limit = Number(pageSize);
const modelConfig = component
? await getService('components').findConfiguration(modelDef)
: await getService('content-types').findConfiguration(modelDef);
? await getService('components').findConfiguration(sourceModel)
: await getService('content-types').findConfiguration(sourceModel);
const mainField = prop(`metadatas.${targetField}.edit.mainField`, modelConfig) || 'id';
const query = strapi.db.queryBuilder(targetedModel.uid);
@ -47,7 +48,7 @@ module.exports = {
}
if (entityId) {
const joinTable = strapi.db.metadata.get(sourceModel).attributes[targetField].joinTable;
const joinTable = strapi.db.metadata.get(sourceModelUid).attributes[targetField].joinTable;
const sourceColumn = component ? joinTable.joinColumn.name : joinTable.inverseJoinColumn.name;
const targetColumn = component ? joinTable.inverseJoinColumn.name : joinTable.joinColumn.name;
@ -66,8 +67,13 @@ module.exports = {
.count()
.first()
.execute();
const fieldsToSelect = ['id', mainField];
if (hasDraftAndPublish(targetedModel)) {
fieldsToSelect.push(PUBLISHED_AT_ATTRIBUTE);
}
const entities = await query
.select([mainField, 'id', PUBLISHED_AT_ATTRIBUTE])
.select(fieldsToSelect)
.orderBy(mainField)
.offset(offset)
.limit(limit)

View File

@ -8,7 +8,7 @@ const validateFindNewSchema = yup
component: yup.string(),
entityId: yup.strapiID(),
q: yup.string(),
omitIds: yup.array().of(yup.strapiID()),
idsToOmit: yup.array().of(yup.strapiID()),
page: yup
.number()
.integer()

View File

@ -1,196 +0,0 @@
'use strict';
// Test a simple default API with no relations
const { omit, pick } = require('lodash/fp');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
const data = {
products: [],
shops: [],
};
const productModel = {
attributes: {
name: {
type: 'string',
},
},
displayName: 'Product',
singularName: 'product',
pluralName: 'products',
description: '',
collectionName: '',
};
const productWithDPModel = {
attributes: {
name: {
type: 'string',
},
},
displayName: 'Product',
singularName: 'product',
pluralName: 'products',
draftAndPublish: true,
description: '',
collectionName: '',
};
const shopModel = {
attributes: {
name: {
type: 'string',
},
products: {
type: 'relation',
relation: 'manyToMany',
target: 'api::product.product',
targetAttribute: 'shops',
},
},
displayName: 'Shop',
singularName: 'shop',
pluralName: 'shops',
};
const shops = [
{
name: 'market',
},
];
const products =
({ withPublished = false }) =>
({ shop }) => {
const shops = [shop[0].id];
const entries = [
{
name: 'tomato',
shops,
publishedAt: new Date(),
},
{
name: 'apple',
shops,
publishedAt: null,
},
];
if (withPublished) {
return entries;
}
return entries.map(omit('publishedAt'));
};
describe('Relation-list route', () => {
describe('without draftAndPublish', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addContentTypes([productModel, shopModel])
.addFixtures(shopModel.singularName, shops)
.addFixtures(productModel.singularName, products({ withPublished: false }))
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
data.shops = await builder.sanitizedFixturesFor(shopModel.singularName, strapi);
data.products = await builder.sanitizedFixturesFor(productModel.singularName, strapi);
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Can get relation-list for products of a shop', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
});
expect(res.body).toHaveLength(data.products.length);
data.products.forEach((product, index) => {
expect(res.body[index]).toStrictEqual(pick(['_id', 'id', 'name'], product));
});
});
test('Can get relation-list for products of a shop and omit some results', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
body: {
idsToOmit: [data.products[0].id],
},
});
expect(res.body).toHaveLength(1);
expect(res.body[0]).toStrictEqual(pick(['_id', 'id', 'name'], data.products[1]));
});
});
describe('with draftAndPublish', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addContentTypes([productWithDPModel, shopModel])
.addFixtures(shopModel.singularName, shops)
.addFixtures(productWithDPModel.singularName, products({ withPublished: true }))
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
data.shops = await builder.sanitizedFixturesFor(shopModel.singularName, strapi);
data.products = await builder.sanitizedFixturesFor(productWithDPModel.singularName, strapi);
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Can get relation-list for products of a shop', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
});
expect(res.body).toHaveLength(data.products.length);
const tomatoProductRes = res.body.find((p) => p.name === 'tomato');
const appleProductRes = res.body.find((p) => p.name === 'apple');
expect(tomatoProductRes).toMatchObject(pick(['_id', 'id', 'name'], data.products[0]));
expect(tomatoProductRes.publishedAt).toBeISODate();
expect(appleProductRes).toStrictEqual({
...pick(['_id', 'id', 'name'], data.products[1]),
publishedAt: null,
});
});
test('Can get relation-list for products of a shop and omit some results', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
body: {
idsToOmit: [data.products[1].id],
},
});
expect(res.body).toHaveLength(1);
expect(res.body[0]).toMatchObject(pick(['_id', 'id', 'name'], data.products[0]));
});
});
});

View File

@ -0,0 +1,260 @@
'use strict';
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
let data = {
products: [],
shops: [],
};
const compo = {
displayName: 'compo',
attributes: {
name: {
type: 'string',
},
compoProducts: {
type: 'relation',
relation: 'manyToMany',
target: 'api::product.product',
},
},
};
const productModel = {
draftAndPublish: true,
attributes: {
name: {
type: 'string',
},
},
displayName: 'Product',
singularName: 'product',
pluralName: 'products',
description: '',
collectionName: '',
};
const shopModel = {
draftAndPublish: true,
attributes: {
name: {
type: 'string',
},
products: {
type: 'relation',
relation: 'manyToMany',
target: 'api::product.product',
targetAttribute: 'shops',
inversedBy: 'shops',
},
myCompo: {
type: 'component',
repeatable: false,
component: 'default.compo',
},
},
displayName: 'Shop',
singularName: 'shop',
pluralName: 'shops',
};
describe('Relations with Draft & Publish', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addContentTypes([productModel])
.addComponent(compo)
.addContentTypes([shopModel])
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
const { body: createdProduct1 } = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product.product',
body: { name: 'Skate' },
});
await rq({
url: `/content-manager/collection-types/api::product.product/${createdProduct1.id}/actions/publish`,
method: 'POST',
});
const { body: createdProduct2 } = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product.product',
body: { name: 'Candle' },
});
data.products.push(createdProduct1);
data.products.push(createdProduct2);
const { body: createdShop } = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::shop.shop',
body: {
name: 'Cazotte Shop',
products: [createdProduct1.id],
myCompo: { compoProducts: [createdProduct2.id] },
},
});
data.shops.push(createdShop);
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
describe('findNew', () => {
test('relation not in a component && no entity', async () => {
let res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/products',
});
expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
publishedAt: null,
},
{
id: expect.any(Number),
name: 'Skate',
publishedAt: expect.any(String),
},
]);
// can omitIds
res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/products',
qs: {
idsToOmit: [data.products[0].id],
},
});
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
publishedAt: null,
},
]);
});
test('relation not in a component && on an entity', async () => {
let res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/products',
qs: {
entityId: data.shops[0].id,
},
});
expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
publishedAt: null,
},
]);
// can omitIds
res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/products',
qs: {
entityId: data.shops[0].id,
idsToOmit: [data.products[1].id],
},
});
expect(res.body.results).toHaveLength(0);
});
test('relation in a component && no entity', async () => {
let res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/compoProducts',
qs: {
component: 'default.compo',
},
});
expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
publishedAt: null,
},
{
id: expect.any(Number),
name: 'Skate',
publishedAt: expect.any(String),
},
]);
// can omitIds
res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/compoProducts',
qs: {
component: 'default.compo',
idsToOmit: [data.products[0].id],
},
});
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
publishedAt: null,
},
]);
});
test('relation in a component && on an entity', async () => {
let res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/compoProducts',
qs: {
entityId: data.shops[0].myCompo.id,
component: 'default.compo',
},
});
expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Skate',
publishedAt: expect.any(String),
},
]);
// can omitIds
res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/compoProducts',
qs: {
entityId: data.shops[0].myCompo.id,
component: 'default.compo',
idsToOmit: [data.products[0].id],
},
});
expect(res.body.results).toHaveLength(0);
});
});
});

View File

@ -0,0 +1,246 @@
'use strict';
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
let data = {
products: [],
shops: [],
};
const compo = {
displayName: 'compo',
attributes: {
name: {
type: 'string',
},
compoProducts: {
type: 'relation',
relation: 'manyToMany',
target: 'api::product.product',
},
},
};
const productModel = {
attributes: {
name: {
type: 'string',
},
},
displayName: 'Product',
singularName: 'product',
pluralName: 'products',
description: '',
collectionName: '',
};
const shopModel = {
attributes: {
name: {
type: 'string',
},
products: {
type: 'relation',
relation: 'manyToMany',
target: 'api::product.product',
targetAttribute: 'shops',
inversedBy: 'shops',
},
myCompo: {
type: 'component',
repeatable: false,
component: 'default.compo',
},
},
displayName: 'Shop',
singularName: 'shop',
pluralName: 'shops',
};
describe('Relations', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addContentTypes([productModel])
.addComponent(compo)
.addContentTypes([shopModel])
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
const { body: createdProduct1 } = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product.product',
body: { name: 'Skate' },
});
const { body: createdProduct2 } = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product.product',
body: { name: 'Candle' },
});
data.products.push(createdProduct1);
data.products.push(createdProduct2);
const { body: createdShop } = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::shop.shop',
body: {
name: 'Cazotte Shop',
products: [createdProduct1.id],
myCompo: { compoProducts: [createdProduct2.id] },
},
});
data.shops.push(createdShop);
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
describe('findNew', () => {
test('relation not in a component && no entity', async () => {
let res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/products',
});
expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
},
{
id: expect.any(Number),
name: 'Skate',
},
]);
// can omitIds
res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/products',
qs: {
idsToOmit: [data.products[0].id],
},
});
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
},
]);
});
test('relation not in a component && on an entity', async () => {
let res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/products',
qs: {
entityId: data.shops[0].id,
},
});
expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
},
]);
// can omitIds
res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/products',
qs: {
entityId: data.shops[0].id,
idsToOmit: [data.products[1].id],
},
});
expect(res.body.results).toHaveLength(0);
});
test('relation in a component && no entity', async () => {
let res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/compoProducts',
qs: {
component: 'default.compo',
},
});
expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
},
{
id: expect.any(Number),
name: 'Skate',
},
]);
// can omitIds
res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/compoProducts',
qs: {
component: 'default.compo',
idsToOmit: [data.products[0].id],
},
});
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Candle',
},
]);
});
test('relation in a component && on an entity', async () => {
let res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/compoProducts',
qs: {
entityId: data.shops[0].myCompo.id,
component: 'default.compo',
},
});
expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([
{
id: expect.any(Number),
name: 'Skate',
},
]);
// can omitIds
res = await rq({
method: 'GET',
url: '/content-manager/relations/api::shop.shop/compoProducts',
qs: {
entityId: data.shops[0].myCompo.id,
component: 'default.compo',
idsToOmit: [data.products[0].id],
},
});
expect(res.body.results).toHaveLength(0);
});
});
});