UnwindDocumentSource.js 8.0 KB

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