@builder6/query-mongodb 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,782 @@
1
+ 'use strict';
2
+
3
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
4
+
5
+ var chai = require('chai');
6
+ var assert = chai.assert;
7
+
8
+ var _require = require('mongodb'),
9
+ ObjectId = _require.ObjectId;
10
+
11
+ var pipelines = require('./pipelines');
12
+ var createGroupKeyPipeline = pipelines.createGroupKeyPipeline,
13
+ createGroupingPipeline = pipelines.createGroupingPipeline,
14
+ createSkipTakePipeline = pipelines.createSkipTakePipeline,
15
+ createCountPipeline = pipelines.createCountPipeline,
16
+ createMatchPipeline = pipelines.createMatchPipeline,
17
+ createSortPipeline = pipelines.createSortPipeline,
18
+ createSummaryPipeline = pipelines.createSummaryPipeline,
19
+ createSelectProjectExpression = pipelines.createSelectProjectExpression,
20
+ createSelectPipeline = pipelines.createSelectPipeline,
21
+ createCompleteFilterPipeline = pipelines.createCompleteFilterPipeline,
22
+ createRemoveNestedFieldsPipeline = pipelines.createRemoveNestedFieldsPipeline;
23
+ var _pipelines$testing = pipelines.testing,
24
+ createGroupStagePipeline = _pipelines$testing.createGroupStagePipeline,
25
+ construct = _pipelines$testing.construct,
26
+ constructRegex = _pipelines$testing.constructRegex,
27
+ parseFilter = _pipelines$testing.parseFilter,
28
+ createFilterPipeline = _pipelines$testing.createFilterPipeline,
29
+ createSearchPipeline = _pipelines$testing.createSearchPipeline,
30
+ checkNestedField = _pipelines$testing.checkNestedField,
31
+ createAddNestedFieldsPipeline = _pipelines$testing.createAddNestedFieldsPipeline,
32
+ divInt = _pipelines$testing.divInt,
33
+ subtractMod = _pipelines$testing.subtractMod,
34
+ isAndChainWithIncompleteAnds = _pipelines$testing.isAndChainWithIncompleteAnds,
35
+ fixAndChainWithIncompleteAnds = _pipelines$testing.fixAndChainWithIncompleteAnds,
36
+ isCorrectFilterOperatorStructure = _pipelines$testing.isCorrectFilterOperatorStructure;
37
+
38
+
39
+ suite('pipelines', function () {
40
+ suite('divInt', function () {
41
+ test('works', function () {
42
+ assert.deepEqual(divInt(14, 3), {
43
+ $divide: [{
44
+ $subtract: [14, { $mod: [14, 3] }]
45
+ }, 3]
46
+ });
47
+ });
48
+ });
49
+
50
+ suite('subtractMod', function () {
51
+ test('works', function () {
52
+ assert.deepEqual(subtractMod(14, 3), {
53
+ $subtract: [14, { $mod: [14, 3] }]
54
+ });
55
+ });
56
+ });
57
+
58
+ suite('createGroupKeyPipeline', function () {
59
+ test('no groupInterval', function () {
60
+ var result = createGroupKeyPipeline('sel', null, 0, 0);
61
+ var wanted = [{ $addFields: { ___group_key_0: '$sel' } }];
62
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
63
+ assert.equal(result.groupIndex, 0);
64
+ });
65
+
66
+ test('numeric groupInterval', function () {
67
+ var result = createGroupKeyPipeline('sel', 15, 0, 0);
68
+ var wanted = [{
69
+ $addFields: {
70
+ ___group_key_0: { $subtract: ['$sel', { $mod: ['$sel', 15] }] }
71
+ }
72
+ }];
73
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
74
+ assert.equal(result.groupIndex, 0);
75
+ });
76
+
77
+ var basicNamedGroupIntervalTest = function basicNamedGroupIntervalTest(name, tzo, mongoModName) {
78
+ var result = createGroupKeyPipeline('sel', name, 0, {
79
+ timezoneOffset: tzo
80
+ });
81
+ var wanted = [{
82
+ $addFields: {
83
+ ___group_key_0: _defineProperty({}, '$' + (mongoModName || name), {
84
+ $subtract: ['$sel', tzo * 60 * 1000]
85
+ })
86
+ }
87
+ }];
88
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
89
+ assert.equal(result.groupIndex, 0);
90
+ };
91
+
92
+ test('groupInterval year, timezoneOffset 0', function () {
93
+ basicNamedGroupIntervalTest('year', 0);
94
+ });
95
+
96
+ test('groupInterval year, timezoneOffset 60', function () {
97
+ basicNamedGroupIntervalTest('year', 60);
98
+ });
99
+
100
+ test('groupInterval quarter, timezoneOffset 60', function () {
101
+ var result = createGroupKeyPipeline('sel', 'quarter', 0, {
102
+ timezoneOffset: 60
103
+ });
104
+ var wanted = [{
105
+ $addFields: {
106
+ ___mp2: { $add: [{ $month: { $subtract: ['$sel', 3600000] } }, 2] }
107
+ }
108
+ }, {
109
+ $addFields: {
110
+ ___group_key_0: {
111
+ $divide: [{ $subtract: ['$___mp2', { $mod: ['$___mp2', 3] }] }, 3]
112
+ }
113
+ }
114
+ }];
115
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
116
+ assert.equal(result.groupIndex, 0);
117
+ });
118
+
119
+ test('groupInterval month, timezoneOffset 60', function () {
120
+ basicNamedGroupIntervalTest('month', 60);
121
+ });
122
+
123
+ test('groupInterval day, timezoneOffset 60', function () {
124
+ basicNamedGroupIntervalTest('day', 60, 'dayOfMonth');
125
+ });
126
+
127
+ test('groupInterval dayOfWeek, timezoneOffset 60', function () {
128
+ var result = createGroupKeyPipeline('sel', 'dayOfWeek', 0, {
129
+ timezoneOffset: 60
130
+ });
131
+ var wanted = [{
132
+ $addFields: {
133
+ ___group_key_0: {
134
+ $subtract: [{ $dayOfWeek: { $subtract: ['$sel', 3600000] } }, 1]
135
+ }
136
+ }
137
+ }];
138
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
139
+ assert.equal(result.groupIndex, 0);
140
+ });
141
+
142
+ test('groupInterval hour, timezoneOffset 60', function () {
143
+ basicNamedGroupIntervalTest('hour', 60);
144
+ });
145
+
146
+ test('groupInterval minute, timezoneOffset 60', function () {
147
+ basicNamedGroupIntervalTest('minute', 60);
148
+ });
149
+
150
+ test('groupInterval second, timezoneOffset 60', function () {
151
+ basicNamedGroupIntervalTest('second', 60);
152
+ });
153
+
154
+ test('unknown groupInterval', function () {
155
+ var result = createGroupKeyPipeline('sel', 'non-existent name', 0, 0);
156
+ var wanted = [{ $addFields: { ___group_key_0: '$sel' } }];
157
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
158
+ assert.equal(result.groupIndex, 0);
159
+ });
160
+ });
161
+
162
+ suite('createGroupStagePipeline', function () {
163
+ test('basics', function () {
164
+ var groupKeyPipeline = ['test'];
165
+ groupKeyPipeline.groupIndex = 99;
166
+ var result = createGroupStagePipeline(false, true, null, groupKeyPipeline);
167
+ var wanted = ['test', { $group: { _id: '$___group_key_99' } }];
168
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
169
+ assert.isUndefined(result.groupIndex);
170
+ });
171
+
172
+ test('not countingSeparately', function () {
173
+ var groupKeyPipeline = ['test'];
174
+ groupKeyPipeline.groupIndex = 99;
175
+ var result = createGroupStagePipeline(false, false, null, groupKeyPipeline);
176
+ var wanted = ['test', { $group: { _id: '$___group_key_99', count: { $sum: 1 } } }];
177
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
178
+ assert.isUndefined(result.groupIndex);
179
+ });
180
+
181
+ test('not countingSeparately, includeDataItems', function () {
182
+ var groupKeyPipeline = ['test'];
183
+ groupKeyPipeline.groupIndex = 99;
184
+ var result = createGroupStagePipeline(true, false, 'itemProjection', groupKeyPipeline);
185
+ var wanted = ['test', {
186
+ $group: {
187
+ _id: '$___group_key_99',
188
+ count: { $sum: 1 },
189
+ items: { $push: 'itemProjection' }
190
+ }
191
+ }];
192
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
193
+ assert.isUndefined(result.groupIndex);
194
+ });
195
+ });
196
+
197
+ suite('createGroupingPipeline', function () {
198
+ test('basics', function () {
199
+ var groupKeyPipeline = ['test'];
200
+ groupKeyPipeline.groupIndex = 99;
201
+ var result = createGroupingPipeline(true, false, true, groupKeyPipeline);
202
+ var wanted = ['test', { $group: { _id: '$___group_key_99' } }, { $project: { _id: 0, key: '$_id' } }, { $sort: { key: -1 } }, { $addFields: { items: null } }];
203
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
204
+ assert.isUndefined(result.groupIndex);
205
+ });
206
+
207
+ test('not countingSeparately', function () {
208
+ var groupKeyPipeline = ['test'];
209
+ groupKeyPipeline.groupIndex = 99;
210
+ var result = createGroupingPipeline(true, false, false, groupKeyPipeline);
211
+ var wanted = ['test', { $group: { _id: '$___group_key_99', count: { $sum: 1 } } }, { $project: { _id: 0, key: '$_id', count: 1 } }, { $sort: { key: -1 } }, { $addFields: { items: null } }];
212
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
213
+ assert.isUndefined(result.groupIndex);
214
+ });
215
+
216
+ test('not countingSeparately, includeDataItems', function () {
217
+ var groupKeyPipeline = ['test'];
218
+ groupKeyPipeline.groupIndex = 99;
219
+ var result = createGroupingPipeline(true, true, false, groupKeyPipeline);
220
+ var wanted = ['test', {
221
+ $group: {
222
+ _id: '$___group_key_99',
223
+ count: { $sum: 1 },
224
+ items: { $push: '$$CURRENT' }
225
+ }
226
+ }, { $project: { _id: 0, key: '$_id', count: 1, items: 1 } }, { $sort: { key: -1 } }];
227
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
228
+ assert.isUndefined(result.groupIndex);
229
+ });
230
+
231
+ test('not countingSeparately, includeDataItems, custom itemProjection', function () {
232
+ var groupKeyPipeline = ['test'];
233
+ groupKeyPipeline.groupIndex = 99;
234
+ var result = createGroupingPipeline(true, true, false, groupKeyPipeline, '$$customProjection$$');
235
+ var wanted = ['test', {
236
+ $group: {
237
+ _id: '$___group_key_99',
238
+ count: { $sum: 1 },
239
+ items: { $push: '$$customProjection$$' }
240
+ }
241
+ }, { $project: { _id: 0, key: '$_id', count: 1, items: 1 } }, { $sort: { key: -1 } }];
242
+ assert.equal(JSON.stringify(result), JSON.stringify(wanted));
243
+ assert.isUndefined(result.groupIndex);
244
+ });
245
+ });
246
+
247
+ suite('createSkipTakePipeline', function () {
248
+ test('no skip or take', function () {
249
+ assert.deepEqual(createSkipTakePipeline(), []);
250
+ });
251
+
252
+ test('skip, no take', function () {
253
+ assert.deepEqual(createSkipTakePipeline(33), [{ $skip: 33 }]);
254
+ });
255
+
256
+ test('no skip, take', function () {
257
+ assert.deepEqual(createSkipTakePipeline(null, 33), [{ $limit: 33 }]);
258
+ });
259
+
260
+ test('skip and take', function () {
261
+ assert.deepEqual(createSkipTakePipeline(33, 44), [{ $skip: 33 }, { $limit: 44 }]);
262
+ });
263
+ });
264
+
265
+ suite('createCountPipeline', function () {
266
+ test('works', function () {
267
+ assert.deepEqual(createCountPipeline(), [{ $count: 'count' }]);
268
+ });
269
+ });
270
+
271
+ suite('createMatchPipeline', function () {
272
+ test('works', function () {
273
+ assert.deepEqual(createMatchPipeline('sel', 'val'), [{ $match: { sel: 'val' } }]);
274
+ });
275
+ });
276
+
277
+ suite('construct', function () {
278
+ test('works', function () {
279
+ assert.deepEqual(construct('field', 'plus', 'val'), {
280
+ field: { plus: 'val' }
281
+ });
282
+ });
283
+ });
284
+
285
+ suite('constructRegex', function () {
286
+ test('works', function () {
287
+ assert.deepEqual(constructRegex('field', 'regex', true), {
288
+ field: { $regex: 'regex', $options: 'i' }
289
+ });
290
+ });
291
+ });
292
+
293
+ suite('parseFilter', function () {
294
+ var testParseFilter = function testParseFilter(input, expectedMatch, expectedFieldList) {
295
+ var result = parseFilter(input, { caseInsensitiveRegex: true });
296
+ var match = result && result.match;
297
+ var fieldList = result ? result.fieldList : [];
298
+ assert.deepEqual(match, expectedMatch);
299
+ assert.deepEqual(fieldList, expectedFieldList);
300
+ };
301
+
302
+ test('string element', function () {
303
+ testParseFilter('thing', {
304
+ thing: { $eq: true }
305
+ }, ['thing']);
306
+ });
307
+
308
+ test('nested array', function () {
309
+ testParseFilter([[['!', 'thing']]], {
310
+ $nor: [{
311
+ thing: { $eq: true }
312
+ }]
313
+ }, ['thing']);
314
+ });
315
+
316
+ test('!', function () {
317
+ testParseFilter(['!', 'thing'], {
318
+ $nor: [{
319
+ thing: { $eq: true }
320
+ }]
321
+ }, ['thing']);
322
+ });
323
+
324
+ test('unknown unary', function () {
325
+ testParseFilter(['&', 'thing'], null, []);
326
+ });
327
+
328
+ test('equal', function () {
329
+ testParseFilter(['thing', '=', 'val'], {
330
+ thing: { $eq: 'val' }
331
+ }, ['thing']);
332
+ });
333
+
334
+ test('equalsObjectId', function () {
335
+ testParseFilter(['thing', 'equalsObjectId', '0123456789abcdef01234567'], {
336
+ thing: {
337
+ $eq: new ObjectId('0123456789abcdef01234567')
338
+ }
339
+ }, ['thing']);
340
+ });
341
+
342
+ test('not equal', function () {
343
+ testParseFilter(['thing', '<>', 'val'], {
344
+ thing: { $ne: 'val' }
345
+ }, ['thing']);
346
+ });
347
+
348
+ test('greater than', function () {
349
+ testParseFilter(['thing', '>', 'val'], {
350
+ thing: { $gt: 'val' }
351
+ }, ['thing']);
352
+ });
353
+
354
+ test('greater than or equal', function () {
355
+ testParseFilter(['thing', '>=', 'val'], {
356
+ thing: { $gte: 'val' }
357
+ }, ['thing']);
358
+ });
359
+
360
+ test('lower than', function () {
361
+ testParseFilter(['thing', '<', 'val'], {
362
+ thing: { $lt: 'val' }
363
+ }, ['thing']);
364
+ });
365
+
366
+ test('lower than or equal', function () {
367
+ testParseFilter(['thing', '<=', 'val'], {
368
+ thing: { $lte: 'val' }
369
+ }, ['thing']);
370
+ });
371
+
372
+ test('startswith', function () {
373
+ testParseFilter(['thing', 'startswith', 'val'], {
374
+ thing: { $regex: '^val', $options: 'i' }
375
+ }, ['thing']);
376
+ });
377
+
378
+ test('endswith', function () {
379
+ testParseFilter(['thing', 'endswith', 'val'], {
380
+ thing: { $regex: 'val$', $options: 'i' }
381
+ }, ['thing']);
382
+ });
383
+
384
+ test('contains', function () {
385
+ testParseFilter(['thing', 'contains', 'val'], {
386
+ thing: { $regex: 'val', $options: 'i' }
387
+ }, ['thing']);
388
+ });
389
+
390
+ test('notcontains', function () {
391
+ testParseFilter(['thing', 'notcontains', 'val'], {
392
+ thing: { $regex: '^((?!val).)*$', $options: 'i' }
393
+ }, ['thing']);
394
+ });
395
+
396
+ test('unknown operator', function () {
397
+ testParseFilter(['thing', '&%&%&%&', 'val'], null, []);
398
+ });
399
+
400
+ test('even number of elements > 2', function () {
401
+ testParseFilter([1, 3, 4, 6], null, []);
402
+ });
403
+
404
+ test('not an array or a string', function () {
405
+ testParseFilter({ barg: 42 }, null, []);
406
+ });
407
+
408
+ test('odd number of elements > 3 without operator in pos 1', function () {
409
+ testParseFilter([1, 'unknown item', 3, 4, 5], null, []);
410
+ });
411
+
412
+ test('odd number of elements > 3 with non-string in pos 1', function () {
413
+ testParseFilter([1, { barg: 42 }, 3, 4, 5], null, []);
414
+ });
415
+
416
+ test('nested field', function () {
417
+ testParseFilter(['thing.year', '=', 'val'], {
418
+ ___thing_year: { $eq: 'val' }
419
+ }, ['thing.year']);
420
+ });
421
+
422
+ test('unrecognized nested field', function () {
423
+ testParseFilter(['thing.unknown', '=', 'val'], {
424
+ 'thing.unknown': { $eq: 'val' }
425
+ }, ['thing.unknown']);
426
+ });
427
+
428
+ test('correct "and" chain', function () {
429
+ testParseFilter([['field1', '=', 42], 'and', ['field2', '>', 10], 'and', ['field3', '<>', 'this thing']], {
430
+ $and: [{
431
+ field1: { $eq: 42 }
432
+ }, {
433
+ field2: { $gt: 10 }
434
+ }, {
435
+ field3: { $ne: 'this thing' }
436
+ }]
437
+ }, ['field1', 'field2', 'field3']);
438
+ });
439
+
440
+ test('short "and" chain with no "ands"', function () {
441
+ testParseFilter([['field1', '=', 42], ['field2', '>', 10]], {
442
+ $and: [{
443
+ field1: { $eq: 42 }
444
+ }, {
445
+ field2: { $gt: 10 }
446
+ }]
447
+ }, ['field1', 'field2']);
448
+ });
449
+
450
+ test('long "and" chain with no "ands"', function () {
451
+ testParseFilter([['field1', '=', 42], ['field2', '>', 10], ['field3', '<>', 'this thing'], ['field4', '=', 11]], {
452
+ $and: [{
453
+ field1: { $eq: 42 }
454
+ }, {
455
+ field2: { $gt: 10 }
456
+ }, {
457
+ field3: { $ne: 'this thing' }
458
+ }, {
459
+ field4: { $eq: 11 }
460
+ }]
461
+ }, ['field1', 'field2', 'field3', 'field4']);
462
+ });
463
+
464
+ test('"and" chain with incomplete "ands"', function () {
465
+ testParseFilter([['field1', '=', 42], 'and', ['field2', '>', 10], ['field3', '<>', 'this thing']], {
466
+ $and: [{
467
+ field1: { $eq: 42 }
468
+ }, {
469
+ field2: { $gt: 10 }
470
+ }, {
471
+ field3: { $ne: 'this thing' }
472
+ }]
473
+ }, ['field1', 'field2', 'field3']);
474
+ });
475
+
476
+ test('correct "or" chain', function () {
477
+ testParseFilter([['field1', '=', 42], 'or', ['field2', '>', 10], 'or', ['field3', '<>', 'this thing']], {
478
+ $or: [{
479
+ field1: { $eq: 42 }
480
+ }, {
481
+ field2: { $gt: 10 }
482
+ }, {
483
+ field3: { $ne: 'this thing' }
484
+ }]
485
+ }, ['field1', 'field2', 'field3']);
486
+ });
487
+
488
+ test('incorrect operator chain', function () {
489
+ testParseFilter([['field1', '=', 42], 'and', ['field2', '>', 10], 'or', ['field3', '<>', 'this thing']], null, []);
490
+ });
491
+
492
+ test('correct combined operator chain', function () {
493
+ testParseFilter([['field1', '=', 42], 'and', [['field2', '>', 10], 'or', ['field3', '<>', 'this thing']]], {
494
+ $and: [{
495
+ field1: { $eq: 42 }
496
+ }, {
497
+ $or: [{
498
+ field2: { $gt: 10 }
499
+ }, {
500
+ field3: { $ne: 'this thing' }
501
+ }]
502
+ }]
503
+ }, ['field1', 'field2', 'field3']);
504
+ });
505
+ });
506
+
507
+ suite('createFilterPipeline', function () {
508
+ test('works', function () {
509
+ assert.deepEqual(createFilterPipeline(['thing', '=', 42]), {
510
+ pipeline: [{ $match: { thing: { $eq: 42 } } }],
511
+ fieldList: ['thing']
512
+ });
513
+ });
514
+
515
+ test('no filter', function () {
516
+ assert.deepEqual(createFilterPipeline(), {
517
+ pipeline: [],
518
+ fieldList: []
519
+ });
520
+ });
521
+
522
+ test('invalid filter', function () {
523
+ assert.deepEqual(createFilterPipeline(['thing', '=']), {
524
+ pipeline: [],
525
+ fieldList: []
526
+ });
527
+ });
528
+ });
529
+
530
+ suite('createSortPipeline', function () {
531
+ test('works', function () {
532
+ assert.deepEqual(createSortPipeline([{ selector: 'field1', desc: true }, { selector: 'field2' }]), [{ $sort: { field1: -1, field2: 1 } }]);
533
+ });
534
+ });
535
+
536
+ suite('createSummaryPipeline', function () {
537
+ test('works', function () {
538
+ assert.deepEqual(createSummaryPipeline([{ summaryType: 'min', selector: 'thing' }, { summaryType: 'max', selector: 'other' }, { summaryType: 'invalid', selector: 'dontknow' }, { summaryType: 'count' }]), [{
539
+ $group: {
540
+ ___minthing: { $min: '$thing' },
541
+ ___maxother: { $max: '$other' },
542
+ ___count: { $sum: 1 },
543
+ _id: null
544
+ }
545
+ }]);
546
+ });
547
+ });
548
+
549
+ suite('createSearchPipeline', function () {
550
+ test('simple values', function () {
551
+ assert.deepEqual(createSearchPipeline('thing', '=', 42), {
552
+ pipeline: [{ $match: { thing: { $eq: 42 } } }],
553
+ fieldList: ['thing']
554
+ });
555
+ });
556
+
557
+ test('list of expr', function () {
558
+ assert.deepEqual(createSearchPipeline(['thing', 'other', 'outlandish'], '=', 42), {
559
+ pipeline: [{
560
+ $match: {
561
+ $or: [{ thing: { $eq: 42 } }, { other: { $eq: 42 } }, { outlandish: { $eq: 42 } }]
562
+ }
563
+ }],
564
+ fieldList: ['thing', 'other', 'outlandish']
565
+ });
566
+ });
567
+ });
568
+
569
+ suite('createSelectProjectExpression', function () {
570
+ test('basics', function () {
571
+ assert.deepEqual(createSelectProjectExpression(['field1', 'field2']), {
572
+ field1: '$field1',
573
+ field2: '$field2'
574
+ });
575
+ });
576
+
577
+ test('explicitId', function () {
578
+ assert.deepEqual(createSelectProjectExpression(['field1', 'field2'], true), {
579
+ field1: '$field1',
580
+ field2: '$field2',
581
+ _id: '$_id'
582
+ });
583
+ });
584
+ });
585
+
586
+ suite('createSelectPipeline', function () {
587
+ test('works', function () {
588
+ assert.deepEqual(createSelectPipeline(['field1', 'field2']), [{
589
+ $project: {
590
+ field1: '$field1',
591
+ field2: '$field2'
592
+ }
593
+ }]);
594
+ });
595
+ });
596
+
597
+ suite('checkNestedField', function () {
598
+ test('Quarter', function () {
599
+ assert.deepEqual(checkNestedField('field.Quarter'), {
600
+ base: 'field',
601
+ nested: 'Quarter',
602
+ filterFieldName: '___field_Quarter'
603
+ });
604
+ });
605
+
606
+ test('year', function () {
607
+ assert.deepEqual(checkNestedField('field.year'), {
608
+ base: 'field',
609
+ nested: 'year',
610
+ filterFieldName: '___field_year'
611
+ });
612
+ });
613
+
614
+ test('no match', function () {
615
+ assert.isUndefined(checkNestedField('field.other'));
616
+ });
617
+ });
618
+
619
+ suite('createAddNestedFieldsPipeline', function () {
620
+ test('no recognized nested fields', function () {
621
+ assert.deepEqual(createAddNestedFieldsPipeline(['field1', 'field2', 'field3.other'], 0), { pipeline: [], nestedFields: [] });
622
+ });
623
+
624
+ test('nested fields, tzo 60', function () {
625
+ assert.deepEqual(createAddNestedFieldsPipeline(['field1', 'field2.year', 'field3.quarter', 'field4.month', 'field3.day', 'field3.dayofweek'], { timezoneOffset: 60 }), {
626
+ pipeline: [{
627
+ $addFields: {
628
+ ___field3_mp2: {
629
+ $add: [{
630
+ $month: {
631
+ $subtract: ['$field3', 3600000]
632
+ }
633
+ }, 2]
634
+ }
635
+ }
636
+ }, {
637
+ $addFields: {
638
+ ___field2_year: {
639
+ $year: {
640
+ $subtract: ['$field2', 3600000]
641
+ }
642
+ },
643
+ ___field3_day: {
644
+ $dayOfMonth: {
645
+ $subtract: ['$field3', 3600000]
646
+ }
647
+ },
648
+ ___field3_dayofweek: {
649
+ $subtract: [{
650
+ $dayOfWeek: {
651
+ $subtract: ['$field3', 3600000]
652
+ }
653
+ }, 1]
654
+ },
655
+ ___field3_quarter: {
656
+ $divide: [{
657
+ $subtract: ['$___field3_mp2', {
658
+ $mod: ['$___field3_mp2', 3]
659
+ }]
660
+ }, 3]
661
+ },
662
+ ___field4_month: {
663
+ $month: {
664
+ $subtract: ['$field4', 3600000]
665
+ }
666
+ }
667
+ }
668
+ }],
669
+ nestedFields: ['___field2_year', '___field3_mp2', '___field3_quarter', '___field4_month', '___field3_day', '___field3_dayofweek']
670
+ });
671
+ });
672
+ });
673
+
674
+ suite('createCompleteFilterPipeline', function () {
675
+ test('works', function () {
676
+ assert.deepEqual(createCompleteFilterPipeline('thing', '=', 42, [['thing2', '>', 13], 'and', ['date.month', '<', 5]], { timezoneOffset: 60 }), {
677
+ pipeline: [{
678
+ $addFields: {
679
+ ___date_month: {
680
+ $month: {
681
+ $subtract: ['$date', 3600000]
682
+ }
683
+ }
684
+ }
685
+ }, {
686
+ $match: {
687
+ thing: {
688
+ $eq: 42
689
+ }
690
+ }
691
+ }, {
692
+ $match: {
693
+ $and: [{
694
+ thing2: {
695
+ $gt: 13
696
+ }
697
+ }, {
698
+ ___date_month: {
699
+ $lt: 5
700
+ }
701
+ }]
702
+ }
703
+ }],
704
+ nestedFields: ['___date_month']
705
+ });
706
+ });
707
+ });
708
+
709
+ suite('createRemoveNestedFieldsPipeline', function () {
710
+ test('works', function () {
711
+ assert.deepEqual(createRemoveNestedFieldsPipeline(['field1', 'field2']), [{ $project: { field1: 0, field2: 0 } }]);
712
+ });
713
+ });
714
+
715
+ suite('correctFilterOperatorStructure', function () {
716
+ test('detect correct structure', function () {
717
+ assert.isTrue(isCorrectFilterOperatorStructure([['field', '=', 42], 'and', ['field2', '>', 10], 'and', ['field3', '=', 15], 'and', ['field4', '=', 11], 'and', ['field8', '>', 100]], 'and'));
718
+ });
719
+
720
+ test('reject missing operators', function () {
721
+ assert.isFalse(isCorrectFilterOperatorStructure([['field', '=', 42], 'and', ['field2', '>', 10], ['field3', '=', 15], 'and', ['field4', '=', 11], 'and', ['field8', '>', 100]], 'and'));
722
+ });
723
+
724
+ test('reject incorrect operators', function () {
725
+ assert.isFalse(isCorrectFilterOperatorStructure([['field', '=', 42], 'and', ['field2', '>', 10], 'or', ['field3', '=', 15], 'and', ['field4', '=', 11], 'and', ['field8', '>', 100]], 'and'));
726
+ });
727
+ });
728
+
729
+ suite('andChainWithIncompleteAnds', function () {
730
+ test('detect short "and" chain with no "ands"', function () {
731
+ assert.isTrue(isAndChainWithIncompleteAnds([['field', '=', 42], ['field2', '>', 10]]));
732
+ });
733
+ test('detect three element "and" chain with no "ands"', function () {
734
+ assert.isTrue(isAndChainWithIncompleteAnds([['field', '=', 42], ['field2', '>', 10], ['field3', '=', 15]]));
735
+ });
736
+ test('detect long "and" chain with no "ands"', function () {
737
+ assert.isTrue(isAndChainWithIncompleteAnds([['field', '=', 42], ['field2', '>', 10], ['field3', '=', 15], ['field4', '=', 11], ['field8', '>', 100]]));
738
+ });
739
+ test('detect long "and" chain with one "and"', function () {
740
+ assert.isTrue(isAndChainWithIncompleteAnds([['field', '=', 42], 'and', ['field2', '>', 10], ['field3', '=', 15], ['field4', '=', 11], ['field8', '>', 100]]));
741
+ });
742
+ test('detect long "and" chain with some "ands"', function () {
743
+ assert.isTrue(isAndChainWithIncompleteAnds([['field', '=', 42], ['field2', '>', 10], ['field3', '=', 15], 'and', ['field4', '=', 11], ['field8', '>', 100], 'and', ['field5', '=', 13]]));
744
+ });
745
+
746
+ test('reject unary operator chain', function () {
747
+ assert.isFalse(isAndChainWithIncompleteAnds(['!', ['field', '=', 10]]));
748
+ });
749
+
750
+ test('reject simple criterion', function () {
751
+ assert.isFalse(isAndChainWithIncompleteAnds(['field', '=', 10]));
752
+ });
753
+
754
+ test('reject chain with invalid operators', function () {
755
+ assert.isFalse(isAndChainWithIncompleteAnds([['field', '=', 42], ['field2', '>', 10], ['field3', '=', 15], 'or', ['field4', '=', 11], ['field8', '>', 100]]));
756
+ });
757
+
758
+ test('reject chain with complete set of operators', function () {
759
+ assert.isFalse(isAndChainWithIncompleteAnds([['field', '=', 42], 'and', ['field2', '>', 10], 'and', ['field3', '=', 15], 'and', ['field4', '=', 11], 'and', ['field8', '>', 100]]));
760
+ });
761
+
762
+ test('fix incomplete very short "and" chain with no "ands"', function () {
763
+ assert.deepEqual(fixAndChainWithIncompleteAnds([['field', '=', 42], ['field2', '>', 10]]), [['field', '=', 42], 'and', ['field2', '>', 10]]);
764
+ });
765
+
766
+ test('fix incomplete short "and" chain with no "ands"', function () {
767
+ assert.deepEqual(fixAndChainWithIncompleteAnds([['field', '=', 42], ['field2', '>', 10], ['field3', '=', 15]]), [['field', '=', 42], 'and', ['field2', '>', 10], 'and', ['field3', '=', 15]]);
768
+ });
769
+
770
+ test('fix incomplete long "and" chain with no "ands"', function () {
771
+ assert.deepEqual(fixAndChainWithIncompleteAnds([['field', '=', 42], ['field2', '>', 10], ['field3', '=', 15], ['field4', '>', 42], ['field5', '=', 'something']]), [['field', '=', 42], 'and', ['field2', '>', 10], 'and', ['field3', '=', 15], 'and', ['field4', '>', 42], 'and', ['field5', '=', 'something']]);
772
+ });
773
+
774
+ test('fix incomplete long "and" chain with one "and"', function () {
775
+ assert.deepEqual(fixAndChainWithIncompleteAnds([['field', '=', 42], 'and', ['field2', '>', 10], ['field3', '=', 15], ['field4', '>', 42], ['field5', '=', 'something']]), [['field', '=', 42], 'and', ['field2', '>', 10], 'and', ['field3', '=', 15], 'and', ['field4', '>', 42], 'and', ['field5', '=', 'something']]);
776
+ });
777
+
778
+ test('fix incomplete long "and" chain with some "ands"', function () {
779
+ assert.deepEqual(fixAndChainWithIncompleteAnds([['field', '=', 42], 'and', ['field2', '>', 10], ['field3', '=', 15], 'and', ['field4', '>', 42], ['field5', '=', 'something']]), [['field', '=', 42], 'and', ['field2', '>', 10], 'and', ['field3', '=', 15], 'and', ['field4', '>', 42], 'and', ['field5', '=', 'something']]);
780
+ });
781
+ });
782
+ });