MatchDocumentSource.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. "use strict";
  2. var assert = require("assert"),
  3. async = require("async"),
  4. DocumentSource = require("../../../../lib/pipeline/documentSources/DocumentSource"),
  5. MatchDocumentSource = require("../../../../lib/pipeline/documentSources/MatchDocumentSource"),
  6. CursorDocumentSource = require("../../../../lib/pipeline/documentSources/CursorDocumentSource"),
  7. ArrayRunner = require("../../../../lib/query/ArrayRunner");
  8. var testRedactSafe = function testRedactSafe(input, safePortion) {
  9. var match = MatchDocumentSource.createFromJson(input);
  10. assert.deepEqual(match.redactSafePortion(), safePortion);
  11. };
  12. var addSource = function addSource(match, data) {
  13. var cds = new CursorDocumentSource(null, new ArrayRunner(data), null);
  14. match.setSource(cds);
  15. };
  16. module.exports = {
  17. "MatchDocumentSource": {
  18. "constructor()": {
  19. "should throw Error when constructing without args": function testConstructor(){
  20. assert.throws(function(){
  21. new MatchDocumentSource();
  22. });
  23. },
  24. "should throw Error when trying to using a $text operator": function testTextOp () {
  25. assert.throws(function(){
  26. new MatchDocumentSource({packet:{ $text:"thisIsntImplemented" } });
  27. });
  28. }
  29. },
  30. "#getSourceName()": {
  31. "should return the correct source name; $match": function testSourceName(){
  32. var mds = new MatchDocumentSource({ packet :{ $exists : false } });
  33. assert.strictEqual(mds.getSourceName(), "$match");
  34. }
  35. },
  36. "#serialize()": {
  37. "should append the match query to the input builder": function sourceToJsonTest(){
  38. var mds = new MatchDocumentSource({ location : { $in : ['Kentucky'] } });
  39. var t = mds.serialize(false);
  40. assert.deepEqual(t, { "$match" : { location : { $in : ['Kentucky'] } }});
  41. }
  42. },
  43. "#createFromJson()": {
  44. "should return a new MatchDocumentSource object from an input object": function createTest(){
  45. var t = MatchDocumentSource.createFromJson({ someval:{$exists:true} });
  46. assert.strictEqual(t instanceof MatchDocumentSource, true);
  47. }
  48. },
  49. "#getNext()": {
  50. "should throw an error if no callback is given": function() {
  51. var mds = new MatchDocumentSource({item:1});
  52. assert.throws(mds.getNext.bind(mds));
  53. },
  54. "should return the current document source": function currSource(next){
  55. var mds = new MatchDocumentSource({item: 1});
  56. addSource(mds, [{ item:1 }]);
  57. mds.getNext(function(err,val) {
  58. assert.deepEqual(val, { item:1 });
  59. next();
  60. });
  61. },
  62. "should return matched sources remaining": function (next){
  63. var mds = new MatchDocumentSource({ item: {$lt: 5} }),
  64. items = [ 1,2,3,4,5,6,7,8,9 ];
  65. addSource(mds, items.map(function(i){return {item:i};}));
  66. debugger;
  67. async.series([
  68. mds.getNext.bind(mds),
  69. mds.getNext.bind(mds),
  70. mds.getNext.bind(mds),
  71. mds.getNext.bind(mds),
  72. mds.getNext.bind(mds),
  73. ],
  74. function(err,res) {
  75. assert.deepEqual([{item:1},{item:2},{item:3},{item:4},null], res);
  76. next();
  77. }
  78. );
  79. },
  80. "should not return matched out documents for sources remaining": function (next){
  81. var mds = new MatchDocumentSource({ item: {$gt: 5} }),
  82. items = [ 1,2,3,4,5,6,7,8,9 ];
  83. addSource(mds, items.map(function(i){return {item:i};}));
  84. async.series([
  85. mds.getNext.bind(mds),
  86. mds.getNext.bind(mds),
  87. mds.getNext.bind(mds),
  88. mds.getNext.bind(mds),
  89. mds.getNext.bind(mds),
  90. ],
  91. function(err,res) {
  92. assert.deepEqual([{item:6},{item:7},{item:8},{item:9},null], res);
  93. next();
  94. }
  95. );
  96. },
  97. "should return EOF for no sources remaining": function (next){
  98. var mds = new MatchDocumentSource({ item: {$gt: 5} }),
  99. items = [ ];
  100. addSource(mds, items.map(function(i){return {item:i};}));
  101. async.series([
  102. mds.getNext.bind(mds),
  103. ],
  104. function(err,res) {
  105. assert.deepEqual([null], res);
  106. next();
  107. }
  108. );
  109. },
  110. },
  111. "#coalesce()": {
  112. "should return false if nextSource is not $match": function dontSkip(){
  113. var mds = new MatchDocumentSource({item: {$lt:3}});
  114. assert.equal(mds.coalesce({}), false);
  115. },
  116. "should return true if nextSource is $limit": function changeLimit(){
  117. var mds = new MatchDocumentSource({item:{$gt:1}}),
  118. mds2 = new MatchDocumentSource({item:{$lt:3}}),
  119. expected = {$and: [{item:{$gt:1}}, {item:{$lt:3}}]};
  120. var actual = mds.coalesce(mds2);
  121. assert.equal(actual, true);
  122. assert.deepEqual(mds.getQuery(), expected);
  123. },
  124. "should merge two MatchDocumentSources together": function() {
  125. var match1 = MatchDocumentSource.createFromJson({ a: 1 }),
  126. match2 = MatchDocumentSource.createFromJson({ b: 1 }),
  127. match3 = MatchDocumentSource.createFromJson({ c: 1 });
  128. // check initial state
  129. assert.deepEqual(match1.getQuery(), {a:1});
  130. assert.deepEqual(match2.getQuery(), {b:1});
  131. assert.deepEqual(match3.getQuery(), {c:1});
  132. assert.doesNotThrow(function() {
  133. match1.coalesce(match2);
  134. });
  135. assert.deepEqual(match1.getQuery(), {$and: [{a:1}, {b:1}]});
  136. },
  137. "should merge three MatchDocumentSources together": function() {
  138. var match1 = MatchDocumentSource.createFromJson({ a: 1 }),
  139. match2 = MatchDocumentSource.createFromJson({ b: 1 }),
  140. match3 = MatchDocumentSource.createFromJson({ c: 1 });
  141. // check initial state
  142. assert.deepEqual(match1.getQuery(), {a:1});
  143. assert.deepEqual(match2.getQuery(), {b:1});
  144. assert.deepEqual(match3.getQuery(), {c:1});
  145. assert.doesNotThrow(function() {
  146. match1.coalesce(match2);
  147. });
  148. assert.deepEqual(match1.getQuery(), {$and: [{a:1}, {b:1}]});
  149. assert.doesNotThrow(function() {
  150. match1.coalesce(match3);
  151. });
  152. assert.deepEqual(match1.getQuery(), {$and: [{$and: [{a:1}, {b:1}]}, {c:1}]});
  153. }
  154. },
  155. "#getQuery()": {
  156. "should return current query": function () {
  157. var mds = new MatchDocumentSource({item: {$gt:1}});
  158. var actual = mds.getQuery();
  159. assert.deepEqual(actual, {item:{$gt:1}});
  160. }
  161. },
  162. "#redactSafePortion()": {
  163. "empty match": function() {
  164. testRedactSafe({}, {});
  165. },
  166. "basic allowed things": function () {
  167. testRedactSafe({a:1},
  168. {a:1});
  169. testRedactSafe({a:'asdf'},
  170. {a:'asdf'});
  171. testRedactSafe({a:/asdf/i},
  172. {a:/asdf/i});
  173. testRedactSafe({a: {$regex: 'adsf'}},
  174. {a: {$regex: 'adsf'}});
  175. testRedactSafe({a: {$regex: 'adsf', $options: 'i'}},
  176. {a: {$regex: 'adsf', $options: 'i'}});
  177. testRedactSafe({a: {$mod: [1, 0]}},
  178. {a: {$mod: [1, 0]}});
  179. testRedactSafe({a: {$type: 1}},
  180. {a: {$type: 1}});
  181. },
  182. "basic disallowed things": function() {
  183. testRedactSafe({a: null},
  184. {});
  185. testRedactSafe({a: {}},
  186. {});
  187. testRedactSafe({a: []},
  188. {});
  189. testRedactSafe({'a.0': 1},
  190. {});
  191. testRedactSafe({'a.0.b': 1},
  192. {});
  193. testRedactSafe({a: {$ne: 1}},
  194. {});
  195. testRedactSafe({a: {$nin: [1, 2, 3]}},
  196. {});
  197. testRedactSafe({a: {$exists: true}}, // could be allowed but currently isn't
  198. {});
  199. testRedactSafe({a: {$exists: false}}, // can never be allowed
  200. {});
  201. testRedactSafe({a: {$size: 1}},
  202. {});
  203. testRedactSafe({$nor: [{a:1}]},
  204. {});
  205. },
  206. "Combinations": function() {
  207. testRedactSafe({a:1, b: 'asdf'},
  208. {a:1, b: 'asdf'});
  209. testRedactSafe({a:1, b: null},
  210. {a:1});
  211. testRedactSafe({a:null, b: null},
  212. {});
  213. },
  214. "$elemMatch": function() {
  215. testRedactSafe({a: {$elemMatch: {b: 1}}},
  216. {a:{$elemMatch:{b: 1}}});
  217. testRedactSafe({a:{$elemMatch:{b:null}}},
  218. {});
  219. testRedactSafe({a:{$elemMatch:{b:null, c:1}}},
  220. {a:{$elemMatch:{c: 1}}});
  221. },
  222. "explicit $and": function(){
  223. testRedactSafe({$and:[{a: 1}]},
  224. {$and:[{a: 1}]});
  225. testRedactSafe({$and:[{a: 1},{b: null}]},
  226. {$and:[{a: 1}]});
  227. testRedactSafe({$and:[{a: 1},{b: null, c:1}]},
  228. {$and:[{a: 1},{c:1}]});
  229. testRedactSafe({$and:[{a: null},{b: null}]},
  230. {});
  231. },
  232. "explicit $or": function() {
  233. testRedactSafe({$or:[{a: 1}]},
  234. {$or:[{a: 1}]});
  235. testRedactSafe({$or:[{a: 1},{b: null}]},
  236. {});
  237. testRedactSafe({$or:[{a: 1},{b: null, c:1}]},
  238. {$or:[{a: 1}, {c:1}]});
  239. testRedactSafe({$or:[{a: null},{b: null}]},
  240. {});
  241. testRedactSafe({},
  242. {});
  243. },
  244. "$all and $in": function() {
  245. testRedactSafe({a:{$all: [1, 0]}},
  246. {a: {$all: [1, 0]}});
  247. testRedactSafe({a:{$all: [1, 0, null]}},
  248. {a: {$all: [1, 0]}});
  249. testRedactSafe({a:{$all: [{$elemMatch: {b:1}}]}}, // could be allowed but currently isn't
  250. {});
  251. testRedactSafe({a:{$all: [1, 0, null]}},
  252. {a: {$all: [1, 0]}});
  253. testRedactSafe({a:{$in: [1, 0]}},
  254. {a: {$in: [1, 0]}});
  255. testRedactSafe({a:{$in: [1, 0, null]}},
  256. {});
  257. }
  258. },
  259. "#isTextQuery()": {
  260. "should return true when $text operator is first stage in pipeline": function () {
  261. var query = {$text:'textQuery'};
  262. assert.ok(MatchDocumentSource.isTextQuery(query)); // true
  263. },
  264. "should return true when $text operator is nested in the pipeline": function () {
  265. var query = {$stage:{$text:'textQuery'}};
  266. assert.ok(MatchDocumentSource.isTextQuery(query)); // true
  267. },
  268. "should return false when $text operator is not in pipeline": function () {
  269. var query = {$notText:'textQuery'};
  270. assert.ok(!MatchDocumentSource.isTextQuery(query)); // false
  271. }
  272. },
  273. "#uassertNoDisallowedClauses()": {
  274. "should throw if invalid stage is in match expression": function () {
  275. var whereQuery = {$where:'where'};
  276. assert.throws(function(){
  277. MatchDocumentSource.uassertNoDisallowedClauses(whereQuery);
  278. });
  279. var nearQuery = {$near:'near'};
  280. assert.throws(function(){
  281. MatchDocumentSource.uassertNoDisallowedClauses(nearQuery);
  282. });
  283. var withinQuery = {$within:'within'};
  284. assert.throws(function(){
  285. MatchDocumentSource.uassertNoDisallowedClauses(withinQuery);
  286. });
  287. var nearSphereQuery = {$nearSphere:'nearSphere'};
  288. assert.throws(function(){
  289. MatchDocumentSource.uassertNoDisallowedClauses(nearSphereQuery);
  290. });
  291. },
  292. "should throw if invalid stage is nested in the match expression": function () {
  293. var whereQuery = {$validStage:{$where:'where'}};
  294. assert.throws(function(){
  295. MatchDocumentSource.uassertNoDisallowedClauses(whereQuery);
  296. });
  297. var nearQuery = {$validStage:{$near:'near'}};
  298. assert.throws(function(){
  299. MatchDocumentSource.uassertNoDisallowedClauses(nearQuery);
  300. });
  301. var withinQuery = {$validStage:{$within:'within'}};
  302. assert.throws(function(){
  303. MatchDocumentSource.uassertNoDisallowedClauses(withinQuery);
  304. });
  305. var nearSphereQuery = {$validStage:{$nearSphere:'nearSphere'}};
  306. assert.throws(function(){
  307. MatchDocumentSource.uassertNoDisallowedClauses(nearSphereQuery);
  308. });
  309. },
  310. "should not throw if invalid stage is not in match expression": function () {
  311. var query = {$valid:'valid'};
  312. assert.doesNotThrow(function(){
  313. MatchDocumentSource.uassertNoDisallowedClauses(query);
  314. });
  315. },
  316. "should not throw if invalid stage is not nested in the match expression": function () {
  317. var query = {$valid:{$anotherValid:'valid'}};
  318. assert.doesNotThrow(function(){
  319. MatchDocumentSource.uassertNoDisallowedClauses(query);
  320. });
  321. },
  322. }
  323. }
  324. };
  325. if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);