aggregate.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. "use strict";
  2. var assert = require("assert"),
  3. aggregate = require("../../");
  4. // Utility to test the various use cases of `aggregate`
  5. function testAggregate(opts){
  6. // SYNC: test one-off usage
  7. var results = aggregate(opts.pipeline, opts.inputs);
  8. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  9. // SYNC: test one-off usage with context
  10. results = aggregate(opts.pipeline, {hi: "there"}, opts.inputs);
  11. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  12. // SYNC: test use with context
  13. var aggregator = aggregate(opts.pipeline, {hi: "there"});
  14. results = aggregator(opts.inputs);
  15. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  16. // SYNC: test reusable aggregator functionality
  17. aggregator = aggregate(opts.pipeline);
  18. results = aggregator(opts.inputs);
  19. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  20. // SYNC: test that it is actually reusable
  21. results = aggregator(opts.inputs);
  22. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected), "Reuse of aggregator should yield the same results!");
  23. // ASYNC: test one-off usage
  24. aggregate(opts.pipeline, opts.inputs, function(err, results){
  25. assert.ifError(err);
  26. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  27. // ASYNC: test one-off usage with context
  28. aggregate(opts.pipeline, {hi: "there"}, opts.inputs, function(err, results){
  29. assert.ifError(err);
  30. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  31. // ASYNC: test reusable aggregator functionality with context
  32. var aggregator = aggregate(opts.pipeline);
  33. aggregator({hi: "there"}, opts.inputs, function(err, results){
  34. assert.ifError(err);
  35. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  36. // ASYNC: test reusable aggregator functionality
  37. var aggregator = aggregate(opts.pipeline);
  38. aggregator(opts.inputs, function(err, results){
  39. assert.ifError(err);
  40. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected));
  41. // ASYNC: test that it is actually reusable
  42. aggregator(opts.inputs, function(err, results){
  43. assert.ifError(err);
  44. assert.equal(JSON.stringify(results), JSON.stringify(opts.expected), "Reuse of aggregator should yield the same results!");
  45. // success!
  46. return opts.next();
  47. });
  48. });
  49. });
  50. });
  51. });
  52. }
  53. module.exports = {
  54. "aggregate": {
  55. "should be able to use an empty pipeline (no-op)": function(next){
  56. testAggregate({
  57. inputs: [1, 2, 3],
  58. pipeline: [],
  59. expected: [1, 2, 3],
  60. next: next
  61. });
  62. },
  63. "should be able to use a $limit operator": function(next){
  64. testAggregate({
  65. inputs: [{_id:0}, {_id:1}, {_id:2}, {_id:3}, {_id:4}, {_id:5}],
  66. pipeline: [{$limit:2}],
  67. expected: [{_id:0}, {_id:1}],
  68. next: next
  69. });
  70. },
  71. "should be able to use a $match operator": function(next){
  72. testAggregate({
  73. 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}],
  74. pipeline: [{$match:{e:1}}],
  75. expected: [{_id:0, e:1}, {_id:2, e:1}, {_id:4, e:1}],
  76. next: next
  77. });
  78. },
  79. "should be able to use a $skip operator": function(next){
  80. testAggregate({
  81. inputs: [{_id:0}, {_id:1}, {_id:2}, {_id:3}, {_id:4}, {_id:5}],
  82. pipeline: [{$skip:2}, {$skip:1}], //testing w/ 2 ensures independent state variables
  83. expected: [{_id:3}, {_id:4}, {_id:5}],
  84. next: next
  85. });
  86. },
  87. "should be able to use a $skip and then a $limit operator together in the same pipeline": function(next){
  88. testAggregate({
  89. 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}],
  90. pipeline: [{$skip:2}, {$limit:1}],
  91. expected: [{_id:2, e:1}],
  92. next: next
  93. });
  94. },
  95. "should be able to construct an instance with $unwind operators properly": function(next){
  96. testAggregate({
  97. inputs: [
  98. {_id:0, nodes:[
  99. {one:[11], two:[2,2]},
  100. {one:[1,1], two:[22]}
  101. ]},
  102. {_id:1, nodes:[
  103. {two:[22], three:[333]},
  104. {one:[1], three:[3,3,3]}
  105. ]}
  106. ],
  107. pipeline: [{$unwind:"$nodes"}, {$unwind:"$nodes.two"}],
  108. expected: [
  109. {_id:0,nodes:{one:[11],two:2}},
  110. {_id:0,nodes:{one:[11],two:2}},
  111. {_id:0,nodes:{one:[1,1],two:22}},
  112. {_id:1,nodes:{two:22,three:[333]}}
  113. ],
  114. next: next
  115. });
  116. },
  117. "should be able to use a $project operator": function(next){
  118. testAggregate({
  119. inputs: [{_id:0, e:1, f:23}, {_id:2, e:2, g:34}, {_id:4, e:3}],
  120. pipeline: [
  121. {$project:{
  122. e:1,
  123. a:{$add:["$e", "$e"]},
  124. b:{$cond:[{$eq:["$e", 2]}, "two", "not two"]}
  125. //TODO: high level test of all other expression operators
  126. }}
  127. ],
  128. 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"}],
  129. next: next
  130. });
  131. },
  132. "should be able to use a $project operator to exclude the _id field": function(next){
  133. testAggregate({
  134. inputs: [{_id:0, e:1, f:23}, {_id:2, e:2, g:34}, {_id:4, e:3}],
  135. pipeline: [
  136. {$project:{
  137. _id:0,
  138. e:1
  139. //TODO: high level test of all other expression operators
  140. }}
  141. ],
  142. expected: [{e:1}, {e:2}, {e:3}],
  143. next: next
  144. });
  145. },
  146. "should be able to construct an instance with $sort operators properly (ascending)": function(next){
  147. testAggregate({
  148. inputs: [
  149. {_id:3.14159}, {_id:-273.15},
  150. {_id:42}, {_id:11}, {_id:1},
  151. {_id:null}, {_id:NaN}
  152. ],
  153. pipeline: [{$sort:{_id:1}}],
  154. expected: [
  155. {_id:null}, {_id:NaN},
  156. {_id:-273.15}, {_id:1}, {_id:3.14159}, {_id:11}, {_id:42}
  157. ],
  158. next: next
  159. });
  160. },
  161. "should be able to construct an instance with $group operators properly": function(next){
  162. testAggregate({
  163. inputs: [
  164. {_id:0, a:1},
  165. {_id:0, a:2},
  166. {_id:0, a:3},
  167. {_id:0, a:4},
  168. {_id:0, a:1.5},
  169. {_id:0, a:null},
  170. {_id:1, b:"a"},
  171. {_id:1, b:"b"},
  172. {_id:1, b:"b"},
  173. {_id:1, b:"c"}
  174. ],
  175. pipeline:[
  176. {$group:{
  177. _id:"$_id",
  178. sum_a:{$sum:"$a"},
  179. //min_a:{$min:"$a"}, //this is busted in this version of mongo
  180. max_a:{$max:"$a"},
  181. avg_a:{$avg:"$a"},
  182. first_b:{$first:"$b"},
  183. last_b:{$last:"$b"},
  184. addToSet_b:{$addToSet:"$b"},
  185. push_b:{$push:"$b"}
  186. }}
  187. ],
  188. expected: [
  189. {
  190. _id:0,
  191. sum_a:11.5,
  192. //min_a:1,
  193. max_a:4,
  194. avg_a:2.3,
  195. first_b:undefined,
  196. last_b:undefined,
  197. addToSet_b:[],
  198. push_b:[]
  199. },
  200. {
  201. _id:1,
  202. sum_a:0,
  203. //min_a:null,
  204. max_a:undefined,
  205. avg_a:0,
  206. first_b:"a",
  207. last_b:"c",
  208. addToSet_b:["a", "b", "c"],
  209. push_b:["a", "b", "b", "c"]
  210. }
  211. ],
  212. next: next
  213. });
  214. },
  215. "should be able to construct an instance with $group using concat": function(next){
  216. testAggregate({
  217. inputs: [
  218. {_id:0, a:null},
  219. {_id:1, a:"a"},
  220. {_id:1, a:"b"},
  221. {_id:1, a:"b"},
  222. {_id:1, a:"c"}
  223. ],
  224. pipeline: [
  225. {$group:{
  226. _id:{$concat:["$a"]}
  227. }}
  228. ],
  229. expected: [
  230. {_id: null},
  231. {_id: "a"},
  232. {_id: "b"},
  233. {_id: "c"}
  234. ],
  235. next: next
  236. });
  237. },
  238. "should be able to successfully use comparisions of objects to nulls without throwing an exception": function(next){
  239. testAggregate({
  240. inputs: [
  241. {
  242. cond:{$or:[
  243. {$eq:["$server","Starmetal.demo.com"]},
  244. ]},
  245. value:"PII"
  246. },
  247. {
  248. cond:{$or:[
  249. {$eq:["$server","Specium.demo.com"]},
  250. {$eq:["$server","Germanium.demo.com"]},
  251. {$eq:["$server","Runite.demo.com"]}
  252. ]},
  253. value:"PI"
  254. },
  255. {
  256. cond:{$or:[
  257. {$eq:["$server","Primal.demo.com"]}
  258. ]},
  259. value:"Confidential"
  260. },
  261. {
  262. cond:{$or:[
  263. {$eq:["$server","Polarite.demo.com"]},
  264. {$eq:["$server","Ryanium.demo.com"]}
  265. ]},
  266. value:"Proprietary"
  267. },
  268. {
  269. cond:{$or:[
  270. {$eq:["$server","Phazon.demo.com"]}
  271. ]},
  272. value:"PHI"
  273. },
  274. {
  275. cond:null,
  276. value:"Authorized"
  277. }
  278. ],
  279. pipeline: [
  280. {$skip:1},
  281. {$limit:1},
  282. {$project:{
  283. retValue:{$cond:[
  284. {$ne:["$cond", null]},
  285. null,
  286. "$value"
  287. ]}
  288. }}
  289. ],
  290. expected: [{"retValue":null}],
  291. next: next
  292. });
  293. },
  294. "should be able to successfully compare a null to a null": function(next){
  295. testAggregate({
  296. inputs: [
  297. {
  298. cond:null,
  299. value:"Authorized"
  300. }
  301. ],
  302. pipeline: [
  303. {$project:{
  304. retValue:{$cond:[
  305. {$eq:["$cond", null]},
  306. "$value",
  307. null
  308. ]}
  309. }}
  310. ],
  311. expected: [{"retValue":"Authorized"}],
  312. next: next
  313. });
  314. },
  315. }
  316. };
  317. if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).grep(process.env.MOCHA_GREP || '').run(process.exit);