GroupDocumentSource.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. "use strict";
  2. var assert = require("assert"),
  3. DocumentSource = require("../../../../lib/pipeline/documentSources/DocumentSource"),
  4. CursorDocumentSource = require("../../../../lib/pipeline/documentSources/CursorDocumentSource"),
  5. GroupDocumentSource = require("../../../../lib/pipeline/documentSources/GroupDocumentSource"),
  6. ArrayRunner = require("../../../../lib/query/ArrayRunner"),
  7. async = require('async'),
  8. utils = require("../expressions/utils"),
  9. expressions = require("../../../../lib/pipeline/expressions");
  10. /// An assertion for `ObjectExpression` instances based on Mongo's `ExpectedResultBase` class
  11. function assertExpectedResult(args) {
  12. {// check for required args
  13. if (args === undefined) throw new TypeError("missing arg: `args` is required");
  14. if (args.spec && args.throw === undefined) args.throw = true; // Assume that spec only tests expect an error to be thrown
  15. //if (args.spec === undefined) throw new Error("missing arg: `args.spec` is required");
  16. if (args.expected !== undefined && args.docs === undefined) throw new Error("must provide docs with expected value");
  17. }// check for required args
  18. // run implementation
  19. if(args.expected && args.docs){
  20. var gds = GroupDocumentSource.createFromJson(args.spec),
  21. next,
  22. results = [],
  23. cds = new CursorDocumentSource(null, new ArrayRunner(args.docs), null);
  24. debugger;
  25. gds.setSource(cds);
  26. async.whilst(
  27. function() {
  28. return next !== null;
  29. },
  30. function(done) {
  31. gds.getNext(function(err, doc) {
  32. if(err) return done(err);
  33. next = doc;
  34. if(next === null) {
  35. return done();
  36. } else {
  37. results.push(next);
  38. return done();
  39. }
  40. });
  41. },
  42. function(err) {
  43. assert.equal(JSON.stringify(results), JSON.stringify(args.expected));
  44. if(args.done) {
  45. return args.done();
  46. }
  47. }
  48. );
  49. }else{
  50. if(args.throw) {
  51. assert.throws(function(){
  52. GroupDocumentSource.createFromJson(args.spec);
  53. });
  54. } else {
  55. assert.doesNotThrow(function(){
  56. var gds = GroupDocumentSource.createFromJson(args.spec);
  57. });
  58. }
  59. }
  60. }
  61. module.exports = {
  62. "GroupDocumentSource": {
  63. "constructor()": {
  64. // $group spec is not an object
  65. "should throw Error when constructing without args": function testConstructor(){
  66. assertExpectedResult({"throw":true});
  67. },
  68. // $group spec is not an object
  69. "should throw Error when $group spec is not an object": function testConstructor(){
  70. assertExpectedResult({spec:"Foo"});
  71. },
  72. // $group spec is an empty object
  73. "should throw Error when $group spec is an empty object": function testConstructor(){
  74. assertExpectedResult({spec:{}});
  75. },
  76. // $group _id is an empty object
  77. "should not throw when _id is an empty object": function advanceTest(){
  78. assertExpectedResult({spec:{_id:{}}, "throw":false});
  79. },
  80. // $group _id is specified as an invalid object expression
  81. "should throw error when _id is an invalid object expression": function testConstructor(){
  82. assertExpectedResult({
  83. spec:{_id:{$add:1, $and:1}},
  84. });
  85. },
  86. // $group with two _id specs
  87. //NOT Implemented can't do this in Javascript
  88. // $group _id is the empty string
  89. "should not throw when _id is an empty string": function advanceTest(){
  90. assertExpectedResult({spec:{_id:""}, "throw":false});
  91. },
  92. // $group _id is a string constant
  93. "should not throw when _id is a string constant": function advanceTest(){
  94. assertExpectedResult({spec:{_id:"abc"}, "throw":false});
  95. },
  96. // $group with _id set to an invalid field path
  97. "should throw when _id is an invalid field path": function advanceTest(){
  98. assertExpectedResult({spec:{_id:"$a.."}});
  99. },
  100. // $group _id is a numeric constant
  101. "should not throw when _id is a numeric constant": function advanceTest(){
  102. assertExpectedResult({spec:{_id:2}, "throw":false});
  103. },
  104. // $group _id is an array constant
  105. "should not throw when _id is an array constant": function advanceTest(){
  106. assertExpectedResult({spec:{_id:[1,2]}, "throw":false});
  107. },
  108. // $group _id is a regular expression (not supported)
  109. "should not throw when _id is a regex": function advanceTest(){
  110. assertExpectedResult({spec:{_id:/a/}, "throw":false});
  111. },
  112. // The name of an aggregate field is specified with a $ prefix
  113. "should throw when aggregate field spec is specified with $ prefix": function advanceTest(){
  114. assertExpectedResult({spec:{_id:1, $foo:{$sum:1}}});
  115. },
  116. // An aggregate field spec that is not an object
  117. "should throw when aggregate field spec is not an object": function advanceTest(){
  118. assertExpectedResult({spec:{_id:1, a:1}});
  119. },
  120. // An aggregate field spec that is not an object
  121. "should throw when aggregate field spec is an empty object": function advanceTest(){
  122. assertExpectedResult({spec:{_id:1, a:{}}});
  123. },
  124. // An aggregate field spec with an invalid accumulator operator
  125. "should throw when aggregate field spec is an invalid accumulator": function advanceTest(){
  126. assertExpectedResult({spec:{_id:1, a:{$bad:1}}});
  127. },
  128. // An aggregate field spec with an array argument
  129. "should throw when aggregate field spec with an array as an argument": function advanceTest(){
  130. assertExpectedResult({spec:{_id:1, a:{$sum:[]}}});
  131. },
  132. // Multiple accumulator operators for a field
  133. "should throw when aggregate field spec with multiple accumulators": function advanceTest(){
  134. assertExpectedResult({spec:{_id:1, a:{$sum:1, $push:1}}});
  135. }
  136. },
  137. "#getSourceName()": {
  138. "should return the correct source name; $group": function testSourceName(){
  139. var gds = new GroupDocumentSource({_id:{}});
  140. assert.strictEqual(gds.getSourceName(), "$group");
  141. }
  142. },
  143. "#getNext, #populate": {
  144. // Aggregation using duplicate field names is allowed currently
  145. // Note: Can't duplicate fields in javascript objects -- skipped
  146. // $group _id is computed from an object expression
  147. "should compute _id from an object expression": function testAdvance_ObjectExpression(){
  148. assertExpectedResult({
  149. docs: [{a:6}],
  150. spec: {_id:{z:"$a"}},
  151. expected: [{_id:{z:6}}]
  152. });
  153. },
  154. // $group _id is a field path expression
  155. "should compute _id from a field path expression": function testAdvance_FieldPathExpression(){
  156. assertExpectedResult({
  157. docs: [{a:5}],
  158. spec: {_id:"$a"},
  159. expected: [{_id:5}]
  160. });
  161. },
  162. // $group _id is a field path expression
  163. "should compute _id from a Date": function testAdvance_Date(){
  164. var d = new Date();
  165. assertExpectedResult({
  166. docs: [{a:d}],
  167. spec: {_id:"$a"},
  168. expected: [{_id:d}]
  169. });
  170. },
  171. // Aggregate the value of an object expression
  172. "should aggregate the value of an object expression": function testAdvance_ObjectExpression(){
  173. assertExpectedResult({
  174. docs: [{a:6}],
  175. spec: {_id:0, z:{$first:{x:"$a"}}},
  176. expected: [{_id:0, z:{x:6}}]
  177. });
  178. },
  179. // Aggregate the value of an operator expression
  180. "should aggregate the value of an operator expression": function testAdvance_OperatorExpression(){
  181. assertExpectedResult({
  182. docs: [{a:6}],
  183. spec: {_id:0, z:{$first:"$a"}},
  184. expected: [{_id:0, z:6}]
  185. });
  186. },
  187. // Aggregate the value of an operator expression
  188. "should aggregate the value of an operator expression with a null id": function testAdvance_Null(){
  189. assertExpectedResult({
  190. docs: [{a:6}],
  191. spec: {_id:null, z:{$first:"$a"}},
  192. expected: [{_id:null, z:6}]
  193. });
  194. },
  195. // A $group performed on a single document
  196. "should make one group with one values": function SingleDocument() {
  197. assertExpectedResult({
  198. docs: [{a:1}],
  199. spec: {_id:0, a:{$sum:"$a"}},
  200. expected: [{_id:0, a:1}]
  201. });
  202. },
  203. // A $group performed on two values for a single key
  204. "should make one group with two values": function TwoValuesSingleKey() {
  205. assertExpectedResult({
  206. docs: [{a:1}, {a:2}],
  207. spec: {_id:0, a:{$push:"$a"}},
  208. expected: [{_id:0, a:[1,2]}]
  209. });
  210. },
  211. // A $group performed on two values with one key each.
  212. "should make two groups with one value": function TwoValuesTwoKeys() {
  213. assertExpectedResult({
  214. docs: [{_id:0,a:1}, {_id:1,a:2}],
  215. spec: {_id:"$_id", a:{$push:"$a"}},
  216. expected: [{_id:0, a:[1]}, {_id:1, a:[2]}]
  217. });
  218. },
  219. // A $group performed on two values with two keys each.
  220. "should make two groups with two values": function FourValuesTwoKeys() {
  221. assertExpectedResult({
  222. docs: [{_id:0,a:1}, {_id:1,a:2}, {_id:0,a:3}, {_id:1,a:4}],
  223. spec: {_id:"$_id", a:{$push:"$a"}},
  224. expected: [{_id:0, a:[1, 3]}, {_id:1, a:[2, 4]}]
  225. });
  226. },
  227. // A $group performed on two values with two keys each and two accumulator operations.
  228. "should make two groups with two values with two accumulators": function FourValuesTwoKeysTwoAccumulators() {
  229. assertExpectedResult({
  230. docs: [{_id:0,a:1}, {_id:1,a:2}, {_id:0,a:3}, {_id:1,a:4}],
  231. spec: {_id:"$_id", list:{$push:"$a"}, sum:{$sum:{$divide:["$a", 2]}}},
  232. expected: [{_id:0, list:[1, 3], sum:2}, {_id:1, list:[2, 4], sum:3}]
  233. });
  234. },
  235. // Null and undefined _id values are grouped together.
  236. "should group null and undefined _id's together": function GroupNullUndefinedIds() {
  237. assertExpectedResult({
  238. docs: [{a:null, b:100}, {b:10}],
  239. spec: {_id:"$a", sum:{$sum:"$b"}},
  240. expected: [{_id:null, sum:110}]
  241. });
  242. },
  243. // A complex _id expression.
  244. "should group based on a complex id": function ComplexId() {
  245. assertExpectedResult({
  246. docs: [{a:"de", b:"ad", c:"beef", d:""}, {a:"d", b:"eadbe", c:"", d:"ef"}],
  247. spec: {_id:{$concat:["$a", "$b", "$c", "$d"]}},
  248. expected: [{_id:'deadbeef'}]
  249. });
  250. },
  251. // An undefined accumulator value is dropped.
  252. "should ignore undefined values during accumulation":function UndefinedAccumulatorValue() {
  253. assertExpectedResult({
  254. docs: [{}],
  255. spec: {_id:0, first:{$first:"$missing"}},
  256. expected: [{_id:0, first:null}]
  257. });
  258. }
  259. }
  260. }
  261. };
  262. if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).grep(process.env.MOCHA_GREP || '').run(process.exit);