mirror of
https://github.com/strapi/strapi.git
synced 2025-07-19 15:06:11 +00:00
526 lines
18 KiB
JavaScript
526 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
const { createStrapiInstance } = require('api-tests/strapi');
|
|
const { createAuthRequest, createRequest } = require('api-tests/request');
|
|
const { createTestBuilder } = require('api-tests/builder');
|
|
const { describeOnCondition } = require('api-tests/utils');
|
|
|
|
const {
|
|
STAGE_MODEL_UID,
|
|
WORKFLOW_MODEL_UID,
|
|
ENTITY_STAGE_ATTRIBUTE,
|
|
} = require('../../../../packages/core/admin/ee/server/constants/workflows');
|
|
|
|
const defaultStages = require('../../../../packages/core/admin/ee/server/constants/default-stages.json');
|
|
|
|
const edition = process.env.STRAPI_DISABLE_EE === 'true' ? 'CE' : 'EE';
|
|
|
|
const productUID = 'api::product.product';
|
|
const model = {
|
|
draftAndPublish: true,
|
|
pluginOptions: {},
|
|
singularName: 'product',
|
|
pluralName: 'products',
|
|
displayName: 'Product',
|
|
kind: 'collectionType',
|
|
attributes: {
|
|
name: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
};
|
|
|
|
describeOnCondition(edition === 'EE')('Review workflows', () => {
|
|
const builder = createTestBuilder();
|
|
|
|
const requests = {
|
|
public: null,
|
|
admin: null,
|
|
};
|
|
let strapi;
|
|
let hasRW;
|
|
let defaultStage;
|
|
let secondStage;
|
|
let testWorkflow;
|
|
|
|
const createEntry = async (uid, data) => {
|
|
const { body } = await requests.admin({
|
|
method: 'POST',
|
|
url: `/content-manager/collection-types/${uid}`,
|
|
body: data,
|
|
});
|
|
return body;
|
|
};
|
|
|
|
const findAll = async (uid) => {
|
|
const { body } = await requests.admin({
|
|
method: 'GET',
|
|
url: `/content-manager/collection-types/${uid}`,
|
|
});
|
|
return body;
|
|
};
|
|
|
|
const updateContentType = async (uid, data) => {
|
|
const result = await requests.admin({
|
|
method: 'PUT',
|
|
url: `/content-type-builder/content-types/${uid}`,
|
|
body: data,
|
|
});
|
|
|
|
expect(result.statusCode).toBe(201);
|
|
};
|
|
|
|
const restart = async () => {
|
|
await strapi.destroy();
|
|
strapi = await createStrapiInstance();
|
|
requests.admin = await createAuthRequest({ strapi });
|
|
};
|
|
|
|
const getRWMorphTableResults = async (connection) =>
|
|
connection
|
|
.select('*')
|
|
.from('strapi_workflows_stages_related_morphs')
|
|
.where('related_type', productUID);
|
|
|
|
beforeAll(async () => {
|
|
await builder.addContentTypes([model]).build();
|
|
// eslint-disable-next-line node/no-extraneous-require
|
|
hasRW = require('@strapi/strapi/lib/utils/ee').features.isEnabled('review-workflows');
|
|
|
|
strapi = await createStrapiInstance();
|
|
requests.public = createRequest({ strapi });
|
|
requests.admin = await createAuthRequest({ strapi });
|
|
|
|
defaultStage = await strapi.query(STAGE_MODEL_UID).create({
|
|
data: { name: 'Stage' },
|
|
});
|
|
secondStage = await strapi.query(STAGE_MODEL_UID).create({
|
|
data: { name: 'Stage 2' },
|
|
});
|
|
testWorkflow = await strapi.query(WORKFLOW_MODEL_UID).create({
|
|
data: {
|
|
uid: 'workflow',
|
|
stages: [defaultStage.id, secondStage.id],
|
|
},
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await strapi.destroy();
|
|
await builder.cleanup();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
testWorkflow = await strapi.query(WORKFLOW_MODEL_UID).update({
|
|
where: { id: testWorkflow.id },
|
|
data: {
|
|
uid: 'workflow',
|
|
stages: [defaultStage.id, secondStage.id],
|
|
},
|
|
});
|
|
await updateContentType(productUID, {
|
|
components: [],
|
|
contentType: model,
|
|
});
|
|
});
|
|
|
|
describe('Get workflows', () => {
|
|
test("It shouldn't be available for public", async () => {
|
|
const res = await requests.public.get('/admin/review-workflows/workflows');
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(401);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(Array.isArray(res.body)).toBeFalsy();
|
|
}
|
|
});
|
|
test('It should be available for every connected users (admin)', async () => {
|
|
const res = await requests.admin.get('/admin/review-workflows/workflows');
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(200);
|
|
expect(Array.isArray(res.body.data)).toBeTruthy();
|
|
// Why 2 workflows ? One added by the test, the other one should be the default workflow added in bootstrap
|
|
expect(res.body.data).toHaveLength(2);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(Array.isArray(res.body)).toBeFalsy();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Get one workflow', () => {
|
|
test("It shouldn't be available for public", async () => {
|
|
const res = await requests.public.get(`/admin/review-workflows/workflows/${testWorkflow.id}`);
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(401);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
test('It should be available for every connected users (admin)', async () => {
|
|
const res = await requests.admin.get(`/admin/review-workflows/workflows/${testWorkflow.id}`);
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.data).toBeInstanceOf(Object);
|
|
expect(res.body.data).toEqual(testWorkflow);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Get workflow stages', () => {
|
|
test("It shouldn't be available for public", async () => {
|
|
const res = await requests.public.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}?populate=stages`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(401);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
test('It should be available for every connected users (admin)', async () => {
|
|
const res = await requests.admin.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}?populate=stages`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.data).toBeInstanceOf(Object);
|
|
expect(res.body.data.stages).toBeInstanceOf(Array);
|
|
expect(res.body.data.stages).toHaveLength(2);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(Array.isArray(res.body)).toBeFalsy();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Get stages', () => {
|
|
test("It shouldn't be available for public", async () => {
|
|
const res = await requests.public.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(401);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
test('It should be available for every connected users (admin)', async () => {
|
|
const res = await requests.admin.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(200);
|
|
expect(Array.isArray(res.body.data)).toBeTruthy();
|
|
expect(res.body.data).toHaveLength(2);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(Array.isArray(res.body)).toBeFalsy();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Get stage by id', () => {
|
|
test("It shouldn't be available for public", async () => {
|
|
const res = await requests.public.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages/${secondStage.id}`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(401);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
test('It should be available for every connected users (admin)', async () => {
|
|
const res = await requests.admin.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages/${secondStage.id}`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.data).toBeInstanceOf(Object);
|
|
expect(res.body.data).toEqual(secondStage);
|
|
} else {
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Replace stages of a workflow', () => {
|
|
let stagesUpdateData;
|
|
|
|
beforeEach(() => {
|
|
stagesUpdateData = [
|
|
defaultStage,
|
|
{ id: secondStage.id, name: 'new_name' },
|
|
{ name: 'new stage' },
|
|
];
|
|
});
|
|
|
|
test("It shouldn't be available for public", async () => {
|
|
const stagesRes = await requests.public.put(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`,
|
|
stagesUpdateData
|
|
);
|
|
const workflowRes = await requests.public.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(stagesRes.status).toBe(401);
|
|
expect(workflowRes.status).toBe(401);
|
|
} else {
|
|
expect(stagesRes.status).toBe(404);
|
|
expect(stagesRes.body.data).toBeUndefined();
|
|
expect(workflowRes.status).toBe(404);
|
|
expect(workflowRes.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
test('It should be available for every connected users (admin)', async () => {
|
|
const stagesRes = await requests.admin.put(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`,
|
|
{ body: { data: stagesUpdateData } }
|
|
);
|
|
const workflowRes = await requests.admin.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}?populate=*`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(stagesRes.status).toBe(200);
|
|
expect(stagesRes.body.data).toBeInstanceOf(Object);
|
|
expect(stagesRes.body.data.id).toEqual(testWorkflow.id);
|
|
expect(workflowRes.status).toBe(200);
|
|
expect(workflowRes.body.data).toBeInstanceOf(Object);
|
|
expect(workflowRes.body.data.stages).toBeInstanceOf(Array);
|
|
expect(workflowRes.body.data.stages[0]).toMatchObject(stagesUpdateData[0]);
|
|
expect(workflowRes.body.data.stages[1]).toMatchObject(stagesUpdateData[1]);
|
|
expect(workflowRes.body.data.stages[2]).toMatchObject({
|
|
id: expect.any(Number),
|
|
...stagesUpdateData[2],
|
|
});
|
|
} else {
|
|
expect(stagesRes.status).toBe(404);
|
|
expect(stagesRes.body.data).toBeUndefined();
|
|
expect(workflowRes.status).toBe(404);
|
|
expect(workflowRes.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
test('It should throw an error if trying to delete all stages in a workflow', async () => {
|
|
const stagesRes = await requests.admin.put(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}/stages`,
|
|
{ body: { data: [] } }
|
|
);
|
|
const workflowRes = await requests.admin.get(
|
|
`/admin/review-workflows/workflows/${testWorkflow.id}?populate=*`
|
|
);
|
|
|
|
if (hasRW) {
|
|
expect(stagesRes.status).toBe(400);
|
|
expect(stagesRes.body.error).toBeDefined();
|
|
expect(stagesRes.body.error.name).toEqual('ApplicationError');
|
|
expect(stagesRes.body.error.message).toBeDefined();
|
|
expect(workflowRes.status).toBe(200);
|
|
expect(workflowRes.body.data).toBeInstanceOf(Object);
|
|
expect(workflowRes.body.data.stages).toBeInstanceOf(Array);
|
|
expect(workflowRes.body.data.stages[0]).toMatchObject({ id: defaultStage.id });
|
|
expect(workflowRes.body.data.stages[1]).toMatchObject({ id: secondStage.id });
|
|
} else {
|
|
expect(stagesRes.status).toBe(404);
|
|
expect(stagesRes.body.data).toBeUndefined();
|
|
expect(workflowRes.status).toBe(404);
|
|
expect(workflowRes.body.data).toBeUndefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Enabling/Disabling review workflows on a content type', () => {
|
|
beforeAll(async () => {
|
|
await createEntry(productUID, { name: 'Product' });
|
|
await createEntry(productUID, { name: 'Product 1' });
|
|
await createEntry(productUID, { name: 'Product 2' });
|
|
});
|
|
|
|
test('when enabled on a content type, entries of this type should be added to the first stage of the workflow', async () => {
|
|
await updateContentType(productUID, {
|
|
components: [],
|
|
contentType: { ...model, reviewWorkflows: true },
|
|
});
|
|
await restart();
|
|
|
|
const response = await requests.admin({
|
|
method: 'GET',
|
|
url: `/content-type-builder/content-types/api::product.product`,
|
|
});
|
|
|
|
expect(response.body.data.schema.reviewWorkflows).toBeTruthy();
|
|
|
|
const morphTableResults = await getRWMorphTableResults(strapi.db.getConnection());
|
|
|
|
expect(morphTableResults.length).toEqual(3);
|
|
for (let i = 0; i < morphTableResults.length; i += 1) {
|
|
const entry = morphTableResults[i];
|
|
expect(entry.related_id).toEqual(i + 1);
|
|
expect(entry.order).toEqual(1);
|
|
}
|
|
});
|
|
|
|
test('when disabled entries in the content type should be removed from any workflow stage', async () => {
|
|
await updateContentType(productUID, {
|
|
components: [],
|
|
contentType: { ...model, reviewWorkflows: false },
|
|
});
|
|
|
|
await restart();
|
|
|
|
const response = await requests.admin({
|
|
method: 'GET',
|
|
url: `/content-type-builder/content-types/api::product.product`,
|
|
});
|
|
expect(response.body.data.schema.reviewWorkflows).toBeFalsy();
|
|
|
|
const morphTableResults = await getRWMorphTableResults(strapi.db.getConnection());
|
|
expect(morphTableResults.length).toEqual(0);
|
|
});
|
|
});
|
|
|
|
describe('update a stage on an entity', () => {
|
|
describe('Review Workflow is enabled', () => {
|
|
beforeAll(async () => {
|
|
await updateContentType(productUID, {
|
|
components: [],
|
|
contentType: { ...model, reviewWorkflows: true },
|
|
});
|
|
await restart();
|
|
});
|
|
test('Should update the accordingly on an entity', async () => {
|
|
const entry = await createEntry(productUID, { name: 'Product' });
|
|
|
|
const response = await requests.admin({
|
|
method: 'PUT',
|
|
url: `/admin/content-manager/collection-types/${productUID}/${entry.id}/stage`,
|
|
body: {
|
|
data: { id: secondStage.id },
|
|
},
|
|
});
|
|
|
|
expect(response.status).toEqual(200);
|
|
expect(response.body.data[ENTITY_STAGE_ATTRIBUTE]).toEqual(
|
|
expect.objectContaining({ id: secondStage.id })
|
|
);
|
|
});
|
|
test('Should throw an error if stage does not exist', async () => {
|
|
const entry = await createEntry(productUID, { name: 'Product' });
|
|
|
|
const response = await requests.admin({
|
|
method: 'PUT',
|
|
url: `/admin/content-manager/collection-types/${productUID}/${entry.id}/stage`,
|
|
body: {
|
|
data: { id: 1234 },
|
|
},
|
|
});
|
|
|
|
expect(response.status).toEqual(400);
|
|
expect(response.body.error).toBeDefined();
|
|
expect(response.body.error.name).toEqual('ApplicationError');
|
|
expect(response.body.error.message).toEqual('Selected stage does not exist');
|
|
});
|
|
});
|
|
describe('Review Workflow is disabled', () => {
|
|
beforeAll(async () => {
|
|
await updateContentType(productUID, {
|
|
components: [],
|
|
contentType: { ...model, reviewWorkflows: false },
|
|
});
|
|
await restart();
|
|
});
|
|
test('Should not update the entity', async () => {
|
|
const entry = await createEntry(productUID, { name: 'Product' });
|
|
|
|
const response = await requests.admin({
|
|
method: 'PUT',
|
|
url: `/admin/content-manager/collection-types/${productUID}/${entry.id}/stage`,
|
|
body: {
|
|
data: { id: secondStage.id },
|
|
},
|
|
});
|
|
|
|
expect(response.status).toEqual(400);
|
|
expect(response.body.error).toBeDefined();
|
|
expect(response.body.error.name).toBe('ApplicationError');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Creating an entity in a review workflow content type', () => {
|
|
beforeAll(async () => {
|
|
await updateContentType(productUID, {
|
|
components: [],
|
|
contentType: { ...model, reviewWorkflows: true },
|
|
});
|
|
await restart();
|
|
});
|
|
|
|
test('when review workflows is enabled on a content type, new entries should be added to the first stage of the default workflow', async () => {
|
|
const adminResponse = await createEntry(productUID, { name: 'Product' });
|
|
expect(await adminResponse[ENTITY_STAGE_ATTRIBUTE].name).toEqual(defaultStages[0].name);
|
|
});
|
|
});
|
|
|
|
describe('Deleting a stage when content already exists', () => {
|
|
test('When content exists in a review stage and this stage is deleted, the content should be moved to the nearest available stage', async () => {
|
|
// Get the default workflow stages
|
|
const res = await requests.admin.get(`/admin/review-workflows/workflows/1/stages`);
|
|
const defaultStages = res.body.data;
|
|
|
|
// Get all products and move ~30% of them to the last stage of the workflow
|
|
const entriesMovedToEnd = [];
|
|
const productsBefore = await findAll(productUID);
|
|
productsBefore.results.forEach(async (entry) => {
|
|
if (Math.random() < 0.3) {
|
|
await requests.admin.put(
|
|
`/admin/content-manager/collection-types/${productUID}/${entry.id}/stage`,
|
|
{
|
|
body: {
|
|
data: { id: defaultStages.slice(-1)[0].id },
|
|
},
|
|
}
|
|
);
|
|
entriesMovedToEnd.push(entry.id);
|
|
}
|
|
});
|
|
|
|
// Delete the first and last stage stage of the default workflow
|
|
await requests.admin.put(`/admin/review-workflows/workflows/1/stages`, {
|
|
body: { data: defaultStages.slice(1, defaultStages.length - 1) },
|
|
});
|
|
|
|
// Expect the content in these stages to be moved to the nearest available stage
|
|
const productsAfter = await findAll(productUID);
|
|
productsAfter.results.forEach(async (entry) => {
|
|
if (entriesMovedToEnd.includes(entry.id)) {
|
|
expect(await entry[ENTITY_STAGE_ATTRIBUTE].name).toEqual(defaultStages[2].name);
|
|
return;
|
|
}
|
|
expect(await entry[ENTITY_STAGE_ATTRIBUTE].name).toEqual(defaultStages[1].name);
|
|
});
|
|
});
|
|
});
|
|
});
|