Files
directus/packages/utils/shared/validate-payload.test.ts
daedalus 8bffd4a55c Fix empty string not checked against _regex validation (#24984)
* allow empty string for regex filter checks

* add changeset

* update tests

* Update packages/utils/shared/generate-joi.ts

Co-authored-by: ian <licitdev@gmail.com>

---------

Co-authored-by: ian <licitdev@gmail.com>
2025-04-10 11:40:42 +08:00

231 lines
7.5 KiB
TypeScript

import type { Filter } from '@directus/types';
import { describe, expect, it, test } from 'vitest';
import { validatePayload } from './validate-payload.js';
describe('validatePayload', () => {
it('returns an empty array when there are no errors', () => {
const mockFilter = { _and: [{ field: { _eq: 'field' } }] } as Filter;
const mockPayload = { field: 'field' };
expect(validatePayload(mockFilter, mockPayload)).toStrictEqual([]);
});
it('returns an array of 1 when there errors with an _and operator', () => {
const mockFilter = { _and: [{ field: { _eq: 'field' } }] } as Filter;
const mockPayload = { field: 'test' };
expect(validatePayload(mockFilter, mockPayload)).toHaveLength(1);
});
it('returns an array of 1 when there errors with an _or operator', () => {
const mockFilter = { _or: [{ field: { _eq: 'field' } }] } as Filter;
const mockPayload = { field: 'test' };
expect(validatePayload(mockFilter, mockPayload)).toHaveLength(1);
});
it('returns an array of 1 when there errors with an _or containing _and operators', () => {
const mockFilter = {
_or: [
{
_and: [{ a: { _eq: 1 } }, { b: { _eq: 1 } }],
},
{
_and: [{ a: { _eq: 2 } }, { b: { _eq: 2 } }],
},
],
} as Filter;
expect(
validatePayload(mockFilter, {
a: 0,
b: 0,
}),
).toHaveLength(4);
expect(
validatePayload(mockFilter, {
a: 0,
b: 1,
}),
).toHaveLength(3);
expect(
validatePayload(mockFilter, {
a: 1,
b: 2,
}),
).toHaveLength(2);
expect(
validatePayload(mockFilter, {
a: 1,
b: 1,
}),
).toHaveLength(0);
expect(
validatePayload(mockFilter, {
a: 2,
b: 2,
}),
).toHaveLength(0);
});
it('returns an empty array when there is no error for filter field that does not exist in payload ', () => {
const mockFilter = { field: { _eq: 'field' } } as Filter;
// intentionally empty payload to simulate "field" was never included in payload
const mockPayload = {};
expect(validatePayload(mockFilter, mockPayload)).toHaveLength(0);
});
it('returns an array of 1 when there is required error for filter field that does not exist in payload and requireAll option flag is true', () => {
const mockFilter = { field: { _eq: 'field' } } as Filter;
// intentionally empty payload to simulate "field" was never included in payload
const mockPayload = {};
const errors = validatePayload(mockFilter, mockPayload, { requireAll: true });
expect(errors).toHaveLength(1);
expect(errors[0]!.message).toBe(`"field" is required`);
});
describe('validates operator: _contains', () => {
const mockFilter = {
_and: [
{
value: {
_contains: 'MATCH-EXACT',
},
},
],
};
const options = { requireAll: true };
test('string values', () => {
expect(validatePayload(mockFilter, { value: 'MATCH-EXACT' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: 'substring-MATCH-EXACT' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: 'match-exact' }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: 'mismatch' }, options)).toHaveLength(1);
});
test('array values', () => {
expect(validatePayload(mockFilter, { value: [123, 'MATCH-EXACT'] }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: [123, 'match-exact'] }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: [] }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: ['mismatch'] }, options)).toHaveLength(1);
});
test('other values', () => {
expect(validatePayload(mockFilter, { value: null }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: undefined }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: 123 }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: {} }, options)).toHaveLength(1);
});
});
describe('validates operator: _icontains', () => {
const mockFilter = {
_and: [
{
value: {
_icontains: 'match-insensitive',
},
},
],
};
const options = { requireAll: true };
test('string values', () => {
expect(validatePayload(mockFilter, { value: 'MATCH-insensitive' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: 'match-insensitive' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: 'substring-match-insensitive' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: 'mismatch' }, options)).toHaveLength(1);
});
test('array values', () => {
expect(validatePayload(mockFilter, { value: [123, 'match-insensitive'] }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: [123, 'MATCH-insensitive'] }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: [123, 'substring-MATCH-insensitive'] }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: [] }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: ['mismatch'] }, options)).toHaveLength(1);
});
test('other values', () => {
expect(validatePayload(mockFilter, { value: null }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: undefined }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: 123 }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: {} }, options)).toHaveLength(1);
});
});
describe('validates operator: _ncontains', () => {
const mockFilter = {
_and: [
{
value: {
_ncontains: 'match',
},
},
],
};
const options = { requireAll: true };
test('string values', () => {
expect(validatePayload(mockFilter, { value: 'foo' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: 'MATCH' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: 'match' }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: 'substring-match' }, options)).toHaveLength(1);
});
test('array values', () => {
expect(validatePayload(mockFilter, { value: [] }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: ['foo'] }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: ['MATCH'] }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: ['foo', 'match'] }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: ['substring-match'] }, options)).toHaveLength(1);
});
test('other values', () => {
expect(validatePayload(mockFilter, { value: null }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: undefined }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: 123 }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: {} }, options)).toHaveLength(1);
});
});
describe('validates operator: _regex', () => {
const mockFilter = {
_and: [
{
value: {
_regex: '^$|foo',
},
},
],
};
const options = { requireAll: true };
test('string value', () => {
expect(validatePayload(mockFilter, { value: 'foo' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: 'bar' }, options)).toHaveLength(1);
});
test('other values', () => {
expect(validatePayload(mockFilter, { value: '' }, options)).toHaveLength(0);
expect(validatePayload(mockFilter, { value: undefined }, options)).toHaveLength(1);
expect(validatePayload(mockFilter, { value: null }, options)).toHaveLength(1);
});
});
});