mirror of
https://github.com/strapi/strapi.git
synced 2025-06-27 00:41:25 +00:00
Add UID validation routes for CTM livechecks
Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
parent
a6708c3e46
commit
db03d9d07d
@ -18,7 +18,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"slug": {
|
||||
"type": "uid"
|
||||
"type": "uid",
|
||||
"targetField": "name"
|
||||
},
|
||||
"price_range": {
|
||||
"enum": ["very_cheap", "cheap", "average", "expensive", "very_expensive"],
|
||||
|
@ -48,6 +48,22 @@
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/explorer/uid/generate",
|
||||
"handler": "ContentManager.generateUID",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/explorer/uid/check-availability",
|
||||
"handler": "ContentManager.checkUIDAvailability",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/explorer/:model",
|
||||
|
@ -1,27 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const parseMultipartBody = require('../utils/parse-multipart');
|
||||
const {
|
||||
validateGenerateUIDInput,
|
||||
validateCheckUIDAvailabilityInput,
|
||||
validateUIDField,
|
||||
} = require('./validation');
|
||||
|
||||
module.exports = {
|
||||
async generateUID(ctx) {
|
||||
const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
|
||||
|
||||
await validateUIDField(contentTypeUID, field);
|
||||
|
||||
const uidService = strapi.plugins['content-manager'].services.uid;
|
||||
|
||||
ctx.body = {
|
||||
data: await uidService.generateUIDField({ contentTypeUID, field, data }),
|
||||
};
|
||||
},
|
||||
|
||||
async checkUIDAvailability(ctx) {
|
||||
const { contentTypeUID, field, value } = await validateCheckUIDAvailabilityInput(
|
||||
ctx.request.body
|
||||
);
|
||||
|
||||
await validateUIDField(contentTypeUID, field);
|
||||
|
||||
const uidService = strapi.plugins['content-manager'].services.uid;
|
||||
|
||||
const isAvailable = await uidService.checkUIDAvailability({ contentTypeUID, field, value });
|
||||
|
||||
ctx.body = {
|
||||
isAvailable,
|
||||
suggestion: !isAvailable
|
||||
? await uidService.findUniqueUID({ contentTypeUID, field, value })
|
||||
: null,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of entities of a content-type matching the query parameters
|
||||
*/
|
||||
async find(ctx) {
|
||||
const contentManagerService =
|
||||
strapi.plugins['content-manager'].services.contentmanager;
|
||||
const contentManagerService = strapi.plugins['content-manager'].services.contentmanager;
|
||||
|
||||
let entities = [];
|
||||
if (_.has(ctx.request.query, '_q')) {
|
||||
entities = await contentManagerService.search(
|
||||
ctx.params,
|
||||
ctx.request.query
|
||||
);
|
||||
entities = await contentManagerService.search(ctx.params, ctx.request.query);
|
||||
} else {
|
||||
entities = await contentManagerService.fetchAll(
|
||||
ctx.params,
|
||||
ctx.request.query
|
||||
);
|
||||
entities = await contentManagerService.fetchAll(ctx.params, ctx.request.query);
|
||||
}
|
||||
|
||||
ctx.body = entities;
|
||||
@ -31,8 +61,7 @@ module.exports = {
|
||||
* Returns an entity of a content type by id
|
||||
*/
|
||||
async findOne(ctx) {
|
||||
const contentManagerService =
|
||||
strapi.plugins['content-manager'].services.contentmanager;
|
||||
const contentManagerService = strapi.plugins['content-manager'].services.contentmanager;
|
||||
|
||||
const entry = await contentManagerService.fetch(ctx.params);
|
||||
|
||||
@ -48,15 +77,11 @@ module.exports = {
|
||||
* Returns a count of entities of a content type matching query parameters
|
||||
*/
|
||||
async count(ctx) {
|
||||
const contentManagerService =
|
||||
strapi.plugins['content-manager'].services.contentmanager;
|
||||
const contentManagerService = strapi.plugins['content-manager'].services.contentmanager;
|
||||
|
||||
let count;
|
||||
if (_.has(ctx.request.query, '_q')) {
|
||||
count = await contentManagerService.countSearch(
|
||||
ctx.params,
|
||||
ctx.request.query
|
||||
);
|
||||
count = await contentManagerService.countSearch(ctx.params, ctx.request.query);
|
||||
} else {
|
||||
count = await contentManagerService.count(ctx.params, ctx.request.query);
|
||||
}
|
||||
@ -70,36 +95,24 @@ module.exports = {
|
||||
* Creates an entity of a content type
|
||||
*/
|
||||
async create(ctx) {
|
||||
const contentManagerService =
|
||||
strapi.plugins['content-manager'].services.contentmanager;
|
||||
const contentManagerService = strapi.plugins['content-manager'].services.contentmanager;
|
||||
|
||||
const { model } = ctx.params;
|
||||
|
||||
try {
|
||||
if (ctx.is('multipart')) {
|
||||
const { data, files } = parseMultipartBody(ctx);
|
||||
ctx.body = await contentManagerService.create(data, {
|
||||
files,
|
||||
model,
|
||||
});
|
||||
} else {
|
||||
// Create an entry using `queries` system
|
||||
ctx.body = await contentManagerService.create(ctx.request.body, {
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
strapi.emit('didCreateFirstContentTypeEntry', ctx.params);
|
||||
} catch (error) {
|
||||
strapi.log.error(error);
|
||||
ctx.badRequest(null, [
|
||||
{
|
||||
messages: [
|
||||
{ id: error.message, message: error.message, field: error.field },
|
||||
],
|
||||
},
|
||||
]);
|
||||
if (ctx.is('multipart')) {
|
||||
const { data, files } = parseMultipartBody(ctx);
|
||||
ctx.body = await contentManagerService.create(data, {
|
||||
files,
|
||||
model,
|
||||
});
|
||||
} else {
|
||||
// Create an entry using `queries` system
|
||||
ctx.body = await contentManagerService.create(ctx.request.body, {
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
strapi.emit('didCreateFirstContentTypeEntry', ctx.params);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -108,31 +121,19 @@ module.exports = {
|
||||
async update(ctx) {
|
||||
const { id, model } = ctx.params;
|
||||
|
||||
const contentManagerService =
|
||||
strapi.plugins['content-manager'].services.contentmanager;
|
||||
const contentManagerService = strapi.plugins['content-manager'].services.contentmanager;
|
||||
|
||||
try {
|
||||
if (ctx.is('multipart')) {
|
||||
const { data, files } = parseMultipartBody(ctx);
|
||||
ctx.body = await contentManagerService.edit({ id }, data, {
|
||||
files,
|
||||
model,
|
||||
});
|
||||
} else {
|
||||
// Return the last one which is the current model.
|
||||
ctx.body = await contentManagerService.edit({ id }, ctx.request.body, {
|
||||
model,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
strapi.log.error(error);
|
||||
ctx.badRequest(null, [
|
||||
{
|
||||
messages: [
|
||||
{ id: error.message, message: error.message, field: error.field },
|
||||
],
|
||||
},
|
||||
]);
|
||||
if (ctx.is('multipart')) {
|
||||
const { data, files } = parseMultipartBody(ctx);
|
||||
ctx.body = await contentManagerService.edit({ id }, data, {
|
||||
files,
|
||||
model,
|
||||
});
|
||||
} else {
|
||||
// Return the last one which is the current model.
|
||||
ctx.body = await contentManagerService.edit({ id }, ctx.request.body, {
|
||||
model,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -140,8 +141,7 @@ module.exports = {
|
||||
* Deletes one entity of a content type matching a query
|
||||
*/
|
||||
async delete(ctx) {
|
||||
const contentManagerService =
|
||||
strapi.plugins['content-manager'].services.contentmanager;
|
||||
const contentManagerService = strapi.plugins['content-manager'].services.contentmanager;
|
||||
|
||||
ctx.body = await contentManagerService.delete(ctx.params);
|
||||
},
|
||||
@ -150,12 +150,8 @@ module.exports = {
|
||||
* Deletes multiple entities of a content type matching a query
|
||||
*/
|
||||
async deleteMany(ctx) {
|
||||
const contentManagerService =
|
||||
strapi.plugins['content-manager'].services.contentmanager;
|
||||
const contentManagerService = strapi.plugins['content-manager'].services.contentmanager;
|
||||
|
||||
ctx.body = await contentManagerService.deleteMany(
|
||||
ctx.params,
|
||||
ctx.request.query
|
||||
);
|
||||
ctx.body = await contentManagerService.deleteMany(ctx.params, ctx.request.query);
|
||||
},
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const yup = require('yup');
|
||||
const { formatYupErrors } = require('strapi-utils');
|
||||
const _ = require('lodash');
|
||||
const { yup, formatYupErrors } = require('strapi-utils');
|
||||
|
||||
const createModelConfigurationSchema = require('./model-configuration');
|
||||
|
||||
@ -19,7 +19,62 @@ const validateKind = kind => {
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
const validateGenerateUIDInput = data => {
|
||||
return yup
|
||||
.object({
|
||||
contentTypeUID: yup.string().required(),
|
||||
field: yup.string().required(),
|
||||
data: yup.object().required(),
|
||||
})
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
})
|
||||
.catch(error => {
|
||||
throw strapi.errors.badRequest('ValidationError', formatYupErrors(error));
|
||||
});
|
||||
};
|
||||
|
||||
const validateCheckUIDAvailabilityInput = data => {
|
||||
return yup
|
||||
.object({
|
||||
contentTypeUID: yup.string().required(),
|
||||
field: yup.string().required(),
|
||||
value: yup
|
||||
.string()
|
||||
.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
|
||||
.required(),
|
||||
})
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
})
|
||||
.catch(error => {
|
||||
throw strapi.errors.badRequest('ValidationError', formatYupErrors(error));
|
||||
});
|
||||
};
|
||||
|
||||
const validateUIDField = (contentTypeUID, field) => {
|
||||
const model = strapi.contentTypes[contentTypeUID];
|
||||
|
||||
if (!model) {
|
||||
throw strapi.errors.badRequest('ValidationError', ['ContentType not found']);
|
||||
}
|
||||
|
||||
if (
|
||||
!_.has(model, ['attributes', field]) ||
|
||||
_.get(model, ['attributes', field, 'type']) !== 'uid'
|
||||
) {
|
||||
throw strapi.errors.badRequest('ValidationError', {
|
||||
field: ['field must be a valid `uid` attribute'],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createModelConfigurationSchema,
|
||||
validateKind,
|
||||
validateGenerateUIDInput,
|
||||
validateCheckUIDAvailabilityInput,
|
||||
validateUIDField,
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const yup = require('yup');
|
||||
const { yup } = require('strapi-utils');
|
||||
const {
|
||||
isListable,
|
||||
hasRelationAttribute,
|
||||
@ -26,9 +26,7 @@ module.exports = (model, schema, opts = {}) =>
|
||||
.noUnknown();
|
||||
|
||||
const createSettingsSchema = (model, schema) => {
|
||||
const validAttributes = Object.keys(schema.attributes).filter(key =>
|
||||
isListable(schema, key)
|
||||
);
|
||||
const validAttributes = Object.keys(schema.attributes).filter(key => isListable(schema, key));
|
||||
|
||||
return yup
|
||||
.object()
|
||||
@ -98,14 +96,11 @@ const createMetadasSchema = (model, schema) => {
|
||||
const createArrayTest = ({ allowUndefined = false } = {}) => ({
|
||||
name: 'isArray',
|
||||
message: '${path} is required and must be an array',
|
||||
test: val =>
|
||||
allowUndefined === true && val === undefined ? true : Array.isArray(val),
|
||||
test: val => (allowUndefined === true && val === undefined ? true : Array.isArray(val)),
|
||||
});
|
||||
|
||||
const createLayoutsSchema = (model, schema, opts = {}) => {
|
||||
const validAttributes = Object.keys(schema.attributes).filter(key =>
|
||||
isListable(schema, key)
|
||||
);
|
||||
const validAttributes = Object.keys(schema.attributes).filter(key => isListable(schema, key));
|
||||
|
||||
const editAttributes = Object.keys(schema.attributes).filter(key =>
|
||||
hasEditableAttribute(schema, key)
|
||||
|
@ -9,6 +9,7 @@
|
||||
"required": true
|
||||
},
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "0.9.1",
|
||||
"classnames": "^2.2.6",
|
||||
"codemirror": "^5.46.0",
|
||||
"draft-js": "^0.10.5",
|
||||
|
@ -0,0 +1,238 @@
|
||||
const uidService = require('../uid');
|
||||
|
||||
describe('Test uid service', () => {
|
||||
describe('generateUIDField', () => {
|
||||
test('Uses modelName if no targetField specified or set', async () => {
|
||||
global.strapi = {
|
||||
contentTypes: {
|
||||
'my-model': {
|
||||
modelName: 'myTestModel',
|
||||
attributes: {
|
||||
slug: {
|
||||
type: 'uid',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
db: {
|
||||
query() {
|
||||
return {
|
||||
find: async () => [],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const uid = await uidService.generateUIDField({
|
||||
contentTypeUID: 'my-model',
|
||||
field: 'slug',
|
||||
data: {},
|
||||
});
|
||||
|
||||
expect(uid).toBe('my-test-model');
|
||||
});
|
||||
|
||||
test('Calls findUniqueUID', async () => {
|
||||
const tmpFn = uidService.findUniqueUID;
|
||||
uidService.findUniqueUID = jest.fn(v => v);
|
||||
|
||||
global.strapi = {
|
||||
contentTypes: {
|
||||
'my-model': {
|
||||
modelName: 'myTestModel',
|
||||
attributes: {
|
||||
title: {
|
||||
type: 'string',
|
||||
},
|
||||
slug: {
|
||||
type: 'uid',
|
||||
targetField: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
db: {
|
||||
query() {
|
||||
return {
|
||||
find: async () => [],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await uidService.generateUIDField({
|
||||
contentTypeUID: 'my-model',
|
||||
field: 'slug',
|
||||
data: {
|
||||
title: 'Test title',
|
||||
},
|
||||
});
|
||||
|
||||
await uidService.generateUIDField({
|
||||
contentTypeUID: 'my-model',
|
||||
field: 'slug',
|
||||
data: {},
|
||||
});
|
||||
|
||||
expect(uidService.findUniqueUID).toHaveBeenCalledTimes(2);
|
||||
|
||||
uidService.findUniqueUID = tmpFn;
|
||||
});
|
||||
|
||||
test('Uses targetField value for generation', async () => {
|
||||
const find = jest.fn(async () => {
|
||||
return [{ slug: 'test-title' }];
|
||||
});
|
||||
|
||||
global.strapi = {
|
||||
contentTypes: {
|
||||
'my-model': {
|
||||
modelName: 'myTestModel',
|
||||
attributes: {
|
||||
title: {
|
||||
type: 'string',
|
||||
},
|
||||
slug: {
|
||||
type: 'uid',
|
||||
targetField: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
db: {
|
||||
query() {
|
||||
return {
|
||||
find,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const uid = await uidService.generateUIDField({
|
||||
contentTypeUID: 'my-model',
|
||||
field: 'slug',
|
||||
data: {
|
||||
title: 'Test title',
|
||||
},
|
||||
});
|
||||
|
||||
expect(uid).toBe('test-title-1');
|
||||
|
||||
// change find response
|
||||
global.strapi.db.query = () => ({ find: jest.fn(async () => []) });
|
||||
|
||||
const uidWithEmptyTarget = await uidService.generateUIDField({
|
||||
contentTypeUID: 'my-model',
|
||||
field: 'slug',
|
||||
data: {
|
||||
title: '',
|
||||
},
|
||||
});
|
||||
|
||||
expect(uidWithEmptyTarget).toBe('my-test-model');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUniqueUID', () => {
|
||||
test('Finds closest match', async () => {
|
||||
const find = jest.fn(async () => {
|
||||
return [
|
||||
{ slug: 'my-test-model' },
|
||||
{ slug: 'my-test-model-1' },
|
||||
// it finds the quickest match possible
|
||||
{ slug: 'my-test-model-4' },
|
||||
];
|
||||
});
|
||||
|
||||
global.strapi = {
|
||||
contentTypes: {
|
||||
'my-model': {
|
||||
modelName: 'myTestModel',
|
||||
attributes: {
|
||||
slug: {
|
||||
type: 'uid',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
db: {
|
||||
query() {
|
||||
return {
|
||||
find,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const uid = await uidService.findUniqueUID({
|
||||
contentTypeUID: 'my-model',
|
||||
field: 'slug',
|
||||
value: 'my-test-model',
|
||||
});
|
||||
|
||||
expect(uid).toBe('my-test-model-2');
|
||||
});
|
||||
|
||||
test('Calls db find', async () => {
|
||||
const find = jest.fn(async () => {
|
||||
return [];
|
||||
});
|
||||
|
||||
global.strapi = {
|
||||
contentTypes: {
|
||||
'my-model': {
|
||||
modelName: 'myTestModel',
|
||||
attributes: {
|
||||
slug: {
|
||||
type: 'uid',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
db: {
|
||||
query() {
|
||||
return {
|
||||
find,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await uidService.findUniqueUID({
|
||||
contentTypeUID: 'my-model',
|
||||
field: 'slug',
|
||||
value: 'my-test-model',
|
||||
});
|
||||
|
||||
expect(find).toHaveBeenCalledWith({
|
||||
slug_contains: 'my-test-model',
|
||||
_limit: -1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CheckUIDAvailability', () => {
|
||||
test('Counts the data in db', async () => {
|
||||
const count = jest.fn(async () => 0);
|
||||
|
||||
global.strapi = {
|
||||
db: {
|
||||
query() {
|
||||
return {
|
||||
count,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const isAvailable = await uidService.checkUIDAvailability({
|
||||
contentTypeUID: 'my-model',
|
||||
field: 'slug',
|
||||
value: 'my-test-model',
|
||||
});
|
||||
|
||||
expect(count).toHaveBeenCalledWith({ slug: 'my-test-model' });
|
||||
expect(isAvailable).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
63
packages/strapi-plugin-content-manager/services/uid.js
Normal file
63
packages/strapi-plugin-content-manager/services/uid.js
Normal file
@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const slugify = require('@sindresorhus/slugify');
|
||||
|
||||
module.exports = {
|
||||
async generateUIDField({ contentTypeUID, field, data }) {
|
||||
const contentType = strapi.contentTypes[contentTypeUID];
|
||||
const { attributes } = contentType;
|
||||
|
||||
const targetField = _.get(attributes, [field, 'targetField']);
|
||||
const targetValue = _.get(data, targetField);
|
||||
|
||||
if (!_.isEmpty(targetValue)) {
|
||||
return this.findUniqueUID({
|
||||
contentTypeUID,
|
||||
field,
|
||||
value: slugify(targetValue),
|
||||
});
|
||||
}
|
||||
|
||||
return this.findUniqueUID({
|
||||
contentTypeUID,
|
||||
field,
|
||||
value: slugify(contentType.modelName),
|
||||
});
|
||||
},
|
||||
|
||||
async findUniqueUID({ contentTypeUID, field, value }) {
|
||||
const query = strapi.db.query(contentTypeUID);
|
||||
|
||||
const possibleColisions = await query
|
||||
.find({
|
||||
[`${field}_contains`]: value,
|
||||
_limit: -1,
|
||||
})
|
||||
.then(results => results.map(result => result[field]));
|
||||
|
||||
if (possibleColisions.length === 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let i = 1;
|
||||
let tmpUId = `${value}-${i}`;
|
||||
while (possibleColisions.includes(tmpUId)) {
|
||||
i += 1;
|
||||
tmpUId = `${value}-${i}`;
|
||||
}
|
||||
|
||||
return tmpUId;
|
||||
},
|
||||
|
||||
async checkUIDAvailability({ contentTypeUID, field, value }) {
|
||||
const query = strapi.db.query(contentTypeUID);
|
||||
|
||||
const count = await query.count({
|
||||
[field]: value,
|
||||
});
|
||||
|
||||
if (count > 0) return false;
|
||||
return true;
|
||||
},
|
||||
};
|
@ -0,0 +1,367 @@
|
||||
// Helpers.
|
||||
const { registerAndLogin } = require('../../../../test/helpers/auth');
|
||||
const createModelsUtils = require('../../../../test/helpers/models');
|
||||
const { createAuthRequest } = require('../../../../test/helpers/request');
|
||||
|
||||
let modelsUtils;
|
||||
let rq;
|
||||
let uid = 'application::uid-model.uid-model';
|
||||
|
||||
describe('Content Manager single types', () => {
|
||||
beforeAll(async () => {
|
||||
const token = await registerAndLogin();
|
||||
rq = createAuthRequest(token);
|
||||
|
||||
modelsUtils = createModelsUtils({ rq });
|
||||
|
||||
await modelsUtils.createContentType({
|
||||
kind: 'collectionType',
|
||||
name: 'uid-model',
|
||||
attributes: {
|
||||
title: {
|
||||
type: 'string',
|
||||
},
|
||||
slug: {
|
||||
type: 'uid',
|
||||
targetField: 'title',
|
||||
},
|
||||
otherField: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => modelsUtils.deleteContentType('uid-model'), 60000);
|
||||
|
||||
describe('Generate UID', () => {
|
||||
test('Throws if input is not provided', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'ValidationError',
|
||||
data: {
|
||||
contentTypeUID: expect.arrayContaining([expect.stringMatching('required field')]),
|
||||
field: expect.arrayContaining([expect.stringMatching('required field')]),
|
||||
data: expect.arrayContaining([expect.stringMatching('required field')]),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Throws when contentType is not found', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: 'non-existent',
|
||||
field: 'slug',
|
||||
data: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'ValidationError',
|
||||
data: ['ContentType not found'],
|
||||
});
|
||||
});
|
||||
|
||||
test('Throws when field is not a uid field', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'otherField',
|
||||
data: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'ValidationError',
|
||||
data: {
|
||||
field: [expect.stringMatching('must be a valid `uid` attribute')],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Generates a unique field when not targetField', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
data: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.data).toBe('uid-model');
|
||||
|
||||
await rq({
|
||||
url: `/content-manager/explorer/${uid}`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
slug: res.body.data,
|
||||
},
|
||||
});
|
||||
|
||||
const secondRes = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
data: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(secondRes.statusCode).toBe(200);
|
||||
expect(secondRes.body.data).toBe('uid-model-1');
|
||||
});
|
||||
|
||||
test('Generates a unique field based on targetField', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
data: {
|
||||
title: 'This is a super title',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.data).toBe('this-is-a-super-title');
|
||||
|
||||
await rq({
|
||||
url: `/content-manager/explorer/${uid}`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
slug: res.body.data,
|
||||
},
|
||||
});
|
||||
|
||||
const secondRes = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
data: {
|
||||
title: 'This is a super title',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(secondRes.statusCode).toBe(200);
|
||||
expect(secondRes.body.data).toBe('this-is-a-super-title-1');
|
||||
});
|
||||
|
||||
test('Avoids colisions with already generated uids', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
data: {
|
||||
title: 'My title',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.data).toBe('my-title');
|
||||
|
||||
await rq({
|
||||
url: `/content-manager/explorer/${uid}`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
slug: res.body.data,
|
||||
},
|
||||
});
|
||||
|
||||
const secondRes = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
data: {
|
||||
title: 'My title',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(secondRes.statusCode).toBe(200);
|
||||
expect(secondRes.body.data).toBe('my-title-1');
|
||||
|
||||
await rq({
|
||||
url: `/content-manager/explorer/${uid}`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
slug: secondRes.body.data,
|
||||
},
|
||||
});
|
||||
|
||||
const thridRes = await rq({
|
||||
url: `/content-manager/explorer/uid/generate`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
data: {
|
||||
title: 'My title 1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(thridRes.statusCode).toBe(200);
|
||||
expect(thridRes.body.data).toBe('my-title-1-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Check UID availability', () => {
|
||||
test('Throws if input is not provided', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/check-availability`,
|
||||
method: 'POST',
|
||||
body: {},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'ValidationError',
|
||||
data: {
|
||||
contentTypeUID: expect.arrayContaining([expect.stringMatching('required field')]),
|
||||
field: expect.arrayContaining([expect.stringMatching('required field')]),
|
||||
value: expect.arrayContaining([expect.stringMatching('required field')]),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Throws on invalid uid value', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/check-availability`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
value: 'Invalid UID valuéééé',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
data: {
|
||||
value: expect.arrayContaining([expect.stringMatching('must match')]),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Throws when contentType is not found', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/check-availability`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: 'non-existent',
|
||||
field: 'slug',
|
||||
value: 'some-slug',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'ValidationError',
|
||||
data: ['ContentType not found'],
|
||||
});
|
||||
});
|
||||
|
||||
test('Throws when field is not a uid field', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/check-availability`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'otherField',
|
||||
value: 'some-slug',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'ValidationError',
|
||||
data: {
|
||||
field: [expect.stringMatching('must be a valid `uid` attribute')],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Checks availability', async () => {
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/check-availability`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
value: 'some-available-slug',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
isAvailable: true,
|
||||
suggestion: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('Gives a suggestion when not available', async () => {
|
||||
// create data
|
||||
await rq({
|
||||
url: `/content-manager/explorer/${uid}`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
slug: 'custom-slug',
|
||||
},
|
||||
});
|
||||
|
||||
const res = await rq({
|
||||
url: `/content-manager/explorer/uid/check-availability`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
contentTypeUID: uid,
|
||||
field: 'slug',
|
||||
value: 'custom-slug',
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
isAvailable: false,
|
||||
suggestion: 'custom-slug-1',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -25,7 +25,7 @@ describe('Content Manager single types', () => {
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => modelsUtils.deleteContentType('single-type'), 60000);
|
||||
afterAll(() => modelsUtils.deleteContentType('single-type-model'), 60000);
|
||||
|
||||
test('Label is not pluralized', async () => {
|
||||
const res = await rq({
|
||||
|
@ -8,7 +8,7 @@
|
||||
"description": "content-type-builder.plugin.description"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "^0.9.1",
|
||||
"@sindresorhus/slugify": "0.9.1",
|
||||
"classnames": "^2.2.6",
|
||||
"fs-extra": "^7.0.0",
|
||||
"immutable": "^3.8.2",
|
||||
|
Loading…
x
Reference in New Issue
Block a user