mirror of
https://github.com/strapi/strapi.git
synced 2025-08-11 10:18:28 +00:00
Entity service tests
This commit is contained in:
parent
c4c570fe3b
commit
cc4b360d47
@ -53,7 +53,7 @@ import convertCustomFieldType from './utils/convert-custom-field-type';
|
|||||||
// TODO: move somewhere else
|
// TODO: move somewhere else
|
||||||
import * as draftAndPublishSync from './migrations/draft-publish';
|
import * as draftAndPublishSync from './migrations/draft-publish';
|
||||||
|
|
||||||
import type { Common, Shared } from './types';
|
import type { Common, Schema, Shared } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the working directories based on the instance options.
|
* Resolve the working directories based on the instance options.
|
||||||
@ -635,7 +635,11 @@ class Strapi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getModel(uid: string) {
|
getModel(uid: Common.UID.ContentType): Schema.ContentType;
|
||||||
|
getModel(uid: Common.UID.Component): Schema.Component;
|
||||||
|
getModel(
|
||||||
|
uid: Common.UID.Component | Common.UID.ContentType
|
||||||
|
): Schema.Component | Schema.ContentType {
|
||||||
return (
|
return (
|
||||||
this.contentTypes[uid as Common.UID.ContentType] ||
|
this.contentTypes[uid as Common.UID.ContentType] ||
|
||||||
this.components[uid as Common.UID.Component]
|
this.components[uid as Common.UID.Component]
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
'use strict';
|
import createEntityService from '..';
|
||||||
|
import entityValidator from '../../entity-validator';
|
||||||
const createEntityService = require('..');
|
|
||||||
const entityValidator = require('../../entity-validator');
|
|
||||||
|
|
||||||
describe('Entity service triggers webhooks', () => {
|
describe('Entity service triggers webhooks', () => {
|
||||||
let instance;
|
let instance: any;
|
||||||
const eventHub = { emit: jest.fn() };
|
const eventHub = { emit: jest.fn() };
|
||||||
let entity = { attr: 'value' };
|
let entity: unknown = { attr: 'value' };
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const model = {
|
const model = {
|
||||||
@ -25,11 +23,11 @@ describe('Entity service triggers webhooks', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
db: {
|
db: {
|
||||||
transaction: (cb) => cb(),
|
transaction: (cb: any) => cb(),
|
||||||
query: () => ({
|
query: () => ({
|
||||||
count: () => 0,
|
count: () => 0,
|
||||||
create: ({ data }) => data,
|
create: ({ data }: any) => data,
|
||||||
update: ({ data }) => data,
|
update: ({ data }: any) => data,
|
||||||
findOne: () => entity,
|
findOne: () => entity,
|
||||||
findMany: () => [entity, entity],
|
findMany: () => [entity, entity],
|
||||||
delete: () => ({}),
|
delete: () => ({}),
|
||||||
@ -38,16 +36,16 @@ describe('Entity service triggers webhooks', () => {
|
|||||||
},
|
},
|
||||||
eventHub,
|
eventHub,
|
||||||
entityValidator,
|
entityValidator,
|
||||||
});
|
} as any);
|
||||||
|
|
||||||
global.strapi = {
|
global.strapi = {
|
||||||
getModel: () => model,
|
getModel: () => model,
|
||||||
};
|
} as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Emit event: Create', async () => {
|
test('Emit event: Create', async () => {
|
||||||
// Create entity
|
// Create entity
|
||||||
await instance.create('test-model', { data: entity });
|
await instance.create('api::test.test-model', { data: entity });
|
||||||
|
|
||||||
// Expect entry.create event to be emitted
|
// Expect entry.create event to be emitted
|
||||||
expect(eventHub.emit).toHaveBeenCalledWith('entry.create', {
|
expect(eventHub.emit).toHaveBeenCalledWith('entry.create', {
|
||||||
@ -61,7 +59,7 @@ describe('Entity service triggers webhooks', () => {
|
|||||||
|
|
||||||
test('Emit event: Update', async () => {
|
test('Emit event: Update', async () => {
|
||||||
// Update entity
|
// Update entity
|
||||||
await instance.update('test-model', 'entity-id', { data: entity });
|
await instance.update('api::test.test-model', 'entity-id', { data: entity });
|
||||||
|
|
||||||
// Expect entry.update event to be emitted
|
// Expect entry.update event to be emitted
|
||||||
expect(eventHub.emit).toHaveBeenCalledWith('entry.update', {
|
expect(eventHub.emit).toHaveBeenCalledWith('entry.update', {
|
||||||
@ -75,7 +73,7 @@ describe('Entity service triggers webhooks', () => {
|
|||||||
|
|
||||||
test('Emit event: Delete', async () => {
|
test('Emit event: Delete', async () => {
|
||||||
// Delete entity
|
// Delete entity
|
||||||
await instance.delete('test-model', 'entity-id', {});
|
await instance.delete('api::test.test-model', 'entity-id', {});
|
||||||
|
|
||||||
// Expect entry.create event to be emitted
|
// Expect entry.create event to be emitted
|
||||||
expect(eventHub.emit).toHaveBeenCalledWith('entry.delete', {
|
expect(eventHub.emit).toHaveBeenCalledWith('entry.delete', {
|
||||||
@ -89,7 +87,7 @@ describe('Entity service triggers webhooks', () => {
|
|||||||
|
|
||||||
test('Emit event: Delete Many', async () => {
|
test('Emit event: Delete Many', async () => {
|
||||||
// Delete entity
|
// Delete entity
|
||||||
await instance.deleteMany('test-model', {});
|
await instance.deleteMany('api::test.test-model', {});
|
||||||
|
|
||||||
// Expect entry.create event to be emitted
|
// Expect entry.create event to be emitted
|
||||||
expect(eventHub.emit).toHaveBeenCalledWith('entry.delete', {
|
expect(eventHub.emit).toHaveBeenCalledWith('entry.delete', {
|
||||||
@ -106,7 +104,7 @@ describe('Entity service triggers webhooks', () => {
|
|||||||
test('Do not emit event when no deleted entity', async () => {
|
test('Do not emit event when no deleted entity', async () => {
|
||||||
entity = null;
|
entity = null;
|
||||||
// Delete non existent entity
|
// Delete non existent entity
|
||||||
await instance.delete('test-model', 'entity-id', {});
|
await instance.delete('api::test.test-model', 'entity-id', {});
|
||||||
|
|
||||||
// Expect entry.create event to be emitted
|
// Expect entry.create event to be emitted
|
||||||
expect(eventHub.emit).toHaveBeenCalledTimes(0);
|
expect(eventHub.emit).toHaveBeenCalledTimes(0);
|
@ -1,15 +1,18 @@
|
|||||||
'use strict';
|
import { EventEmitter } from 'events';
|
||||||
|
import { errors } from '@strapi/utils';
|
||||||
|
import createEntityService from '..';
|
||||||
|
import entityValidator from '../../entity-validator';
|
||||||
|
import createEventHub from '../../event-hub';
|
||||||
|
import type { Schema, Utils } from '../../../types';
|
||||||
|
import uploadFiles from '../../utils/upload-files';
|
||||||
|
|
||||||
jest.mock('bcryptjs', () => ({ hashSync: () => 'secret-password' }));
|
jest.mock('bcryptjs', () => ({ hashSync: () => 'secret-password' }));
|
||||||
|
|
||||||
const { EventEmitter } = require('events');
|
|
||||||
const { ValidationError } = require('@strapi/utils').errors;
|
|
||||||
const createEntityService = require('..');
|
|
||||||
const entityValidator = require('../../entity-validator');
|
|
||||||
|
|
||||||
jest.mock('../../utils/upload-files', () => jest.fn(() => Promise.resolve()));
|
jest.mock('../../utils/upload-files', () => jest.fn(() => Promise.resolve()));
|
||||||
|
|
||||||
describe('Entity service', () => {
|
describe('Entity service', () => {
|
||||||
|
const eventHub = createEventHub();
|
||||||
|
|
||||||
global.strapi = {
|
global.strapi = {
|
||||||
getModel: jest.fn(() => ({})),
|
getModel: jest.fn(() => ({})),
|
||||||
config: {
|
config: {
|
||||||
@ -22,27 +25,28 @@ describe('Entity service', () => {
|
|||||||
allowedEvents: new Map([['ENTRY_CREATE', 'entry.create']]),
|
allowedEvents: new Map([['ENTRY_CREATE', 'entry.create']]),
|
||||||
addAllowedEvent: jest.fn(),
|
addAllowedEvent: jest.fn(),
|
||||||
},
|
},
|
||||||
};
|
} as any;
|
||||||
|
|
||||||
describe('Decorator', () => {
|
describe('Decorator', () => {
|
||||||
test.each(['create', 'update', 'findMany', 'findOne', 'delete', 'count', 'findPage'])(
|
test.each(['create', 'update', 'findMany', 'findOne', 'delete', 'count', 'findPage'] as const)(
|
||||||
'Can decorate',
|
'Can decorate',
|
||||||
async (method) => {
|
async (method) => {
|
||||||
const instance = createEntityService({
|
const instance = createEntityService({
|
||||||
strapi: global.strapi,
|
strapi: global.strapi,
|
||||||
db: {},
|
db: {} as any,
|
||||||
eventHub: new EventEmitter(),
|
eventHub,
|
||||||
|
entityValidator,
|
||||||
});
|
});
|
||||||
|
|
||||||
const methodFn = jest.fn();
|
const methodFn = jest.fn();
|
||||||
const decorator = () => ({
|
|
||||||
[method]: methodFn,
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.decorate(decorator);
|
instance.decorate((old) => ({
|
||||||
|
...old,
|
||||||
|
[method]: methodFn,
|
||||||
|
}));
|
||||||
|
|
||||||
const args = [{}, {}];
|
const args = [{}, {}];
|
||||||
await instance[method](...args);
|
await (instance[method] as Utils.Function.Any)(...args);
|
||||||
expect(methodFn).toHaveBeenCalled();
|
expect(methodFn).toHaveBeenCalled();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -61,28 +65,30 @@ describe('Entity service', () => {
|
|||||||
|
|
||||||
const fakeDB = {
|
const fakeDB = {
|
||||||
query: jest.fn(() => fakeQuery),
|
query: jest.fn(() => fakeQuery),
|
||||||
transaction: (cb) => cb(),
|
transaction: (cb: Utils.Function.Any) => cb(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fakeStrapi = {
|
const fakeStrapi = {
|
||||||
...global.strapi,
|
...global.strapi,
|
||||||
|
query: fakeQuery,
|
||||||
getModel: jest.fn(() => {
|
getModel: jest.fn(() => {
|
||||||
return { kind: 'singleType' };
|
return { kind: 'singleType' };
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const instance = createEntityService({
|
const instance = createEntityService({
|
||||||
strapi: fakeStrapi,
|
strapi: fakeStrapi as any,
|
||||||
db: fakeDB,
|
db: fakeDB as any,
|
||||||
eventHub: new EventEmitter(),
|
eventHub,
|
||||||
|
entityValidator,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await instance.findMany('test-model');
|
const result = await instance.findMany('api::test.test-model');
|
||||||
|
|
||||||
expect(fakeStrapi.getModel).toHaveBeenCalledTimes(1);
|
expect(fakeStrapi.getModel).toHaveBeenCalledTimes(1);
|
||||||
expect(fakeStrapi.getModel).toHaveBeenCalledWith('test-model');
|
expect(fakeStrapi.getModel).toHaveBeenCalledWith('api::test.test-model');
|
||||||
|
|
||||||
expect(fakeDB.query).toHaveBeenCalledWith('test-model');
|
expect(fakeDB.query).toHaveBeenCalledWith('api::test.test-model');
|
||||||
expect(fakeQuery.findOne).toHaveBeenCalledWith({});
|
expect(fakeQuery.findOne).toHaveBeenCalledWith({});
|
||||||
expect(result).toEqual(data);
|
expect(result).toEqual(data);
|
||||||
});
|
});
|
||||||
@ -97,11 +103,14 @@ describe('Entity service', () => {
|
|||||||
})),
|
})),
|
||||||
findOne: jest.fn(),
|
findOne: jest.fn(),
|
||||||
};
|
};
|
||||||
const fakeModels = {};
|
const fakeModels: Record<string, Schema.ContentType | Schema.Component> = {};
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
global.strapi.getModel.mockImplementation((modelName) => fakeModels[modelName]);
|
jest
|
||||||
global.strapi.query.mockImplementation(() => fakeQuery);
|
.mocked(global.strapi.getModel)
|
||||||
|
.mockImplementation((modelName: string) => fakeModels[modelName] as any);
|
||||||
|
|
||||||
|
jest.mocked(global.strapi.query).mockImplementation(() => fakeQuery as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -109,16 +118,16 @@ describe('Entity service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
global.strapi.getModel.mockImplementation(() => ({}));
|
jest.mocked(global.strapi.getModel).mockImplementation(() => ({} as any));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('assign default values', () => {
|
describe('assign default values', () => {
|
||||||
let instance;
|
let instance: any;
|
||||||
const entityUID = 'api::entity.entity';
|
const entityUID = 'api::entity.entity';
|
||||||
const relationUID = 'api::relation.relation';
|
const relationUID = 'api::relation.relation';
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const fakeEntities = {
|
const fakeEntities: Record<string, Record<string, unknown>> = {
|
||||||
[relationUID]: {
|
[relationUID]: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -138,9 +147,16 @@ describe('Entity service', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fakeModels[entityUID] = {
|
fakeModels[entityUID] = {
|
||||||
|
modelType: 'contentType',
|
||||||
uid: entityUID,
|
uid: entityUID,
|
||||||
kind: 'contentType',
|
kind: 'collectionType',
|
||||||
modelName: 'test-model',
|
modelName: 'test-model',
|
||||||
|
globalId: 'test-model',
|
||||||
|
info: {
|
||||||
|
singularName: 'entity',
|
||||||
|
pluralName: 'entities',
|
||||||
|
displayName: 'ENTITY',
|
||||||
|
},
|
||||||
options: {},
|
options: {},
|
||||||
attributes: {
|
attributes: {
|
||||||
attrStringDefaultRequired: {
|
attrStringDefaultRequired: {
|
||||||
@ -173,9 +189,17 @@ describe('Entity service', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fakeModels[relationUID] = {
|
fakeModels[relationUID] = {
|
||||||
uid: relationUID,
|
uid: relationUID,
|
||||||
kind: 'contentType',
|
modelType: 'contentType',
|
||||||
|
globalId: 'relation',
|
||||||
|
info: {
|
||||||
|
displayName: 'RELATION',
|
||||||
|
singularName: 'relation',
|
||||||
|
pluralName: 'relations',
|
||||||
|
},
|
||||||
|
kind: 'collectionType',
|
||||||
modelName: 'relation',
|
modelName: 'relation',
|
||||||
attributes: {
|
attributes: {
|
||||||
Name: {
|
Name: {
|
||||||
@ -185,32 +209,43 @@ describe('Entity service', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const fakeQuery = (uid) => ({
|
const fakeQuery = (uid: string) =>
|
||||||
create: jest.fn(({ data }) => data),
|
({
|
||||||
count: jest.fn(({ where }) => {
|
create: jest.fn(({ data }) => data),
|
||||||
return where.id.$in.filter((id) => Boolean(fakeEntities[uid][id])).length;
|
count: jest.fn(({ where }) => {
|
||||||
}),
|
return where.id.$in.filter((id: string) => Boolean(fakeEntities[uid][id])).length;
|
||||||
});
|
}),
|
||||||
|
} as any);
|
||||||
|
|
||||||
const fakeDB = {
|
const fakeDB = {
|
||||||
transaction: (cb) => cb(),
|
transaction: (cb: Utils.Function.Any) => cb(),
|
||||||
query: jest.fn((uid) => fakeQuery(uid)),
|
query: jest.fn((uid) => fakeQuery(uid)),
|
||||||
};
|
} as any;
|
||||||
|
|
||||||
global.strapi.db = fakeDB;
|
global.strapi = {
|
||||||
|
...global.strapi,
|
||||||
|
db: fakeDB,
|
||||||
|
query: jest.fn((uid) => fakeQuery(uid)),
|
||||||
|
} as any;
|
||||||
|
|
||||||
instance = createEntityService({
|
instance = createEntityService({
|
||||||
strapi: global.strapi,
|
strapi: global.strapi,
|
||||||
db: fakeDB,
|
db: fakeDB,
|
||||||
eventHub: new EventEmitter(),
|
eventHub,
|
||||||
entityValidator,
|
entityValidator,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
global.strapi.db = {
|
global.strapi = {
|
||||||
|
...global.strapi,
|
||||||
|
db: {
|
||||||
|
query: jest.fn(() => fakeQuery),
|
||||||
|
},
|
||||||
query: jest.fn(() => fakeQuery),
|
query: jest.fn(() => fakeQuery),
|
||||||
};
|
} as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should create record with all default attributes', async () => {
|
test('should create record with all default attributes', async () => {
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
||||||
@ -315,7 +350,7 @@ describe('Entity service', () => {
|
|||||||
|
|
||||||
const res = instance.create(entityUID, { data });
|
const res = instance.create(entityUID, { data });
|
||||||
await expect(res).rejects.toThrowError(
|
await expect(res).rejects.toThrowError(
|
||||||
new ValidationError(
|
new errors.ValidationError(
|
||||||
`1 relation(s) of type api::relation.relation associated with this entity do not exist`
|
`1 relation(s) of type api::relation.relation associated with this entity do not exist`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -323,19 +358,24 @@ describe('Entity service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with files', () => {
|
describe('with files', () => {
|
||||||
let instance;
|
let instance: any;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
fakeModels['test-model'] = {
|
fakeModels['api::test.test-model'] = {
|
||||||
uid: 'test-model',
|
uid: 'api::test.test-model',
|
||||||
kind: 'collectionType',
|
kind: 'collectionType',
|
||||||
collectionName: 'test-model',
|
collectionName: 'test-model',
|
||||||
|
info: {
|
||||||
|
displayName: 'test-model',
|
||||||
|
singularName: 'test-model',
|
||||||
|
pluralName: 'test-models',
|
||||||
|
},
|
||||||
options: {},
|
options: {},
|
||||||
attributes: {
|
attributes: {
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
activity: {
|
activity: {
|
||||||
displayName: 'activity',
|
|
||||||
type: 'component',
|
type: 'component',
|
||||||
repeatable: true,
|
repeatable: true,
|
||||||
component: 'basic.activity',
|
component: 'basic.activity',
|
||||||
@ -343,12 +383,11 @@ describe('Entity service', () => {
|
|||||||
},
|
},
|
||||||
modelType: 'contentType',
|
modelType: 'contentType',
|
||||||
modelName: 'test-model',
|
modelName: 'test-model',
|
||||||
|
globalId: 'test-model',
|
||||||
};
|
};
|
||||||
|
|
||||||
fakeModels['basic.activity'] = {
|
fakeModels['basic.activity'] = {
|
||||||
collectionName: 'components_basic_activities',
|
collectionName: 'components_basic_activities',
|
||||||
info: {
|
|
||||||
displayName: 'activity',
|
|
||||||
},
|
|
||||||
options: {},
|
options: {},
|
||||||
attributes: {
|
attributes: {
|
||||||
docs: {
|
docs: {
|
||||||
@ -375,6 +414,7 @@ describe('Entity service', () => {
|
|||||||
getModel: jest.fn((modelName) => fakeModels[modelName]),
|
getModel: jest.fn((modelName) => fakeModels[modelName]),
|
||||||
query: jest.fn(() => fakeQuery),
|
query: jest.fn(() => fakeQuery),
|
||||||
db: {
|
db: {
|
||||||
|
...fakeDB,
|
||||||
dialect: {
|
dialect: {
|
||||||
client: 'sqlite',
|
client: 'sqlite',
|
||||||
},
|
},
|
||||||
@ -384,17 +424,17 @@ describe('Entity service', () => {
|
|||||||
global.strapi = {
|
global.strapi = {
|
||||||
...global.strapi,
|
...global.strapi,
|
||||||
...fakeStrapi,
|
...fakeStrapi,
|
||||||
};
|
} as any;
|
||||||
|
|
||||||
instance = createEntityService({
|
instance = createEntityService({
|
||||||
strapi: global.strapi,
|
strapi: global.strapi,
|
||||||
db: fakeDB,
|
db: fakeDB,
|
||||||
eventHub: new EventEmitter(),
|
eventHub,
|
||||||
entityValidator,
|
entityValidator,
|
||||||
});
|
} as any);
|
||||||
});
|
});
|
||||||
test('should create record with attached files', async () => {
|
|
||||||
const uploadFiles = require('../../utils/upload-files');
|
test.only('should create record with attached files', async () => {
|
||||||
const data = {
|
const data = {
|
||||||
name: 'demoEvent',
|
name: 'demoEvent',
|
||||||
activity: [{ name: 'Powering the Aviation of the Future' }],
|
activity: [{ name: 'Powering the Aviation of the Future' }],
|
||||||
@ -411,13 +451,13 @@ describe('Entity service', () => {
|
|||||||
|
|
||||||
fakeQuery.findOne.mockResolvedValue({ id: 1, ...data });
|
fakeQuery.findOne.mockResolvedValue({ id: 1, ...data });
|
||||||
|
|
||||||
await instance.create('test-model', { data, files });
|
await instance.create('api::test.test-model', { data, files });
|
||||||
|
|
||||||
expect(global.strapi.getModel).toBeCalled();
|
expect(global.strapi.getModel).toBeCalled();
|
||||||
expect(uploadFiles).toBeCalled();
|
expect(uploadFiles).toBeCalled();
|
||||||
expect(uploadFiles).toBeCalledTimes(1);
|
expect(uploadFiles).toBeCalledTimes(1);
|
||||||
expect(uploadFiles).toBeCalledWith(
|
expect(uploadFiles).toBeCalledWith(
|
||||||
'test-model',
|
'api::test.test-model',
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'demoEvent',
|
name: 'demoEvent',
|
||||||
@ -439,12 +479,12 @@ describe('Entity service', () => {
|
|||||||
|
|
||||||
describe('Update', () => {
|
describe('Update', () => {
|
||||||
describe('assign default values', () => {
|
describe('assign default values', () => {
|
||||||
let instance;
|
let instance: any;
|
||||||
|
|
||||||
const entityUID = 'api::entity.entity';
|
const entityUID = 'api::entity.entity';
|
||||||
const relationUID = 'api::relation.relation';
|
const relationUID = 'api::relation.relation';
|
||||||
|
|
||||||
const fakeEntities = {
|
const fakeEntities: Record<string, Record<string, any>> = {
|
||||||
[entityUID]: {
|
[entityUID]: {
|
||||||
0: {
|
0: {
|
||||||
id: 0,
|
id: 0,
|
||||||
@ -471,10 +511,12 @@ describe('Entity service', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const fakeModels = {
|
const fakeModels: Record<string, Schema.ContentType> = {
|
||||||
[entityUID]: {
|
[entityUID]: {
|
||||||
kind: 'collectionType',
|
kind: 'collectionType',
|
||||||
modelName: 'entity',
|
modelName: 'entity',
|
||||||
|
globalId: 'entity',
|
||||||
|
modelType: 'contentType',
|
||||||
collectionName: 'entity',
|
collectionName: 'entity',
|
||||||
uid: entityUID,
|
uid: entityUID,
|
||||||
options: {},
|
options: {},
|
||||||
@ -496,8 +538,16 @@ describe('Entity service', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
[relationUID]: {
|
[relationUID]: {
|
||||||
kind: 'contentType',
|
kind: 'collectionType',
|
||||||
|
globalId: 'entity',
|
||||||
|
modelType: 'contentType',
|
||||||
modelName: 'relation',
|
modelName: 'relation',
|
||||||
|
uid: relationUID,
|
||||||
|
info: {
|
||||||
|
singularName: 'relation',
|
||||||
|
pluralName: 'relations',
|
||||||
|
displayName: 'RELATION',
|
||||||
|
},
|
||||||
attributes: {
|
attributes: {
|
||||||
Name: {
|
Name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -509,11 +559,11 @@ describe('Entity service', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const fakeQuery = (key) => ({
|
const fakeQuery = (key: string) => ({
|
||||||
findOne: jest.fn(({ where }) => fakeEntities[key][where.id]),
|
findOne: jest.fn(({ where }) => fakeEntities[key][where.id]),
|
||||||
count: jest.fn(({ where }) => {
|
count: jest.fn(({ where }) => {
|
||||||
let ret = 0;
|
let ret = 0;
|
||||||
where.id.$in.forEach((id) => {
|
where.id.$in.forEach((id: string) => {
|
||||||
const entity = fakeEntities[key][id];
|
const entity = fakeEntities[key][id];
|
||||||
if (!entity) return;
|
if (!entity) return;
|
||||||
ret += 1;
|
ret += 1;
|
||||||
@ -534,18 +584,19 @@ describe('Entity service', () => {
|
|||||||
|
|
||||||
global.strapi = {
|
global.strapi = {
|
||||||
...global.strapi,
|
...global.strapi,
|
||||||
getModel: jest.fn((uid) => {
|
getModel: jest.fn((uid: string) => {
|
||||||
return fakeModels[uid];
|
return fakeModels[uid];
|
||||||
}),
|
}),
|
||||||
|
query: jest.fn((key) => fakeQuery(key)),
|
||||||
db: fakeDB,
|
db: fakeDB,
|
||||||
};
|
} as any;
|
||||||
|
|
||||||
instance = createEntityService({
|
instance = createEntityService({
|
||||||
strapi: global.strapi,
|
strapi: global.strapi,
|
||||||
db: fakeDB,
|
db: fakeDB,
|
||||||
eventHub: new EventEmitter(),
|
eventHub: new EventEmitter(),
|
||||||
entityValidator,
|
entityValidator,
|
||||||
});
|
} as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`should fail if the entity doesn't exist`, async () => {
|
test(`should fail if the entity doesn't exist`, async () => {
|
||||||
@ -587,7 +638,7 @@ describe('Entity service', () => {
|
|||||||
|
|
||||||
const res = instance.update(entityUID, 0, { data });
|
const res = instance.update(entityUID, 0, { data });
|
||||||
await expect(res).rejects.toThrowError(
|
await expect(res).rejects.toThrowError(
|
||||||
new ValidationError(
|
new errors.ValidationError(
|
||||||
`1 relation(s) of type api::relation.relation associated with this entity do not exist`
|
`1 relation(s) of type api::relation.relation associated with this entity do not exist`
|
||||||
)
|
)
|
||||||
);
|
);
|
@ -34,6 +34,14 @@ export * from './types';
|
|||||||
|
|
||||||
const { transformParamsToQuery } = convertQueryParams;
|
const { transformParamsToQuery } = convertQueryParams;
|
||||||
|
|
||||||
|
type Decoratable<T> = T & {
|
||||||
|
decorate(
|
||||||
|
decorator: (old: Types.EntityService) => Types.EntityService & {
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
): void;
|
||||||
|
};
|
||||||
|
|
||||||
type Context = {
|
type Context = {
|
||||||
contentType: Schema.ContentType;
|
contentType: Schema.ContentType;
|
||||||
};
|
};
|
||||||
@ -89,11 +97,11 @@ const createDefaultImplementation = ({
|
|||||||
*/
|
*/
|
||||||
uploadFiles,
|
uploadFiles,
|
||||||
|
|
||||||
async wrapParams(options: any) {
|
async wrapParams(options: any = {}) {
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
|
|
||||||
async wrapResult(result: any) {
|
async wrapResult(result: any = {}) {
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -237,10 +245,6 @@ const createDefaultImplementation = ({
|
|||||||
});
|
});
|
||||||
const { data, files } = wrappedParams;
|
const { data, files } = wrappedParams;
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
throw new Error('cannot update');
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = strapi.getModel(uid);
|
const model = strapi.getModel(uid);
|
||||||
|
|
||||||
const entityToUpdate = await db.query(uid).findOne({ where: { id: entityId } });
|
const entityToUpdate = await db.query(uid).findOne({ where: { id: entityId } });
|
||||||
@ -455,7 +459,7 @@ export default (ctx: {
|
|||||||
db: Database;
|
db: Database;
|
||||||
eventHub: EventHub;
|
eventHub: EventHub;
|
||||||
entityValidator: EntityValidator;
|
entityValidator: EntityValidator;
|
||||||
}): Types.EntityService => {
|
}): Decoratable<Types.EntityService> => {
|
||||||
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||||
ctx.strapi.webhookStore?.addAllowedEvent(key, value);
|
ctx.strapi.webhookStore?.addAllowedEvent(key, value);
|
||||||
});
|
});
|
||||||
@ -507,5 +511,5 @@ export default (ctx: {
|
|||||||
return newService;
|
return newService;
|
||||||
});
|
});
|
||||||
|
|
||||||
return service as unknown as Types.EntityService;
|
return service as unknown as Decoratable<Types.EntityService>;
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { uniqBy, castArray, isNil, isArray, mergeWith } from 'lodash';
|
|||||||
import { has, prop, isObject, isEmpty } from 'lodash/fp';
|
import { has, prop, isObject, isEmpty } from 'lodash/fp';
|
||||||
import strapiUtils from '@strapi/utils';
|
import strapiUtils from '@strapi/utils';
|
||||||
import validators from './validators';
|
import validators from './validators';
|
||||||
import { Common, Schema, Attribute, UID, Shared } from '../../types';
|
import { Common, Schema, Attribute, Shared } from '../../types';
|
||||||
import type * as Types from '../entity-service/types';
|
import type * as Types from '../entity-service/types';
|
||||||
|
|
||||||
type CreateOrUpdate = 'creation' | 'update';
|
type CreateOrUpdate = 'creation' | 'update';
|
||||||
@ -37,12 +37,12 @@ interface ValidatorContext {
|
|||||||
interface AttributeValidatorMetas {
|
interface AttributeValidatorMetas {
|
||||||
attr: Attribute.Any;
|
attr: Attribute.Any;
|
||||||
updatedAttribute: { name: string; value: unknown };
|
updatedAttribute: { name: string; value: unknown };
|
||||||
model: Schema.ContentType;
|
model: Schema.ContentType | Schema.Component;
|
||||||
entity?: Entity;
|
entity?: Entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModelValidatorMetas {
|
interface ModelValidatorMetas {
|
||||||
model: Schema.ContentType;
|
model: Schema.ContentType | Schema.Component;
|
||||||
data: Record<string, unknown>;
|
data: Record<string, unknown>;
|
||||||
entity?: Entity;
|
entity?: Entity;
|
||||||
}
|
}
|
||||||
@ -299,7 +299,7 @@ const createModelValidator =
|
|||||||
const createValidateEntity = (createOrUpdate: CreateOrUpdate) => {
|
const createValidateEntity = (createOrUpdate: CreateOrUpdate) => {
|
||||||
return async <TUID extends Common.UID.ContentType, TData extends Types.Params.Data.Input<TUID>>(
|
return async <TUID extends Common.UID.ContentType, TData extends Types.Params.Data.Input<TUID>>(
|
||||||
model: Shared.ContentTypes[TUID],
|
model: Shared.ContentTypes[TUID],
|
||||||
data: TData | Partial<TData>,
|
data: TData | Partial<TData> | undefined,
|
||||||
options?: { isDraft?: boolean },
|
options?: { isDraft?: boolean },
|
||||||
entity?: Entity
|
entity?: Entity
|
||||||
): Promise<TData> => {
|
): Promise<TData> => {
|
||||||
@ -312,11 +312,7 @@ const createValidateEntity = (createOrUpdate: CreateOrUpdate) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validator = createModelValidator(createOrUpdate)(
|
const validator = createModelValidator(createOrUpdate)(
|
||||||
{
|
{ model, data, entity },
|
||||||
model,
|
|
||||||
data,
|
|
||||||
entity,
|
|
||||||
},
|
|
||||||
{ isDraft: options?.isDraft ?? false }
|
{ isDraft: options?.isDraft ?? false }
|
||||||
)
|
)
|
||||||
.test('relations-test', 'check that all relations exist', async function (data) {
|
.test('relations-test', 'check that all relations exist', async function (data) {
|
||||||
@ -342,11 +338,11 @@ const createValidateEntity = (createOrUpdate: CreateOrUpdate) => {
|
|||||||
/**
|
/**
|
||||||
* Builds an object containing all the media and relations being associated with an entity
|
* Builds an object containing all the media and relations being associated with an entity
|
||||||
*/
|
*/
|
||||||
const buildRelationsStore = ({
|
const buildRelationsStore = <TUID extends Common.UID.ContentType | Common.UID.Component>({
|
||||||
uid,
|
uid,
|
||||||
data,
|
data,
|
||||||
}: {
|
}: {
|
||||||
uid: Common.UID.ContentType | Common.UID.Component;
|
uid: TUID;
|
||||||
data: Record<string, unknown> | null;
|
data: Record<string, unknown> | null;
|
||||||
}): Record<string, ID[]> => {
|
}): Record<string, ID[]> => {
|
||||||
if (!uid) {
|
if (!uid) {
|
||||||
@ -357,7 +353,7 @@ const buildRelationsStore = ({
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentModel: Schema.ContentType = strapi.getModel(uid);
|
const currentModel: Common.Schemas[TUID] = strapi.getModel(uid);
|
||||||
|
|
||||||
return Object.keys(currentModel.attributes).reduce((result, attributeName: string) => {
|
return Object.keys(currentModel.attributes).reduce((result, attributeName: string) => {
|
||||||
const attribute = currentModel.attributes[attributeName];
|
const attribute = currentModel.attributes[attributeName];
|
||||||
@ -470,7 +466,7 @@ const checkRelationsExist = async (relationsStore: Record<string, ID[]> = {}) =>
|
|||||||
for (const [key, value] of Object.entries(relationsStore)) {
|
for (const [key, value] of Object.entries(relationsStore)) {
|
||||||
const evaluate = async () => {
|
const evaluate = async () => {
|
||||||
const uniqueValues = uniqBy(value, `id`);
|
const uniqueValues = uniqBy(value, `id`);
|
||||||
const count = await strapi.query(key as UID.ContentType).count({
|
const count = await strapi.query(key as Common.UID.Schema).count({
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
$in: uniqueValues.map((v) => v.id),
|
$in: uniqueValues.map((v) => v.id),
|
||||||
@ -500,7 +496,7 @@ export interface EntityValidator {
|
|||||||
) => Promise<Types.Params.Data.Input<TUID>>;
|
) => Promise<Types.Params.Data.Input<TUID>>;
|
||||||
validateEntityUpdate: <TUID extends Common.UID.ContentType>(
|
validateEntityUpdate: <TUID extends Common.UID.ContentType>(
|
||||||
model: Shared.ContentTypes[TUID],
|
model: Shared.ContentTypes[TUID],
|
||||||
data: Partial<Types.Params.Data.Input<TUID>>,
|
data: Partial<Types.Params.Data.Input<TUID>> | undefined,
|
||||||
options?: { isDraft?: boolean },
|
options?: { isDraft?: boolean },
|
||||||
entity?: Entity
|
entity?: Entity
|
||||||
) => Promise<Types.Params.Data.Input<TUID>>;
|
) => Promise<Types.Params.Data.Input<TUID>>;
|
||||||
|
@ -9,7 +9,7 @@ export type Enumeration<TValues extends string[] = []> = Attribute.OfType<'enume
|
|||||||
EnumerationProperties<TValues> &
|
EnumerationProperties<TValues> &
|
||||||
// Options
|
// Options
|
||||||
Attribute.ConfigurableOption &
|
Attribute.ConfigurableOption &
|
||||||
Attribute.DefaultOption<TValues> &
|
Attribute.DefaultOption<TValues[number]> &
|
||||||
Attribute.PrivateOption &
|
Attribute.PrivateOption &
|
||||||
Attribute.RequiredOption &
|
Attribute.RequiredOption &
|
||||||
Attribute.WritableOption &
|
Attribute.WritableOption &
|
||||||
|
@ -8,7 +8,7 @@ export interface MediaProperties<
|
|||||||
TKind extends MediaKind | undefined = undefined,
|
TKind extends MediaKind | undefined = undefined,
|
||||||
TMultiple extends Utils.Expression.BooleanValue = Utils.Expression.False
|
TMultiple extends Utils.Expression.BooleanValue = Utils.Expression.False
|
||||||
> {
|
> {
|
||||||
allowedTypes?: TKind;
|
allowedTypes?: TKind | TKind[];
|
||||||
multiple?: TMultiple;
|
multiple?: TMultiple;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,4 +141,8 @@ export interface SingleType extends ContentType {
|
|||||||
*/
|
*/
|
||||||
export interface Component extends Schema {
|
export interface Component extends Schema {
|
||||||
modelType: 'component';
|
modelType: 'component';
|
||||||
|
|
||||||
|
uid: Common.UID.Component;
|
||||||
|
|
||||||
|
category: string;
|
||||||
}
|
}
|
||||||
|
@ -195,8 +195,8 @@ const isTypedAttribute = (attribute: Attribute, type: string) => {
|
|||||||
*/
|
*/
|
||||||
const getContentTypeRoutePrefix = (contentType: Model) => {
|
const getContentTypeRoutePrefix = (contentType: Model) => {
|
||||||
return isSingleType(contentType)
|
return isSingleType(contentType)
|
||||||
? _.kebabCase(contentType.info.singularName)
|
? _.kebabCase(contentType.info?.singularName)
|
||||||
: _.kebabCase(contentType.info.pluralName);
|
: _.kebabCase(contentType.info?.pluralName);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -67,10 +67,10 @@ export type AnyAttribute =
|
|||||||
export type Kind = 'singleType' | 'collectionType';
|
export type Kind = 'singleType' | 'collectionType';
|
||||||
|
|
||||||
export interface Model {
|
export interface Model {
|
||||||
modelType: 'contentType';
|
modelType: 'contentType' | 'component';
|
||||||
uid: string;
|
uid: string;
|
||||||
kind: Kind;
|
kind?: Kind;
|
||||||
info: {
|
info?: {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
singularName: string;
|
singularName: string;
|
||||||
pluralName: string;
|
pluralName: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user