UnwindDocumentSource.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. "use strict";
  2. var assert = require("assert"),
  3. async = require("async"),
  4. DocumentSource = require("../../../../lib/pipeline/documentSources/DocumentSource"),
  5. UnwindDocumentSource = require("../../../../lib/pipeline/documentSources/UnwindDocumentSource"),
  6. CursorDocumentSource = require("../../../../lib/pipeline/documentSources/CursorDocumentSource"),
  7. ArrayRunner = require("../../../../lib/query/ArrayRunner");
  8. //HELPERS
  9. var assertExhausted = function assertExhausted(pds) {
  10. assert.ok(pds.eof());
  11. assert.ok(!pds.advance());
  12. };
  13. /**
  14. * Tests if the given rep is the same as what the pds resolves to as JSON.
  15. * MUST CALL WITH A PDS AS THIS (e.g. checkJsonRepresentation.call(this, rep) where this is a PDS)
  16. **/
  17. var checkJsonRepresentation = function checkJsonRepresentation(self, rep) {
  18. var pdsRep = self.serialize(true);
  19. assert.deepEqual(pdsRep, rep);
  20. };
  21. var createUnwind = function createUnwind(unwind) {
  22. //let unwind be optional
  23. if (!unwind) {
  24. unwind = "$a";
  25. }
  26. var spec = {$unwind: unwind},
  27. specElement = unwind,
  28. unwindDs = UnwindDocumentSource.createFromJson(specElement);
  29. checkJsonRepresentation(unwindDs, spec);
  30. return unwindDs;
  31. };
  32. var addSource = function addSource(unwind, data) {
  33. var cds = new CursorDocumentSource(null, new ArrayRunner(data), null);
  34. unwind.setSource(cds);
  35. };
  36. var checkResults = function checkResults(data, expectedResults, path, next) {
  37. if (expectedResults instanceof Function)
  38. next = expectedResults, expectedResults = null, path = null;
  39. if (path instanceof Function)
  40. next = path, path = null;
  41. var unwind = createUnwind(path);
  42. addSource(unwind, data || []);
  43. expectedResults = expectedResults || [];
  44. expectedResults.push(null);
  45. //Load the results from the DocumentSourceUnwind
  46. var docs = [], i = 0;
  47. async.doWhilst(
  48. function(cb) {
  49. unwind.getNext(function(err, val) {
  50. docs[i] = val;
  51. return cb(err);
  52. });
  53. },
  54. function() {
  55. return docs[i++] !== null;
  56. },
  57. function(err) {
  58. assert.deepEqual(expectedResults, docs);
  59. next();
  60. }
  61. );
  62. };
  63. var throwsException = function throwsException(data, path, expectedResults) {
  64. assert.throws(function () {
  65. checkResults(data, path, expectedResults);
  66. });
  67. };
  68. //TESTS
  69. module.exports = {
  70. "UnwindDocumentSource": {
  71. "constructor()": {
  72. "should not throw Error when constructing without args": function (){
  73. assert.doesNotThrow(function(){
  74. new UnwindDocumentSource();
  75. });
  76. }
  77. },
  78. "#getSourceName()": {
  79. "should return the correct source name; $unwind": function (){
  80. var pds = new UnwindDocumentSource();
  81. assert.strictEqual(pds.getSourceName(), "$unwind");
  82. }
  83. },
  84. "#getNext()": {
  85. "should return EOF if source is empty": function (next){
  86. var pds = createUnwind();
  87. addSource(pds, []);
  88. pds.getNext(function(err,doc) {
  89. assert.strictEqual(doc, null);
  90. next();
  91. });
  92. },
  93. "should return document if source documents exist": function (next){
  94. var pds = createUnwind();
  95. addSource(pds, [{_id:0, a:[1]}]);
  96. pds.getNext(function(err,doc) {
  97. assert.notStrictEqual(doc, null);
  98. next();
  99. });
  100. },
  101. "should return document if source documents exist and advance the source": function (next){
  102. var pds = createUnwind();
  103. addSource(pds, [{_id:0, a:[1,2]}]);
  104. pds.getNext(function(err,doc) {
  105. assert.notStrictEqual(doc, null);
  106. assert.strictEqual(doc.a, 1);
  107. pds.getNext(function(err,doc) {
  108. assert.strictEqual(doc.a, 2);
  109. next();
  110. });
  111. });
  112. },
  113. "should return unwound documents": function (next){
  114. var pds = createUnwind();
  115. addSource(pds, [{_id:0, a:[1,2]}]);
  116. var docs = [], i = 0;
  117. async.doWhilst(
  118. function(cb) {
  119. pds.getNext(function(err, val) {
  120. docs[i] = val;
  121. return cb(err);
  122. });
  123. },
  124. function() {
  125. return docs[i++] !== null;
  126. },
  127. function(err) {
  128. assert.deepEqual([{_id:0, a:1},{_id:0, a:2},null], docs);
  129. next();
  130. }
  131. );
  132. },
  133. "A document without the unwind field produces no results.": function (next){
  134. checkResults([{}],next);
  135. },
  136. "A document with a null field produces no results.": function (next){
  137. checkResults([{a:null}],next);
  138. },
  139. "A document with an empty array produces no results.": function (next){
  140. checkResults([{a:[]}],next);
  141. },
  142. "A document with a number field produces a UserException.": function (next){
  143. throwsException([{a:1}],next);
  144. next();
  145. },
  146. "An additional document with a number field produces a UserException.": function (next){
  147. throwsException([{a:[1]}, {a:1}],next);
  148. next();
  149. },
  150. "A document with a string field produces a UserException.": function (next){
  151. throwsException([{a:"foo"}],next);
  152. next();
  153. },
  154. "A document with an object field produces a UserException.": function (next){
  155. throwsException([{a:{}}],next);
  156. next();
  157. },
  158. "Unwind an array with one value.": function (next){
  159. checkResults(
  160. [{_id:0, a:[1]}],
  161. [{_id:0,a:1}],
  162. next
  163. );
  164. },
  165. "Unwind an array with two values.": function (next){
  166. checkResults(
  167. [{_id:0, a:[1, 2]}],
  168. [{_id:0,a:1}, {_id:0,a:2}],
  169. next
  170. );
  171. },
  172. "Unwind an array with two values, one of which is null.": function (next){
  173. checkResults(
  174. [{_id:0, a:[1, null]}],
  175. [{_id:0,a:1}, {_id:0,a:null}],
  176. next
  177. );
  178. },
  179. "Unwind two documents with arrays.": function (next){
  180. checkResults(
  181. [{_id:0, a:[1,2]}, {_id:0, a:[3,4]}],
  182. [{_id:0,a:1}, {_id:0,a:2}, {_id:0,a:3}, {_id:0,a:4}],
  183. next
  184. );
  185. },
  186. "Unwind an array in a nested document.": function (next){
  187. checkResults(
  188. [{_id:0,a:{b:[1,2],c:3}}],
  189. [{_id:0,a:{b:1,c:3}},{_id:0,a:{b:2,c:3}}],
  190. "$a.b",
  191. next
  192. );
  193. },
  194. "A missing array (that cannot be nested below a non object field) produces no results.": function (next){
  195. checkResults(
  196. [{_id:0,a:4}],
  197. [],
  198. "$a.b",
  199. next
  200. );
  201. },
  202. "Unwind an array in a doubly nested document.": function (next){
  203. checkResults(
  204. [{_id:0,a:{b:{d:[1,2],e:4},c:3}}],
  205. [{_id:0,a:{b:{d:1,e:4},c:3}},{_id:0,a:{b:{d:2,e:4},c:3}}],
  206. "$a.b.d",
  207. next
  208. );
  209. },
  210. "Unwind several documents in a row.": function (next){
  211. checkResults(
  212. [
  213. {_id:0,a:[1,2,3]},
  214. {_id:1},
  215. {_id:2},
  216. {_id:3,a:[10,20]},
  217. {_id:4,a:[30]}
  218. ],
  219. [
  220. {_id:0,a:1},
  221. {_id:0,a:2},
  222. {_id:0,a:3},
  223. {_id:3,a:10},
  224. {_id:3,a:20},
  225. {_id:4,a:30}
  226. ],
  227. next
  228. );
  229. },
  230. "Unwind several more documents in a row.": function (next){
  231. checkResults(
  232. [
  233. {_id:0,a:null},
  234. {_id:1},
  235. {_id:2,a:['a','b']},
  236. {_id:3},
  237. {_id:4,a:[1,2,3]},
  238. {_id:5,a:[4,5,6]},
  239. {_id:6,a:[7,8,9]},
  240. {_id:7,a:[]}
  241. ],
  242. [
  243. {_id:2,a:'a'},
  244. {_id:2,a:'b'},
  245. {_id:4,a:1},
  246. {_id:4,a:2},
  247. {_id:4,a:3},
  248. {_id:5,a:4},
  249. {_id:5,a:5},
  250. {_id:5,a:6},
  251. {_id:6,a:7},
  252. {_id:6,a:8},
  253. {_id:6,a:9}
  254. ],
  255. next
  256. );
  257. }
  258. },
  259. "#createFromJson()": {
  260. "should error if called with non-string": function testNonObjectPassed() {
  261. //Date as arg
  262. assert.throws(function() {
  263. var pds = createUnwind(new Date());
  264. });
  265. //Array as arg
  266. assert.throws(function() {
  267. var pds = createUnwind([]);
  268. });
  269. //Empty args
  270. assert.throws(function() {
  271. var pds = UnwindDocumentSource.createFromJson();
  272. });
  273. //Top level operator
  274. assert.throws(function() {
  275. var pds = createUnwind({$add: []});
  276. });
  277. }
  278. },
  279. "#getDependencies": {
  280. "should get dependent field paths": function () {
  281. var pds = createUnwind("$x.y.z"),
  282. deps = {};
  283. assert.strictEqual(pds.getDependencies(deps), DocumentSource.GetDepsReturn.SEE_NEXT);
  284. assert.deepEqual(deps, {"x.y.z":1});
  285. }
  286. }
  287. }
  288. };
  289. if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).grep(process.env.MOCHA_GREP || '').run(process.exit);