kinvey-flex-sdk
Version:
SDK for creating Kinvey Flex Services
1,384 lines (1,229 loc) • 56.3 kB
JavaScript
/* eslint prefer-arrow-callback: 0 */ // turning off because should.throws breaks with =>
/**
* Copyright (c) 2018 Kinvey Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
const should = require('should');
const randomString = require('uuid').v4;
const Query = require('../../../../lib/service/modules/query');
describe('Query', () => {
describe('constructor', () => {
it('should throw error when fields is not an array', () => {
should.throws(() => {
const query = new Query();
query.fields = {};
return query;
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'fields must be an Array';
});
});
it('should parse a limit from string', () => {
const query = new Query();
query.limit = '3';
query.toPlainObject().limit.should.eql(3);
});
it('should parse a skip from string', () => {
const query = new Query();
query.skip = '10';
query.toPlainObject().skip.should.eql(10);
});
});
describe('fields', () => {
it('should throw an error on invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.fields = {};
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'fields must be an Array';
});
});
it('should set the fields', () => {
const fields = [randomString(), randomString()];
const query = new Query();
query.fields = fields;
query.toPlainObject().fields.should.eql(fields);
});
it('should reset the fields', () => {
const query = new Query();
query.fields = [];
query.toPlainObject().fields.should.deepEqual([]);
});
});
describe('limit', () => {
it('should throw an error on invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.limit = {};
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'limit must be a number';
});
});
it('should set the limit', () => {
const limit = 10;
const query = new Query();
query.limit = limit;
query.toPlainObject().limit.should.eql(limit);
});
it('should unset the limit', () => {
const query = new Query();
query.limit = null;
should.equal(query.toPlainObject().limit, null);
});
});
describe('skip', () => {
it('should throw an error on invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.skip = {};
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'skip must be a number';
});
});
it('should set the skip', () => {
const skip = 10;
const query = new Query();
query.skip = skip;
query.toPlainObject().skip.should.eql(skip);
});
it('should unset the skip', () => {
const query = new Query();
query.skip = 0;
query.toPlainObject().skip.should.eql(0);
});
});
describe('sort', () => {
it('should set the sort', () => {
const sort = {};
sort[randomString()] = 1;
const query = new Query();
query.sort = sort;
query.toPlainObject().sort.should.eql(sort);
});
it('should reset the sort.', () => {
const query = new Query();
query.sort = {};
query.toPlainObject().sort.should.deepEqual({});
});
});
describe('equalTo()', () => {
it('should add an equal to filter', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.equalTo(field, value);
query.toPlainObject().filter[field].should.eql(value);
});
it('should add an equal to filter for = 0', () => {
const field = randomString();
const value = 0;
const query = new Query();
query.equalTo(field, value);
query.toPlainObject().filter[field].should.eql(0);
});
it('should add an equal to filter for = null', () => {
const field = randomString();
const value = null;
const query = new Query();
query.equalTo(field, value);
should.equal(query.toPlainObject().filter[field], null);
});
it('should add an equal to filter for = false', () => {
const field = randomString();
const value = false;
const query = new Query();
query.equalTo(field, value);
query.toPlainObject().filter[field].should.eql(false);
});
it('should add an equal to filter for = empty string', () => {
const field = randomString();
const value = '';
const query = new Query();
query.equalTo(field, value);
query.toPlainObject().filter[field].should.eql('');
});
it('should add an equal to filter for = NaN', () => {
const field = randomString();
const value = NaN;
const query = new Query();
query.equalTo(field, value);
query.toPlainObject().filter[field].should.eql(NaN);
});
it('should add an equal to filter for = empty object', () => {
const field = randomString();
const value = {};
const query = new Query();
query.equalTo(field, value);
query.toPlainObject().filter[field].should.eql({});
});
it('should discard any existing filters on the same field', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.equalTo(field, randomString()); // Should be discarded
query.equalTo(field, value);
query.toPlainObject().filter[field].should.eql(value);
});
it('should return the query', () => {
const query = new Query().equalTo(randomString(), randomString());
query.should.be.an.instanceOf(Query);
});
});
describe('contains()', () => {
it('should accept a single value', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.contains(field, value);
query.filter[field].should.deepEqual({ $in: [value] });
});
it('should accept an array of values', () => {
const field = randomString();
const value = [randomString(), randomString()];
const query = new Query();
query.contains(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $in: value });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.greaterThan(field, randomString());
query.contains(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$gt');
});
it('should return the query', () => {
const query = new Query().contains(randomString(), [randomString()]);
query.should.be.an.instanceOf(Query);
});
});
describe('containsAll()', () => {
it('should accept a single value and add a contains all filter', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.containsAll(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $all: [value] });
});
it('should accept an array of values and add a contains all filter', () => {
const field = randomString();
const value = [randomString(), randomString()];
const query = new Query();
query.containsAll(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $all: value });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.greaterThan(field, randomString());
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$gt');
});
it('should return the query', () => {
const query = new Query().containsAll(randomString(), [randomString()]);
query.should.be.an.instanceOf(Query);
});
});
describe('greaterThan()', () => {
it('should throw an error with invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.greaterThan(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'You must supply a number or string.';
});
});
it('should add a greater than filter', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.greaterThan(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $gt: value });
});
it('should add a greater than filter > 0', () => {
const field = randomString();
const value = 0;
const query = new Query();
query.greaterThan(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $gt: 0 });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.greaterThan(field, randomString());
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().greaterThan(randomString(), randomString());
query.should.be.an.instanceOf(Query);
});
});
describe('greaterThanOrEqualTo()', () => {
it('should throw an error with invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.greaterThanOrEqualTo(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'You must supply a number or string.';
});
});
it('should add a greater than or equal filter', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.greaterThanOrEqualTo(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $gte: value });
});
it('should add a greater than or equal filter for >= 0', () => {
const field = randomString();
const value = 0;
const query = new Query();
query.greaterThanOrEqualTo(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $gte: 0 });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.greaterThanOrEqualTo(field, randomString());
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().greaterThanOrEqualTo(randomString(), randomString());
query.should.be.an.instanceOf(Query);
});
});
describe('lessThan()', () => {
it('should throw an error with invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.lessThan(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'You must supply a number or string.';
});
});
it('should add a less than filter', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.lessThan(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $lt: value });
});
it('should add a less than filter for < 0', () => {
const field = randomString();
const value = 0;
const query = new Query();
query.lessThan(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $lt: 0 });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.lessThan(field, randomString());
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().lessThan(randomString(), randomString());
query.should.be.an.instanceOf(Query);
});
});
describe('lessThanOrEqualTo()', () => {
it('should throw an error with invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.lessThanOrEqualTo(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'You must supply a number or string.';
});
});
it('should add a less than or equal filter', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.lessThanOrEqualTo(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $lte: value });
});
it('should add a less than or equal filter for <= 0', () => {
const field = randomString();
const value = 0;
const query = new Query();
query.lessThanOrEqualTo(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $lte: 0 });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.lessThanOrEqualTo(field, randomString());
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().lessThanOrEqualTo(randomString(), randomString());
query.should.be.an.instanceOf(Query);
});
});
describe('notEqualTo()', () => {
it('should add a not equal to filter', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.notEqualTo(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $ne: value });
});
it('should add an equal to filter for = 0', () => {
const field = randomString();
const value = 0;
const query = new Query();
query.notEqualTo(field, value);
query.toPlainObject().filter[field].should.containEql({ $ne: 0 });
});
it('should add an equal to filter for = null', () => {
const field = randomString();
const value = null;
const query = new Query();
query.notEqualTo(field, value);
query.toPlainObject().filter[field].should.containEql({ $ne: null });
});
it('should add an equal to filter for = false', () => {
const field = randomString();
const value = false;
const query = new Query();
query.notEqualTo(field, value);
query.toPlainObject().filter[field].should.containEql({ $ne: false });
});
it('should add an equal to filter for = empty string', () => {
const field = randomString();
const value = '';
const query = new Query();
query.notEqualTo(field, value);
query.toPlainObject().filter[field].should.containEql({ $ne: '' });
});
it('should add an equal to filter for = NaN', () => {
const field = randomString();
const value = NaN;
const query = new Query();
query.notEqualTo(field, value);
query.toPlainObject().filter[field].should.containEql({ $ne: NaN });
});
it('should add an equal to filter for = empty object', () => {
const field = randomString();
const value = {};
const query = new Query();
query.notEqualTo(field, value);
query.toPlainObject().filter[field].should.containEql({ $ne: {} });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.notEqualTo(field, randomString());
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().notEqualTo(randomString(), randomString());
query.should.be.an.instanceof(Query);
});
it('should filter out fields not equal to null', () => {
const entity1 = { customProperty: null };
const entity2 = { customProperty: randomString() };
const query = new Query().notEqualTo('customProperty', null);
const result = query.process([entity1, entity2]);
result.length.should.eql(1);
result[0].should.deepEqual(entity2);
});
});
describe('notContainedIn()', () => {
it('should accept a single value and add a not contained in filter', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.notContainedIn(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $nin: [value] });
});
it('should accept an array of values and add a not contained in filter', () => {
const field = randomString();
const value = [randomString(), randomString()];
const query = new Query();
query.notContainedIn(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $nin: value });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.notContainedIn(field, randomString());
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().notContainedIn(randomString(), randomString());
query.should.be.an.instanceof(Query);
});
});
describe('and()', () => {
describe('when called with arguments', () => {
it('should throw an error with invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.and(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError'
&& err.debug === 'query argument must be of type: Kinvey.Query[] or Object[].';
});
});
it('should join a query', () => {
const query = new Query();
query.and(new Query());
query.toPlainObject().filter.should.have.property('$and');
query.toPlainObject().filter.$and.length.should.equal(2);
});
it('should join a query object', () => {
const query = new Query();
query.and({ filter: {} });
query.toPlainObject().filter.should.have.property('$and');
query.toPlainObject().filter.$and.length.should.eql(2);
});
it('should join multiple queries at once', () => {
const query = new Query();
query.and(new Query(), new Query());
query.toPlainObject().filter.should.have.property('$and');
query.toPlainObject().filter.$and.length.should.eql(3);
});
it('should return the query', () => {
const query = new Query().and(new Query());
query.should.be.an.instanceOf(Query);
});
});
describe('when called without arguments', () => {
it('should return a subquery', () => {
const query1 = new Query();
const query2 = query1.and();
query2.should.be.an.instanceOf(Query);
query2._parent.should.deepEqual(query1);
});
it('should update the original query', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.and().greaterThan(field, value);
query.toPlainObject().filter.should.have.property('$and');
query.toPlainObject().filter.$and[1][field].should.deepEqual({ $gt: value });
});
});
});
describe('nor()', () => {
describe('when called with arguments', () => {
it('should throw an error with invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.nor(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError'
&& err.debug === 'query argument must be of type: Kinvey.Query[] or Object[].';
});
});
it('should join a query', () => {
const query = new Query();
query.nor(new Query());
query.toPlainObject().filter.should.have.property('$nor');
query.toPlainObject().filter.$nor.length.should.eql(2);
});
it('should join a query object', () => {
const query = new Query();
query.nor({ filter: {} });
query.toPlainObject().filter.should.have.property('$nor');
query.toPlainObject().filter.$nor.length.should.eql(2);
});
it('should join multiple queries at once', () => {
const query = new Query();
query.nor(new Query(), new Query());
query.toPlainObject().filter.should.have.property('$nor');
query.toPlainObject().filter.$nor.length.should.equal(3);
});
it('should return the query', () => {
const query = new Query().nor(new Query());
query.should.be.an.instanceOf(Query);
});
});
describe('when called without arguments', () => {
it('should return a subquery', () => {
const query1 = new Query();
const query2 = query1.nor();
query2.should.be.an.instanceOf(Query);
query2._parent.should.deepEqual(query1);
});
it('should update the original query', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.nor().greaterThan(field, value);
query.toPlainObject().filter.should.have.property('$nor');
query.toPlainObject().filter.$nor[1][field].should.deepEqual({ $gt: value });
});
});
});
describe('or()', () => {
describe('when called with arguments', () => {
it('should throw an error with invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.or(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError'
&& err.debug === 'query argument must be of type: Kinvey.Query[] or Object[].';
});
});
it('should join a query', () => {
const query = new Query();
query.or(new Query());
query.toPlainObject().filter.should.have.property('$or');
query.toPlainObject().filter.$or.length.should.eql(2);
});
it('should join a query object', () => {
const query = new Query();
query.or({ filter: {} });
query.toPlainObject().filter.should.have.property('$or');
query.toPlainObject().filter.$or.length.should.eql(2);
});
it('should join multiple queries at once', () => {
const query = new Query();
query.or(new Query(), new Query());
query.toPlainObject().filter.should.have.property('$or');
query.toPlainObject().filter.$or.length.should.eql(3);
});
it('should return the query', () => {
const query = new Query().or(new Query());
query.should.be.an.instanceOf(Query);
});
});
describe('when called without arguments', () => {
it('should return a subquery', () => {
const query1 = new Query();
const query2 = query1.or();
query2.should.be.an.instanceOf(Query);
query2._parent.should.deepEqual(query1);
});
it('should update the original query', () => {
const field = randomString();
const value = randomString();
const query = new Query();
query.or().greaterThan(field, value);
query.toPlainObject().filter.should.have.property('$or');
query.toPlainObject().filter.$or[1][field].should.deepEqual({ $gt: value });
});
});
});
describe('the exists method', () => {
it('should add an exists filter', () => {
const field = randomString();
const query = new Query();
query.exists(field);
query.toPlainObject().filter[field].should.deepEqual({ $exists: true });
});
it('should add an exists filter with flag set to false', () => {
const field = randomString();
const query = new Query();
query.exists(field, false);
query.toPlainObject().filter[field].should.deepEqual({ $exists: false });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.exists(field);
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().exists(randomString());
query.should.be.an.instanceOf(Query);
});
});
describe('mod()', () => {
it('should throw an error for invalid arguments divisor', () => {
should.throws(() => {
const query = new Query();
query.mod(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'divisor must be a number';
});
});
it('should throw an error for invalid arguments remainder', () => {
should.throws(() => {
const query = new Query();
query.mod(randomString(), 5, null);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'remainder must be a number';
});
});
it('should add a mod filter', () => {
const field = randomString();
const divisor = 5;
const remainder = 0;
const query = new Query();
query.mod(field, divisor, remainder);
query.toPlainObject().filter[field].should.deepEqual({ $mod: [divisor, remainder] });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.mod(field, 5);
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().mod(randomString(), 5);
query.should.be.an.instanceOf(Query);
});
});
describe('matches()', () => {
it('throw an error for unsupported ignoreCase option', () => {
should.throws(() => {
const field = randomString();
const query = new Query();
query.matches(field, /^abc/, { ignoreCase: true });
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'ignoreCase is not supported.';
});
});
it('should throw an error for unanchored expression', () => {
const debugErr = 'regExp must have `^` at the beginning of the expression to make it an anchored expression.';
should.throws(() => {
const field = randomString();
const query = new Query();
query.matches(field, '/abc/');
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === debugErr;
});
});
it('should add a match filter by string', () => {
const field = randomString();
const value = `^${randomString()}`;
const query = new Query();
query.matches(field, value);
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.deepEqual({ $regex: value });
});
it('should add a match filter by RegExp literal', () => {
const field = randomString();
const value = /^foo/;
const query = new Query();
query.matches(field, value);
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.deepEqual({ $regex: value.source });
});
it('should add a match filter by RegExp object.', () => {
const field = randomString();
const value = new RegExp('^foo');
const query = new Query();
query.matches(field, value);
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.deepEqual({ $regex: value.source });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.matches(field, `^${randomString()}`);
query.greaterThan(field, randomString());
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.have.property(['$gt']);
});
it('should return the query', () => {
const field = randomString();
const query = new Query();
const value = query.matches(field, /^foo/);
value.should.deepEqual(query);
});
describe('with options', () => {
it('should throw if the ignoreCase flag is part of the RegExp.', () => {
should.throws(() => {
const field = randomString();
const query = new Query();
query.matches(field, /^foo/i);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'ignoreCase is not supported.';
});
});
it('should unset the ignoreCase flag if `options.ignoreCase` is `false`', () => {
const field = randomString();
const value = /^foo/i;
const query = new Query();
query.matches(field, value, { ignoreCase: false });
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.not.have.property('$options');
});
it('should set the multiline flag if part of the RegExp', () => {
const field = randomString();
const value = /^foo/m;
const query = new Query();
query.matches(field, value);
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.containEql({ $options: 'm' });
});
it('should set the multiline flag if `options.multiline`', () => {
const field = randomString();
const value = /^foo/;
const query = new Query();
query.matches(field, value, { multiline: true });
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.containEql({ $options: 'm' });
});
it('should unset the multiline flag if `options.multiline` is `false`', () => {
const field = randomString();
const value = /^foo/m;
const query = new Query();
query.matches(field, value, { multiline: false });
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.not.have.property('$options');
});
it('should set the extended flag if `options.extended`', () => {
const field = randomString();
const value = `^${randomString()}`;
const query = new Query();
query.matches(field, value, { extended: true });
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.containEql({ $options: 'x' });
});
it('should not set the multiline flag if `options.extended` is `false`', () => {
const field = randomString();
const value = `^${randomString()}`;
const query = new Query();
query.matches(field, value, { extended: false });
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.not.have.property('$options');
});
it('should set the dotMatchesAll flag if `options.dotMatchesAll`', () => {
const field = randomString();
const value = `^${randomString()}`;
const query = new Query();
query.matches(field, value, { dotMatchesAll: true });
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.containEql({ $options: 's' });
});
it('should not set the dotMatchesAll flag if `options.dotMatchesAll` is `false`', () => {
const field = randomString();
const value = `^${randomString()}`;
const query = new Query();
query.matches(field, value, { dotMatchesAll: false });
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.not.have.property('$options');
});
it('should set multiple flags.', () => {
const field = randomString();
const value = /^foo/im;
const query = new Query();
query.matches(field, value, {
ignoreCase: false,
extended: true,
dotMatchesAll: true
});
query.toPlainObject().filter.should.have.property(field);
query.toPlainObject().filter[field].should.containEql({ $options: 'mxs' });
});
});
});
describe('near()', () => {
it('should throw an error on invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.near(randomString(), []);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'coord must be a [number, number]';
});
});
it('should add a near filter', () => {
const field = randomString();
const coord = [-1, 1];
const query = new Query();
query.near(field, coord);
query.toPlainObject().filter[field].should.deepEqual({ $nearSphere: coord });
});
it('should add a near filter, with $maxDistance', () => {
const field = randomString();
const coord = [-1, 1];
const maxDistance = 10;
const query = new Query();
query.near(field, coord, maxDistance);
query.toPlainObject().filter[field].should.deepEqual({ $nearSphere: coord, $maxDistance: maxDistance });
});
it('should return the query', () => {
const query = new Query().near(randomString(), [-1, 1]);
query.should.be.an.instanceOf(Query);
});
});
describe('withinBox()', () => {
it('should throw an error on invalid arguments: bottomLeftCoord', () => {
should.throws(() => {
const query = new Query();
query.withinBox(randomString(), [], [1, 1]);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'bottomLeftCoord must be a [number, number]';
});
});
it('should throw an error on invalid arguments: upperRightCoord', () => {
should.throws(() => {
const query = new Query();
query.withinBox(randomString(), [1, 1], []);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'upperRightCoord must be a [number, number]';
});
});
it('should add a within box filter', () => {
const field = randomString();
const box = [[-1, -1], [1, 1]];
const query = new Query();
query.withinBox(field, box[0], box[1]);
query.toPlainObject().filter[field].should.deepEqual({ $within: { $box: box } });
});
it('should return the query', () => {
const query = new Query().withinBox(randomString(), [-1, -1], [1, 1]);
query.should.be.an.instanceOf(Query);
});
});
describe('withinPolygon()', () => {
it('should throw an error on invalid arguments: coord', () => {
should.throws(() => {
const query = new Query();
query.withinPolygon(randomString(), []);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'coords must be [[number, number]]';
});
});
it('should add a within polygon filter', () => {
const field = randomString();
const polygon = [[-1, -1], [-1, 1], [1, 1]];
const query = new Query();
query.withinPolygon(field, polygon);
query.toPlainObject().filter[field].should.deepEqual({ $within: { $polygon: polygon } });
});
it('should return the query', () => {
const query = new Query().withinPolygon(randomString(), [[-1, -1], [-1, 1], [1, 1]]);
query.should.be.an.instanceOf(Query);
});
});
describe('size()', () => {
it('should throw an error on invalid arguments', () => {
should.throws(() => {
const query = new Query();
query.size(randomString(), null);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'size must be a number';
});
});
it('should add a size filter', () => {
const field = randomString();
const value = 10;
const query = new Query();
query.size(field, value);
query.toPlainObject().filter[field].should.deepEqual({ $size: value });
});
it('should respect any existing filters on the same field', () => {
const field = randomString();
const query = new Query();
query.size(field, 10);
query.containsAll(field, [randomString()]);
query.toPlainObject().filter[field].should.have.property('$all');
});
it('should return the query', () => {
const query = new Query().size(randomString(), 10);
query.should.be.an.instanceOf(Query);
});
});
describe('ascending()', () => {
it('should set the field', () => {
const field = randomString();
const query = new Query();
query.ascending(field);
query.toPlainObject().sort[field].should.eql(1);
});
it('should append a field', () => {
const field1 = randomString();
const field2 = randomString();
const query = new Query();
query.ascending(field1);
query.ascending(field2);
query.toPlainObject().sort[field1].should.eql(1);
query.toPlainObject().sort[field2].should.eql(1);
});
it('should append a descending field', () => {
const field1 = randomString();
const field2 = randomString();
const query = new Query();
query.ascending(field1);
query.descending(field2);
query.toPlainObject().sort[field1].should.eql(1);
query.toPlainObject().sort[field2].should.eql(-1);
});
it('should sort the data in ascending order', () => {
const entity1 = { _id: 1, customProperty: randomString() };
const entity2 = { _id: 2, customProperty: randomString() };
const query = new Query().ascending('_id');
query.fields = ['customProperty'];
const result = query.process([entity2, entity1]);
result[0].customProperty.should.eql(entity1.customProperty);
result[1].customProperty.should.eql(entity2.customProperty);
});
it('should put docs with null or undefined values for sort field at the beginning of the list', () => {
const entity1 = { _id: 1, customProperty: randomString() };
const entity2 = { _id: null, customProperty: randomString() };
const entity3 = { _id: 2, customProperty: randomString() };
const entity4 = { customProperty: randomString() };
const entity5 = { _id: null, customProperty: randomString() };
const query = new Query().ascending('_id');
query.fields = ['customProperty'];
const result = query.process([entity5, entity4, entity1, entity3, entity2]);
result[0].customProperty.should.eql(entity5.customProperty);
result[1].customProperty.should.eql(entity4.customProperty);
result[2].customProperty.should.eql(entity2.customProperty);
result[3].customProperty.should.eql(entity1.customProperty);
result[4].customProperty.should.eql(entity3.customProperty);
});
});
describe('descending()', () => {
it('should set the field', () => {
const field = randomString();
const query = new Query();
query.descending(field);
query.toPlainObject().sort[field].should.eql(-1);
});
it('should append a field', () => {
const field1 = randomString();
const field2 = randomString();
const query = new Query();
query.descending(field1);
query.descending(field2);
query.toPlainObject().sort[field1].should.eql(-1);
query.toPlainObject().sort[field2].should.eql(-1);
});
it('should append a ascending field', () => {
const field1 = randomString();
const field2 = randomString();
const query = new Query();
query.descending(field1);
query.ascending(field2);
query.toPlainObject().sort[field1].should.eql(-1);
query.toPlainObject().sort[field2].should.eql(1);
});
it('should sort the data in descending order', () => {
const entity1 = { _id: 1, customProperty: randomString() };
const entity2 = { _id: 2, customProperty: randomString() };
const query = new Query().descending('_id');
query.fields = ['customProperty'];
const result = query.process([entity1, entity2]);
result[0].customProperty.should.equal(entity2.customProperty);
result[1].customProperty.should.equal(entity1.customProperty);
});
it('should put docs with null or undefined values for sort field at the end of the list', () => {
const entity1 = { _id: 1, customProperty: randomString() };
const entity2 = { _id: null, customProperty: randomString() };
const entity3 = { _id: 2, customProperty: randomString() };
const entity4 = { customProperty: randomString() };
const entity5 = { _id: null, customProperty: randomString() };
const query = new Query().descending('_id');
query.fields = ['customProperty'];
const result = query.process([entity5, entity4, entity1, entity3, entity2]);
result[0].customProperty.should.eql(entity3.customProperty);
result[1].customProperty.should.eql(entity1.customProperty);
result[2].customProperty.should.eql(entity5.customProperty);
result[3].customProperty.should.eql(entity4.customProperty);
result[4].customProperty.should.eql(entity2.customProperty);
});
});
describe('when chained', () => {
it('should respect AND-NOR precedence.', () => {
// A & B ^ C -> ((A & B) ^ C) -> nor(and(A, B), C).
const a = 'A';
const b = 'B';
const c = 'C';
const query = new Query();
query.exists(a)
.and()
.exists(b)
.nor()
.exists(c);
query.toPlainObject().filter.$nor[0].$and.length.should.eql(2);
query.toPlainObject().filter.$nor[0].$and[0].should.have.property(a);
query.toPlainObject().filter.$nor[0].$and[1].should.have.property(b);
query.toPlainObject().filter.$nor[1].should.have.property(c);
});
it('should respect AND-NOR-AND precedence.', () => {
// A & B ^ C & D -> ((A & B) ^ (C & D) -> nor(and(A, B), and(C, D)).
const a = 'A';
const b = 'B';
const c = 'C';
const d = 'D';
const query = new Query();
query.exists(a)
.and()
.exists(b)
.nor()
.exists(c)
.and()
.exists(d);
query.toPlainObject().filter.$nor[0].$and.length.should.eql(2);
query.toPlainObject().filter.$nor[0].$and[0].should.have.property(a);
query.toPlainObject().filter.$nor[0].$and[1].should.have.property(b);
query.toPlainObject().filter.$nor[1].$and.length.should.eql(2);
query.toPlainObject().filter.$nor[1].$and[0].should.have.property(c);
query.toPlainObject().filter.$nor[1].$and[1].should.have.property(d);
});
it('should respect AND-OR precedence.', () => {
// A & B | C -> ((A & B) | C) -> or(and(A, B), C).
const a = 'A';
const b = 'B';
const c = 'C';
const query = new Query();
query.exists(a)
.and()
.exists(b)
.or()
.exists(c);
query.toPlainObject().filter.$or[0].$and.length.should.eql(2);
query.toPlainObject().filter.$or[0].$and[0].should.have.property(a);
query.toPlainObject().filter.$or[0].$and[1].should.have.property(b);
query.toPlainObject().filter.$or[1].should.have.property(c);
});
it('should respect AND-OR-AND precedence.', () => {
// A & B | C & D -> ((A & B) | (C & D) -> or(and(A, B), and(C, D)).
const a = 'A';
const b = 'B';
const c = 'C';
const d = 'D';
const query = new Query();
query.exists(a)
.and()
.exists(b)
.or()
.exists(c)
.and()
.exists(d);
query.toPlainObject().filter.$or[0].$and.length.should.eql(2);
query.toPlainObject().filter.$or[0].$and[0].should.have.property(a);
query.toPlainObject().filter.$or[0].$and[1].should.have.property(b);
query.toPlainObject().filter.$or[1].$and.length.should.eql(2);
query.toPlainObject().filter.$or[1].$and[0].should.have.property(c);
query.toPlainObject().filter.$or[1].$and[1].should.have.property(d);
});
it('should respect NOR-OR precedence.', () => {
// A ^ B | C -> ((A ^ B) | C) -> or(nor(A, B), C).
const a = 'A';
const b = 'B';
const c = 'C';
const query = new Query();
query.exists(a)
.nor()
.exists(b)
.or()
.exists(c);
query.toPlainObject().filter.$or[0].$nor.length.should.eql(2);
query.toPlainObject().filter.$or[0].$nor[0].should.have.property(a);
query.toPlainObject().filter.$or[0].$nor[1].should.have.property(b);
query.toPlainObject().filter.$or[1].should.have.property(c);
});
it('should respect OR-NOR-AND precedence.', () => {
// A | B ^ C & D -> (A | (B ^ (C & D))) -> or(nor(B, and(C, D)), A).
const a = 'A';
const b = 'B';
const c = 'C';
const d = 'D';
const query = new Query();
query.exists(a)
.or()
.exists(b)
.nor()
.exists(c)
.and()
.exists(d);
query.toPlainObject().filter.$or[0].should.have.property(a);
query.toPlainObject().filter.$or[1].$nor[0].should.have.property(b);
query.toPlainObject().filter.$or[1].$nor[1].$and[0].should.have.property(c);
query.toPlainObject().filter.$or[1].$nor[1].$and[1].should.have.property(d);
});
it('should respect (AND-OR)-NOR-AND precedence.', () => {
// (A & B | C) ^ D & E -> (((A & B) | C) ^ (D & E)) ->
// nor(or(and(A, B), C), and(D, E));
const a = 'A';
const b = 'B';
const c = 'C';
const d = 'D';
const e = 'E';
const query = new Query();
query.exists(a)
.and()
.exists(b)
.or()
.exists(c);
query.nor()
.exists(d)
.and()
.exists(e);
query.toPlainObject().filter.$nor[0].$or[0].$and[0].should.have.property(a);
query.toPlainObject().filter.$nor[0].$or[0].$and[1].should.have.property(b);
query.toPlainObject().filter.$nor[0].$or[1].should.have.property(c);
query.toPlainObject().filter.$nor[1].$and[0].should.have.property(d);
query.toPlainObject().filter.$nor[1].$and[1].should.have.property(e);
});
it('should set the limit on the top-level query.', () => {
const value = 10;
const query = new Query();
query.and().limit = value;
query.toPlainObject().limit.should.eql(value);
});
it('should set the skip on the top-level query.', () => {
const value = 10;
const query = new Query();
query.and().skip = value;
query.toPlainObject().skip.should.eql(value);
});
it('should set the ascending sort on the top-level query.', () => {
const field = randomString();
const query = new Query();
query.and().ascending(field);
query.toPlainObject().sort[field].should.eql(1);
});
it('should set the descending sort on the top-level query.', () => {
const field = randomString();
const query = new Query();
query.and().descending(field);
query.toPlainObject().sort[field].should.eql(-1);
});
it('should set the sort on the top-level query.', () => {
const value = {};
value[randomString()] = 1;
const query = new Query();
query.and().sort = value;
query.toPlainObject().sort.should.eql(value);
});
});
describe('comparators', () => {
it('should add a $gt filter', () => {
const query = new Query();
query.greaterThan('field', 1);
const queryString = query.toQueryString();
queryString.should.containEql({ query: '{"field":{"$gt":1}}' });
});
it('throw an error when $gt comparator is not a string or number', () => {
should.throws(() => {
const query = new Query();
query.greaterThan('field', {});
query.process([]);
}, function error(err) {
return err.toString() === 'Error: QueryError' && err.debug === 'You must supply a number or string.';
});
});
it('should add a $gte filter', () => {
const query = new Query();
query.greaterThanOrEqualTo('field', 1);
const queryString = query.toQueryString();
queryString.should.containEql({ query: '{"field":{"$gte":1}}' });
});
it('throw an error when $gte comparator is not a string or number', () => {
should.throws(() => {
const query = new