aggregate.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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. debugger;
  57. testAggregate({
  58. inputs: [1, 2, 3],
  59. pipeline: [],
  60. expected: [1, 2, 3],
  61. next: next
  62. });
  63. },
  64. "should be able to use a limit operator": function(next){
  65. testAggregate({
  66. inputs: [{_id:0}, {_id:1}, {_id:2}, {_id:3}, {_id:4}, {_id:5}],
  67. pipeline: [{$limit:2}],
  68. expected: [{_id:0}, {_id:1}],
  69. next: next
  70. });
  71. },
  72. "should be able to use a match operator": function(next){
  73. testAggregate({
  74. 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}],
  75. pipeline: [{$match:{e:1}}],
  76. expected: [{_id:0, e:1}, {_id:2, e:1}, {_id:4, e:1}],
  77. next: next
  78. });
  79. },
  80. "should be able to use a skip operator": function(next){
  81. testAggregate({
  82. inputs: [{_id:0}, {_id:1}, {_id:2}, {_id:3}, {_id:4}, {_id:5}],
  83. pipeline: [{$skip:2}, {$skip:1}], //testing w/ 2 ensures independent state variables
  84. expected: [{_id:3}, {_id:4}, {_id:5}],
  85. next: next
  86. });
  87. },
  88. "should be able to use a skip and then a limit operator together in the same pipeline": function(next){
  89. testAggregate({
  90. 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}],
  91. pipeline: [{$skip:2}, {$limit:1}],
  92. expected: [{_id:2, e:1}],
  93. next: next
  94. });
  95. },
  96. "should be able to construct an instance with unwind operators properly": function(next){
  97. testAggregate({
  98. inputs: [
  99. {_id:0, nodes:[
  100. {one:[11], two:[2,2]},
  101. {one:[1,1], two:[22]}
  102. ]},
  103. {_id:1, nodes:[
  104. {two:[22], three:[333]},
  105. {one:[1], three:[3,3,3]}
  106. ]}
  107. ],
  108. pipeline: [{$unwind:"$nodes"}, {$unwind:"$nodes.two"}],
  109. expected: [
  110. {_id:0,nodes:{one:[11],two:2}},
  111. {_id:0,nodes:{one:[11],two:2}},
  112. {_id:0,nodes:{one:[1,1],two:22}},
  113. {_id:1,nodes:{two:22,three:[333]}}
  114. ],
  115. next: next
  116. });
  117. },
  118. "should be able to use a project operator": function(next){
  119. // NOTE: Test case broken until expression is fixed
  120. testAggregate({
  121. inputs: [{_id:0, e:1, f:23}, {_id:2, e:2, g:34}, {_id:4, e:3}],
  122. pipeline: [
  123. {$project:{
  124. e:1,
  125. a:{$add:["$e", "$e"]},
  126. b:{$cond:[{$eq:["$e", 2]}, "two", "not two"]}
  127. //TODO: high level test of all other expression operators
  128. }}
  129. ],
  130. 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"}],
  131. next: next
  132. });
  133. },
  134. "should be able to use a project operator to exclude the _id field": function(next){
  135. // NOTE: Test case broken until expression is fixed
  136. testAggregate({
  137. inputs: [{_id:0, e:1, f:23}, {_id:2, e:2, g:34}, {_id:4, e:3}],
  138. pipeline: [
  139. {$project:{
  140. _id:0,
  141. e:1
  142. //TODO: high level test of all other expression operators
  143. }}
  144. ],
  145. expected: [{e:1}, {e:2}, {e:3}],
  146. next: next
  147. });
  148. },
  149. "should be able to project out a whole document and leave an empty": function(next) {
  150. testAggregate({
  151. inputs: [{_id:0, a:1}, {_id:1, a:2, b:1}, {_id:2, b:2, c:1}],
  152. pipeline: [
  153. {$project:{
  154. _id:0,
  155. a:1
  156. //TODO: high level test of all other expression operators
  157. }}
  158. ],
  159. expected: [{a:1}, {a:2}, {}],
  160. next: next
  161. });
  162. },
  163. "should be able to construct an instance with sort operators properly (ascending)": function(next){
  164. testAggregate({
  165. inputs: [
  166. {_id:3.14159}, {_id:-273.15},
  167. {_id:42}, {_id:11}, {_id:1},
  168. {_id:null}, {_id:NaN}
  169. ],
  170. pipeline: [{$sort:{_id:1}}],
  171. expected: [
  172. {_id:null}, {_id:NaN},
  173. {_id:-273.15}, {_id:1}, {_id:3.14159}, {_id:11}, {_id:42}
  174. ],
  175. next: next
  176. });
  177. },
  178. "should be able to construct an instance with $group operators properly": function(next){
  179. // NOTE: Test case broken until expression is fixed
  180. debugger;
  181. testAggregate({
  182. inputs: [
  183. {_id:0, a:1},
  184. {_id:0, a:2},
  185. {_id:0, a:3},
  186. {_id:0, a:4},
  187. {_id:0, a:1.5},
  188. {_id:0, a:null},
  189. {_id:1, b:"a"},
  190. {_id:1, b:"b"},
  191. {_id:1, b:"b"},
  192. {_id:1, b:"c"}
  193. ],
  194. pipeline:[
  195. {$group:{
  196. _id:"$_id",
  197. sum_a:{$sum:"$a"},
  198. //min_a:{$min:"$a"}, //this is busted in this version of mongo
  199. max_a:{$max:"$a"},
  200. avg_a:{$avg:"$a"},
  201. first_b:{$first:"$b"},
  202. last_b:{$last:"$b"},
  203. addToSet_b:{$addToSet:"$b"},
  204. push_b:{$push:"$b"}
  205. }}
  206. ],
  207. expected: [
  208. {
  209. _id:0,
  210. sum_a:11.5,
  211. //min_a:1,
  212. max_a:4,
  213. avg_a:2.3,
  214. first_b:undefined,
  215. last_b:undefined,
  216. addToSet_b:[],
  217. push_b:[]
  218. },
  219. {
  220. _id:1,
  221. sum_a:0,
  222. //min_a:null,
  223. max_a:undefined,
  224. avg_a:0,
  225. first_b:"a",
  226. last_b:"c",
  227. addToSet_b:["a", "b", "c"],
  228. push_b:["a", "b", "b", "c"]
  229. }
  230. ],
  231. next: next
  232. });
  233. },
  234. "should be able to construct an instance with $group using concat": function(next){
  235. // NOTE: Test case broken until expression is fixed; not sure if that concat is supposed to work
  236. testAggregate({
  237. inputs: [
  238. {_id:0, a:null},
  239. {_id:1, a:"a"},
  240. {_id:1, a:"b"},
  241. {_id:1, a:"b"},
  242. {_id:1, a:"c"}
  243. ],
  244. pipeline: [
  245. {$group:{
  246. _id:{$concat:["$a"]}
  247. }}
  248. ],
  249. expected: [
  250. {_id: null},
  251. {_id: "a"},
  252. {_id: "b"},
  253. {_id: "c"}
  254. ],
  255. next: next
  256. });
  257. },
  258. "should be able to successfully use comparisions of objects to nulls without throwing an exception": function(next){
  259. testAggregate({
  260. inputs: [
  261. {
  262. cond:{$or:[
  263. {$eq:["$server","Starmetal.demo.com"]},
  264. ]},
  265. value:"PII"
  266. },
  267. {
  268. cond:{$or:[
  269. {$eq:["$server","Specium.demo.com"]},
  270. {$eq:["$server","Germanium.demo.com"]},
  271. {$eq:["$server","Runite.demo.com"]}
  272. ]},
  273. value:"PI"
  274. },
  275. {
  276. cond:{$or:[
  277. {$eq:["$server","Primal.demo.com"]}
  278. ]},
  279. value:"Confidential"
  280. },
  281. {
  282. cond:{$or:[
  283. {$eq:["$server","Polarite.demo.com"]},
  284. {$eq:["$server","Ryanium.demo.com"]}
  285. ]},
  286. value:"Proprietary"
  287. },
  288. {
  289. cond:{$or:[
  290. {$eq:["$server","Phazon.demo.com"]}
  291. ]},
  292. value:"PHI"
  293. },
  294. {
  295. cond:null,
  296. value:"Authorized"
  297. }
  298. ],
  299. pipeline: [
  300. {$skip:1},
  301. {$limit:1},
  302. {$project:{
  303. retValue:{$cond:[
  304. {$ne:["$cond", null]},
  305. null,
  306. "$value"
  307. ]}
  308. }}
  309. ],
  310. expected: [{"retValue":null}],
  311. next: next
  312. });
  313. },
  314. "should be able to successfully compare a null to a null": function(next){
  315. testAggregate({
  316. inputs: [
  317. {
  318. cond:null,
  319. value:"Authorized"
  320. }
  321. ],
  322. pipeline: [
  323. {$project:{
  324. retValue:{$cond:[
  325. {$eq:["$cond", null]},
  326. "$value",
  327. null
  328. ]}
  329. }}
  330. ],
  331. expected: [{"retValue":"Authorized"}],
  332. next: next
  333. });
  334. },
  335. }
  336. };
  337. if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).grep(process.env.MOCHA_GREP || '').run(process.exit);