aggregate_test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. "use strict";
  2. /*jshint camelcase:false*/
  3. if (!module.parent) return require.cache[__filename] = 0, (new(require("mocha"))()).addFile(__filename).ui("exports").run(process.exit);
  4. var assert = require("assert"),
  5. aggregate = require("../../");
  6. // Utility to test the various use cases of `aggregate`
  7. function testAggregate(opts){
  8. if (!opts.asyncOnly){
  9. // SYNC: test one-off usage
  10. var results = aggregate(opts.pipeline, opts.inputs).toArray();
  11. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  12. // SYNC: test one-off usage with context
  13. results = aggregate(opts.pipeline, {hi: "there"}, opts.inputs).toArray();
  14. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  15. // SYNC: test use with context
  16. var aggregator = aggregate(opts.pipeline, {hi: "there"});
  17. results = aggregator(opts.inputs).toArray();
  18. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  19. // SYNC: test reusable aggregator functionality
  20. aggregator = aggregate(opts.pipeline);
  21. results = aggregator(opts.inputs).toArray();
  22. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  23. // SYNC: test that it is actually reusable
  24. results = aggregator(opts.inputs).toArray();
  25. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected), "should allow sync aggregator reuse");
  26. }
  27. // ASYNC: test one-off usage
  28. aggregate(opts.pipeline, opts.inputs, function(err, results){
  29. assert.ifError(err);
  30. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  31. // ASYNC: test one-off usage with context
  32. aggregate(opts.pipeline, {hi: "there"}, opts.inputs, function(err, results){
  33. assert.ifError(err);
  34. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  35. // ASYNC: test reusable aggregator functionality with context
  36. var aggregator = aggregate(opts.pipeline);
  37. aggregator({hi: "there"}, opts.inputs, function(err, results){
  38. assert.ifError(err);
  39. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  40. // ASYNC: test reusable aggregator functionality
  41. var aggregator = aggregate(opts.pipeline);
  42. aggregator(opts.inputs, function(err, results){
  43. assert.ifError(err);
  44. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  45. // ASYNC: test that it is actually reusable
  46. aggregator(opts.inputs, function(err, results){
  47. assert.ifError(err);
  48. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected), "should allow async aggregator reuse");
  49. // success!
  50. return opts.next();
  51. });
  52. });
  53. });
  54. });
  55. });
  56. }
  57. exports.aggregate = {
  58. "should be able to use an empty pipeline (no-op)": function(next){
  59. testAggregate({
  60. inputs: [1, 2, 3],
  61. pipeline: [],
  62. expected: [1, 2, 3],
  63. next: next
  64. });
  65. },
  66. "should be able to use a limit operator": function(next){
  67. testAggregate({
  68. inputs: [{_id:0}, {_id:1}, {_id:2}, {_id:3}, {_id:4}, {_id:5}],
  69. pipeline: [{$limit:2}],
  70. expected: [{_id:0}, {_id:1}],
  71. next: next
  72. });
  73. },
  74. "should be able to use a match operator": function(next){
  75. testAggregate({
  76. inputs: [{_id:0, e:1}, {_id:1, e:0}, {_id:2, e:1}, {_id:3, e:0}, {_id:4, e:1}, {_id:5, e:0}],
  77. pipeline: [{$match:{e:1}}],
  78. expected: [{_id:0, e:1}, {_id:2, e:1}, {_id:4, e:1}],
  79. next: next
  80. });
  81. },
  82. "should be able to use a skip operator": function(next){
  83. testAggregate({
  84. inputs: [{_id:0}, {_id:1}, {_id:2}, {_id:3}, {_id:4}, {_id:5}],
  85. pipeline: [{$skip:2}, {$skip:1}], //testing w/ 2 ensures independent state variables
  86. expected: [{_id:3}, {_id:4}, {_id:5}],
  87. next: next
  88. });
  89. },
  90. "should be able to use a skip and then a limit operator together in the same pipeline": function(next){
  91. testAggregate({
  92. inputs: [{_id:0, e:1}, {_id:1, e:0}, {_id:2, e:1}, {_id:3, e:0}, {_id:4, e:1}, {_id:5, e:0}],
  93. pipeline: [{$skip:2}, {$limit:1}],
  94. expected: [{_id:2, e:1}],
  95. next: next
  96. });
  97. },
  98. "should be able to construct an instance with unwind operators properly": function(next){
  99. testAggregate({
  100. inputs: [
  101. {_id:0, nodes:[
  102. {one:[11], two:[2,2]},
  103. {one:[1,1], two:[22]}
  104. ]},
  105. {_id:1, nodes:[
  106. {two:[22], three:[333]},
  107. {one:[1], three:[3,3,3]}
  108. ]}
  109. ],
  110. pipeline: [{$unwind:"$nodes"}, {$unwind:"$nodes.two"}],
  111. expected: [
  112. {_id:0,nodes:{one:[11],two:2}},
  113. {_id:0,nodes:{one:[11],two:2}},
  114. {_id:0,nodes:{one:[1,1],two:22}},
  115. {_id:1,nodes:{two:22,three:[333]}}
  116. ],
  117. next: next
  118. });
  119. },
  120. "should be able to use a project operator": function(next){
  121. // NOTE: Test case broken until expression is fixed
  122. testAggregate({
  123. inputs: [{_id:0, e:1, f:23}, {_id:2, e:2, g:34}, {_id:4, e:3}],
  124. pipeline: [
  125. {$project:{
  126. e:1,
  127. a:{$add:["$e", "$e"]},
  128. b:{$cond:[{$eq:["$e", 2]}, "two", "not two"]}
  129. //TODO: high level test of all other expression operators
  130. }}
  131. ],
  132. expected: [{_id:0, e:1, a:2, b:"not two"}, {_id:2, e:2, a:4, b:"two"}, {_id:4, e:3, a:6, b:"not two"}],
  133. next: next
  134. });
  135. },
  136. "should be able to use a project operator to exclude the _id field": function(next){
  137. // NOTE: Test case broken until expression is fixed
  138. testAggregate({
  139. inputs: [{_id:0, e:1, f:23}, {_id:2, e:2, g:34}, {_id:4, e:3}],
  140. pipeline: [
  141. {$project:{
  142. _id:0,
  143. e:1
  144. //TODO: high level test of all other expression operators
  145. }}
  146. ],
  147. expected: [{e:1}, {e:2}, {e:3}],
  148. next: next
  149. });
  150. },
  151. "should be able to project out a whole document and leave an empty": function(next) {
  152. testAggregate({
  153. inputs: [{_id:0, a:1}, {_id:1, a:2, b:1}, {_id:2, b:2, c:1}],
  154. pipeline: [
  155. {$project:{
  156. _id:0,
  157. a:1
  158. //TODO: high level test of all other expression operators
  159. }}
  160. ],
  161. expected: [{a:1}, {a:2}, {}],
  162. next: next
  163. });
  164. },
  165. "should be able to construct an instance with sort operators properly (ascending)": function(next){
  166. testAggregate({
  167. asyncOnly: true, //TODO: fix this once sync mode is fixed again -- EAGLESIX-5093
  168. inputs: [
  169. {_id:3.14159}, {_id:-273.15},
  170. {_id:42}, {_id:11}, {_id:1},
  171. {_id:null}, {_id:NaN}
  172. ],
  173. pipeline: [{$sort:{_id:1}}],
  174. expected: [
  175. {_id:null}, {_id:NaN},
  176. {_id:-273.15}, {_id:1}, {_id:3.14159}, {_id:11}, {_id:42}
  177. ],
  178. next: next
  179. });
  180. },
  181. "should be able to construct an instance with $group operators properly": function(next){
  182. testAggregate({
  183. asyncOnly: true, //TODO: fix this once sync mode is fixed again -- EAGLESIX-5093
  184. inputs: [
  185. {_id:0, a:1},
  186. {_id:0, a:2},
  187. {_id:0, a:3},
  188. {_id:0, a:4},
  189. {_id:0, a:1.5},
  190. {_id:0, a:null},
  191. {_id:1, b:"a"},
  192. {_id:1, b:"b"},
  193. {_id:1, b:"b"},
  194. {_id:1, b:"c"}
  195. ],
  196. pipeline:[
  197. {$group:{
  198. _id:"$_id",
  199. sum_a:{$sum:"$a"},
  200. //min_a:{$min:"$a"}, //this is busted in this version of mongo
  201. max_a:{$max:"$a"},
  202. avg_a:{$avg:"$a"},
  203. first_b:{$first:"$b"},
  204. last_b:{$last:"$b"},
  205. addToSet_b:{$addToSet:"$b"},
  206. push_b:{$push:"$b"}
  207. }}
  208. ],
  209. expected: [
  210. {
  211. _id:0,
  212. sum_a:11.5,
  213. //min_a:1,
  214. max_a:4,
  215. avg_a:2.3,
  216. first_b:null,
  217. last_b:null,
  218. addToSet_b:[],
  219. push_b:[]
  220. },
  221. {
  222. _id:1,
  223. sum_a:0,
  224. //min_a:null,
  225. max_a:null,
  226. avg_a:0,
  227. first_b:"a",
  228. last_b:"c",
  229. addToSet_b:["a", "b", "c"],
  230. push_b:["a", "b", "b", "c"]
  231. }
  232. ],
  233. next: next
  234. });
  235. },
  236. "should be able to construct an instance with $group using concat": function(next){
  237. testAggregate({
  238. asyncOnly: true, //TODO: fix this once sync mode is fixed again -- EAGLESIX-5093
  239. inputs: [
  240. {_id:0, a:null},
  241. {_id:1, a:"a"},
  242. {_id:1, a:"b"},
  243. {_id:1, a:"b"},
  244. {_id:1, a:"c"}
  245. ],
  246. pipeline: [
  247. {$group:{
  248. _id:{$concat:["$a"]}
  249. }}
  250. ],
  251. expected: [
  252. {_id: null},
  253. {_id: "a"},
  254. {_id: "b"},
  255. {_id: "c"}
  256. ],
  257. next: next
  258. });
  259. },
  260. "should be able to successfully use comparisions of objects to nulls without throwing an exception": function(next){
  261. testAggregate({
  262. inputs: [
  263. {
  264. cond:{$or:[
  265. {$eq:["$server","Starmetal.demo.com"]},
  266. ]},
  267. value:"PII"
  268. },
  269. {
  270. cond:{$or:[
  271. {$eq:["$server","Specium.demo.com"]},
  272. {$eq:["$server","Germanium.demo.com"]},
  273. {$eq:["$server","Runite.demo.com"]}
  274. ]},
  275. value:"PI"
  276. },
  277. {
  278. cond:{$or:[
  279. {$eq:["$server","Primal.demo.com"]}
  280. ]},
  281. value:"Confidential"
  282. },
  283. {
  284. cond:{$or:[
  285. {$eq:["$server","Polarite.demo.com"]},
  286. {$eq:["$server","Ryanium.demo.com"]}
  287. ]},
  288. value:"Proprietary"
  289. },
  290. {
  291. cond:{$or:[
  292. {$eq:["$server","Phazon.demo.com"]}
  293. ]},
  294. value:"PHI"
  295. },
  296. {
  297. cond:null,
  298. value:"Authorized"
  299. }
  300. ],
  301. pipeline: [
  302. {$skip:1},
  303. {$limit:1},
  304. {$project:{
  305. retValue:{$cond:[
  306. {$ne:["$cond", null]},
  307. null,
  308. "$value"
  309. ]}
  310. }}
  311. ],
  312. expected: [{"retValue":null}],
  313. next: next
  314. });
  315. },
  316. "should be able to successfully compare a null to a null": function(next){
  317. testAggregate({
  318. inputs: [
  319. {
  320. cond:null,
  321. value:"Authorized"
  322. }
  323. ],
  324. pipeline: [
  325. {$project:{
  326. retValue:{$cond:[
  327. {$eq:["$cond", null]},
  328. "$value",
  329. null
  330. ]}
  331. }}
  332. ],
  333. expected: [{"retValue":"Authorized"}],
  334. next: next
  335. });
  336. },
  337. "should be able to handle a large array of inputs (async only, sync will crash)": function(next) {
  338. this.timeout(10000);
  339. var inputs = [],
  340. expected = [];
  341. for(var i = 0; i < 10000; i++){
  342. inputs.push({a:i, b:[i,i,i,i,i]});
  343. expected.push({foo:i, bar:i});
  344. expected.push({foo:i, bar:i});
  345. expected.push({foo:i, bar:i});
  346. expected.push({foo:i, bar:i});
  347. expected.push({foo:i, bar:i});
  348. }
  349. testAggregate({
  350. asyncOnly: true, //TODO: fix this once sync mode is fixed again -- EAGLESIX-5093
  351. inputs: inputs,
  352. pipeline: [
  353. {$unwind:"$b"},
  354. {$project:{
  355. foo: "$a",
  356. bar: "$b",
  357. }},
  358. ],
  359. expected: expected,
  360. next: next
  361. });
  362. },
  363. "should be able to explain an empty pipeline": function(){
  364. var pipeline = [],
  365. expected = [],
  366. actual = aggregate({
  367. pipeline: pipeline,
  368. explain: true
  369. });
  370. assert.deepEqual(actual, expected);
  371. },
  372. "should be able to explain a full pipeline": function(){
  373. var pipeline = [
  374. {$match:{e:1}},
  375. {$match:{d:1}},
  376. {$skip:2},
  377. {$limit:1},
  378. {$project:{
  379. foo: "$a"
  380. }},
  381. {$group:{
  382. _id:{$concat:["$foo"]}
  383. }}
  384. ],
  385. expected = [
  386. {$match:{$and:[{e:1},{d:1}]}},
  387. {$limit:3},
  388. {$skip:2},
  389. {$project:{
  390. foo: "$a"
  391. }},
  392. {$group:{
  393. _id:{$concat:["$foo"]}
  394. }}
  395. ],
  396. actual = aggregate({
  397. pipeline: pipeline,
  398. explain: true
  399. });
  400. assert.deepEqual(actual, expected);
  401. },
  402. "should be able to explain a full pipeline with inputs": function(){
  403. var pipeline = [
  404. {$match:{e:1}},
  405. {$match:{d:1}},
  406. {$skip:2},
  407. {$limit:1},
  408. {$project:{
  409. foo: "$a"
  410. }},
  411. {$group:{
  412. _id:{$concat:["$foo"]}
  413. }}
  414. ],
  415. expected = [
  416. {"$cursor":{
  417. "query":{"$and":[{"e":1},{"d":1}]},
  418. "fields":{"a":1,"_id":1},
  419. "plan":{
  420. "type":"ArrayRunner",
  421. "nDocs":1,
  422. "position":0,
  423. "state":"RUNNER_ADVANCED"
  424. }
  425. }},
  426. {"$match":{"$and":[{"e":1},{"d":1}]}},
  427. {"$limit":3},
  428. {"$skip":2},
  429. {"$project":{"foo":"$a"}},
  430. {"$group":{"_id":{"$concat":["$foo"]}}}
  431. ],
  432. actual = aggregate({
  433. pipeline: pipeline,
  434. explain: true
  435. }, [{e:1,d:2,a:4}]);
  436. assert.deepEqual(actual, expected);
  437. },
  438. "should throw parse errors if called sync-ly": function(){
  439. assert.throws(function(){
  440. aggregate([{"$project":{"foo":"bar"}}], [{"bar":1}]);
  441. });
  442. assert.throws(function(){
  443. aggregate([{"$project":{"foo":"bar"}}]);
  444. });
  445. },
  446. "should return parse errors in the callback if called async-ly": function(done){
  447. aggregate([{"$project":{"foo":"bar"}}], [{"bar":1}], function(err, results){
  448. assert(err, "Expected Error");
  449. done();
  450. });
  451. },
  452. "should throw pipeline errors if called sync-ly": function(){
  453. assert.throws(function(){
  454. aggregate([{"$project":{"sum":{"$add":["$foo", "$bar"]}}}], [{"foo":1, "bar":"baz"}]).toArray();
  455. });
  456. var agg = aggregate([{"$project":{"sum":{"$add":["$foo", "$bar"]}}}]);
  457. assert.throws(function(){
  458. agg([{"foo":1, "bar":"baz"}]).toArray();
  459. });
  460. assert.doesNotThrow(function(){
  461. agg([{"foo":1, "bar":2}]).toArray();
  462. });
  463. },
  464. "should return pipeline errors in the callback if called async-ly": function(done){
  465. aggregate([{"$project":{"sum":{"$add":["$foo", "$bar"]}}}], [{"foo":1, "bar":"baz"}], function(err, results){
  466. assert(err, "Expected Error");
  467. var agg = aggregate([{"$project":{"sum":{"$add":["$foo", "$bar"]}}}]);
  468. agg([{"foo":1, "bar":"baz"}], function(err, results){
  469. assert(err, "Expected Error");
  470. agg([{"foo":1, "bar":2}], function(err, results){
  471. assert.ifError(err, "UnExpected Error");
  472. done();
  473. });
  474. });
  475. });
  476. },
  477. "should be able to each over a cursor": function(done) {
  478. var docs = [{a:1}, {a:2}, {a:3}],
  479. expected = docs.slice(0,2),
  480. counter = 0,
  481. iterator = function(err, doc) {
  482. assert.ifError(err);
  483. assert.deepEqual(doc, expected[counter++]);
  484. if (doc === null) return done();
  485. };
  486. expected.push(null);
  487. aggregate([{$limit:2}], docs).each(iterator);
  488. },
  489. "should be able to forEach over a cursor": function(done) {
  490. var docs = [{a:1}, {a:2}, {a:3}],
  491. expected = docs.slice(0,2),
  492. counter = 0,
  493. iterator = function(doc) {
  494. assert.deepEqual(doc, expected[counter++]);
  495. },
  496. callback = function(err) {
  497. assert.ifError(err);
  498. done();
  499. };
  500. aggregate([{$limit:2}], docs).forEach(iterator, callback);
  501. },
  502. };