Files
directus/tests-blackbox/routes/items/singleton.test.ts
ian f1a8e0446f Fix duplicated results and functions in nested filters (#14798)
* Speed query up by reusing existing aliases which reduces table joins

* Use subquery in top level m2o to remove duplicates

* Fix linting

* Apply distinct on primary key field in subqueries

* Use distinct instead as there are only primary keys

* Apply subquery on top level

* Try remove sub sub query

* Test if working for all vendors

* Add support for _none and _some

* Use subquery only when field depth > 1

* Add tests

* Use original table names for columns with functions (#14690)

* Use original table names for columns with functions

* Extract filter function path parsing as shared util

* Fix filter function path when adding node

* Pass the originalCollectionName into filter functions

* Update unit test

* Replace functions within deep GraphQL

* Fix invalid operator error for _none and _some

* Add filter function tests

* Revert triggering for all vendors

* Simplify aliasMap

* Replace functions in filter within GraphQL aggregate query

* Add API support for filtering of alias field

* Mark schema as optional

* Shift logical operators upwards

* Separate recursive parseFilter

* Rework shifting of logical operators

* Error on invalid usage of _none and _some

* Use inner join to preserve sort order

* Run tests for all vendors

* Reuse aliasMap for sort and filter

* Sort on top level query

* Remove unnecessary limit on wrapper query

* Refactor applyQuery options

* Remove duplicates from nested multi relational sort

* Fix offset in MSSQL requiring OrderBy

* Disable schema cache

* Use inner query only for nested sort or multi relational filter

* Fix MSSQL duplicate order column

* Use inner query only for multi relational

* Additional integration tests

* Order within partition for multi relational sorts

* Rename to directus_row_number

* Fix unit test

* Add base sort and filter tests

* Fix Oracle uppercased rowNumber column

* Fix unit test

* Fix top level query sort with function

* Parse functions in inner query

* Increase clarity with knex.ref()

* Remove sort filter for top level primary key

* Fix unit test

* Bypass queries with groupBy

* Add collection to aliasMap to fix functions in nested sort

* Fix multi relational sort with functions

* Add tests for filter and sort with functions

* Fix accidental deletion of brackets

* Fix top level alias filter node interface

* Update M2M sort tests

* Add M2A tests

* Cast m2a primary key as varchar2 for oracle

* Enable filtering tests for M2A

* Fix prototype polluting assignment in aliasMap

* Remove unnecessary currentKey

* Simplify code to increase readability

Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>

* Fix linting and missing 'this' error

* Revert optional chaining

* Add mysql5 to tests

* Fix mysql5 missing rowNumber()

* Overcome indexing delays in MySQL5

* Verify MySQL5 sorting is in order as the result count varies between runs

* Skip joining when sorting field already exists

* Simplify variable assignment

Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>

* Fix linting

* Reduce duplicate logic with vars

* Transform _func fields in GraphQL only for valid functions

* Fix unit test

* Fix unsupported date_part() in CrDB

Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>
Co-authored-by: Roger Stringer <roger@directus.io>
Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>
Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
2022-12-21 11:56:18 -05:00

230 lines
6.4 KiB
TypeScript

import request from 'supertest';
import { getUrl } from '@common/config';
import vendors from '@common/get-dbs-to-test';
import * as common from '@common/index';
import { collectionSingleton, collectionSingletonO2M, seedDBValues } from './singleton.seed';
import { requestGraphQL, SeedFunctions } from '@common/index';
let isSeeded = false;
beforeAll(async () => {
isSeeded = await seedDBValues();
}, 300000);
test('Seed Database Values', () => {
expect(isSeeded).toStrictEqual(true);
});
describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => {
const localCollectionSingleton = `${collectionSingleton}_${pkType}`;
const localCollectionSingletonO2M = `${collectionSingletonO2M}_${pkType}`;
describe(`pkType: ${pkType}`, () => {
describe('GET /:collection', () => {
describe('retrieves singleton', () => {
it.each(vendors)('%s', async (vendor) => {
// Action
const response = await request(getUrl(vendor))
.get(`/items/${localCollectionSingleton}`)
.set('Authorization', `Bearer ${common.USER.ADMIN.TOKEN}`);
const gqlResponse = await requestGraphQL(getUrl(vendor), false, common.USER.ADMIN.TOKEN, {
query: {
[localCollectionSingleton]: {
name: true,
o2m: {
id: true,
},
},
},
});
// Assert
expect(response.statusCode).toEqual(200);
expect(response.body.data).toMatchObject({ name: 'parent', o2m: expect.anything() });
expect(gqlResponse.statusCode).toEqual(200);
expect(gqlResponse.body.data).toMatchObject({
[localCollectionSingleton]: { name: 'parent', o2m: expect.anything() },
});
});
});
describe('Error handling', () => {
describe('returns an error when an invalid id is used', () => {
it.each(vendors)('%s', async (vendor) => {
// Action
const response = await request(getUrl(vendor))
.get(`/items/${localCollectionSingleton}/invalid_id`)
.set('Authorization', `Bearer ${common.USER.ADMIN.TOKEN}`);
const gqlResponse = await requestGraphQL(getUrl(vendor), false, common.USER.ADMIN.TOKEN, {
query: {
[localCollectionSingleton]: {
__args: {
filter: {
id: {
_eq: 'invalid_id',
},
},
},
name: true,
o2m: {
id: true,
},
},
},
});
// Assert
expect(response.statusCode).toBe(403);
expect(gqlResponse.statusCode).toBe(400);
});
});
});
});
describe('PATCH /:collection', () => {
describe(`updates singleton's name with no relations`, () => {
it.each(vendors)('%s', async (vendor) => {
// Setup
const newName = 'parent_updated';
const newName2 = 'parent_updated2';
// Action
const response = await request(getUrl(vendor))
.patch(`/items/${localCollectionSingleton}`)
.send({ name: newName })
.set('Authorization', `Bearer ${common.USER.ADMIN.TOKEN}`);
const mutationKey = `update_${localCollectionSingleton}`;
const gqlResponse = await requestGraphQL(getUrl(vendor), false, common.USER.ADMIN.TOKEN, {
mutation: {
[mutationKey]: {
__args: {
data: {
name: newName2,
},
},
name: true,
},
},
});
// Assert
expect(response.statusCode).toEqual(200);
expect(response.body.data).toMatchObject({
name: newName,
});
expect(gqlResponse.statusCode).toBe(200);
expect(gqlResponse.body.data[mutationKey]).toEqual({
name: newName2,
});
});
});
describe('updates o2m items', () => {
it.each(vendors)('%s', async (vendor) => {
// Setup
const existingItem = (
await request(getUrl(vendor))
.get(`/items/${localCollectionSingleton}`)
.set('Authorization', `Bearer ${common.USER.ADMIN.TOKEN}`)
).body.data;
const o2mNameNew = 'child_o2m_new';
const o2mNameNew2 = 'child_o2m_new2';
const o2mNameUpdated = 'child_o2m_updated';
const o2mNameUpdated2 = 'child_o2m_updated2';
const body = {
o2m: {
create: [
{
id:
pkType === 'string'
? SeedFunctions.generatePrimaryKeys(pkType, {
quantity: 1,
seed: `${localCollectionSingletonO2M}_update_o2m`,
})[0]
: undefined,
name: o2mNameNew,
},
],
update: [
{
id: existingItem.o2m[0],
name: o2mNameUpdated,
},
],
delete: [],
},
};
// Action
const response = await request(getUrl(vendor))
.patch(`/items/${localCollectionSingleton}?fields=*.*`)
.send(body)
.set('Authorization', `Bearer ${common.USER.ADMIN.TOKEN}`);
const gqlResponsePre = await requestGraphQL(getUrl(vendor), false, common.USER.ADMIN.TOKEN, {
query: {
[localCollectionSingleton]: {
o2m: {
id: true,
name: true,
},
},
},
});
const updatedO2M = gqlResponsePre.body.data[localCollectionSingleton].o2m;
const newO2mItem: any = { name: o2mNameNew2 };
if (pkType === 'string') {
newO2mItem.id = SeedFunctions.generatePrimaryKeys(pkType, {
quantity: 1,
seed: `${localCollectionSingletonO2M}_update_o2m2`,
})[0];
}
updatedO2M.push(newO2mItem);
updatedO2M[0].name = o2mNameUpdated2;
const mutationKey = `update_${localCollectionSingleton}`;
const gqlResponse = await requestGraphQL(getUrl(vendor), false, common.USER.ADMIN.TOKEN, {
mutation: {
[mutationKey]: {
__args: {
data: {
o2m: updatedO2M,
},
},
o2m: {
name: true,
},
},
},
});
// Assert
expect(response.statusCode).toEqual(200);
expect(response.body.data.o2m).toBeDefined();
expect(response.body.data.o2m.length).toBe(2);
expect(response.body.data.o2m.map((item: any) => item.name)).toEqual(
expect.arrayContaining([o2mNameNew, o2mNameUpdated])
);
expect(gqlResponse.statusCode).toEqual(200);
expect(gqlResponse.body.data[mutationKey].o2m).toBeDefined();
expect(gqlResponse.body.data[mutationKey].o2m.length).toBe(3);
expect(gqlResponse.body.data[mutationKey].o2m.map((item: any) => item.name)).toEqual(
expect.arrayContaining([o2mNameNew2, o2mNameUpdated2])
);
});
});
});
});
});