strapi/tests/api/core/content-manager/api/basic-dp-compo.test.api.js
Jamie Howard 8c5105d949
Publish multiple locales (#20258)
* fix: date comparison

* feat(core): document service publish multiple locales (#20046)

* feat(core): document service (un)publish multiple locales

* fix(core): cleanup locale param types

* feat(content-manager): wip - multiple locale support

* Count draft relations across locales (#20116)

* feat(content-manager): publish multiple locales from CM route

* feat(core): multiple locales in document service discard draft

* feat(content-manager): use bulkpublish for locale publish

* feat(content-manager): use query from bulkpublish locales

* feat(content-manager): publishMany

* feat(content-manager): api test for bulk locale + document publish

* chore(content-manager): fix for build

* fix(core): avoid * in locale data

* chore(content-manager): pr amends and validation improvement

* feat(content-manager): use transaction in document manager publish many

* feat(core): throw when a non string locale is provided and not supported

* fix(core): doc service find many

* chore: clean up

* feat(content-manager): Select fields that require validation in availablelocales (#20156)

* feat(content-manager): wip - select more fields in availablelocales

* feat(content-manager): wip available locales using entity traversal

* feat(content-manager): wip test available locales returns fields with validation

* feat(content-manager): test available locales returns fields with validation

* fix(content-manager): ensure sensitive info not exposed in available statuses

* fix(content-manager): sanitize document metadata available status

* fix(content-manager): sanitize document metadata available status at controller level

* fix(content-manager): populate only for validatable fields

* chore: clean up

* chore: clean up

* chore: clean up

* fix(content-manager): build issues

* fix(content-manager): allow null locales

* fix(content-manager): history service (#20185)

* fix(content-manager): history service

* chore(content-manager): clean up

* fix: pr feedback

* chore: update actions deps

* chore: update utility deps

* chore: upgrade sentry

* chore: upgrade graphql-tools

* fix: http-errors ugprade

* chore: add fs-extra types where needed

* docs(typescript): type system cheat sheet

* chore!: remove deprecated verbose option from ts:generate-types

* chore: clean up

fix(content-manager) correctly count bulk publish results

* fix(content-manager): pr feedback and test improvements

* feat(i18n): bulk locale publish modal in CM edit view (#20069)

* feat(i18n): wip bulk locale publish modal

* fix(i18n): wip - fe bulk locale publish

* feat(content-manager): multi locale publish, integrate with backend and add basic e2e test

* feat(i18n): wip - display validation errors in bulk locale modal

* chore: clean up

* chore(i18n): design system changes

* feat(i18n): display correct status after publish and clean up error messaging

* feat(i18n): access onclose from modal body

* fix(i18n): selected locale change

* fix(i18n): locale table state

* fix(i18n): edit view e2e test

* chore(content-manager): validation tweak wip

* feat(i18n): cover validation cases in i18n e2e tests

* chore: clean up

* fix(i18n): edit view more document actions disabled state

* chore: feedback

* fix(i18n): send all params to publish many

* fix(i18n): place bulk locale publish 3rd in array

* fix(content-manager): validation error extraction

* fix(content-manager): pr feedback

* fix: build

* chore(content-manager): simplify exports

* chore(i18n): revert package

---------

Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com>
Co-authored-by: Jean-Sébastien Herbaux <jean-sebastien.herbaux@epitech.eu>
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>

* fix(i18n): disable publish button after bulk locale publish

* fix(content-manager): ce e2e fix

* chore: typography

* fix(i18n): e2e edit view test

* fix: wip date comparison

* fix: wip date comparison

* fix(content-manager): simplify date comparison

* fix: clean up metadata api test

* chore: update api tests

* fix: draft versions must be ahead of published to be considered modified

* fix: published modified calculation

* fix: clean up

* fix: simplify draft and publish comparison

* chore: clean up

* fix: flaky fe tests

* fix: pr feedback

* fix(i18n): error message extraction in bulk locale modal

* chore: remove old publish action

* chore: pr feedback

* chore: refactor error types & validation messages

* chore: use anchor link for locale changes

* chore: fix releases

* fix: clean up

* chore: snapshot

---------

Co-authored-by: Marc-Roig <marc12info@gmail.com>
Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com>
Co-authored-by: Jean-Sébastien Herbaux <jean-sebastien.herbaux@epitech.eu>
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com>
2024-05-10 18:20:35 +01:00

388 lines
11 KiB
JavaScript

'use strict';
// Test a simple default API with no relations
const { createTestBuilder } = require('api-tests/builder');
const { createStrapiInstance } = require('api-tests/strapi');
const { createAuthRequest } = require('api-tests/request');
const { async } = require('@strapi/utils');
const builder = createTestBuilder();
let strapi;
let rq;
const data = {
productsWithCompoAndDP: [],
};
const compo = {
displayName: 'compo',
attributes: {
name: {
type: 'string',
required: true,
},
description: {
type: 'text',
minLength: 4,
maxLength: 30,
},
},
};
const productWithCompoAndDP = {
attributes: {
name: {
type: 'string',
},
description: {
type: 'text',
},
compo: {
type: 'component',
component: 'default.compo',
required: true,
},
},
draftAndPublish: true,
displayName: 'product with compo and DP',
singularName: 'product-with-compo-and-dp',
pluralName: 'product-with-compo-and-dps',
description: '',
collectionName: '',
pluginOptions: {
i18n: {
localized: true,
},
},
};
const extraLocales = ['fr', 'it', 'es'];
describe('CM API - Basic + compo', () => {
beforeAll(async () => {
await builder.addComponent(compo).addContentType(productWithCompoAndDP).build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
// Create new locales
for (const extraLocale of extraLocales) {
await rq({
method: 'POST',
url: '/i18n/locales',
body: {
code: extraLocale,
name: `Locale name: (${extraLocale})`,
isDefault: false,
},
});
}
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Create product with compo', async () => {
const product = {
name: 'Product 1',
description: 'Product description',
compo: {
name: 'compo name',
description: 'short',
},
};
const res = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product,
});
expect(res.statusCode).toBe(201);
expect(res.body.data).toMatchObject(product);
expect(res.body.data.publishedAt).toBeNull();
data.productsWithCompoAndDP.push(res.body.data);
});
test('Read product with compo', async () => {
const res = await rq({
method: 'GET',
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${data.productsWithCompoAndDP[0].documentId}`,
});
expect(res.statusCode).toBe(200);
expect(res.body.data).toMatchObject(data.productsWithCompoAndDP[0]);
expect(res.body.data.publishedAt).toBeNull();
});
test('Update product with compo', async () => {
const product = {
name: 'Product 1 updated',
description: 'Updated Product description',
compo: {
name: 'compo name updated',
description: 'update',
},
};
const res = await rq({
method: 'PUT',
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${data.productsWithCompoAndDP[0].documentId}`,
body: product,
});
expect(res.statusCode).toBe(200);
expect(res.body.data).toMatchObject(product);
expect(res.body.data.documentId).toEqual(data.productsWithCompoAndDP[0].documentId);
expect(res.body.data.publishedAt).toBeNull();
data.productsWithCompoAndDP[0] = res.body.data;
});
test('Delete product with compo', async () => {
const res = await rq({
method: 'DELETE',
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${data.productsWithCompoAndDP[0].documentId}`,
});
expect(res.statusCode).toBe(200);
data.productsWithCompoAndDP.shift();
});
describe('validation', () => {
test('Can create product with compo - compo required', async () => {
const product = {
name: 'Product 1',
description: 'Product description',
compo: null,
};
const res = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product,
});
expect(res.statusCode).toBe(201);
expect(res.body.data).toMatchObject(product);
data.productsWithCompoAndDP.push(res.body.data);
});
test('Can create product with compo - minLength', async () => {
const product = {
name: 'Product 1',
description: 'Product description',
compo: {
name: 'compo name',
description: '',
},
};
const res = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product,
});
expect(res.statusCode).toBe(201);
expect(res.body.data).toMatchObject(product);
data.productsWithCompoAndDP.push(res.body.data);
});
test('Cannot create product with compo - maxLength', async () => {
const product = {
name: 'Product 1',
description: 'Product description',
compo: {
name: 'compo name',
description: 'A very long description that exceed the min length.',
},
};
const res = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product,
});
expect(res.statusCode).toBe(400);
expect(res.body).toMatchObject({
data: null,
error: {
status: 400,
name: 'ValidationError',
message: 'compo.description must be at most 30 characters',
details: {
errors: [
{
path: ['compo', 'description'],
message: 'compo.description must be at most 30 characters',
name: 'ValidationError',
},
],
},
},
});
});
test('Can create product with compo - required', async () => {
const product = {
name: 'Product 1',
description: 'Product description',
compo: {
description: 'short',
},
};
const res = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product,
});
expect(res.statusCode).toBe(201);
expect(res.body.data).toMatchObject(product);
data.productsWithCompoAndDP.push(res.body.data);
});
});
describe('Publication', () => {
test('Can publish product with compo - required', async () => {
const product = {
name: 'Product 1',
description: 'Product description',
compo: {
name: 'compo name',
description: 'short',
},
};
const res = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product,
});
const publishRes = await rq({
method: 'POST',
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${res.body.data.documentId}/actions/publish`,
body: product,
});
expect(publishRes.statusCode).toBe(200);
// TODO: Validate document is published
});
test('Can bulk publish product with compo - required', async () => {
const product1 = {
name: 'Product 1',
description: 'Product description',
compo: {
name: 'compo name',
description: 'short',
},
};
const product2 = {
name: 'Product 2',
description: 'Product description',
compo: {
name: 'compo name',
description: 'short',
},
};
const {
body: {
data: { documentId: documentId1 },
},
} = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product1,
});
const {
body: {
data: { documentId: documentId2 },
},
} = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product2,
});
const publishRes = await rq({
method: 'POST',
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/actions/bulkPublish`,
body: {
documentIds: [documentId1, documentId2],
},
});
expect(publishRes.statusCode).toBe(200);
expect(publishRes.body).toMatchObject({ count: 2 });
});
test('BulkPublish across multiple documents and locales', async () => {
// Create multiple documents in the default locales
const numberOfDocuments = 5;
const defaultDocuments = {};
for (let i = 0; i < numberOfDocuments; i += 1) {
const product = {
name: `Product ${i}`,
description: `Product description ${i}`,
compo: {
name: `compo name ${i}`,
description: `short ${i}`,
},
};
const {
body: {
data: { documentId },
},
} = await rq({
method: 'POST',
url: '/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp',
body: product,
});
defaultDocuments[documentId] = product;
}
// Add extra locales to each document
await async.map(Object.entries(defaultDocuments), async ([documentId, product]) => {
await async.map(extraLocales, async (locale) => {
await rq({
method: 'PUT',
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/${documentId}`,
body: {
name: `Product ${product.name} ${locale}`,
compo: {
name: `compo name ${product.compo.name} ${locale}`,
description: `short ${product.compo.description} ${locale}`,
},
},
qs: {
locale,
},
});
});
});
// Bulk publish all the documents
const bulkPublishRes = await rq({
method: 'POST',
url: `/content-manager/collection-types/api::product-with-compo-and-dp.product-with-compo-and-dp/actions/bulkPublish`,
body: {
documentIds: Object.keys(defaultDocuments),
},
qs: {
locale: ['en', ...extraLocales],
},
});
expect(bulkPublishRes.statusCode).toBe(200);
expect(bulkPublishRes.body).toMatchObject({
count: numberOfDocuments * (extraLocales.length + 1),
});
// TODO verify that all the drafts are still there
});
});
});