diff --git a/packages/core/database/lib/entity-manager/relations-orderer.js b/packages/core/database/lib/entity-manager/relations-orderer.js index 3a9c3fe757..ebe031d450 100644 --- a/packages/core/database/lib/entity-manager/relations-orderer.js +++ b/packages/core/database/lib/entity-manager/relations-orderer.js @@ -1,7 +1,7 @@ 'use strict'; const _ = require('lodash/fp'); - +const { InvalidRelationError } = require('../errors'); /** * When connecting relations, the order you connect them matters. * @@ -72,7 +72,7 @@ const sortConnectArray = (connectArr, initialArr = []) => { } else { // If we reach this point, it means that the adjacent relation is not in the connect array // and it is not in the database. This should not happen. - throw new Error( + throw new InvalidRelationError( `There was a problem connecting relation with id ${rel.id} at position ${JSON.stringify( rel.position )}. The relation with id ${adjacentRelId} needs to be connected first.` diff --git a/packages/core/database/lib/errors/index.js b/packages/core/database/lib/errors/index.js index 5a97acdc7e..a4d35e33ea 100644 --- a/packages/core/database/lib/errors/index.js +++ b/packages/core/database/lib/errors/index.js @@ -5,6 +5,7 @@ const NotNullError = require('./not-null'); const InvalidTimeError = require('./invalid-time'); const InvalidDateError = require('./invalid-date'); const InvalidDateTimeError = require('./invalid-datetime'); +const InvalidRelationError = require('./invalid-relation'); module.exports = { DatabaseError, @@ -12,4 +13,5 @@ module.exports = { InvalidTimeError, InvalidDateError, InvalidDateTimeError, + InvalidRelationError, }; diff --git a/packages/core/database/lib/errors/invalid-relation.js b/packages/core/database/lib/errors/invalid-relation.js new file mode 100644 index 0000000000..53383e5826 --- /dev/null +++ b/packages/core/database/lib/errors/invalid-relation.js @@ -0,0 +1,14 @@ +'use strict'; + +const DatabaseError = require('./database'); + +class InvalidRelationError extends DatabaseError { + constructor(message) { + super(); + this.name = 'InvalidRelationFormat'; + this.message = message || 'Invalid relation format'; + this.details = {}; + } +} + +module.exports = InvalidRelationError; diff --git a/packages/core/strapi/lib/services/entity-service/index.js b/packages/core/strapi/lib/services/entity-service/index.js index 36e85a788f..14ad6ac78e 100644 --- a/packages/core/strapi/lib/services/entity-service/index.js +++ b/packages/core/strapi/lib/services/entity-service/index.js @@ -2,7 +2,7 @@ const _ = require('lodash'); const delegate = require('delegates'); -const { InvalidTimeError, InvalidDateError, InvalidDateTimeError } = +const { InvalidTimeError, InvalidDateError, InvalidDateTimeError, InvalidRelationError } = require('@strapi/database').errors; const { webhook: webhookUtils, @@ -34,7 +34,12 @@ const transformLoadParamsToQuery = (uid, field, params = {}, pagination = {}) => // TODO: those should be strapi events used by the webhooks not the other way arround const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents; -const databaseErrorsToTransform = [InvalidTimeError, InvalidDateTimeError, InvalidDateError]; +const databaseErrorsToTransform = [ + InvalidTimeError, + InvalidDateTimeError, + InvalidDateError, + InvalidRelationError, +]; const creationPipeline = (data, context) => { return applyTransforms(data, context); diff --git a/packages/core/strapi/tests/api/relations.test.api.js b/packages/core/strapi/tests/api/relations.test.api.js index 6c186b76ad..6bb3ccc22f 100644 --- a/packages/core/strapi/tests/api/relations.test.api.js +++ b/packages/core/strapi/tests/api/relations.test.api.js @@ -120,7 +120,7 @@ const createEntry = async (pluralName, data, populate) => { body: { data }, qs: { populate }, }); - return body.data; + return body; }; const updateEntry = async (pluralName, id, data, populate) => { @@ -130,7 +130,7 @@ const updateEntry = async (pluralName, id, data, populate) => { body: { data }, qs: { populate }, }); - return body.data; + return body; }; const createShop = async ({ @@ -228,9 +228,9 @@ describe('Relations', () => { const createdProduct2 = await createEntry('products', { name: 'Candle' }); const createdProduct3 = await createEntry('products', { name: 'Mug' }); - data.products.push(createdProduct1); - data.products.push(createdProduct2); - data.products.push(createdProduct3); + data.products.push(createdProduct1.data); + data.products.push(createdProduct2.data); + data.products.push(createdProduct3.data); id1 = data.products[0].id; id2 = data.products[1].id; @@ -271,7 +271,7 @@ describe('Relations', () => { populateShop ); - expect(shop).toMatchObject({ + expect(shop.data).toMatchObject({ attributes: { myCompo: { compo_products_mw: { data: [{ id: id1 }, { id: id2 }] }, @@ -310,7 +310,7 @@ describe('Relations', () => { populateShop ); - expect(shop).toMatchObject({ + expect(shop.data).toMatchObject({ attributes: { myCompo: { compo_products_mw: { data: [{ id: id2 }, { id: id1 }] }, @@ -357,7 +357,7 @@ describe('Relations', () => { const updatedShop = await updateEntry( 'shops', - createdShop.id, + createdShop.data.id, { name: 'Cazotte Shop', products_ow: { connect: relationToAdd }, @@ -367,7 +367,7 @@ describe('Relations', () => { products_mm: { connect: relationToAdd }, products_mw: { connect: relationToAdd }, myCompo: { - id: createdShop.attributes.myCompo.id, + id: createdShop.data.attributes.myCompo.id, compo_products_ow: { connect: relationToAdd }, compo_products_mw: { connect: relationToAdd }, }, @@ -375,7 +375,7 @@ describe('Relations', () => { populateShop ); - expect(updatedShop).toMatchObject({ + expect(updatedShop.data).toMatchObject({ attributes: { myCompo: { compo_products_mw: { data: [{ id: id1 }, { id: id2 }, { id: id3 }] }, @@ -415,7 +415,7 @@ describe('Relations', () => { const updatedShop = await updateEntry( 'shops', - createdShop.id, + createdShop.data.id, { name: 'Cazotte Shop', products_ow: { connect: relationToAdd, disconnect: relationToRemove }, @@ -425,7 +425,7 @@ describe('Relations', () => { products_mm: { connect: relationToAdd, disconnect: relationToRemove }, products_mw: { connect: relationToAdd, disconnect: relationToRemove }, myCompo: { - id: createdShop.attributes.myCompo.id, + id: createdShop.data.attributes.myCompo.id, compo_products_ow: { connect: relationToAdd, disconnect: relationToRemove }, compo_products_mw: { connect: relationToAdd, disconnect: relationToRemove }, }, @@ -433,7 +433,7 @@ describe('Relations', () => { populateShop ); - expect(updatedShop).toMatchObject({ + expect(updatedShop.data).toMatchObject({ attributes: { myCompo: { compo_products_mw: { data: [{ id: id2 }, { id: id3 }] }, @@ -473,7 +473,7 @@ describe('Relations', () => { const updatedShop = await updateEntry( 'shops', - createdShop.id, + createdShop.data.id, { name: 'Cazotte Shop', products_ow: { connect: relationToAdd, disconnect: relationToRemove }, @@ -483,7 +483,7 @@ describe('Relations', () => { products_mm: { connect: relationToAdd, disconnect: relationToRemove }, products_mw: { connect: relationToAdd, disconnect: relationToRemove }, myCompo: { - id: createdShop.attributes.myCompo.id, + id: createdShop.data.attributes.myCompo.id, compo_products_ow: { connect: relationToAdd, disconnect: relationToRemove }, compo_products_mw: { connect: relationToAdd, disconnect: relationToRemove }, }, @@ -491,7 +491,7 @@ describe('Relations', () => { populateShop ); - expect(updatedShop).toMatchObject({ + expect(updatedShop.data).toMatchObject({ attributes: { myCompo: { compo_products_mw: { data: [{ id: id2 }, { id: id3 }] }, @@ -527,21 +527,21 @@ describe('Relations', () => { const updatedShop = await updateEntry( 'shops', - createdShop.id, + createdShop.data.id, { name: 'Cazotte Shop', products_om: { connect: relationToChange }, products_mm: { connect: relationToChange }, products_mw: { connect: relationToChange }, myCompo: { - id: createdShop.attributes.myCompo.id, + id: createdShop.data.attributes.myCompo.id, compo_products_mw: { connect: relationToChange }, }, }, populateShop ); - expect(updatedShop).toMatchObject({ + expect(updatedShop.data).toMatchObject({ attributes: { myCompo: { compo_products_mw: { data: [{ id: id3 }, { id: id2 }, { id: id1 }] }, @@ -572,21 +572,21 @@ describe('Relations', () => { const updatedShop = await updateEntry( 'shops', - createdShop.id, + createdShop.data.id, { name: 'Cazotte Shop', products_om: { connect: relationToChange }, products_mm: { connect: relationToChange }, products_mw: { connect: relationToChange }, myCompo: { - id: createdShop.attributes.myCompo.id, + id: createdShop.data.attributes.myCompo.id, compo_products_mw: { connect: relationToChange }, }, }, populateShop ); - expect(updatedShop).toMatchObject({ + expect(updatedShop.data).toMatchObject({ attributes: { myCompo: { compo_products_mw: { data: [{ id: id1 }, { id: id3 }, { id: id2 }] }, @@ -617,21 +617,21 @@ describe('Relations', () => { const updatedShop = await updateEntry( 'shops', - createdShop.id, + createdShop.data.id, { name: 'Cazotte Shop', products_om: { connect: relationToChange }, products_mm: { connect: relationToChange }, products_mw: { connect: relationToChange }, myCompo: { - id: createdShop.attributes.myCompo.id, + id: createdShop.data.attributes.myCompo.id, compo_products_mw: { connect: relationToChange }, }, }, populateShop ); - expect(updatedShop).toMatchObject({ + expect(updatedShop.data).toMatchObject({ attributes: { myCompo: { compo_products_mw: { data: [{ id: id3 }, { id: id2 }, { id: id1 }] }, @@ -675,7 +675,7 @@ describe('Relations', () => { const updatedShop = await updateEntry( 'shops', - createdShop.id, + createdShop.data.id, { name: 'Cazotte Shop', products_ow: { disconnect: relationsToDisconnectOne }, @@ -685,7 +685,7 @@ describe('Relations', () => { products_mm: { disconnect: relationsToDisconnectMany }, products_mw: { disconnect: relationsToDisconnectMany }, myCompo: { - id: createdShop.attributes.myCompo.id, + id: createdShop.data.attributes.myCompo.id, compo_products_ow: { disconnect: relationsToDisconnectOne }, compo_products_mw: { disconnect: relationsToDisconnectMany }, }, @@ -693,7 +693,7 @@ describe('Relations', () => { populateShop ); - expect(updatedShop).toMatchObject({ + expect(updatedShop.data).toMatchObject({ attributes: { myCompo: { compo_products_ow: { data: null }, @@ -733,7 +733,7 @@ describe('Relations', () => { const updatedShop = await updateEntry( 'shops', - createdShop.id, + createdShop.data.id, { name: 'Cazotte Shop', products_ow: { disconnect: relationsToDisconnectMany }, @@ -743,7 +743,7 @@ describe('Relations', () => { products_mm: { disconnect: relationsToDisconnectMany }, products_mw: { disconnect: relationsToDisconnectMany }, myCompo: { - id: createdShop.attributes.myCompo.id, + id: createdShop.data.attributes.myCompo.id, compo_products_ow: { disconnect: relationsToDisconnectMany }, compo_products_mw: { disconnect: relationsToDisconnectMany }, }, @@ -751,7 +751,7 @@ describe('Relations', () => { populateShop ); - expect(updatedShop).toMatchObject({ + expect(updatedShop.data).toMatchObject({ attributes: { myCompo: { compo_products_ow: { data: { id: id1 } }, @@ -779,7 +779,7 @@ describe('Relations', () => { }); const expectedCreatedShop = shopFactory({ anyToManyRel: [{ id: id2 }, { id: id1 }] }); - expect(createdShop).toMatchObject(expectedCreatedShop); + expect(createdShop.data).toMatchObject(expectedCreatedShop); }); test('Connect new relation at the end', async () => { @@ -791,7 +791,7 @@ describe('Relations', () => { }); const expectedCreatedShop = shopFactory({ anyToManyRel: [{ id: id1 }, { id: id2 }] }); - expect(createdShop).toMatchObject(expectedCreatedShop); + expect(createdShop.data).toMatchObject(expectedCreatedShop); }); test('Create relations using before and after', async () => { @@ -807,7 +807,7 @@ describe('Relations', () => { const expectedShop = shopFactory({ anyToManyRel: [{ id: id2 }, { id: id1 }, { id: id3 }], }); - expect(createdShop).toMatchObject(expectedShop); + expect(createdShop.data).toMatchObject(expectedShop); }); test('Update relations using before and after', async () => { @@ -818,7 +818,7 @@ describe('Relations', () => { ], }); - const updatedShop = await updateShop(shop, { + const updatedShop = await updateShop(shop.data, { anyToManyRel: [ { id: id1, position: { before: id2 } }, { id: id2, position: { start: true } }, @@ -829,7 +829,7 @@ describe('Relations', () => { const expectedShop = shopFactory({ anyToManyRel: [{ id: id2 }, { id: id1 }, { id: id3 }], }); - expect(updatedShop).toMatchObject(expectedShop); + expect(updatedShop.data).toMatchObject(expectedShop); }); }); @@ -841,7 +841,7 @@ describe('Relations', () => { ], }); - const updatedShop = await updateShop(shop, { + const updatedShop = await updateShop(shop.data, { anyToManyRel: [ { id: id1, position: { end: true } }, { id: id1, position: { start: true } }, @@ -852,22 +852,19 @@ describe('Relations', () => { const expectedShop = shopFactory({ anyToManyRel: [{ id: id2 }, { id: id1 }], }); - expect(updatedShop).toMatchObject(expectedShop); + expect(updatedShop.data).toMatchObject(expectedShop); }); - test.only('Update relations with invalid connect array', async () => { + test('Update relations with invalid connect array in strict mode', async () => { const shop = await createShop({ anyToManyRel: [{ id: id1, position: { end: true } }], }); // Connect relation before id2, but id2 is not in the DB or in the connect array - const updatedShop = await updateShop(shop, { + const updatedShop = await updateShop(shop.data, { anyToManyRel: [{ id: id1, position: { after: id2 } }], }); - const expectedShop = shopFactory({ - anyToManyRel: [{ id: id1 }], - }); - expect(updatedShop).toMatchObject(expectedShop); + expect(updatedShop.error).toMatchObject({ status: 400, name: 'ValidationError' }); }); });