forest-express-sequelize
Version:
Official Express/Sequelize Liana for Forest
340 lines (289 loc) • 13.8 kB
JavaScript
import moment from 'moment';
import Sequelize from 'sequelize';
import { Schemas, SchemaUtils } from 'forest-express';
import FiltersParser from '../../src/services/filters-parser';
import Operators from '../../src/utils/operators';
import { NoMatchingOperatorError } from '../../src/services/errors';
describe('services > filters-parser', () => {
const schema = {
fields: [],
};
const sequelizeOptions = {
sequelize: Sequelize,
};
const timezone = 'Europe/Paris';
const OPERATORS = Operators.getInstance(sequelizeOptions);
const defaultFiltersParser = new FiltersParser(schema, timezone, sequelizeOptions);
const getExpectedCondition = (field, conditions) => {
const result = {};
const tmp = {};
conditions.forEach((condition) => {
tmp[condition.operator] = condition.value;
});
result[field] = tmp;
return result;
};
const defaultCondition = {
field: 'name',
operator: 'starts_with',
value: 'toto',
};
const defaultExpectedCondition = getExpectedCondition('name', [{ operator: OPERATORS.LIKE, value: 'toto%' }]);
const defaultCondition2 = {
field: 'id',
operator: 'greater_than',
value: 3,
};
const defaultExpectedCondition2 = getExpectedCondition('id', [{ operator: OPERATORS.GT, value: 3 }]);
const defaultDateCondition = {
field: 'createdAt',
operator: 'previous_week',
};
const defaultExpectedDateCondition = getExpectedCondition('createdAt', [
{ operator: OPERATORS.GTE, value: moment().subtract(1, 'week').startOf('isoweek').toDate() },
{ operator: OPERATORS.LTE, value: moment().subtract(1, 'week').endOf('isoweek').toDate() },
]);
describe('formatOperatorValue function', () => {
const values = [5, 'toto,tutu ', null];
values.forEach((value) => {
it(`should return the appropriate value (${typeof value})`, () => {
expect.assertions(15);
expect(defaultFiltersParser.formatOperatorValue('starts_with', value)).toStrictEqual({ [OPERATORS.LIKE]: `${value}%` });
expect(defaultFiltersParser.formatOperatorValue('ends_with', value)).toStrictEqual({ [OPERATORS.LIKE]: `%${value}` });
expect(defaultFiltersParser.formatOperatorValue('contains', value)).toStrictEqual({ [OPERATORS.LIKE]: `%${value}%` });
expect(defaultFiltersParser.formatOperatorValue('not', value)).toStrictEqual({ [OPERATORS.NOT]: value });
expect(defaultFiltersParser.formatOperatorValue('greater_than', value)).toStrictEqual({ [OPERATORS.GT]: value });
expect(defaultFiltersParser.formatOperatorValue('less_than', value)).toStrictEqual({ [OPERATORS.LT]: value });
expect(defaultFiltersParser.formatOperatorValue('before', value)).toStrictEqual({ [OPERATORS.LT]: value });
expect(defaultFiltersParser.formatOperatorValue('after', value)).toStrictEqual({ [OPERATORS.GT]: value });
expect(defaultFiltersParser.formatOperatorValue('not_contains', value)).toStrictEqual({ [OPERATORS.NOT_LIKE]: `%${value}%` });
expect(defaultFiltersParser.formatOperatorValue('not_equal', value)).toStrictEqual({ [OPERATORS.NE]: value });
expect(defaultFiltersParser.formatOperatorValue('present', value)).toStrictEqual({ [OPERATORS.NE]: null });
expect(defaultFiltersParser.formatOperatorValue('equal', value)).toStrictEqual({ [OPERATORS.EQ]: value });
expect(defaultFiltersParser.formatOperatorValue('blank', value)).toStrictEqual({ [OPERATORS.EQ]: null });
expect(defaultFiltersParser.formatOperatorValue('blank', value, true)).toStrictEqual({
[OPERATORS.OR]: [{
[OPERATORS.EQ]: null,
}, {
[OPERATORS.EQ]: '',
}],
});
expect(defaultFiltersParser.formatOperatorValue('in', value)).toStrictEqual(
typeof value === 'string'
? { [OPERATORS.IN]: value.split(',').map((elem) => elem.trim()) }
: { [OPERATORS.IN]: value },
);
});
it('should raise an error on unknown operator', () => {
expect.assertions(1);
expect(defaultFiltersParser.formatOperatorValue.bind('random', value)).toThrow(NoMatchingOperatorError);
});
});
it('should return the appropriate value (array)', () => {
expect.assertions(1);
expect(defaultFiltersParser.formatOperatorValue('includes_all', [1, 2])).toStrictEqual({ [OPERATORS.CONTAINS]: [1, 2] });
});
});
describe('formatAggregatorOperator function', () => {
it('should return the appropriate sequelize operator', () => {
expect.assertions(2);
expect(defaultFiltersParser.formatAggregatorOperator('and')).toStrictEqual(OPERATORS.AND);
expect(defaultFiltersParser.formatAggregatorOperator('or')).toStrictEqual(OPERATORS.OR);
});
it('should raise an error on unknown operator', () => {
expect.assertions(1);
expect(defaultFiltersParser.formatAggregatorOperator.bind('random')).toThrow(NoMatchingOperatorError);
});
});
describe('formatField function', () => {
it('should format default field correctly', () => {
expect.assertions(1);
expect(defaultFiltersParser.formatField('myField')).toStrictEqual('myField');
});
it('should format nested fields correctly', () => {
expect.assertions(1);
expect(defaultFiltersParser.formatField('myCollection:myField')).toStrictEqual('$myCollection.myField$');
});
});
describe('formatCondition function', () => {
it('should handle basic condition correctly', async () => {
expect.assertions(1);
expect(await defaultFiltersParser.formatCondition(defaultCondition))
.toStrictEqual(defaultExpectedCondition);
});
it('should handle time/date condition correctly', async () => {
expect.assertions(1);
expect(await defaultFiltersParser.formatCondition(defaultDateCondition))
.toStrictEqual(defaultExpectedDateCondition);
});
it('should throw an error on empty condition', async () => {
expect.assertions(2);
await expect(defaultFiltersParser.formatCondition()).rejects.toThrow(Error);
await expect(defaultFiltersParser.formatCondition({})).rejects.toThrow(Error);
});
describe('on a smart field', () => {
it('should call formatOperatorValue', async () => {
expect.assertions(5);
const resultIsTextField = 'resultOfIsTextField';
jest.spyOn(defaultFiltersParser, 'isTextField').mockReturnValue(resultIsTextField);
const formattedCondition = 'myFormattedCondition';
jest.spyOn(defaultFiltersParser, 'formatOperatorValue').mockReturnValue(formattedCondition);
const condition = { field: 'power', operator: 'is', value: 'mine' };
expect(await defaultFiltersParser.formatCondition(condition, true))
.toStrictEqual(formattedCondition);
expect(defaultFiltersParser.isTextField).toHaveBeenCalledTimes(1);
expect(defaultFiltersParser.isTextField).toHaveBeenCalledWith('power');
expect(defaultFiltersParser.formatOperatorValue).toHaveBeenCalledTimes(1);
expect(defaultFiltersParser.formatOperatorValue).toHaveBeenCalledWith('is', 'mine', resultIsTextField);
});
});
});
describe('formatAggregation function', () => {
it('should format correctly with \'and\' as aggregator', async () => {
expect.assertions(1);
const expectedFormatedAggregation = {
[OPERATORS.AND]: [defaultExpectedCondition, defaultExpectedDateCondition],
};
expect(await defaultFiltersParser.formatAggregation('and', [
defaultExpectedCondition,
defaultExpectedDateCondition,
])).toStrictEqual(expectedFormatedAggregation);
});
it('should format correctly with \'or\' as aggregator', async () => {
expect.assertions(1);
const expectedFormatedAggregation = {
[OPERATORS.OR]: [defaultExpectedCondition, defaultExpectedDateCondition],
};
expect(await defaultFiltersParser.formatAggregation('or', [
defaultExpectedCondition,
defaultExpectedDateCondition,
])).toStrictEqual(expectedFormatedAggregation);
});
it('should format correctly with \'and\' as nested aggregators', async () => {
expect.assertions(1);
const expectedNestedAggregation = {
[OPERATORS.AND]: [
defaultExpectedCondition,
defaultExpectedCondition2,
],
};
const expectedFormatedAggregation = {
[OPERATORS.OR]: [
defaultExpectedDateCondition,
expectedNestedAggregation,
],
};
expect(await defaultFiltersParser.formatAggregation('or', [
defaultExpectedDateCondition,
expectedNestedAggregation,
])).toStrictEqual(expectedFormatedAggregation);
});
it('should throw an error on empty condition', async () => {
expect.assertions(2);
await expect(defaultFiltersParser.formatAggregation()).rejects.toThrow(Error);
await expect(defaultFiltersParser.formatAggregation({})).rejects.toThrow(Error);
});
});
describe('perform function', () => {
describe('with nothing provided', () => {
it('should be null', async () => {
expect.assertions(1);
expect(await defaultFiltersParser.perform()).toBeNull();
});
});
describe('with a filter on a reference', () => {
const schemaWithFields = {
fields: [{ field: 'car', reference: 'car.id' }],
};
Schemas.schemas = { car: { fields: [{ field: 'id', type: 'Number' }] } };
const filters = '{ "field": "car:brandName", "operator": "starts_with", "value": "Ferrari" }';
const filtersParser = new FiltersParser(schemaWithFields, timezone, sequelizeOptions);
it('should not be null', async () => {
expect.assertions(1);
const spy = jest.spyOn(SchemaUtils, 'isSmartField').mockReturnValue(false);
expect(await filtersParser.perform(filters))
.toStrictEqual({ '$car.brandName$': { [OPERATORS.LIKE]: 'Ferrari%' } });
spy.mockRestore();
});
});
});
describe('getPreviousIntervalCondition function', () => {
describe('working scenarii', () => {
describe('with \'and\' aggregator + flat conditions + 1 previous interval', () => {
const aggregator = {
aggregator: 'and',
conditions: [defaultCondition, defaultCondition2, defaultDateCondition],
};
const filtersParser = new FiltersParser(schema, timezone, sequelizeOptions);
it('should generate the right condition', () => {
expect.assertions(1);
expect(filtersParser.getPreviousIntervalCondition(aggregator))
.toStrictEqual(defaultDateCondition);
});
});
describe('with no aggregator + flat conditions + 1 previous interval', () => {
const aggregator = defaultDateCondition;
const filtersParser = new FiltersParser(schema, timezone, sequelizeOptions);
it('should generate the right condition', () => {
expect.assertions(1);
expect(filtersParser.getPreviousIntervalCondition(aggregator))
.toStrictEqual(defaultDateCondition);
});
});
});
describe('not working scenarii', () => {
describe('with \'and\' aggregator + flat conditions + 2 previous intervals', () => {
const aggregator = {
aggregator: 'and',
conditions: [defaultCondition, defaultDateCondition, defaultDateCondition],
};
const filtersParser = new FiltersParser(schema, timezone, sequelizeOptions);
it('should not generate conditions', () => {
expect.assertions(1);
expect(filtersParser.getPreviousIntervalCondition(aggregator)).toBeNull();
});
});
describe('with \'or\' aggregator + flat conditions + 1 previous interval', () => {
const aggregator = {
aggregator: 'or',
conditions: [defaultCondition, defaultCondition2, defaultDateCondition],
};
const filtersParser = new FiltersParser(schema, timezone, sequelizeOptions);
it('should not generate conditions', () => {
expect.assertions(1);
expect(filtersParser.getPreviousIntervalCondition(aggregator)).toBeNull();
});
});
describe('with \'and\' aggregator + flat conditions + 0 previous interval', () => {
const aggregator = {
aggregator: 'and',
conditions: [defaultCondition, defaultCondition2],
};
const filtersParser = new FiltersParser(schema, timezone, sequelizeOptions);
it('should not generate conditions', () => {
expect.assertions(1);
expect(filtersParser.getPreviousIntervalCondition(aggregator)).toBeNull();
});
});
describe('with \'and\' aggregator + nested conditions + 1 previous interval', () => {
const aggregator = {
aggregator: 'and',
conditions: [{ aggregator: 'or', conditions: [defaultCondition, defaultCondition2] }, defaultDateCondition],
};
const filtersParser = new FiltersParser(schema, timezone, sequelizeOptions);
it('should not generate conditions', () => {
expect.assertions(1);
expect(filtersParser.getPreviousIntervalCondition(aggregator)).toBeNull();
});
});
describe('with no aggregator + flat conditions + 0 previous interval', () => {
const aggregator = defaultCondition;
const filtersParser = new FiltersParser(schema, timezone, sequelizeOptions);
it('should not generate conditions', () => {
expect.assertions(1);
expect(filtersParser.getPreviousIntervalCondition(aggregator)).toBeNull();
});
});
});
});
});