foxhound 2.0.18 → 2.0.20

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.
Files changed (36) hide show
  1. package/README.md +3 -2
  2. package/docs/README.md +32 -11
  3. package/docs/_cover.md +3 -2
  4. package/docs/_sidebar.md +21 -1
  5. package/docs/_topbar.md +7 -0
  6. package/docs/api/README.md +73 -0
  7. package/docs/api/addFilter.md +170 -0
  8. package/docs/api/addJoin.md +128 -0
  9. package/docs/api/addRecord.md +108 -0
  10. package/docs/api/addSort.md +109 -0
  11. package/docs/api/behaviorFlags.md +123 -0
  12. package/docs/api/buildQuery.md +146 -0
  13. package/docs/api/clone.md +115 -0
  14. package/docs/api/queryOverrides.md +82 -0
  15. package/docs/api/setCap.md +115 -0
  16. package/docs/api/setDataElements.md +95 -0
  17. package/docs/api/setDialect.md +78 -0
  18. package/docs/api/setDistinct.md +76 -0
  19. package/docs/api/setIDUser.md +80 -0
  20. package/docs/api/setScope.md +70 -0
  21. package/docs/architecture.md +134 -42
  22. package/docs/dialects/README.md +2 -0
  23. package/docs/dialects/postgresql.md +126 -0
  24. package/docs/quickstart.md +196 -0
  25. package/docs/retold-catalog.json +40 -1
  26. package/docs/retold-keyword-index.json +7996 -2630
  27. package/package.json +2 -2
  28. package/source/Foxhound-Dialects.js +4 -0
  29. package/source/dialects/DGraph/FoxHound-Dialect-DGraph.js +954 -0
  30. package/source/dialects/MongoDB/FoxHound-Dialect-MongoDB.js +902 -0
  31. package/source/dialects/PostgreSQL/FoxHound-Dialect-PostgreSQL.js +865 -0
  32. package/source/dialects/Solr/FoxHound-Dialect-Solr.js +895 -0
  33. package/test/FoxHound-Dialect-DGraph_tests.js +547 -0
  34. package/test/FoxHound-Dialect-MongoDB_tests.js +485 -0
  35. package/test/FoxHound-Dialect-PostgreSQL_tests.js +342 -0
  36. package/test/FoxHound-Dialect-Solr_tests.js +551 -0
@@ -0,0 +1,485 @@
1
+ /**
2
+ * Unit tests for FoxHound MongoDB Dialect
3
+ *
4
+ * @license MIT
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ */
8
+
9
+ var Chai = require('chai');
10
+ var Expect = Chai.expect;
11
+ var Assert = Chai.assert;
12
+
13
+ var libFable = require('fable');
14
+ const _Fable = new libFable({Product:'FoxhoundTestsMongoDB'});
15
+ var libFoxHound = require('../source/FoxHound.js');
16
+
17
+ var _AnimalSchema = (
18
+ [
19
+ { Column: "IDAnimal", Type:"AutoIdentity" },
20
+ { Column: "GUIDAnimal", Type:"AutoGUID" },
21
+ { Column: "CreateDate", Type:"CreateDate" },
22
+ { Column: "CreatingIDUser", Type:"CreateIDUser" },
23
+ { Column: "UpdateDate", Type:"UpdateDate" },
24
+ { Column: "UpdatingIDUser", Type:"UpdateIDUser" },
25
+ { Column: "Deleted", Type:"Deleted" },
26
+ { Column: "DeletingIDUser", Type:"DeleteIDUser" },
27
+ { Column: "DeleteDate", Type:"DeleteDate" }
28
+ ]);
29
+
30
+ var _AnimalSchemaWithoutDeleted = (
31
+ [
32
+ { Column: "IDAnimal", Type:"AutoIdentity" },
33
+ { Column: "GUIDAnimal", Type:"AutoGUID" },
34
+ { Column: "CreateDate", Type:"CreateDate" },
35
+ { Column: "CreatingIDUser", Type:"CreateIDUser" },
36
+ { Column: "UpdateDate", Type:"UpdateDate" },
37
+ { Column: "UpdatingIDUser", Type:"UpdateIDUser" }
38
+ ]);
39
+
40
+ suite
41
+ (
42
+ 'FoxHound-Dialect-MongoDB',
43
+ function()
44
+ {
45
+ setup
46
+ (
47
+ function()
48
+ {
49
+ }
50
+ );
51
+
52
+ suite
53
+ (
54
+ 'Object Sanity',
55
+ function()
56
+ {
57
+ test
58
+ (
59
+ 'initialize should build a happy little object',
60
+ function()
61
+ {
62
+ var testFoxHound = libFoxHound.new(_Fable).setDialect('MongoDB');
63
+ Expect(testFoxHound.dialect.name)
64
+ .to.equal('MongoDB');
65
+ Expect(testFoxHound)
66
+ .to.be.an('object', 'FoxHound with MongoDB should initialize as an object directly from the require statement.');
67
+ }
68
+ );
69
+ }
70
+ );
71
+
72
+ suite
73
+ (
74
+ 'Basic Query Generation',
75
+ function()
76
+ {
77
+ test
78
+ (
79
+ 'Create Query',
80
+ function()
81
+ {
82
+ var tmpQuery = libFoxHound.new(_Fable)
83
+ .setDialect('MongoDB')
84
+ .setScope('Animal')
85
+ .addRecord({IDAnimal:null, Name:'Foo Foo', Age:15});
86
+ tmpQuery.buildCreateQuery();
87
+ _Fable.log.trace('Create Query', tmpQuery.query);
88
+ var tmpOp = JSON.parse(tmpQuery.query.body);
89
+ Expect(tmpOp.collection).to.equal('Animal');
90
+ Expect(tmpOp.operation).to.equal('insertOne');
91
+ Expect(tmpOp.document.Name).to.equal('Foo Foo');
92
+ Expect(tmpOp.document.Age).to.equal(15);
93
+ }
94
+ );
95
+ test
96
+ (
97
+ 'Create Query with Schema uses $$AUTOINCREMENT for AutoIdentity',
98
+ function()
99
+ {
100
+ var tmpQuery = libFoxHound.new(_Fable)
101
+ .setDialect('MongoDB')
102
+ .setScope('Animal')
103
+ .addRecord({IDAnimal:null, GUIDAnimal:false, CreateDate:null, CreatingIDUser:null, UpdateDate:null, UpdatingIDUser:null, Deleted:0, Name:'Foo Foo', Age:15});
104
+ tmpQuery.query.schema = _AnimalSchema;
105
+ tmpQuery.query.IDUser = 37;
106
+ tmpQuery.query.UUID = 'test-guid-value';
107
+ tmpQuery.buildCreateQuery();
108
+ _Fable.log.trace('Create Query with Schema', tmpQuery.query);
109
+ var tmpOp = JSON.parse(tmpQuery.query.body);
110
+ Expect(tmpOp.document.IDAnimal).to.equal('$$AUTOINCREMENT');
111
+ Expect(tmpOp.document.GUIDAnimal).to.equal('test-guid-value');
112
+ Expect(tmpOp.document.CreateDate).to.equal('$$NOW');
113
+ Expect(tmpOp.document.CreatingIDUser).to.equal(37);
114
+ Expect(tmpOp.document.UpdateDate).to.equal('$$NOW');
115
+ Expect(tmpOp.document.UpdatingIDUser).to.equal(37);
116
+ Expect(tmpOp.document.Deleted).to.equal(0);
117
+ // DeleteDate and DeletingIDUser should be skipped
118
+ Expect(tmpOp.document).to.not.have.property('DeleteDate');
119
+ Expect(tmpOp.document).to.not.have.property('DeletingIDUser');
120
+ Expect(tmpOp.document.Name).to.equal('Foo Foo');
121
+ }
122
+ );
123
+ test
124
+ (
125
+ 'Create Query with existing GUID passes it through',
126
+ function()
127
+ {
128
+ var tmpQuery = libFoxHound.new(_Fable)
129
+ .setDialect('MongoDB')
130
+ .setScope('Animal')
131
+ .addRecord({IDAnimal:null, GUIDAnimal:'my-custom-guid-value'});
132
+ tmpQuery.query.schema = _AnimalSchema;
133
+ tmpQuery.query.UUID = 'should-not-use-this';
134
+ tmpQuery.buildCreateQuery();
135
+ var tmpOp = JSON.parse(tmpQuery.query.body);
136
+ Expect(tmpOp.document.GUIDAnimal).to.equal('my-custom-guid-value');
137
+ }
138
+ );
139
+ test
140
+ (
141
+ 'Bad Create Query',
142
+ function()
143
+ {
144
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('MongoDB');
145
+ tmpQuery.buildCreateQuery();
146
+ tmpQuery.addRecord({});
147
+ tmpQuery.buildCreateQuery();
148
+ _Fable.log.trace('Create Query', tmpQuery.query);
149
+ Expect(tmpQuery.query.body)
150
+ .to.equal(false);
151
+ }
152
+ );
153
+ test
154
+ (
155
+ 'Read Query',
156
+ function()
157
+ {
158
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('MongoDB').setScope('Animal');
159
+ tmpQuery.addSort({Column:'Cost',Direction:'Descending'});
160
+ tmpQuery.buildReadQuery();
161
+ _Fable.log.trace('Simple Select Query', tmpQuery.query);
162
+ var tmpOp = JSON.parse(tmpQuery.query.body);
163
+ Expect(tmpOp.collection).to.equal('Animal');
164
+ Expect(tmpOp.operation).to.equal('find');
165
+ Expect(tmpOp.filter).to.deep.equal({});
166
+ Expect(tmpOp.sort.Cost).to.equal(-1);
167
+ }
168
+ );
169
+ test
170
+ (
171
+ 'Read Query with Distinct',
172
+ function()
173
+ {
174
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('MongoDB').setScope('Animal');
175
+ tmpQuery.addSort({Column:'Cost',Direction:'Descending'})
176
+ .setDistinct(true);
177
+ tmpQuery.buildReadQuery();
178
+ var tmpOp = JSON.parse(tmpQuery.query.body);
179
+ Expect(tmpOp.distinct).to.equal(true);
180
+ }
181
+ );
182
+ test
183
+ (
184
+ 'Complex Read Query with cap, begin, dataElements, sort, filter',
185
+ function()
186
+ {
187
+ var tmpQuery = libFoxHound.new(_Fable)
188
+ .setDialect('MongoDB')
189
+ .setScope('Animal')
190
+ .setCap(10)
191
+ .setBegin(5)
192
+ .setDataElements(['Name', 'Age', 'Cost'])
193
+ .setSort([{Column:'Age',Direction:'Ascending'}])
194
+ .setFilter({Column:'Age',Operator:'=',Value:'15',Connector:'AND',Parameter:'Age'});
195
+ tmpQuery.addSort('Cost');
196
+ tmpQuery.buildReadQuery();
197
+ _Fable.log.trace('Select Query', tmpQuery.query);
198
+ var tmpOp = JSON.parse(tmpQuery.query.body);
199
+ Expect(tmpOp.filter.Age).to.equal('15');
200
+ Expect(tmpOp.projection.Name).to.equal(1);
201
+ Expect(tmpOp.projection.Age).to.equal(1);
202
+ Expect(tmpOp.projection.Cost).to.equal(1);
203
+ Expect(tmpOp.sort.Age).to.equal(1);
204
+ Expect(tmpOp.sort.Cost).to.equal(1);
205
+ Expect(tmpOp.limit).to.equal(10);
206
+ Expect(tmpOp.skip).to.equal(5);
207
+ }
208
+ );
209
+ test
210
+ (
211
+ 'Complex Read Query with Filters including OR, IN, IS NOT NULL',
212
+ function()
213
+ {
214
+ var tmpQuery = libFoxHound.new(_Fable)
215
+ .setDialect('MongoDB')
216
+ .setScope('Animal')
217
+ .setDataElements(['Name', 'Age', 'Cost'])
218
+ .setCap(100)
219
+ .addFilter('Age', '25')
220
+ .addFilter('', '', '(')
221
+ .addFilter('Color', 'Red')
222
+ .addFilter('Color', 'Green', '=', 'OR')
223
+ .addFilter('', '', ')')
224
+ .addFilter('Description', '', 'IS NOT NULL')
225
+ .addFilter('IDOffice', [10, 11, 15, 18, 22], 'IN');
226
+ tmpQuery.buildReadQuery();
227
+ _Fable.log.trace('Select Query', tmpQuery.query);
228
+ var tmpOp = JSON.parse(tmpQuery.query.body);
229
+ // Should have $and at top level due to complexity
230
+ Expect(tmpOp.filter).to.have.property('$and');
231
+ var tmpAndArray = tmpOp.filter.$and;
232
+ // Find the $or group
233
+ var tmpOrGroup = tmpAndArray.find(function(c) { return c.hasOwnProperty('$or'); });
234
+ Expect(tmpOrGroup).to.exist;
235
+ Expect(tmpOrGroup.$or).to.have.length(2);
236
+ Expect(tmpOrGroup.$or[0].Color).to.equal('Red');
237
+ Expect(tmpOrGroup.$or[1].Color).to.equal('Green');
238
+ // Find IS NOT NULL
239
+ var tmpNotNull = tmpAndArray.find(function(c) { return c.Description && c.Description.$ne === null; });
240
+ Expect(tmpNotNull).to.exist;
241
+ // Find IN
242
+ var tmpIn = tmpAndArray.find(function(c) { return c.IDOffice && c.IDOffice.$in; });
243
+ Expect(tmpIn).to.exist;
244
+ Expect(tmpIn.IDOffice.$in).to.deep.equal([10, 11, 15, 18, 22]);
245
+ }
246
+ );
247
+ test
248
+ (
249
+ 'Read Query with Deleted schema auto-adds Deleted filter',
250
+ function()
251
+ {
252
+ var tmpQuery = libFoxHound.new(_Fable)
253
+ .setDialect('MongoDB')
254
+ .setScope('Animal')
255
+ .addFilter('Age', '3');
256
+ tmpQuery.query.schema = _AnimalSchema;
257
+ tmpQuery.buildReadQuery();
258
+ var tmpOp = JSON.parse(tmpQuery.query.body);
259
+ // Filter should include both Age and Deleted
260
+ Expect(JSON.stringify(tmpOp.filter)).to.contain('"Deleted"');
261
+ }
262
+ );
263
+ test
264
+ (
265
+ 'Read Query with Deleted tracking disabled does NOT add Deleted filter',
266
+ function()
267
+ {
268
+ var tmpQuery = libFoxHound.new(_Fable)
269
+ .setDialect('MongoDB')
270
+ .setScope('Animal')
271
+ .addFilter('Age', '3');
272
+ tmpQuery.query.schema = _AnimalSchema;
273
+ tmpQuery.query.disableDeleteTracking = true;
274
+ tmpQuery.buildReadQuery();
275
+ var tmpOp = JSON.parse(tmpQuery.query.body);
276
+ Expect(JSON.stringify(tmpOp.filter)).to.not.contain('"Deleted"');
277
+ }
278
+ );
279
+ test
280
+ (
281
+ 'Read Query without limit has no skip or limit',
282
+ function()
283
+ {
284
+ var tmpQuery = libFoxHound.new(_Fable)
285
+ .setDialect('MongoDB')
286
+ .setScope('Animal');
287
+ tmpQuery.buildReadQuery();
288
+ var tmpOp = JSON.parse(tmpQuery.query.body);
289
+ Expect(tmpOp).to.not.have.property('limit');
290
+ Expect(tmpOp).to.not.have.property('skip');
291
+ }
292
+ );
293
+ test
294
+ (
295
+ 'Update Query',
296
+ function()
297
+ {
298
+ var tmpQuery = libFoxHound.new(_Fable)
299
+ .setDialect('MongoDB')
300
+ .setScope('Animal')
301
+ .addFilter('IDAnimal', 9)
302
+ .addRecord({Name:'Froggy', Age:12});
303
+ tmpQuery.buildUpdateQuery();
304
+ _Fable.log.trace('Update Query', tmpQuery.query);
305
+ var tmpOp = JSON.parse(tmpQuery.query.body);
306
+ Expect(tmpOp.collection).to.equal('Animal');
307
+ Expect(tmpOp.operation).to.equal('updateMany');
308
+ Expect(tmpOp.filter.IDAnimal).to.equal(9);
309
+ Expect(tmpOp.update.$set.Name).to.equal('Froggy');
310
+ Expect(tmpOp.update.$set.Age).to.equal(12);
311
+ }
312
+ );
313
+ test
314
+ (
315
+ 'Update Query with Schema skips identity and create columns',
316
+ function()
317
+ {
318
+ var tmpQuery = libFoxHound.new(_Fable)
319
+ .setDialect('MongoDB')
320
+ .setScope('Animal')
321
+ .addFilter('IDAnimal', 9)
322
+ .addRecord({IDAnimal:9, CreateDate:'2020-01-01', CreatingIDUser:1, UpdateDate:null, UpdatingIDUser:null, Name:'Froggy', Age:12});
323
+ tmpQuery.query.schema = _AnimalSchema;
324
+ tmpQuery.query.IDUser = 37;
325
+ tmpQuery.buildUpdateQuery();
326
+ var tmpOp = JSON.parse(tmpQuery.query.body);
327
+ Expect(tmpOp.update.$set).to.not.have.property('IDAnimal');
328
+ Expect(tmpOp.update.$set).to.not.have.property('CreateDate');
329
+ Expect(tmpOp.update.$set).to.not.have.property('CreatingIDUser');
330
+ Expect(tmpOp.update.$set.UpdateDate).to.equal('$$NOW');
331
+ Expect(tmpOp.update.$set.UpdatingIDUser).to.equal(37);
332
+ Expect(tmpOp.update.$set.Name).to.equal('Froggy');
333
+ }
334
+ );
335
+ test
336
+ (
337
+ 'Count Query',
338
+ function()
339
+ {
340
+ var tmpQuery = libFoxHound.new(_Fable)
341
+ .setDialect('MongoDB')
342
+ .setScope('Animal')
343
+ .addFilter('Age', '3');
344
+ tmpQuery.buildCountQuery();
345
+ _Fable.log.trace('Count Query', tmpQuery.query);
346
+ var tmpOp = JSON.parse(tmpQuery.query.body);
347
+ Expect(tmpOp.collection).to.equal('Animal');
348
+ Expect(tmpOp.operation).to.equal('countDocuments');
349
+ Expect(tmpOp.filter.Age).to.equal('3');
350
+ }
351
+ );
352
+ test
353
+ (
354
+ 'Delete Query with soft delete schema',
355
+ function()
356
+ {
357
+ var tmpQuery = libFoxHound.new(_Fable)
358
+ .setDialect('MongoDB')
359
+ .setScope('Animal')
360
+ .addFilter('IDAnimal', 9);
361
+ tmpQuery.query.schema = _AnimalSchema;
362
+ tmpQuery.query.IDUser = 37;
363
+ tmpQuery.buildDeleteQuery();
364
+ _Fable.log.trace('Delete Query', tmpQuery.query);
365
+ var tmpOp = JSON.parse(tmpQuery.query.body);
366
+ Expect(tmpOp.operation).to.equal('updateMany');
367
+ Expect(tmpOp.update.$set.Deleted).to.equal(1);
368
+ Expect(tmpOp.update.$set.DeleteDate).to.equal('$$NOW');
369
+ Expect(tmpOp.update.$set.DeletingIDUser).to.equal(37);
370
+ }
371
+ );
372
+ test
373
+ (
374
+ 'Delete Query without schema does hard delete',
375
+ function()
376
+ {
377
+ var tmpQuery = libFoxHound.new(_Fable)
378
+ .setDialect('MongoDB')
379
+ .setScope('Animal')
380
+ .addFilter('IDAnimal', 9);
381
+ tmpQuery.buildDeleteQuery();
382
+ _Fable.log.trace('Delete Query', tmpQuery.query);
383
+ var tmpOp = JSON.parse(tmpQuery.query.body);
384
+ Expect(tmpOp.operation).to.equal('deleteMany');
385
+ Expect(tmpOp.filter.IDAnimal).to.equal(9);
386
+ }
387
+ );
388
+ test
389
+ (
390
+ 'Undelete Query',
391
+ function()
392
+ {
393
+ var tmpQuery = libFoxHound.new(_Fable)
394
+ .setDialect('MongoDB')
395
+ .setScope('Animal')
396
+ .addFilter('IDAnimal', 9);
397
+ tmpQuery.query.schema = _AnimalSchema;
398
+ tmpQuery.query.IDUser = 37;
399
+ tmpQuery.buildUndeleteQuery();
400
+ _Fable.log.trace('Undelete Query', tmpQuery.query);
401
+ var tmpOp = JSON.parse(tmpQuery.query.body);
402
+ Expect(tmpOp.operation).to.equal('updateMany');
403
+ Expect(tmpOp.update.$set.Deleted).to.equal(0);
404
+ Expect(tmpOp.update.$set.UpdateDate).to.equal('$$NOW');
405
+ Expect(tmpOp.update.$set.UpdatingIDUser).to.equal(37);
406
+ }
407
+ );
408
+ test
409
+ (
410
+ 'Undelete Query without Deleted column returns noop',
411
+ function()
412
+ {
413
+ var tmpQuery = libFoxHound.new(_Fable)
414
+ .setDialect('MongoDB')
415
+ .setScope('Animal')
416
+ .addFilter('IDAnimal', 9);
417
+ tmpQuery.query.schema = _AnimalSchemaWithoutDeleted;
418
+ tmpQuery.buildUndeleteQuery();
419
+ var tmpOp = JSON.parse(tmpQuery.query.body);
420
+ Expect(tmpOp.operation).to.equal('noop');
421
+ }
422
+ );
423
+ test
424
+ (
425
+ 'mongoOperation is stored in query.parameters',
426
+ function()
427
+ {
428
+ var tmpQuery = libFoxHound.new(_Fable)
429
+ .setDialect('MongoDB')
430
+ .setScope('Animal');
431
+ tmpQuery.buildReadQuery();
432
+ Expect(tmpQuery.query.parameters.mongoOperation).to.be.an('object');
433
+ Expect(tmpQuery.query.parameters.mongoOperation.collection).to.equal('Animal');
434
+ Expect(tmpQuery.query.parameters.mongoOperation.operation).to.equal('find');
435
+ }
436
+ );
437
+ test
438
+ (
439
+ 'Filter with comparison operators',
440
+ function()
441
+ {
442
+ var tmpQuery = libFoxHound.new(_Fable)
443
+ .setDialect('MongoDB')
444
+ .setScope('Animal')
445
+ .addFilter('Age', 10, '>')
446
+ .addFilter('Cost', 100, '<=');
447
+ tmpQuery.buildReadQuery();
448
+ var tmpOp = JSON.parse(tmpQuery.query.body);
449
+ Expect(tmpOp.filter.Age.$gt).to.equal(10);
450
+ Expect(tmpOp.filter.Cost.$lte).to.equal(100);
451
+ }
452
+ );
453
+ test
454
+ (
455
+ 'Filter with LIKE operator converts to regex',
456
+ function()
457
+ {
458
+ var tmpQuery = libFoxHound.new(_Fable)
459
+ .setDialect('MongoDB')
460
+ .setScope('Animal')
461
+ .addFilter('Name', '%Foo%', 'LIKE');
462
+ tmpQuery.buildReadQuery();
463
+ var tmpOp = JSON.parse(tmpQuery.query.body);
464
+ Expect(tmpOp.filter.Name.$regex).to.equal('.*Foo.*');
465
+ Expect(tmpOp.filter.Name.$options).to.equal('i');
466
+ }
467
+ );
468
+ test
469
+ (
470
+ 'Filter with IS NULL',
471
+ function()
472
+ {
473
+ var tmpQuery = libFoxHound.new(_Fable)
474
+ .setDialect('MongoDB')
475
+ .setScope('Animal')
476
+ .addFilter('Description', '', 'IS NULL');
477
+ tmpQuery.buildReadQuery();
478
+ var tmpOp = JSON.parse(tmpQuery.query.body);
479
+ Expect(tmpOp.filter.Description).to.equal(null);
480
+ }
481
+ );
482
+ }
483
+ );
484
+ }
485
+ );