diff --git a/packages/core/admin/admin/src/pages/HomePage/index.js b/packages/core/admin/admin/src/pages/HomePage/index.js index ae1448fdd8..ae08990b0f 100644 --- a/packages/core/admin/admin/src/pages/HomePage/index.js +++ b/packages/core/admin/admin/src/pages/HomePage/index.js @@ -31,7 +31,7 @@ const LogoContainer = styled(Box)` `; const HomePage = () => { - // // Temporary until we develop the menu API + // Temporary until we develop the menu API const { collectionTypes, singleTypes, isLoading: isLoadingForModels } = useModels(); const { guidedTourState, isGuidedTourVisible, isSkipped } = useGuidedTour(); diff --git a/packages/core/strapi/lib/services/entity-service/__tests__/entity-service.test.js b/packages/core/strapi/lib/services/entity-service/__tests__/entity-service.test.js index 3866b8780a..f1358547df 100644 --- a/packages/core/strapi/lib/services/entity-service/__tests__/entity-service.test.js +++ b/packages/core/strapi/lib/services/entity-service/__tests__/entity-service.test.js @@ -186,4 +186,133 @@ describe('Entity service', () => { }); }); }); + + describe('Update', () => { + describe('assign default values', () => { + let instance; + + const entityUID = 'api::entity.entity'; + const relationUID = 'api::relation.relation'; + const fakeEntities = { + 0: { + id: 0, + Name: 'TestEntity', + createdAt: '2022-09-28T15:11:22.995Z', + updatedAt: '2022-09-29T09:01:02.949Z', + publishedAt: null, + }, + 1: { + id: 1, + Name: 'TestRelation', + createdAt: '2022-09-28T15:11:22.995Z', + updatedAt: '2022-09-29T09:01:02.949Z', + publishedAt: null, + }, + 2: null, + }; + beforeAll(() => { + const fakeModel = { + kind: 'collectionType', + modelName: 'entity', + collectionName: 'entity', + uid: entityUID, + privateAttributes: [], + options: {}, + info: { + singularName: 'entity', + pluralName: 'entities', + displayName: 'ENTITY', + }, + attributes: { + Name: { + type: 'string', + }, + addresses: { + type: 'relation', + relation: 'oneToMany', + target: relationUID, + mappedBy: 'entity', + }, + updatedBy: { + type: 'relation', + relation: 'oneToOne', + target: 'admin::user', + configurable: false, + writable: false, + visible: false, + useJoinTable: false, + private: true, + }, + }, + }; + + const fakeQuery = { + findOne: jest.fn(({ where }) => fakeEntities[where.id]), + update: jest.fn(({ where }) => ({ + ...fakeEntities[where.id], + addresses: { + count: 1, + }, + })), + }; + + const fakeDB = { + query: jest.fn(() => fakeQuery), + }; + + const fakeStrapi = { + getModel: jest.fn(() => fakeModel), + }; + + instance = createEntityService({ + strapi: fakeStrapi, + db: fakeDB, + eventHub: null, // bypass event emission for update tests + entityValidator, + }); + }); + + test(`should fail if the entity doesn't exist`, async () => { + expect( + await instance.update(entityUID, Math.random() * (10000 - 100) + 100, {}) + ).toBeNull(); + }); + + test('should successfully update with an existing relation', async () => { + const data = { + Name: 'TestEntry', + addresses: { + connect: [ + { + id: 1, + }, + ], + }, + updatedBy: 1, + }; + expect(await instance.update(entityUID, 0, { data })).toMatchObject({ + ...fakeEntities[0], + addresses: { + count: 1, + }, + }); + }); + + test('should throw an error when trying to associate a relation that does not exist', async () => { + const data = { + Name: 'TestEntry', + addresses: { + connect: [ + { + id: 2, + }, + ], + }, + updatedBy: 1, + }; + + await expect(instance.update(entityUID, 0, { data })).rejects.toThrow(); + }); + }); + }); }); diff --git a/packages/core/strapi/lib/services/entity-service/index.js b/packages/core/strapi/lib/services/entity-service/index.js index 0df7e2ea0d..cafecf6849 100644 --- a/packages/core/strapi/lib/services/entity-service/index.js +++ b/packages/core/strapi/lib/services/entity-service/index.js @@ -9,7 +9,7 @@ const { contentTypes: contentTypesUtils, sanitize, } = require('@strapi/utils'); -const { ValidationError } = require('@strapi/utils').errors; +const { ValidationError, ApplicationError } = require('@strapi/utils').errors; const { transformParamsToQuery } = require('@strapi/utils').convertQueryParams; const uploadFiles = require('../utils/upload-files'); @@ -47,6 +47,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) }, async emitEvent(uid, event, entity) { + if (!eventHub) return; const model = strapi.getModel(uid); const sanitizedEntity = await sanitize.sanitizers.defaultSanitizeOutput(model, entity); @@ -168,6 +169,38 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) entityToUpdate ); + // Create an array of the relations we are attempting to associate with this + // entity. + const relationChecks = []; + Object.keys(data).forEach((key) => { + const attribute = model.attributes[key]; + if (attribute?.type !== 'relation') { + return; + } + if (!data[key]?.connect) { + return; + } + relationChecks.push({ uid: attribute.target, data: data[key].connect }); + }); + + // Confirm that these relations exists in the DB before performing the query. + await Promise.all( + relationChecks.map(async (check) => { + await Promise.all( + check.data.map(async (d) => { + const relationEntity = await db.query(check.uid).findOne({ where: { id: d.id } }); + if (relationEntity) { + return; + } + // Trying to associate a relation with this entity that does not exist + throw new ApplicationError( + `Relation of type ${check.uid} with id ${d.id} does not exist` + ); + }) + ); + }) + ); + const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams)); // TODO: wrap in transaction diff --git a/packages/core/upload/tests/admin/file-folder.test.e2e.js b/packages/core/upload/tests/admin/file-folder.test.e2e.js index 800b684a89..a39e02bd9b 100644 --- a/packages/core/upload/tests/admin/file-folder.test.e2e.js +++ b/packages/core/upload/tests/admin/file-folder.test.e2e.js @@ -209,6 +209,7 @@ describe('File', () => { data.files[1] = file; }); }); + describe('Move a file from root level to a folder', () => { test('when replacing the file', async () => { const res = await rq({