1
import { useEnv } from '@directus/env';
2
import { parseFilter, parseJSON } from '@directus/utils';
3
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
4
import { sanitizeQuery } from './sanitize-query.js';
6
// This is required because logger uses global env which is imported before the tests run. Can be
7
// reduce to just mock the file when logger is also using useLogger everywhere @TODO
8
vi.mock('@directus/env', () => ({ useEnv: vi.fn().mockReturnValue({}) }));
10
vi.mock('@directus/utils', async () => {
11
const actual = await vi.importActual<typeof import('@directus/utils')>('@directus/utils');
15
parseJSON: vi.fn().mockImplementation(actual.parseJSON),
16
parseFilter: vi.fn().mockImplementation((value) => value),
21
vi.mocked(useEnv).mockReturnValue({});
28
describe('limit', () => {
29
test.each([-1, 0, 100])('should accept number %i', (limit) => {
30
const sanitizedQuery = sanitizeQuery({ limit });
32
expect(sanitizedQuery.limit).toBe(limit);
35
test('should accept string 1', () => {
38
const sanitizedQuery = sanitizeQuery({ limit });
40
expect(sanitizedQuery.limit).toBe(1);
44
describe('max limit', () => {
45
test('should replace -1', () => {
46
vi.mocked(useEnv).mockReturnValue({ QUERY_LIMIT_MAX: 100 });
48
const sanitizedQuery = sanitizeQuery({ limit: -1 });
50
expect(sanitizedQuery.limit).toBe(100);
53
test.each([1, 25, 150])('should accept number %i', (limit) => {
54
vi.mocked(useEnv).mockReturnValue({ QUERY_LIMIT_MAX: 100 });
56
const sanitizedQuery = sanitizeQuery({ limit });
58
expect(sanitizedQuery.limit).toBe(limit);
61
test('should apply max if no limit passed in request', () => {
62
vi.mocked(useEnv).mockReturnValue({ QUERY_LIMIT_DEFAULT: 100, QUERY_LIMIT_MAX: 1000 });
64
const sanitizedQuery = sanitizeQuery({});
66
expect(sanitizedQuery.limit).toBe(100);
69
test('should apply lower value if no limit passed in request', () => {
70
vi.mocked(useEnv).mockReturnValue({ QUERY_LIMIT_MAX: 100, QUERY_LIMIT_DEFAULT: 25 });
72
const sanitizedQuery = sanitizeQuery({});
74
expect(sanitizedQuery.limit).toBe(25);
77
test('should apply limit from request if no max defined', () => {
78
const sanitizedQuery = sanitizeQuery({ limit: 150 });
80
expect(sanitizedQuery.limit).toBe(150);
83
test('should apply limit from request if max is unlimited', () => {
84
vi.mocked(useEnv).mockReturnValue({ QUERY_LIMIT_MAX: -1 });
86
const sanitizedQuery = sanitizeQuery({ limit: 150 });
88
expect(sanitizedQuery.limit).toBe(150);
92
describe('fields', () => {
93
test('should accept valid value', () => {
94
const fields = ['field_a', 'field_b'];
96
const sanitizedQuery = sanitizeQuery({ fields });
98
expect(sanitizedQuery.fields).toEqual(['field_a', 'field_b']);
101
test('should split as csv when it is a string', () => {
102
const fields = 'field_a,field_b';
104
const sanitizedQuery = sanitizeQuery({ fields });
106
expect(sanitizedQuery.fields).toEqual(['field_a', 'field_b']);
109
test('should split as nested csv when it is an array', () => {
110
const fields = ['field_a,field_b', 'field_c'];
112
const sanitizedQuery = sanitizeQuery({ fields });
114
expect(sanitizedQuery.fields).toEqual(['field_a', 'field_b', 'field_c']);
117
test('should trim', () => {
118
const fields = [' field_a '];
120
const sanitizedQuery = sanitizeQuery({ fields });
122
expect(sanitizedQuery.fields).toEqual(['field_a']);
126
describe('group', () => {
127
test('should accept valid value', () => {
128
const groupBy = ['group_a', 'group_b'];
130
const sanitizedQuery = sanitizeQuery({ groupBy });
132
expect(sanitizedQuery.group).toEqual(['group_a', 'group_b']);
135
test('should split as csv when it is a string', () => {
136
const groupBy = 'group_a,group_b';
138
const sanitizedQuery = sanitizeQuery({ groupBy });
140
expect(sanitizedQuery.group).toEqual(['group_a', 'group_b']);
143
test('should split as nested csv when it is an array', () => {
144
const groupBy = ['group_a,group_b', 'group_c'];
146
const sanitizedQuery = sanitizeQuery({ groupBy });
148
expect(sanitizedQuery.group).toEqual(['group_a', 'group_b', 'group_c']);
151
test('should trim', () => {
152
const groupBy = [' group_a '];
154
const sanitizedQuery = sanitizeQuery({ groupBy });
156
expect(sanitizedQuery.group).toEqual(['group_a']);
160
describe('aggregate', () => {
161
test('should accept valid value', () => {
162
const aggregate = { count: '*' };
164
const sanitizedQuery = sanitizeQuery({ aggregate });
166
expect(sanitizedQuery.aggregate).toEqual({ count: ['*'] });
169
test('should parse as json when it is a string', () => {
170
const aggregate = '{ "count": "*" }';
172
const sanitizedQuery = sanitizeQuery({ aggregate });
174
expect(sanitizedQuery.aggregate).toEqual({ count: ['*'] });
178
describe('sort', () => {
179
test('should accept valid value', () => {
180
const sort = ['field_a', 'field_b'];
182
const sanitizedQuery = sanitizeQuery({ sort });
184
expect(sanitizedQuery.sort).toEqual(['field_a', 'field_b']);
187
test('should split as csv when it is a string', () => {
188
const sort = 'field_a,field_b';
190
const sanitizedQuery = sanitizeQuery({ sort });
192
expect(sanitizedQuery.sort).toEqual(['field_a', 'field_b']);
195
test('should trim csv array results', () => {
196
const sort = 'field_a, field_b';
198
const sanitizedQuery = sanitizeQuery({ sort });
200
expect(sanitizedQuery.sort).toEqual(['field_a', 'field_b']);
204
describe('filter', () => {
205
test('should accept valid filter', () => {
206
const filter = { field_a: { _eq: 'test' } };
208
const sanitizedQuery = sanitizeQuery({ filter });
210
expect(sanitizedQuery.filter).toEqual({ field_a: { _eq: 'test' } });
213
test('should throw error on invalid filter', () => {
214
const filter = { field_a: null };
216
vi.mocked(parseFilter).mockImplementationOnce(() => {
220
expect(() => sanitizeQuery({ filter })).toThrowError('Invalid query. Invalid filter object.');
223
test('should parse as json when it is a string', () => {
224
const filter = '{ "field_a": { "_eq": "test" } }';
226
const sanitizedQuery = sanitizeQuery({ filter });
228
expect(sanitizedQuery.filter).toEqual({ field_a: { _eq: 'test' } });
231
test('should throw error on invalid json', () => {
232
const filter = '{ "field_a": }';
234
vi.mocked(parseJSON).mockImplementationOnce(() => {
238
expect(() => sanitizeQuery({ filter })).toThrowError('Invalid query. Invalid JSON for filter object.');
242
describe('offset', () => {
243
test('should accept number 1', () => {
246
const sanitizedQuery = sanitizeQuery({ offset });
248
expect(sanitizedQuery.offset).toBe(1);
251
test('should accept string 1', () => {
254
const sanitizedQuery = sanitizeQuery({ offset });
256
expect(sanitizedQuery.offset).toBe(1);
259
test('should accept zero #18370', () => {
262
const sanitizedQuery = sanitizeQuery({ offset });
264
expect(sanitizedQuery.offset).toBe(0);
267
test('should accept string zero #18370', () => {
270
const sanitizedQuery = sanitizeQuery({ offset });
272
expect(sanitizedQuery.offset).toBe(0);
276
describe('page', () => {
277
test('should accept number 1', () => {
280
const sanitizedQuery = sanitizeQuery({ page });
282
expect(sanitizedQuery.page).toBe(1);
285
test('should accept string 1', () => {
288
const sanitizedQuery = sanitizeQuery({ page });
290
expect(sanitizedQuery.page).toBe(1);
293
test('should ignore zero', () => {
296
const sanitizedQuery = sanitizeQuery({ page });
298
expect(sanitizedQuery.page).toBeUndefined();
302
describe('meta', () => {
304
{ input: '*', expected: ['total_count', 'filter_count'] },
305
{ input: 'total_count', expected: ['total_count'] },
306
{ input: 'total_count,filter_count', expected: ['total_count', 'filter_count'] },
307
{ input: ['total_count', 'filter_count'], expected: ['total_count', 'filter_count'] },
308
])('should accept $input', ({ input, expected }) => {
309
const sanitizedQuery = sanitizeQuery({ meta: input }) as any;
311
expect(sanitizedQuery.meta).toEqual(expected);
315
describe('search', () => {
316
test('should accept valid value', () => {
317
const search = 'test';
319
const sanitizedQuery = sanitizeQuery({ search });
321
expect(sanitizedQuery.search).toBe('test');
324
test('should ignore non-string', () => {
325
const search = ['test'];
327
const sanitizedQuery = sanitizeQuery({ search });
329
expect(sanitizedQuery.search).toBeUndefined();
333
describe('export', () => {
334
test('should accept valid value', () => {
335
const format = 'json';
337
const sanitizedQuery = sanitizeQuery({ export: format });
339
expect(sanitizedQuery.export).toBe('json');
343
describe('deep', () => {
344
test('should accept valid value', () => {
345
const deep = { deep: { relational_field: { _sort: ['name'] } } };
347
const sanitizedQuery = sanitizeQuery({ deep });
349
expect(sanitizedQuery.deep).toEqual({ deep: { relational_field: { _sort: ['name'] } } });
352
test('should parse as json when it is a string', () => {
353
const deep = { deep: { relational_field: { _sort: ['name'] } } };
355
const sanitizedQuery = sanitizeQuery({ deep });
357
expect(sanitizedQuery.deep).toEqual({ deep: { relational_field: { _sort: ['name'] } } });
360
test('should ignore non-underscore-prefixed queries', () => {
361
const deep = { deep: { relational_field_a: { _sort: ['name'] }, relational_field_b: { sort: ['name'] } } };
363
const sanitizedQuery = sanitizeQuery({ deep });
365
expect(sanitizedQuery.deep).toEqual({ deep: { relational_field_a: { _sort: ['name'] } } });
368
test('should work in combination with query limit', () => {
369
vi.mocked(useEnv).mockReturnValue({ QUERY_LIMIT_DEFAULT: 100, QUERY_LIMIT_MAX: 1000 });
371
const deep = { deep: { relational_field_a: { _sort: ['name'] } } };
373
const sanitizedQuery = sanitizeQuery({ deep });
375
expect(sanitizedQuery.deep).toEqual({ deep: { relational_field_a: { _limit: 100, _sort: ['name'] } } });
379
describe('alias', () => {
380
test('should accept valid value', () => {
381
const alias = { field_a: 'testField' };
383
const sanitizedQuery = sanitizeQuery({ alias });
385
expect(sanitizedQuery.alias).toEqual({ field_a: 'testField' });
388
test('should parse as json when it is a string', () => {
389
const alias = '{ "field_a": "testField" }';
391
const sanitizedQuery = sanitizeQuery({ alias });
393
expect(sanitizedQuery.alias).toEqual({ field_a: 'testField' });