From b4e9bea3ba5122f9a4aec381dc3cce9963e64841 Mon Sep 17 00:00:00 2001 From: Jussi Vatjus-Anttila Date: Thu, 19 Oct 2017 21:20:13 +0300 Subject: [PATCH] fix leanQuery with flat option (#21) * fix leanQuery with flat option * several smaller fixes and promise tests * boolean conversion allows numbers (0=false, !0 = true) and undefined (=true) * fix: count promise resolves now same count object than callback * add promise tests which validate same thing than callback tests * disconnect mongoose after tests --- README.md | 1 + lib/index.js | 20 ++-- lib/tools.js | 12 +- package.json | 26 ++-- test/tests.js | 322 ++++++++++++++++++++++++++++++++++---------------- 5 files changed, 252 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index c92a3b1..2229c0f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ model.find({group: "users"}).select("name").skip(1).limit(5).populate('name') |versio|Changes| |------|-------| +|0.4.0|Fix lean query with flatten + couple other and add promise tests| |0.3.0|Big refactoring, see more from release note.. e.g. mongoose 4.x support| |0.2.1|added oid support, fixed aggregate and support mongoose => 3.8.1 |0.2.0|replace underscore with lodash, possible to return promise when no callback in use| diff --git a/lib/index.js b/lib/index.js index 8085e5f..bf100e1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -45,7 +45,7 @@ module.exports = function QueryPlugin (schema, options) { break; case('count'): if (!callback) { - return this.count(q.q); + return this.count(q.q).exec().then(count => ({count})); } this.count(q.q, (error,count) => { if(error) callback(error); @@ -107,15 +107,15 @@ module.exports = function QueryPlugin (schema, options) { return query.findOne(callback); } } else { - if( q.fl ) { + if (q.fl) { if (callback) { query.find((error, docs) => { - if(error) callback(error); + if (error) callback(error); else { - let arr = []; + const arr = []; docs.forEach((doc) => { - let json = doc.toJSON({virtuals: true}); - arr.push( flatten(json)); + const json = opt.lean ? doc : doc.toJSON({virtuals: true}); + arr.push(flatten(json)); }); callback(error, arr); } @@ -123,12 +123,12 @@ module.exports = function QueryPlugin (schema, options) { } else { return new Promise((resolve, reject) => { query.find((error, docs) => { - if(error) reject(error); + if (error) reject(error); else { - let arr = []; + const arr = []; docs.forEach((doc) => { - let json = doc.toJSON({virtuals: true}); - arr.push( flatten(json)); + const json = opt.lean ? doc : doc.toJSON({virtuals: true}); + arr.push(flatten(json)); }); resolve(arr); } diff --git a/lib/tools.js b/lib/tools.js index 244079f..a6115e5 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -1,4 +1,5 @@ const util = require('util') + , _ = require('lodash') , logger = require('./logger'); module.exports.toJSON = function(str){ @@ -10,8 +11,13 @@ module.exports.toJSON = function(str){ json = {}; } return json; -} +}; module.exports.toBool = function (str) { + if (_.isUndefined(str)) + return true; + if (_.isNumber(str)) { + return str === 0 ? false : true; + } if (str.toLowerCase() === "true" || str.toLowerCase() === "yes" ){ return true; @@ -22,7 +28,7 @@ module.exports.toBool = function (str) { } else { return -1; } -} +}; function parseDate(str) { //31/2/2010 var m = str.match(/^(\d{1,2})[\/\s\.\-\,](\d{1,2})[\/\s\.\-\,](\d{4})$/); @@ -41,4 +47,4 @@ module.exports.isStringValidDate = function(str){ if(isDate(parseDate(str)))return true; if(isDate(parseDate2(str)))return true; return false; -} +}; diff --git a/package.json b/package.json index 20242de..f602f37 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,37 @@ { "name": "mongoose-query", "description": "mongoose Query lib", - "keywords": ["mongoose", "query", "mongodb"], - "version": "0.3.0", + "keywords": [ + "mongoose", + "query", + "mongodb" + ], + "version": "0.4.0", "homepage": "https://github.com/jupe/mongoose-query", "author": "Jussi Vatjus-Anttila )", "main": "lib/index", - "bugs" : { - "url" : "https://github.com/jupe/mongoose-query/issues" + "bugs": { + "url": "https://github.com/jupe/mongoose-query/issues" }, "repository": { - "type": "git", - "url": "https://github.com/jupe/mongoose-query" + "type": "git", + "url": "https://github.com/jupe/mongoose-query" }, - "licenses" : "MIT", + "license": "MIT", "dependencies": { "mongoose": "^4.11.4", "flat": "*", "lodash": "*" }, "devDependencies": { - "mocha": "3.5.3", - "chai": "*", - "request": "*" + "chai": "*", + "mocha": "^4.0.1", + "request": "*" }, "contributors": [ "Jussi Vatjus-Anttila " ], "scripts": { - "test": "mocha -R list" + "test": "mocha -R list" } } diff --git a/test/tests.js b/test/tests.js index 6d7fce7..7237a2b 100644 --- a/test/tests.js +++ b/test/tests.js @@ -16,10 +16,10 @@ mongoose.Promise = Promise; let isPromise = function (obj) { return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'; -} +}; let assertPromise = function(obj) { expect(isPromise(obj)).to.be.true; -} +}; const ObjectId = Schema.ObjectId; let OrigSchema = new mongoose.Schema({ @@ -57,18 +57,16 @@ let create = (i, max, callback) => { callback(); }); } -} +}; describe('Query:basic', function() { - before( function(done){ - mongoose.connect( "mongodb://localhost/mongoose-query-tests" ); + before(function(done){ + const useMongoClient = true; + mongoose.connect("mongodb://localhost/mongoose-query-tests", {useMongoClient}); mongoose.connection.on('connected', done); }); - before( function(done) { - OrigTestModel.remove({}, done); - }); - before( function(done) { + before(function(done) { this.timeout(10000); let obj = new OrigTestModel(); obj.save((error, doc) => { @@ -77,7 +75,16 @@ describe('Query:basic', function() { create(0, docCount, done); }); }); - }) + }); + after(function(done) { + OrigTestModel.remove({}, done); + }); + after(function(done) { + TestModel.remove({}, done); + }); + after(function(done) { + mongoose.disconnect(done); + }); it('parseQuery', function() { let defaultResp = { q: {}, @@ -123,104 +130,140 @@ describe('Query:basic', function() { }); it('find', function(done) { - var req = {q:'{}'}; + const req = {q:'{}'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.equal( data.length, defaultLimit ); - assert.isTrue( (data[0].orig+'').match(/([0-9a-z]{24})/) != null ); + + const validateData = (obj) => { + assert.equal(obj.length, defaultLimit); + assert.isTrue((obj[0].orig + '').match(/([0-9a-z]{24})/) != null); + _.each(obj, (doc) => { + assert.isTrue(!_.isPlainObject(doc)) + }); + }; + validateData(data); //alternative: - assertPromise(TestModel.query(req)); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('regex', function(done) { - var req = {q:'{"title": {"$regex": "/^testa/"}, "i": { "$lt": 20}}'}; + const req = {q:'{"title": {"$regex": "/^testa/"}, "i": { "$lt": 20}}'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.equal( data.length, 10 ); - assert.isTrue( (data[0].orig+'').match(/([0-9a-z]{24})/) != null ); + const validateData = (obj) => { + assert.equal( obj.length, 10 ); + assert.isTrue( (obj[0].orig+'').match(/([0-9a-z]{24})/) != null ); + }; + validateData(data); //alternative - assertPromise(TestModel.query(req)); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('findOne & sort', function(done) { - var req = {q:'{}', t: 'findOne', s: '{"msg": 1}'}; + const req = {q:'{}', t: 'findOne', s: '{"msg": 1}'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.typeOf( data, 'Object' ); - assert.equal( data.title, 'testa' ); - assert.equal( data.msg, 'i#0' ); + const validateData = (obj) => { + assert.typeOf(obj, 'Object'); + assert.equal(obj.title, 'testa'); + assert.equal(obj.msg, 'i#0'); + }; + validateData(data); //alternative - assertPromise(TestModel.query(req)); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('exact', function(done) { - var req = {q:'{"msg":"i#3"}'}; + const req = {q:'{"msg":"i#3"}'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.equal( data.length, 1 ); - assert.equal( data[0].msg, "i#3" ); + const validateData = (obj) => { + assert.equal(obj.length, 1); + assert.equal(obj[0].msg, "i#3"); + }; + validateData(data); //alternative - assertPromise(TestModel.query(req)); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('populate', function(done) { - var req = {q:'{"msg":"i#3"}', p: 'orig'}; + const req = {q:'{"msg":"i#3"}', p: 'orig'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.equal( data.length, 1 ); - assert.equal( data[0].msg, "i#3" ); - assert.equal( data[0].orig.value, "original" ); + const validateData = (obj) => { + assert.equal(obj.length, 1); + assert.equal(obj[0].msg, "i#3"); + assert.equal(obj[0].orig.value, "original"); + }; + validateData(data); //alternative - assertPromise(TestModel.query(req)); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('limit & select', function(done) { - var req = {q:'{}', f: 'title', l:'3', s: '{"title": -1}'}; + const req = {q:'{}', f: 'title', l:'3', s: '{"title": -1}'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.equal( data.length, 3 ); - assert.equal( data[0].msg, undefined ); - assert.equal( data[0].title, "testb" ); - assert.equal( data[1].msg, undefined ); - assert.equal( data[1].title, "testb" ); - assert.equal( data[2].msg, undefined ); - assert.equal( data[2].title, "testb" ); + const validateData = (obj) => { + assert.equal(obj.length, 3); + assert.equal(obj[0].msg, undefined); + assert.equal(obj[0].title, "testb"); + assert.equal(obj[1].msg, undefined); + assert.equal(obj[1].title, "testb"); + assert.equal(obj[2].msg, undefined); + assert.equal(obj[2].title, "testb"); + }; + validateData(data); //alternative - assertPromise(TestModel.query(req)); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('skip', function(done) { - var req = {q:'{}', sk:'3'}; + const req = {q:'{}', sk:'3'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.equal( data.length, defaultLimit ); + const validateData = (obj) => { + assert.equal( obj.length, defaultLimit ); + }; + validateData(data); //alternative - assertPromise(TestModel.query(req)); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('count', function(done) { - var req = {q:'{"$or": [ {"msg":"i#1"}, {"msg":"i#2"}]}', t:'count'}; + const req = {q:'{"$or": [ {"msg":"i#1"}, {"msg":"i#2"}]}', t:'count'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.typeOf( data, 'object' ); - assert.equal( data.count, 2 ); + const validateData = (obj) => { + assert.typeOf(obj, 'object'); + assert.equal(obj.count, 2); + }; + validateData(data); //alternative - assertPromise(TestModel.query(req)); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('distinct', function(done) { - var req = {f:'title', t:'distinct'}; + const req = {f:'title', t:'distinct'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); assert.equal( data.length, 2 ); @@ -230,83 +273,111 @@ describe('Query:basic', function() { }); }); it('flatten', function(done) { - var req = {q:'{}', fl: 'true', l:'1'}; + const req = {q:'{}', fl: 'true', l:'1'}; TestModel.query(req, function(error, data){ assert.equal(error, undefined); - assert.typeOf(data, 'array'); - data.forEach( function(item){ - assert.typeOf(item, 'object'); - assert.equal(item['nest.ed'], 'value') - }); + const validateData = (obj) => { + assert.typeOf(obj, 'array'); + obj.forEach(function (item) { + assert.typeOf(item, 'object'); + assert.equal(item['nest.ed'], 'value') + }); + }; + validateData(data); //this is not supported when no callback is used - assert.instanceOf(TestModel.query(req), Promise); - done(); + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('!empty', function(done){ //Field exists and is not empty - var req = {'nest.ed': '{!empty}-'}; + const req = {'nest.ed': '{!empty}-'}; TestModel.query(req, function(error, data){ assert.equal(error, undefined); - assert.equal(data[0].nest.ed, 'value'); - //alternative - assertPromise(TestModel.query(req)); - done(); + const validateData = (obj) => { + assert.equal(obj[0].nest.ed, 'value'); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('!empty', function(done){ //Field exists and is not empty - var req = {'empty': '{!empty}-'}; + const req = {'empty': '{!empty}-'}; TestModel.query(req, function(error, data){ assert.equal(error, undefined); - assert.equal(data.length, 0); - //alternative - assertPromise(TestModel.query(req)); - done(); + const validateData = (obj) => { + assert.equal(obj.length, 0); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('empty', function(done){ //Field is empty or not exists - var req = {'empty': '{empty}-'}; + const req = {'empty': '{empty}-'}; TestModel.query(req, function(error, data){ assert.equal(error, undefined); - assert.equal(data.length, defaultLimit); - //alternative - assertPromise(TestModel.query(req)); - done(); + const validateData = (obj) => { + assert.equal(obj.length, defaultLimit); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('limit more than default', function(done){ //Field is empty or not exists - var req = {'l': '2000'}; + const req = {'l': '2000'}; TestModel.query(req, function(error, data){ assert.equal(error, undefined); - assert.equal(data.length, 2000); - //alternative - assertPromise(TestModel.query(req)); - done(); + const validateData = (obj) => { + assert.equal(obj.length, 2000); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('limit with skip', function(done){ //Field is empty or not exists var req = {'l': '2000', 'sk': '2500'}; - TestModel.query(req, function(error, data){ + TestModel.query(req, function(error, data) { assert.equal(error, undefined); - assert.equal(data.length, 1500); - //alternative - assertPromise(TestModel.query(req)); - done(); + const validateData = (obj) => { + assert.equal(obj.length, 1500); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('limit with filter', function(done){ //Field is empty or not exists - var req = {'l': '2000', 'q': '{ "title": "testa"}'}; + const req = {'l': '2000', 'q': '{ "title": "testa"}'}; TestModel.query(req, function(error, data){ assert.equal(error, undefined); - assert.equal(data.length, 2000); - //alternative - assertPromise(TestModel.query(req)); - done(); + const validateData = (obj) => { + assert.equal(obj.length, 2000); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('limit with sort', function(done){ @@ -314,22 +385,63 @@ describe('Query:basic', function() { var req = {'l': '2000', 's': '{ "i": -1 }'}; TestModel.query(req, function(error, data){ assert.equal(error, undefined); - assert.equal(data.length, 2000); - //alternative - assertPromise(TestModel.query(req)); - done(); + const validateData = (obj) => { + assert.equal(obj.length, 2000); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); it('oid wildcard', function(done) { var req = {q:'{"_id": "oid:57ae125aaf1b792c1768982b"}'}; TestModel.query(req, function(error, data){ assert.equal( error, undefined ); - assert.equal( data.length, 1); - assert.equal( data[0]._id, "57ae125aaf1b792c1768982b" ); - - //alternative - assertPromise(TestModel.query(req)); - done(); + const validateData = (obj) => { + assert.equal( obj.length, 1); + assert.equal( obj[0]._id, "57ae125aaf1b792c1768982b" ); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.query(req); + assertPromise(promise); + promise.then(validateData).then(done); + }); + }); + it('leanQuery', function(done) { + const req = {}; + TestModel.leanQuery(req, function(error, data) { + assert.equal( error, undefined ); + const validateData = (obj) => { + assert.equal( obj.length, defaultLimit ); + _.each(obj, (json) => { + assert.isTrue(_.isPlainObject(json)) + }); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.leanQuery(req); + assertPromise(promise); + promise.then(validateData).then(done); + }); + }); + it('leanQuery with flatten', function(done) { + const req = {fl: 1}; + TestModel.leanQuery(req, function(error, data) { + assert.equal( error, undefined ); + const validateData = (obj) => { + assert.equal(obj.length, defaultLimit); + _.each(obj, (json) => { + assert.isTrue(_.isPlainObject(json)) + }); + }; + validateData(data); + //this is not supported when no callback is used + const promise = TestModel.leanQuery(req); + assertPromise(promise); + promise.then(validateData).then(done); }); }); });