UNPKG

loopback-datasource-juggler

Version:
1,532 lines (1,403 loc) 79.7 kB
// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Node module: loopback-datasource-juggler // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT // This test written in mocha+should.js 'use strict'; /* global getSchema:false, connectorCapabilities:false */ const async = require('async'); const bdd = require('./helpers/bdd-if'); const should = require('./init.js'); const uid = require('./helpers/uid-generator'); const createTestSetupForParentRef = require('./helpers/setup-parent-ref'); let db, User; describe('basic-querying', function() { before(function(done) { const userModelDef = { seq: {type: Number, index: true}, name: {type: String, index: true, sort: true}, email: {type: String, index: true}, birthday: {type: Date, index: true}, role: {type: String, index: true}, order: {type: Number, index: true, sort: true}, tag: {type: String, index: true}, vip: {type: Boolean}, address: { street: String, city: String, state: String, zipCode: String, tags: [ { tag: String, }, ], }, friends: [ { name: String, }, ], addressLoc: { lat: Number, lng: Number, }, }; db = getSchema(); // connectors that do not support geo-point types connectorCapabilities.geoPoint = (db.adapter.name != 'dashdb') && (db.adapter.name != 'db2') && (db.adapter.name != 'informix') && (db.adapter.name != 'cassandra'); if (connectorCapabilities.geoPoint) userModelDef.addressLoc = {type: 'GeoPoint'}; User = db.define('User', userModelDef); db.automigrate(done); }); describe('ping', function() { it('should be able to test connections', function(done) { db.ping(function(err) { should.not.exist(err); done(); }); }); }); describe('findById', function() { before(function(done) { db = getSchema(); User.destroyAll(done); }); it('should query by id: not found', function(done) { const unknownId = uid.fromConnector(db) || 1; User.findById(unknownId, function(err, u) { should.not.exist(u); should.not.exist(err); done(); }); }); it('should query by id: found', function(done) { User.create(function(err, u) { should.not.exist(err); should.exist(u.id); User.findById(u.id, function(err, u) { should.exist(u); should.not.exist(err); u.should.be.an.instanceOf(User); done(); }); }); }); }); describe('findByIds', function() { let createdUsers; before(function(done) { db = getSchema(); const people = [ {name: 'a', vip: true}, {name: 'b', vip: null}, {name: 'c'}, {name: 'd', vip: true}, {name: 'e'}, {name: 'f'}, ]; db.automigrate(['User'], function(err) { User.create(people, function(err, users) { should.not.exist(err); // Users might be created in parallel and the generated ids can be // out of sequence createdUsers = users; done(); }); }); }); it('should query by ids', function(done) { User.findByIds( [createdUsers[2].id, createdUsers[1].id, createdUsers[0].id], function(err, users) { should.exist(users); should.not.exist(err); const names = users.map(function(u) { return u.name; }); names.should.eql( [createdUsers[2].name, createdUsers[1].name, createdUsers[0].name], ); done(); }, ); }); it('should query by ids and condition', function(done) { User.findByIds([ createdUsers[0].id, createdUsers[1].id, createdUsers[2].id, createdUsers[3].id], {where: {vip: true}}, function(err, users) { should.exist(users); should.not.exist(err); const names = users.map(function(u) { return u.name; }); names.should.eql(createdUsers.slice(0, 4). filter(function(u) { return u.vip; }).map(function(u) { return u.name; })); done(); }); }); bdd.itIf(connectorCapabilities.nullDataValueExists !== false, 'should query by ids to check null property', function(done) { User.findByIds([ createdUsers[0].id, createdUsers[1].id], {where: {vip: null}}, function(err, users) { should.not.exist(err); should.exist(users); users.length.should.eql(1); users[0].name.should.eql(createdUsers[1].name); done(); }); }); }); describe('find', function() { before(seed); before(function setupDelayingLoadedHook() { User.observe('loaded', nextAfterDelay); }); after(function removeDelayingLoadHook() { User.removeObserver('loaded', nextAfterDelay); }); it('should query collection', function(done) { User.find(function(err, users) { should.exists(users); should.not.exists(err); users.should.have.lengthOf(6); done(); }); }); it('should query limited collection', function(done) { User.find({limit: 3}, function(err, users) { should.exists(users); should.not.exists(err); users.should.have.lengthOf(3); done(); }); }); bdd.itIf(connectorCapabilities.supportPagination !== false, 'should query collection with skip & ' + 'limit', function(done) { User.find({skip: 1, limit: 4, order: 'seq'}, function(err, users) { should.exists(users); should.not.exists(err); users[0].seq.should.be.eql(1); users.should.have.lengthOf(4); done(); }); }); bdd.itIf(connectorCapabilities.supportPagination !== false, 'should query collection with offset & ' + 'limit', function(done) { User.find({offset: 2, limit: 3, order: 'seq'}, function(err, users) { should.exists(users); should.not.exists(err); users[0].seq.should.be.eql(2); users.should.have.lengthOf(3); done(); }); }); it('should query filtered collection', function(done) { User.find({where: {role: 'lead'}}, function(err, users) { should.exists(users); should.not.exists(err); users.should.have.lengthOf(2); done(); }); }); bdd.itIf(connectorCapabilities.adhocSort !== false, 'should query collection sorted by numeric ' + 'field', function(done) { User.find({order: 'order'}, function(err, users) { should.exists(users); should.not.exists(err); users.forEach(function(u, i) { u.order.should.eql(i + 1); }); done(); }); }); bdd.itIf(connectorCapabilities.adhocSort !== false, 'should query collection desc sorted by ' + 'numeric field', function(done) { User.find({order: 'order DESC'}, function(err, users) { should.exists(users); should.not.exists(err); users.forEach(function(u, i) { u.order.should.eql(users.length - i); }); done(); }); }); bdd.itIf(connectorCapabilities.adhocSort !== false, 'should query collection sorted by string ' + 'field', function(done) { User.find({order: 'name'}, function(err, users) { should.exists(users); should.not.exists(err); users.shift().name.should.equal('George Harrison'); users.shift().name.should.equal('John Lennon'); users.pop().name.should.equal('Stuart Sutcliffe'); done(); }); }); bdd.itIf(connectorCapabilities.adhocSort !== false, 'should query collection desc sorted by ' + 'string field', function(done) { User.find({order: 'name DESC'}, function(err, users) { should.exists(users); should.not.exists(err); users.pop().name.should.equal('George Harrison'); users.pop().name.should.equal('John Lennon'); users.shift().name.should.equal('Stuart Sutcliffe'); done(); }); }); bdd.itIf(connectorCapabilities.adhocSort !== false, 'should query sorted desc by order integer field' + ' even though there is an async model loaded hook', function(done) { User.find({order: 'order DESC'}, function(err, users) { if (err) return done(err); should.exists(users); const order = users.map(function(u) { return u.order; }); order.should.eql([6, 5, 4, 3, 2, 1]); done(); }); }); it('should support "and" operator that is satisfied', function(done) { User.find({where: {and: [ {name: 'John Lennon'}, {role: 'lead'}, ]}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); done(); }); }); it('should support "and" operator that is not satisfied', function(done) { User.find({where: {and: [ {name: 'John Lennon'}, {role: 'member'}, ]}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); bdd.itIf(connectorCapabilities.supportOrOperator !== false, 'should support "or" that is ' + 'satisfied', function(done) { User.find({where: {or: [ {name: 'John Lennon'}, {role: 'lead'}, ]}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 2); done(); }); }); bdd.itIf(connectorCapabilities.supportOrOperator !== false, 'should support "or" operator that is ' + 'not satisfied', function(done) { User.find({where: {or: [ {name: 'XYZ'}, {role: 'Hello1'}, ]}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); bdd.itIf(connectorCapabilities.nullDataValueExists !== false, 'should support where date "neq" null', function(done) { User.find({where: {birthday: {'neq': null}, }}, function(err, users) { should.not.exist(err); should.exist(users); users.should.have.property('length', 2); should(users[0].name).be.oneOf('John Lennon', 'Paul McCartney'); should(users[1].name).be.oneOf('John Lennon', 'Paul McCartney'); done(); }); }); bdd.itIf(connectorCapabilities.nullDataValueExists !== false, 'should support where date is null', function(done) { User.find({where: {birthday: null, }}, function(err, users) { should.not.exist(err); should.exist(users); users.should.have.property('length', 4); done(); }); }); it('should support date "gte" that is satisfied', function(done) { User.find({where: {birthday: {'gte': new Date('1980-12-08')}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('John Lennon'); done(); }); }); it('should support date "gt" that is not satisfied', function(done) { User.find({where: {birthday: {'gt': new Date('1980-12-08')}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); it('should support date "gt" that is satisfied', function(done) { User.find({where: {birthday: {'gt': new Date('1980-12-07')}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('John Lennon'); done(); }); }); bdd.itIf(connectorCapabilities.cloudantCompatible !== false, 'should support date "lt" that is satisfied', function(done) { User.find({where: {birthday: {'lt': new Date('1980-12-07')}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('Paul McCartney'); done(); }); }); it('should support number "gte" that is satisfied', function(done) { User.find({where: {order: {'gte': 3}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 4); users.map(u => u.name).should.containDeep([ 'George Harrison', 'Ringo Starr', 'Pete Best', 'Stuart Sutcliffe', ]); done(); }); }); it('should support number "gt" that is not satisfied', function(done) { User.find({where: {order: {'gt': 6}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); it('should support number "gt" that is satisfied', function(done) { User.find({where: {order: {'gt': 5}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('Ringo Starr'); done(); }); }); it('should support number "lt" that is satisfied', function(done) { User.find({where: {order: {'lt': 2}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('Paul McCartney'); done(); }); }); bdd.itIf(connectorCapabilities.ignoreUndefinedConditionValue !== false, 'should support number "gt" ' + 'that is satisfied by null value', function(done) { User.find({order: 'seq', where: {order: {'gt': null}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); bdd.itIf(connectorCapabilities.ignoreUndefinedConditionValue !== false, 'should support number "lt" ' + 'that is not satisfied by null value', function(done) { User.find({where: {order: {'lt': null}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); bdd.itIf(connectorCapabilities.ignoreUndefinedConditionValue !== false, 'should support string "gte" ' + 'that is satisfied by null value', function(done) { User.find({order: 'seq', where: {name: {'gte': null}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); bdd.itIf(connectorCapabilities.cloudantCompatible !== false, 'should support string "gte" that is satisfied', function(done) { User.find({where: {name: {'gte': 'Paul McCartney'}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 4); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.greaterThanOrEqual('Paul McCartney'); } done(); }); }); it('should support string "gt" that is not satisfied', function(done) { User.find({where: {name: {'gt': 'xyz'}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); bdd.itIf(connectorCapabilities.cloudantCompatible !== false, 'should support string "gt" that is satisfied', function(done) { User.find({where: {name: {'gt': 'Paul McCartney'}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 3); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.greaterThan('Paul McCartney'); } done(); }); }); bdd.itIf(connectorCapabilities.cloudantCompatible !== false, 'should support string "lt" that is satisfied', function(done) { User.find({where: {name: {'lt': 'Paul McCartney'}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 2); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.lessThan('Paul McCartney'); } done(); }); }); it('should support boolean "gte" that is satisfied', function(done) { User.find({where: {vip: {'gte': true}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 3); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.oneOf(['John Lennon', 'Stuart Sutcliffe', 'Paul McCartney']); users[ix].vip.should.be.true(); } done(); }); }); it('should support boolean "gt" that is not satisfied', function(done) { User.find({where: {vip: {'gt': true}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); it('should support boolean "gt" that is satisfied', function(done) { User.find({where: {vip: {'gt': false}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 3); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.oneOf(['John Lennon', 'Stuart Sutcliffe', 'Paul McCartney']); users[ix].vip.should.be.true(users[ix].name + ' should be VIP'); } done(); }); }); it('should support boolean "lt" that is satisfied', function(done) { User.find({where: {vip: {'lt': true}, }}, function(err, users) { should.not.exist(err); users.should.have.property('length', 2); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.oneOf(['Ringo Starr', 'George Harrison']); users[ix].vip.should.be.false(users[ix].name + ' should not be VIP'); } done(); }); }); bdd.itIf(connectorCapabilities.supportInq, 'supports non-empty inq', function() { // note there is no record with seq=100 return User.find({where: {seq: {inq: [0, 1, 100]}}}) .then(result => { const seqsFound = result.map(r => r.seq); should(seqsFound).eql([0, 1]); }); }); bdd.itIf(connectorCapabilities.supportInq, 'supports empty inq', function() { return User.find({where: {seq: {inq: []}}}) .then(result => { const seqsFound = result.map(r => r.seq); should(seqsFound).eql([]); }); }); const itWhenIlikeSupported = connectorCapabilities.ilike; bdd.describeIf(itWhenIlikeSupported, 'ilike', function() { it('should support "like" that is satisfied', function(done) { User.find({where: {name: {like: 'John'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(1); users[0].name.should.equal('John Lennon'); done(); }); }); it('should sanitize invalid usage of like', async () => { const users = await User.find({where: {tag: {like: '['}}}); users.should.have.length(1); users[0].should.have.property('name', 'John Lennon'); }); it('should support "like" that is not satisfied', function(done) { User.find({where: {name: {like: 'Bob'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(0); done(); }); }); it('should support "ilike" that is satisfied', function(done) { User.find({where: {name: {ilike: 'john'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(1); users[0].name.should.equal('John Lennon'); done(); }); }); it('should support "ilike" that is not satisfied', function(done) { User.find({where: {name: {ilike: 'bob'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(0); done(); }); }); it('should properly sanitize invalid ilike filter', async () => { const users = await User.find({where: {name: {ilike: '['}}}); users.should.be.empty(); }); }); const itWhenNilikeSupported = connectorCapabilities.nilike !== false; bdd.describeIf(itWhenNilikeSupported, 'nilike', function() { it('should support "nlike" that is satisfied', function(done) { User.find({where: {name: {nlike: 'John'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(5); users[0].name.should.equal('Paul McCartney'); done(); }); }); it('should support "nilike" that is satisfied', function(done) { User.find({where: {name: {nilike: 'john'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(5); users[0].name.should.equal('Paul McCartney'); done(); }); }); }); describe('geo queries', function() { describe('near filter', function() { it('supports a basic "near" query', function(done) { User.find({ where: { addressLoc: { near: {lat: 29.9, lng: -90.07}, }, }, }, function(err, users) { if (err) done(err); users.should.have.property('length', 3); users[0].name.should.equal('John Lennon'); users[0].should.be.instanceOf(User); users[0].addressLoc.should.not.be.null(); done(); }); }); it('supports "near" inside a coumpound query with "and"', function(done) { User.find({ where: { and: [ { addressLoc: { near: {lat: 29.9, lng: -90.07}, }, }, { vip: true, }, ], }, }, function(err, users) { if (err) done(err); users.should.have.property('length', 2); users[0].name.should.equal('John Lennon'); users[0].should.be.instanceOf(User); users[0].addressLoc.should.not.be.null(); users[0].vip.should.be.true(); done(); }); }); it('supports "near" inside a complex coumpound query with multiple "and"', function(done) { User.find({ where: { and: [ { and: [ { addressLoc: { near: {lat: 29.9, lng: -90.07}, }, }, { order: 2, }, ], }, { vip: true, }, ], }, }, function(err, users) { if (err) done(err); users.should.have.property('length', 1); users[0].name.should.equal('John Lennon'); users[0].should.be.instanceOf(User); users[0].addressLoc.should.not.be.null(); users[0].vip.should.be.true(); users[0].order.should.equal(2); done(); }); }); it('supports multiple "near" queries with "or"', function(done) { User.find({ where: { or: [ { addressLoc: { near: {lat: 29.9, lng: -90.04}, maxDistance: 300, }, }, { addressLoc: { near: {lat: 22.97, lng: -88.03}, maxDistance: 50, }, }, ], }, }, function(err, users) { if (err) done(err); users.should.have.property('length', 2); users[0].addressLoc.should.not.be.null(); users[0].name.should.equal('Paul McCartney'); users[0].should.be.instanceOf(User); users[1].addressLoc.should.not.equal(null); users[1].name.should.equal('John Lennon'); done(); }); }); it('supports multiple "near" queries with "or" ' + 'inside a coumpound query with "and"', function(done) { User.find({ where: { and: [ { or: [ { addressLoc: { near: {lat: 29.9, lng: -90.04}, maxDistance: 300, }, }, { addressLoc: { near: {lat: 22.7, lng: -89.03}, maxDistance: 50, }, }, ], }, { vip: true, }, ], }, }, function(err, users) { if (err) done(err); users.should.have.property('length', 1); users[0].addressLoc.should.not.be.null(); users[0].name.should.equal('John Lennon'); users[0].should.be.instanceOf(User); users[0].vip.should.be.true(); done(); }); }); }); }); it('should only include fields as specified', function(done) { let remaining = 0; function sample(fields) { return { expect: function(arr) { remaining++; User.find({fields: fields}, function(err, users) { remaining--; if (err) return done(err); should.exists(users); if (remaining === 0) { done(); } users.forEach(function(user) { const obj = user.toObject(); Object.keys(obj) .forEach(function(key) { // if the obj has an unexpected value if (obj[key] !== undefined && arr.indexOf(key) === -1) { console.log('Given fields:', fields); console.log('Got:', key, obj[key]); console.log('Expected:', arr); throw new Error('should not include data for key: ' + key); } }); }); }); }, }; } sample({name: true}).expect(['name']); sample({name: false}).expect([ 'id', 'seq', 'email', 'role', 'order', 'birthday', 'vip', 'address', 'friends', 'addressLoc', 'tag', ]); sample({name: false, id: true}).expect(['id']); sample({id: true}).expect(['id']); sample('id').expect(['id']); sample(['id']).expect(['id']); sample(['email']).expect(['email']); }); it('should ignore non existing properties when excluding', function(done) { return User.find({fields: {notExist: false}}, (err, users) => { if (err) return done(err); users.forEach(user => { switch (user.seq) { // all fields depending on each document case 0: case 1: Object.keys(user.__data).should.containDeep(['id', 'seq', 'name', 'order', 'role', 'birthday', 'vip', 'address', 'friends']); break; case 4: // seq 4 Object.keys(user.__data).should.containDeep(['id', 'seq', 'name', 'order']); break; default: // Other records, seq 2, 3, 5 Object.keys(user.__data).should.containDeep(['id', 'seq', 'name', 'order', 'vip']); } }); done(); }); }); const describeWhenNestedSupported = connectorCapabilities.nestedProperty; bdd.describeIf(describeWhenNestedSupported, 'query with nested property', function() { it('should support nested property in query', function(done) { User.find({where: {'address.city': 'San Jose'}}, function(err, users) { if (err) return done(err); users.length.should.be.equal(1); for (let i = 0; i < users.length; i++) { users[i].address.city.should.be.eql('San Jose'); } done(); }); }); it('should support nested property with regex over arrays in query', function(done) { User.find({where: {'friends.name': {regexp: /^Ringo/}}}, function(err, users) { if (err) return done(err); users.length.should.be.equal(2); const expectedUsers = ['John Lennon', 'Paul McCartney']; expectedUsers.indexOf(users[0].name).should.not.equal(-1); expectedUsers.indexOf(users[1].name).should.not.equal(-1); done(); }); }); it('should support nested property with gt in query', function(done) { User.find({where: {'address.city': {gt: 'San'}}}, function(err, users) { if (err) return done(err); users.length.should.be.equal(2); for (let i = 0; i < users.length; i++) { users[i].address.state.should.be.eql('CA'); } done(); }); }); bdd.itIf(connectorCapabilities.adhocSort, 'should support nested property for order in query', function(done) { User.find({where: {'address.state': 'CA'}, order: 'address.city DESC'}, function(err, users) { if (err) return done(err); users.length.should.be.equal(2); users[0].address.city.should.be.eql('San Mateo'); users[1].address.city.should.be.eql('San Jose'); done(); }); }); it('should support multi-level nested array property in query', function(done) { User.find({where: {'address.tags.tag': 'business'}}, function(err, users) { if (err) return done(err); users.length.should.be.equal(1); users[0].address.tags[0].tag.should.be.equal('business'); users[0].address.tags[1].tag.should.be.equal('rent'); done(); }); }); it('should fail when querying with an invalid value for a type', function(done) { User.find({where: {birthday: 'notadate'}}, function(err, users) { should.exist(err); err.message.should.equal('Invalid date: notadate'); done(); }); }); }); it('preserves empty values from the database', async () => { // https://github.com/strongloop/loopback-datasource-juggler/issues/1692 // Initially, all Players were always active, no property was needed const Player = db.define('Player', {name: String}); await db.automigrate('Player'); const created = await Player.create({name: 'Pen'}); // Later on, we decide to introduce `active` property Player.defineProperty('active', { type: Boolean, default: false, }); await db.autoupdate('Player'); // And query existing data const found = await Player.findOne(); should(found.toObject().active).be.oneOf([ undefined, // databases supporting `undefined` value null, // databases representing `undefined` as `null` (e.g. SQL) ]); }); describe('check __parent relationship in embedded models', () => { createTestSetupForParentRef(() => User.modelBuilder); it('should fill the parent in embedded model', async () => { const user = await User.findOne({where: {name: 'John Lennon'}}); user.should.have.property('address'); should(user.address).have.property('__parent'); should(user.address.__parent).be.instanceof(User).and.equal(user); }); it('should assign the container model as parent in list property', async () => { const user = await User.findOne({where: {name: 'John Lennon'}}); user.should.have.property('friends'); should(user.friends).have.property('parent'); should(user.friends.parent).be.instanceof(User).and.equal(user); }); it('should have the complete chain of parents available in embedded list element', async () => { const user = await User.findOne({where: {name: 'John Lennon'}}); user.friends.forEach((userFriend) => { userFriend.should.have.property('__parent'); should(userFriend.__parent).equal(user); }); }); }); }); describe('find after createAll', function() { before(function seedData(done) { seed(done, true); }); before(function setupDelayingLoadedHook() { User.observe('loaded', nextAfterDelay); }); after(function removeDelayingLoadHook() { User.removeObserver('loaded', nextAfterDelay); }); it('should query collection', function(done) { User.find(function(err, users) { should.exists(users); should.not.exists(err); users.should.have.lengthOf(6); done(); }); }); it('should query limited collection', function(done) { User.find({limit: 3}, function(err, users) { should.exists(users); should.not.exists(err); users.should.have.lengthOf(3); done(); }); }); bdd.itIf( connectorCapabilities.supportPagination !== false, 'should query collection with skip & ' + 'limit', function(done) { User.find({skip: 1, limit: 4, order: 'seq'}, function(err, users) { should.exists(users); should.not.exists(err); users[0].seq.should.be.eql(1); users.should.have.lengthOf(4); done(); }); }, ); bdd.itIf( connectorCapabilities.supportPagination !== false, 'should query collection with offset & ' + 'limit', function(done) { User.find({offset: 2, limit: 3, order: 'seq'}, function(err, users) { should.exists(users); should.not.exists(err); users[0].seq.should.be.eql(2); users.should.have.lengthOf(3); done(); }); }, ); it('should query filtered collection', function(done) { User.find({where: {role: 'lead'}}, function(err, users) { should.exists(users); should.not.exists(err); users.should.have.lengthOf(2); done(); }); }); bdd.itIf( connectorCapabilities.adhocSort !== false, 'should query collection sorted by numeric ' + 'field', function(done) { User.find({order: 'order'}, function(err, users) { should.exists(users); should.not.exists(err); users.forEach(function(u, i) { u.order.should.eql(i + 1); }); done(); }); }, ); bdd.itIf( connectorCapabilities.adhocSort !== false, 'should query collection desc sorted by ' + 'numeric field', function(done) { User.find({order: 'order DESC'}, function(err, users) { should.exists(users); should.not.exists(err); users.forEach(function(u, i) { u.order.should.eql(users.length - i); }); done(); }); }, ); bdd.itIf( connectorCapabilities.adhocSort !== false, 'should query collection sorted by string ' + 'field', function(done) { User.find({order: 'name'}, function(err, users) { should.exists(users); should.not.exists(err); users.shift().name.should.equal('George Harrison'); users.shift().name.should.equal('John Lennon'); users.pop().name.should.equal('Stuart Sutcliffe'); done(); }); }, ); bdd.itIf( connectorCapabilities.adhocSort !== false, 'should query collection desc sorted by ' + 'string field', function(done) { User.find({order: 'name DESC'}, function(err, users) { should.exists(users); should.not.exists(err); users.pop().name.should.equal('George Harrison'); users.pop().name.should.equal('John Lennon'); users.shift().name.should.equal('Stuart Sutcliffe'); done(); }); }, ); bdd.itIf( connectorCapabilities.adhocSort !== false, 'should query sorted desc by order integer field' + ' even though there is an async model loaded hook', function(done) { User.find({order: 'order DESC'}, function(err, users) { if (err) return done(err); should.exists(users); const order = users.map(function(u) { return u.order; }); order.should.eql([6, 5, 4, 3, 2, 1]); done(); }); }, ); it('should support "and" operator that is satisfied', function(done) { User.find( {where: {and: [{name: 'John Lennon'}, {role: 'lead'}]}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); done(); }, ); }); it('should support "and" operator that is not satisfied', function(done) { User.find( {where: {and: [{name: 'John Lennon'}, {role: 'member'}]}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }, ); }); bdd.itIf( connectorCapabilities.supportOrOperator !== false, 'should support "or" that is ' + 'satisfied', function(done) { User.find( {where: {or: [{name: 'John Lennon'}, {role: 'lead'}]}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 2); done(); }, ); }, ); bdd.itIf( connectorCapabilities.supportOrOperator !== false, 'should support "or" operator that is ' + 'not satisfied', function(done) { User.find( {where: {or: [{name: 'XYZ'}, {role: 'Hello1'}]}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }, ); }, ); bdd.itIf( connectorCapabilities.nullDataValueExists !== false, 'should support where date "neq" null', function(done) { User.find({where: {birthday: {neq: null}}}, function(err, users) { should.not.exist(err); should.exist(users); users.should.have.property('length', 2); should(users[0].name).be.oneOf('John Lennon', 'Paul McCartney'); should(users[1].name).be.oneOf('John Lennon', 'Paul McCartney'); done(); }); }, ); bdd.itIf( connectorCapabilities.nullDataValueExists !== false, 'should support where date is null', function(done) { User.find({where: {birthday: null}}, function(err, users) { should.not.exist(err); should.exist(users); users.should.have.property('length', 4); done(); }); }, ); it('should support date "gte" that is satisfied', function(done) { User.find( {where: {birthday: {gte: new Date('1980-12-08')}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('John Lennon'); done(); }, ); }); it('should support date "gt" that is not satisfied', function(done) { User.find( {where: {birthday: {gt: new Date('1980-12-08')}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }, ); }); it('should support date "gt" that is satisfied', function(done) { User.find( {where: {birthday: {gt: new Date('1980-12-07')}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('John Lennon'); done(); }, ); }); bdd.itIf( connectorCapabilities.cloudantCompatible !== false, 'should support date "lt" that is satisfied', function(done) { User.find( {where: {birthday: {lt: new Date('1980-12-07')}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('Paul McCartney'); done(); }, ); }, ); it('should support number "gte" that is satisfied', function(done) { User.find({where: {order: {gte: 3}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 4); users .map((u) => u.name) .should.containDeep([ 'George Harrison', 'Ringo Starr', 'Pete Best', 'Stuart Sutcliffe', ]); done(); }); }); it('should support number "gt" that is not satisfied', function(done) { User.find({where: {order: {gt: 6}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); it('should support number "gt" that is satisfied', function(done) { User.find({where: {order: {gt: 5}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('Ringo Starr'); done(); }); }); it('should support number "lt" that is satisfied', function(done) { User.find({where: {order: {lt: 2}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 1); users[0].name.should.equal('Paul McCartney'); done(); }); }); bdd.itIf( connectorCapabilities.ignoreUndefinedConditionValue !== false, 'should support number "gt" ' + 'that is satisfied by null value', function(done) { User.find( {order: 'seq', where: {order: {gt: null}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }, ); }, ); bdd.itIf( connectorCapabilities.ignoreUndefinedConditionValue !== false, 'should support number "lt" ' + 'that is not satisfied by null value', function(done) { User.find({where: {order: {lt: null}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }, ); bdd.itIf( connectorCapabilities.ignoreUndefinedConditionValue !== false, 'should support string "gte" ' + 'that is satisfied by null value', function(done) { User.find( {order: 'seq', where: {name: {gte: null}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }, ); }, ); bdd.itIf( connectorCapabilities.cloudantCompatible !== false, 'should support string "gte" that is satisfied', function(done) { User.find( {where: {name: {gte: 'Paul McCartney'}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 4); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.greaterThanOrEqual('Paul McCartney'); } done(); }, ); }, ); it('should support string "gt" that is not satisfied', function(done) { User.find({where: {name: {gt: 'xyz'}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); bdd.itIf( connectorCapabilities.cloudantCompatible !== false, 'should support string "gt" that is satisfied', function(done) { User.find( {where: {name: {gt: 'Paul McCartney'}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 3); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.greaterThan('Paul McCartney'); } done(); }, ); }, ); bdd.itIf( connectorCapabilities.cloudantCompatible !== false, 'should support string "lt" that is satisfied', function(done) { User.find( {where: {name: {lt: 'Paul McCartney'}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 2); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.lessThan('Paul McCartney'); } done(); }, ); }, ); it('should support boolean "gte" that is satisfied', function(done) { User.find({where: {vip: {gte: true}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 3); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.oneOf([ 'John Lennon', 'Stuart Sutcliffe', 'Paul McCartney', ]); users[ix].vip.should.be.true(); } done(); }); }); it('should support boolean "gt" that is not satisfied', function(done) { User.find({where: {vip: {gt: true}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 0); done(); }); }); it('should support boolean "gt" that is satisfied', function(done) { User.find({where: {vip: {gt: false}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 3); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.oneOf([ 'John Lennon', 'Stuart Sutcliffe', 'Paul McCartney', ]); users[ix].vip.should.be.true(users[ix].name + ' should be VIP'); } done(); }); }); it('should support boolean "lt" that is satisfied', function(done) { User.find({where: {vip: {lt: true}}}, function(err, users) { should.not.exist(err); users.should.have.property('length', 2); for (let ix = 0; ix < users.length; ix++) { users[ix].name.should.be.oneOf(['Ringo Starr', 'George Harrison']); users[ix].vip.should.be.false( users[ix].name + ' should not be VIP', ); } done(); }); }); bdd.itIf( connectorCapabilities.supportInq, 'supports non-empty inq', function() { // note there is no record with seq=100 return User.find({where: {seq: {inq: [0, 1, 100]}}}).then( (result) => { const seqsFound = result.map((r) => r.seq); should(seqsFound).eql([0, 1]); }, ); }, ); bdd.itIf( connectorCapabilities.supportInq, 'supports empty inq', function() { return User.find({where: {seq: {inq: []}}}).then((result) => { const seqsFound = result.map((r) => r.seq); should(seqsFound).eql([]); }); }, ); const itWhenIlikeSupported = connectorCapabilities.ilike; bdd.describeIf(itWhenIlikeSupported, 'ilike', function() { it('should support "like" that is satisfied', function(done) { User.find({where: {name: {like: 'John'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(1); users[0].name.should.equal('John Lennon'); done(); }); }); it('should sanitize invalid usage of like', async () => { const users = await User.find({where: {tag: {like: '['}}}); users.should.have.length(1); users[0].should.have.property('name', 'John Lennon'); }); it('should support "like" that is not satisfied', function(done) { User.find({where: {name: {like: 'Bob'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(0); done(); }); }); it('should support "ilike" that is satisfied', function(done) { User.find({where: {name: {ilike: 'john'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(1); users[0].name.should.equal('John Lennon'); done(); }); }); it('should support "ilike" that is not satisfied', function(done) { User.find({where: {name: {ilike: 'bob'}}}, function(err, users) { if (err) return done(err); users.length.should.equal(0); done(); }); }); it('should properly sanitize invalid ilike filter', async () => { const users = await User.find({where: {name: {ilike: '['}}}); users.should.be.empty(); });