MatchDocumentSource_test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. "use strict";
  2. if (!module.parent) return require.cache[__filename] = 0, (new(require("mocha"))()).addFile(__filename).ui("exports").run(process.exit);
  3. var assert = require("assert"),
  4. async = require("async"),
  5. MatchDocumentSource = require("../../../../lib/pipeline/documentSources/MatchDocumentSource"),
  6. CursorDocumentSource = require("../../../../lib/pipeline/documentSources/CursorDocumentSource"),
  7. ArrayRunner = require("../../../../lib/query/ArrayRunner");
  8. function testRedactSafe(input, safePortion) {
  9. var match = MatchDocumentSource.createFromJson(input);
  10. assert.deepEqual(match.redactSafePortion(), safePortion);
  11. }
  12. 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. async.series([
  67. mds.getNext.bind(mds),
  68. mds.getNext.bind(mds),
  69. mds.getNext.bind(mds),
  70. mds.getNext.bind(mds),
  71. mds.getNext.bind(mds),
  72. ],
  73. function(err,res) {
  74. assert.deepEqual([{item:1},{item:2},{item:3},{item:4},null], res);
  75. next();
  76. }
  77. );
  78. },
  79. "should not return matched out documents for sources remaining": function (next){
  80. var mds = new MatchDocumentSource({ item: {$gt: 5} }),
  81. items = [ 1,2,3,4,5,6,7,8,9 ];
  82. addSource(mds, items.map(function(i){return {item:i};}));
  83. async.series([
  84. mds.getNext.bind(mds),
  85. mds.getNext.bind(mds),
  86. mds.getNext.bind(mds),
  87. mds.getNext.bind(mds),
  88. mds.getNext.bind(mds),
  89. ],
  90. function(err,res) {
  91. assert.deepEqual([{item:6},{item:7},{item:8},{item:9},null], res);
  92. next();
  93. }
  94. );
  95. },
  96. "should return EOF for no sources remaining": function (next){
  97. var mds = new MatchDocumentSource({ item: {$gt: 5} }),
  98. items = [ ];
  99. addSource(mds, items.map(function(i){return {item:i};}));
  100. async.series([
  101. mds.getNext.bind(mds),
  102. ],
  103. function(err,res) {
  104. assert.deepEqual([null], res);
  105. next();
  106. }
  107. );
  108. },
  109. },
  110. "#coalesce()": {
  111. "should return false if nextSource is not $match": function dontSkip(){
  112. var mds = new MatchDocumentSource({item: {$lt:3}});
  113. assert.equal(mds.coalesce({}), false);
  114. },
  115. "should return true if nextSource is $limit": function changeLimit(){
  116. var mds = new MatchDocumentSource({item:{$gt:1}}),
  117. mds2 = new MatchDocumentSource({item:{$lt:3}}),
  118. expected = {$and: [{item:{$gt:1}}, {item:{$lt:3}}]};
  119. var actual = mds.coalesce(mds2);
  120. assert.equal(actual, true);
  121. assert.deepEqual(mds.getQuery(), expected);
  122. },
  123. "should merge two MatchDocumentSources together": function() {
  124. var match1 = MatchDocumentSource.createFromJson({ a: 1 }),
  125. match2 = MatchDocumentSource.createFromJson({ b: 1 }),
  126. match3 = MatchDocumentSource.createFromJson({ c: 1 });
  127. // check initial state
  128. assert.deepEqual(match1.getQuery(), {a:1});
  129. assert.deepEqual(match2.getQuery(), {b:1});
  130. assert.deepEqual(match3.getQuery(), {c:1});
  131. assert.doesNotThrow(function() {
  132. match1.coalesce(match2);
  133. });
  134. assert.deepEqual(match1.getQuery(), {$and: [{a:1}, {b:1}]});
  135. },
  136. "should merge three MatchDocumentSources together": function() {
  137. var match1 = MatchDocumentSource.createFromJson({ a: 1 }),
  138. match2 = MatchDocumentSource.createFromJson({ b: 1 }),
  139. match3 = MatchDocumentSource.createFromJson({ c: 1 });
  140. // check initial state
  141. assert.deepEqual(match1.getQuery(), {a:1});
  142. assert.deepEqual(match2.getQuery(), {b:1});
  143. assert.deepEqual(match3.getQuery(), {c:1});
  144. assert.doesNotThrow(function() {
  145. match1.coalesce(match2);
  146. });
  147. assert.deepEqual(match1.getQuery(), {$and: [{a:1}, {b:1}]});
  148. assert.doesNotThrow(function() {
  149. match1.coalesce(match3);
  150. });
  151. assert.deepEqual(match1.getQuery(), {$and: [{$and: [{a:1}, {b:1}]}, {c:1}]});
  152. }
  153. },
  154. "#getQuery()": {
  155. "should return current query": function () {
  156. var mds = new MatchDocumentSource({item: {$gt:1}});
  157. var actual = mds.getQuery();
  158. assert.deepEqual(actual, {item:{$gt:1}});
  159. }
  160. },
  161. "#redactSafePortion()": {
  162. "empty match": function() {
  163. testRedactSafe({}, {});
  164. },
  165. "basic allowed things": function () {
  166. testRedactSafe({a:1},
  167. {a:1});
  168. testRedactSafe({a:"asdf"},
  169. {a:"asdf"});
  170. testRedactSafe({a:/asdf/i},
  171. {a:/asdf/i});
  172. testRedactSafe({a: {$regex: "adsf"}},
  173. {a: {$regex: "adsf"}});
  174. testRedactSafe({a: {$regex: "adsf", $options: "i"}},
  175. {a: {$regex: "adsf", $options: "i"}});
  176. testRedactSafe({a: {$mod: [1, 0]}},
  177. {a: {$mod: [1, 0]}});
  178. testRedactSafe({a: {$type: 1}},
  179. {a: {$type: 1}});
  180. },
  181. "basic disallowed things": function() {
  182. testRedactSafe({a: null},
  183. {});
  184. testRedactSafe({a: {}},
  185. {});
  186. testRedactSafe({a: []},
  187. {});
  188. testRedactSafe({"a.0": 1},
  189. {});
  190. testRedactSafe({"a.0.b": 1},
  191. {});
  192. testRedactSafe({a: {$ne: 1}},
  193. {});
  194. testRedactSafe({a: {$nin: [1, 2, 3]}},
  195. {});
  196. testRedactSafe({a: {$exists: true}}, // could be allowed but currently isn't
  197. {});
  198. testRedactSafe({a: {$exists: false}}, // can never be allowed
  199. {});
  200. testRedactSafe({a: {$size: 1}},
  201. {});
  202. testRedactSafe({$nor: [{a:1}]},
  203. {});
  204. },
  205. "Combinations": function() {
  206. testRedactSafe({a:1, b: "asdf"},
  207. {a:1, b: "asdf"});
  208. testRedactSafe({a:1, b: null},
  209. {a:1});
  210. testRedactSafe({a:null, b: null},
  211. {});
  212. },
  213. "$elemMatch": function() {
  214. testRedactSafe({a: {$elemMatch: {b: 1}}},
  215. {a:{$elemMatch:{b: 1}}});
  216. testRedactSafe({a:{$elemMatch:{b:null}}},
  217. {});
  218. testRedactSafe({a:{$elemMatch:{b:null, c:1}}},
  219. {a:{$elemMatch:{c: 1}}});
  220. },
  221. "explicit $and": function(){
  222. testRedactSafe({$and:[{a: 1}]},
  223. {$and:[{a: 1}]});
  224. testRedactSafe({$and:[{a: 1},{b: null}]},
  225. {$and:[{a: 1}]});
  226. testRedactSafe({$and:[{a: 1},{b: null, c:1}]},
  227. {$and:[{a: 1},{c:1}]});
  228. testRedactSafe({$and:[{a: null},{b: null}]},
  229. {});
  230. },
  231. "explicit $or": function() {
  232. testRedactSafe({$or:[{a: 1}]},
  233. {$or:[{a: 1}]});
  234. testRedactSafe({$or:[{a: 1},{b: null}]},
  235. {});
  236. testRedactSafe({$or:[{a: 1},{b: null, c:1}]},
  237. {$or:[{a: 1}, {c:1}]});
  238. testRedactSafe({$or:[{a: null},{b: null}]},
  239. {});
  240. testRedactSafe({},
  241. {});
  242. },
  243. "$all and $in": function() {
  244. testRedactSafe({a:{$all: [1, 0]}},
  245. {a: {$all: [1, 0]}});
  246. testRedactSafe({a:{$all: [1, 0, null]}},
  247. {a: {$all: [1, 0]}});
  248. testRedactSafe({a:{$all: [{$elemMatch: {b:1}}]}}, // could be allowed but currently isn't
  249. {});
  250. testRedactSafe({a:{$all: [1, 0, null]}},
  251. {a: {$all: [1, 0]}});
  252. testRedactSafe({a:{$in: [1, 0]}},
  253. {a: {$in: [1, 0]}});
  254. testRedactSafe({a:{$in: [1, 0, null]}},
  255. {});
  256. }
  257. },
  258. "#isTextQuery()": {
  259. "should return true when $text operator is first stage in pipeline": function () {
  260. var query = {$text:"textQuery"};
  261. assert.ok(MatchDocumentSource.isTextQuery(query)); // true
  262. },
  263. "should return true when $text operator is nested in the pipeline": function () {
  264. var query = {$stage:{$text:"textQuery"}};
  265. assert.ok(MatchDocumentSource.isTextQuery(query)); // true
  266. },
  267. "should return false when $text operator is not in pipeline": function () {
  268. var query = {$notText:"textQuery"};
  269. assert.ok(!MatchDocumentSource.isTextQuery(query)); // false
  270. }
  271. },
  272. "#uassertNoDisallowedClauses()": {
  273. "should throw if invalid stage is in match expression": function () {
  274. var whereQuery = {$where:"where"};
  275. assert.throws(function(){
  276. MatchDocumentSource.uassertNoDisallowedClauses(whereQuery);
  277. });
  278. var nearQuery = {$near:"near"};
  279. assert.throws(function(){
  280. MatchDocumentSource.uassertNoDisallowedClauses(nearQuery);
  281. });
  282. var withinQuery = {$within:"within"};
  283. assert.throws(function(){
  284. MatchDocumentSource.uassertNoDisallowedClauses(withinQuery);
  285. });
  286. var nearSphereQuery = {$nearSphere:"nearSphere"};
  287. assert.throws(function(){
  288. MatchDocumentSource.uassertNoDisallowedClauses(nearSphereQuery);
  289. });
  290. },
  291. "should throw if invalid stage is nested in the match expression": function () {
  292. var whereQuery = {$validStage:{$where:"where"}};
  293. assert.throws(function(){
  294. MatchDocumentSource.uassertNoDisallowedClauses(whereQuery);
  295. });
  296. var nearQuery = {$validStage:{$near:"near"}};
  297. assert.throws(function(){
  298. MatchDocumentSource.uassertNoDisallowedClauses(nearQuery);
  299. });
  300. var withinQuery = {$validStage:{$within:"within"}};
  301. assert.throws(function(){
  302. MatchDocumentSource.uassertNoDisallowedClauses(withinQuery);
  303. });
  304. var nearSphereQuery = {$validStage:{$nearSphere:"nearSphere"}};
  305. assert.throws(function(){
  306. MatchDocumentSource.uassertNoDisallowedClauses(nearSphereQuery);
  307. });
  308. },
  309. "should not throw if invalid stage is not in match expression": function () {
  310. var query = {$valid:"valid"};
  311. assert.doesNotThrow(function(){
  312. MatchDocumentSource.uassertNoDisallowedClauses(query);
  313. });
  314. },
  315. "should not throw if invalid stage is not nested in the match expression": function () {
  316. var query = {$valid:{$anotherValid:"valid"}};
  317. assert.doesNotThrow(function(){
  318. MatchDocumentSource.uassertNoDisallowedClauses(query);
  319. });
  320. },
  321. }
  322. }
  323. };