strapi/tests/api/plugins/graphql/relations.test.api.js

953 lines
23 KiB
JavaScript

'use strict';
// Helpers.
const { pick } = require('lodash/fp');
const { createTestBuilder } = require('api-tests/builder');
const { createStrapiInstance } = require('api-tests/strapi');
const { createAuthRequest } = require('api-tests/request');
const builder = createTestBuilder();
let strapi;
let rq;
let graphqlQuery;
// Utils
const selectFields = pick(['name', 'color']);
const rgbColorComponent = {
attributes: {
name: {
type: 'text',
},
red: {
type: 'integer',
},
green: {
type: 'integer',
},
blue: {
type: 'integer',
},
},
displayName: 'rgbColor',
};
const articleModel = {
attributes: {
name: {
type: 'richtext',
},
content: {
type: 'richtext',
},
},
singularName: 'article',
pluralName: 'articles',
displayName: 'Article',
description: '',
collectionName: '',
};
const labelModel = {
attributes: {
name: {
type: 'richtext',
},
articles: {
type: 'relation',
relation: 'manyToMany',
target: 'api::article.article',
targetAttribute: 'labels',
},
color: {
type: 'component',
component: 'default.rgb-color',
repeatable: false,
},
},
singularName: 'label',
pluralName: 'labels',
displayName: 'Label',
description: '',
collectionName: '',
};
const carModel = {
attributes: {
name: {
type: 'text',
},
},
singularName: 'car',
pluralName: 'cars',
displayName: 'Car',
description: '',
collectionName: '',
};
const personModel = {
attributes: {
name: {
type: 'text',
},
privateName: {
type: 'text',
private: true,
},
privateCars: {
type: 'relation',
relation: 'oneToMany',
target: 'api::car.car',
targetAttribute: 'person',
private: true,
},
},
displayName: 'Person',
singularName: 'person',
pluralName: 'people',
description: '',
collectionName: '',
};
describe('Test Graphql Relations API End to End', () => {
beforeAll(async () => {
await builder
.addComponent(rgbColorComponent)
.addContentTypes([articleModel, labelModel, carModel, personModel])
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
graphqlQuery = (body) => {
return rq({
url: '/graphql',
method: 'POST',
body,
});
};
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
describe('Test relations features', () => {
const data = {
labels: [],
articles: [],
people: [],
cars: [],
};
const labelsPayload = [
{ name: 'label 1', color: null },
{ name: 'label 2', color: null },
{ name: 'labelWithColor', color: { name: 'tomato', red: 255, green: 99, blue: 71 } },
];
const articlesPayload = [{ name: 'article 1' }, { name: 'article 2' }];
test.each(labelsPayload)('Create label %o', async (label) => {
const res = await graphqlQuery({
query: /* GraphQL */ `
mutation createLabel($data: LabelInput!) {
createLabel(data: $data) {
data {
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
`,
variables: {
data: label,
},
});
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({
data: {
createLabel: {
data: {
attributes: label,
},
},
},
});
});
test('List labels', async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
{
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
`,
});
const { body } = res;
expect(res.statusCode).toBe(200);
expect(body).toMatchObject({
data: {
labels_connection: {
data: labelsPayload.map((label) => ({
documentId: expect.any(String),
attributes: label,
})),
},
},
});
// assign for later use
data.labels = data.labels.concat(res.body.data.labels_connection.data);
});
test.each(articlesPayload)('Create article linked to every labels %o', async (article) => {
const res = await graphqlQuery({
query: /* GraphQL */ `
mutation createArticle($data: ArticleInput!) {
createArticle(data: $data) {
data {
documentId
attributes {
name
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
}
}
}
`,
variables: {
data: {
...article,
labels: data.labels.map((t) => t.documentId),
},
},
});
const { body } = res;
expect(res.statusCode).toBe(200);
expect(body).toMatchObject({
data: {
createArticle: {
data: {
documentId: expect.any(String),
attributes: {
...selectFields(article),
labels_connection: {
data: expect.arrayContaining(data.labels),
},
},
},
},
},
});
data.articles.push(body.data.createArticle.data);
});
test('List articles with labels', async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
{
articles_connection {
data {
documentId
attributes {
name
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
}
}
}
`,
});
const { body } = res;
expect(res.statusCode).toBe(200);
expect(body).toMatchObject({
data: {
articles_connection: {
data: expect.arrayContaining(data.articles),
},
},
});
// assign for later use
data.articles = res.body.data.articles_connection.data;
});
test('List Labels with articles', async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
{
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
articles_connection {
data {
documentId
attributes {
name
}
}
}
}
}
}
}
`,
});
const { body } = res;
expect(res.statusCode).toBe(200);
expect(body).toMatchObject({
data: {
labels_connection: {
data: expect.arrayContaining(
data.labels.map((label) => ({
documentId: label.documentId,
attributes: {
...label.attributes,
articles_connection: {
data: expect.arrayContaining(
data.articles.map((article) => ({
documentId: article.documentId,
attributes: selectFields(article.attributes),
}))
),
},
},
}))
),
},
},
});
// assign for later use
data.labels = res.body.data.labels_connection.data;
});
test('List labels with articles paginated', async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
query labels($pagination: PaginationArg!) {
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
articles_connection(pagination: $pagination) {
data {
documentId
attributes {
name
}
}
}
}
}
}
}
`,
variables: {
pagination: {
page: 1,
pageSize: 1,
},
},
});
const { body } = res;
expect(res.statusCode).toBe(200);
expect(body).toMatchObject({
data: {
labels_connection: {
data: expect.arrayContaining(
data.labels.map((label) => ({
documentId: label.documentId,
attributes: {
...label.attributes,
articles_connection: {
data: expect.arrayContaining(
data.articles.slice(0, 1).map((article) => ({
documentId: article.documentId,
attributes: selectFields(article.attributes),
}))
),
},
},
}))
),
},
},
});
// assign for later use
data.labels = res.body.data.labels_connection.data;
});
test('Deep query', async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
{
articles_connection(filters: { labels: { name: { contains: "label 1" } } }) {
data {
documentId
attributes {
name
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
}
}
}
`,
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchObject({
data: {
articles_connection: {
data: expect.arrayContaining(data.articles),
},
},
});
});
test('Deep query with empty object param', async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
{
articles_connection(filters: { labels: { name: {} } }) {
data {
documentId
attributes {
name
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
}
}
}
`,
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchObject({
data: {
articles_connection: {
data: expect.arrayContaining(data.articles),
},
},
});
});
test('Relation filtering', async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
{
articles_connection {
data {
documentId
attributes {
name
labels_connection(filters: { name: { endsWith: "Color" } }) {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
}
}
}
`,
});
const { body } = res;
expect(res.statusCode).toBe(200);
expect(body.data?.articles_connection?.data).toBeInstanceOf(Array);
body.data.articles_connection.data.forEach((article) => {
const { data: labels } = article.attributes.labels_connection;
expect(labels).toBeInstanceOf(Array);
for (const label of labels) {
expect(label.attributes).toMatchObject(
expect.objectContaining({ name: expect.stringMatching(/.*Color$/) })
);
}
});
});
test('Update Article relations removes correctly a relation', async () => {
const article = data.articles[0];
const labels = [data.labels[0]];
// if I remove a label from an article is it working
const res = await graphqlQuery({
query: /* GraphQL */ `
mutation updateArticle($documentId: ID!, $data: ArticleInput!) {
updateArticle(documentId: $documentId, data: $data) {
data {
documentId
attributes {
name
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
}
}
}
`,
variables: {
documentId: article.documentId,
data: {
labels: labels.map((label) => label.documentId),
},
},
});
expect(res.body).toMatchObject({
data: {
updateArticle: {
data: {
documentId: article.documentId,
attributes: {
...selectFields(article.attributes),
labels_connection: {
data: labels.map((label) => ({
documentId: label.documentId,
attributes: {
...selectFields(label.attributes),
color: null,
},
})),
},
},
},
},
},
});
});
test('Delete Labels and test Articles relations', async () => {
for (const label of data.labels) {
const res = await graphqlQuery({
query: /* GraphQL */ `
mutation deleteLabel($documentId: ID!) {
deleteLabel(documentId: $documentId) {
documentId
}
}
`,
variables: {
documentId: label.documentId,
},
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchObject({
data: {
deleteLabel: {
documentId: label.documentId,
},
},
});
}
const res = await graphqlQuery({
query: /* GraphQL */ `
{
articles_connection {
data {
documentId
attributes {
name
labels_connection {
data {
documentId
attributes {
name
color {
name
red
green
blue
}
}
}
}
}
}
}
}
`,
});
const { body } = res;
expect(res.statusCode).toBe(200);
expect(body).toMatchObject({
data: {
articles_connection: {
data: expect.arrayContaining(
data.articles.map((article) => ({
documentId: article.documentId,
attributes: {
...selectFields(article.attributes),
labels_connection: { data: [] },
},
}))
),
},
},
});
});
test('Delete Articles', async () => {
for (const article of data.articles) {
const res = await graphqlQuery({
query: /* GraphQL */ `
mutation deleteArticle($documentId: ID!) {
deleteArticle(documentId: $documentId) {
documentId
}
}
`,
variables: {
documentId: article.documentId,
},
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchObject({
data: {
deleteArticle: {
documentId: article.documentId,
},
},
});
}
});
test('Create person', async () => {
const person = {
name: 'Chuck Norris',
};
const res = await graphqlQuery({
query: /* GraphQL */ `
mutation createPerson($data: PersonInput!) {
createPerson(data: $data) {
data {
documentId
attributes {
name
}
}
}
}
`,
variables: {
data: person,
},
});
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({
data: {
createPerson: {
data: {
documentId: expect.anything(),
attributes: {
name: person.name,
},
},
},
},
});
data.people.push(res.body.data.createPerson.data);
});
test("Can't list a private field", async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
{
people_connection {
data {
attributes {
name
privateName
}
}
}
}
`,
});
expect(res.statusCode).toBe(400);
expect(res.body).toMatchObject({
errors: [
{
message: 'Cannot query field "privateName" on type "Person".',
},
],
});
});
test('Create a car linked to a person (oneToMany)', async () => {
const car = {
name: 'Peugeot 508',
person: data.people[0].documentId,
};
const res = await graphqlQuery({
query: /* GraphQL */ `
mutation createCar($data: CarInput!) {
createCar(data: $data) {
data {
documentId
attributes {
name
person {
data {
documentId
attributes {
name
}
}
}
}
}
}
}
`,
variables: {
data: car,
},
});
expect(res.statusCode).toBe(200);
expect(res.body).toMatchObject({
data: {
createCar: {
data: {
documentId: expect.anything(),
attributes: {
name: car.name,
person: {
data: data.people[0],
},
},
},
},
},
});
data.cars.push({ documentId: res.body.data.createCar.data.documentId });
});
test("Can't list a private oneToMany relation", async () => {
const res = await graphqlQuery({
query: /* GraphQL */ `
{
people_connection {
data {
attributes {
name
privateCars
}
}
}
}
`,
});
expect(res.statusCode).toBe(400);
expect(res.body).toMatchObject({
errors: [
{
message: 'Cannot query field "privateCars" on type "Person".',
},
],
});
});
test(`Can't edit a private relation `, async () => {
const newPerson = {
name: 'Check Norris Junior',
privateCars: [],
};
const mutationRes = await graphqlQuery({
query: /* GraphQL */ `
mutation updatePerson($documentId: ID!, $data: PersonInput!) {
updatePerson(documentId: $documentId, data: $data) {
data {
documentId
}
}
}
`,
variables: {
documentId: data.people[0].documentId,
data: newPerson,
},
});
expect(mutationRes.statusCode).toBe(400);
expect(mutationRes.body.errors[0].message).toBe(
`Variable "$data" got invalid value { name: "Check Norris Junior", privateCars: [] }; Field "privateCars" is not defined by type "PersonInput".`
);
const queryRes = await graphqlQuery({
query: /* GraphQL */ `
query ($documentId: ID!) {
car(documentId: $documentId) {
data {
attributes {
person {
data {
documentId
}
}
}
}
}
}
`,
variables: {
documentId: data.cars[0].documentId,
},
});
expect(queryRes.statusCode).toBe(200);
expect(queryRes.body).toEqual({
data: {
car: {
data: {
attributes: {
person: { data: { documentId: data.people[0].documentId } },
},
},
},
},
});
});
});
});