aggregate.js 9.2 KB

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