MatchDocumentSource.js 11 KB

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