You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
9.7 KiB
250 lines
9.7 KiB
import type { Core } from '@strapi/types';
|
|
|
|
import { createTestSetup, destroyTestSetup } from '../../../utils/builder-helper';
|
|
import resources from './resources/index';
|
|
import { ARTICLE_UID, CATEGORY_UID, Category } from './utils';
|
|
|
|
describe('Document Service', () => {
|
|
let testUtils;
|
|
let strapi: Core.Strapi;
|
|
|
|
let testName;
|
|
let createdCategory;
|
|
|
|
beforeAll(async () => {
|
|
testUtils = await createTestSetup(resources);
|
|
strapi = testUtils.strapi;
|
|
testName = testUtils.data.category[0].name;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await destroyTestSetup(testUtils);
|
|
});
|
|
|
|
describe('Scalar unique fields', () => {
|
|
it('can create a draft document with a duplicated unique field value', async () => {
|
|
await strapi.documents(CATEGORY_UID).create({
|
|
data: { name: testName },
|
|
});
|
|
});
|
|
|
|
it('can update a draft document to have a duplicated unique field value', async () => {
|
|
const uniqueName = `${testName}-1`;
|
|
|
|
const category: Category = await strapi.documents(CATEGORY_UID).create({
|
|
data: { name: uniqueName },
|
|
});
|
|
createdCategory = category;
|
|
|
|
await strapi.documents(CATEGORY_UID).update({
|
|
documentId: category.documentId,
|
|
data: { name: testName },
|
|
});
|
|
});
|
|
|
|
it('cannot publish a document to have a duplicated unique field value in the same publication state', async () => {
|
|
const name = `unique-name`;
|
|
|
|
const category = await strapi.documents(CATEGORY_UID).create({ data: { name } });
|
|
|
|
// Publish that category
|
|
const publishRes = strapi
|
|
.documents(CATEGORY_UID)
|
|
.publish({ documentId: category.documentId });
|
|
await expect(publishRes).resolves.not.toThrowError();
|
|
|
|
// Reset the name of the draft category
|
|
await strapi
|
|
.documents(CATEGORY_UID)
|
|
.update({ documentId: category.documentId, data: { name: 'other-not-unique-name' } });
|
|
|
|
// Now we can create a new category with the same name as the published category
|
|
// When we try to publish it, it should throw an error
|
|
const newCategory = await strapi.documents(CATEGORY_UID).create({ data: { name } });
|
|
expect(
|
|
strapi.documents(CATEGORY_UID).publish({ documentId: newCategory.documentId })
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
it('can save and publish multiple entries with an empty string in a unique field', async () => {
|
|
// Create two categories with empty names (which is a unique field)
|
|
const category = await strapi.documents(CATEGORY_UID).create({ data: { name: '' } });
|
|
expect(category).toBeDefined();
|
|
const category2 = await strapi.documents(CATEGORY_UID).create({ data: { name: '' } });
|
|
expect(category2).toBeDefined();
|
|
|
|
// Publish categories, no error should be thrown
|
|
await strapi.documents(CATEGORY_UID).publish({ documentId: category.documentId });
|
|
await strapi.documents(CATEGORY_UID).publish({ documentId: category2.documentId });
|
|
});
|
|
});
|
|
|
|
describe('Component unique fields', () => {
|
|
const uniqueTextShort = 'unique-text-short';
|
|
const uniqueTextLong = 'This is a unique long text used for testing purposes.';
|
|
const uniqueNumberInteger = 42;
|
|
const uniqueNumberDecimal = 3.14;
|
|
const uniqueNumberBigInteger = 1234567890123;
|
|
const uniqueNumberFloat = 6.28318;
|
|
const uniqueEmail = 'unique@example.com';
|
|
const uniqueDateDate = '2023-01-01';
|
|
const uniqueDateDateTime = '2023-01-01T00:00:00.000Z';
|
|
const uniqueDateTime = '12:00:00';
|
|
|
|
const testValues = {
|
|
ComponentTextShort: uniqueTextShort,
|
|
ComponentTextLong: uniqueTextLong,
|
|
ComponentNumberInteger: uniqueNumberInteger,
|
|
ComponentNumberDecimal: uniqueNumberDecimal,
|
|
ComponentNumberBigInteger: uniqueNumberBigInteger,
|
|
ComponentNumberFloat: uniqueNumberFloat,
|
|
ComponentEmail: uniqueEmail,
|
|
ComponentDateDate: uniqueDateDate,
|
|
ComponentDateDateTime: uniqueDateDateTime,
|
|
ComponentDateTime: uniqueDateTime,
|
|
};
|
|
|
|
const otherLocale = 'fr';
|
|
|
|
/**
|
|
* Modifies the given value to ensure uniqueness based on the field type.
|
|
* For 'Number' fields, it increments the value by a specified amount.
|
|
* For 'Date' fields, it increments the last number found in the string representation of the date.
|
|
* For other field types, it appends '-different' to the string representation of the value.
|
|
*/
|
|
const modifyToDifferentValue = (
|
|
field: string,
|
|
currentValue: string | number,
|
|
options: { increment?: number; suffix?: string } = {
|
|
increment: 1,
|
|
suffix: 'different',
|
|
}
|
|
) => {
|
|
if (field.includes('Number')) {
|
|
return (currentValue as number) + options.increment;
|
|
}
|
|
|
|
if (field.includes('Date')) {
|
|
return (currentValue as string).replace(/(\d+)(?=\D*$)/, (match) => {
|
|
const num = parseInt(match, 10) + options.increment;
|
|
return num < 10 ? `0${num}` : num.toString();
|
|
});
|
|
}
|
|
|
|
return `${currentValue}-${options.suffix}`;
|
|
};
|
|
|
|
type TestCase = {
|
|
description: string;
|
|
createData: (field: string, value: string | number, repeatTheSameValue?: boolean) => any;
|
|
};
|
|
|
|
for (const [field, value] of Object.entries(testValues)) {
|
|
// We want to test the behaviour of component unique fields in different
|
|
// contexts: component, repeatable component, and dynamic zones.
|
|
const testCases: TestCase[] = [
|
|
{
|
|
description: 'identifiers',
|
|
createData: (field, value) => ({ identifiers: { nestedUnique: { [field]: value } } }),
|
|
},
|
|
{
|
|
description: 'repeatableIdentifiers',
|
|
createData: (field, value, repeatTheSameValue = false) => ({
|
|
repeatableIdentifiers: [
|
|
{ nestedUnique: { [field]: value } },
|
|
{
|
|
nestedUnique: {
|
|
[field]: repeatTheSameValue ? value : modifyToDifferentValue(field, value),
|
|
},
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
{
|
|
description: 'identifiersDz',
|
|
createData: (field, value) => ({
|
|
identifiersDz: [{ __component: 'article.compo-unique-all', [field]: value }],
|
|
}),
|
|
},
|
|
];
|
|
|
|
for (const { description, createData } of testCases) {
|
|
it(`cannot create multiple entities with the same unique ${field} value in the same ${description}, locale and publication state`, async () => {
|
|
const isRepeatable = description === 'repeatableIdentifiers';
|
|
|
|
if (isRepeatable) {
|
|
// When testing the repeatable component, we first need to ensure that the
|
|
// unique field value must be unique within the current entity.
|
|
const createdArticle = await strapi.documents(ARTICLE_UID).create({
|
|
data: createData(field, value, true),
|
|
});
|
|
|
|
await expect(
|
|
strapi.documents(ARTICLE_UID).publish({
|
|
documentId: createdArticle.documentId,
|
|
})
|
|
).rejects.toThrow('2 errors occurred');
|
|
}
|
|
|
|
// Create an article in the default locale and publish it.
|
|
const article = await strapi.documents(ARTICLE_UID).create({
|
|
data: createData(field, value),
|
|
});
|
|
await strapi.documents(ARTICLE_UID).publish({ documentId: article.documentId });
|
|
|
|
// Create and publish an article in a different locale with the same
|
|
// unique value as the first article. This should succeed as
|
|
// validation only occurs within the same locale.
|
|
const articleDifferentLocale = await strapi.documents(ARTICLE_UID).create({
|
|
data: createData(field, value),
|
|
locale: otherLocale,
|
|
});
|
|
|
|
expect(articleDifferentLocale).toBeDefined();
|
|
await strapi
|
|
.documents(ARTICLE_UID)
|
|
.publish({ documentId: articleDifferentLocale.documentId, locale: otherLocale });
|
|
|
|
// Attempt to create another article in the default locale with the same unique value.
|
|
// The draft articles should collide and NOT trigger a uniqueness error, as this is a draft
|
|
const createdArticle = await strapi.documents(ARTICLE_UID).create({
|
|
data: isRepeatable
|
|
? // In testing the repeatable we now want to test that it is
|
|
// validated against other entities and don't want to trigger a
|
|
// validation error internal to the current entity.
|
|
{
|
|
[description]: [createData(field, value)[description][0]],
|
|
}
|
|
: createData(field, value),
|
|
});
|
|
|
|
// Attempt to publish the draft article with the same unique value.
|
|
// This should trigger a uniqueness error as the published article has the same value.
|
|
await expect(
|
|
strapi.documents(ARTICLE_UID).publish({ documentId: createdArticle.documentId })
|
|
).rejects.toThrow('This attribute must be unique');
|
|
|
|
const modificationOptions = isRepeatable
|
|
? // When creating the first article with a repeatable field, we
|
|
// already applied modifications to the value to ensure
|
|
// uniqueness.
|
|
// Here we want to apply a different modification to the value to
|
|
// avoid collisions with the first article.
|
|
{
|
|
increment: 10,
|
|
suffix: 'new-suffix',
|
|
}
|
|
: undefined;
|
|
const differentValue = modifyToDifferentValue(field, value, modificationOptions);
|
|
|
|
// Create an article in the same locale with a different unique value.
|
|
const secondArticle = await strapi.documents(ARTICLE_UID).create({
|
|
data: createData(field, differentValue),
|
|
});
|
|
expect(secondArticle).toBeDefined();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|