foxhound 2.0.27 → 2.0.29

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,463 @@
1
+ /**
2
+ * Unit tests for FoxHound Oracle 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
+
12
+ var libFable = require('fable');
13
+ const _Fable = new libFable({Product:'FoxhoundTestsOracle'});
14
+ var libFoxHound = require('../source/FoxHound.js');
15
+
16
+ var _AnimalSchema = (
17
+ [
18
+ { Column: "IDAnimal", Type:"AutoIdentity" },
19
+ { Column: "GUIDAnimal", Type:"AutoGUID" },
20
+ { Column: "CreateDate", Type:"CreateDate" },
21
+ { Column: "CreatingIDUser", Type:"CreateIDUser" },
22
+ { Column: "UpdateDate", Type:"UpdateDate" },
23
+ { Column: "UpdatingIDUser", Type:"UpdateIDUser" },
24
+ { Column: "Deleted", Type:"Deleted" },
25
+ { Column: "DeletingIDUser", Type:"DeleteIDUser" },
26
+ { Column: "DeleteDate", Type:"DeleteDate" },
27
+ { Column: "Name", Type:"String" },
28
+ { Column: "Age", Type:"Integer" }
29
+ ]);
30
+
31
+ var _AnimalSchemaWithoutDeleted = (
32
+ [
33
+ { Column: "IDAnimal", Type:"AutoIdentity" },
34
+ { Column: "GUIDAnimal", Type:"AutoGUID" },
35
+ { Column: "CreateDate", Type:"CreateDate" },
36
+ { Column: "CreatingIDUser", Type:"CreateIDUser" },
37
+ { Column: "UpdateDate", Type:"UpdateDate" },
38
+ { Column: "UpdatingIDUser", Type:"UpdateIDUser" }
39
+ ]);
40
+
41
+ suite
42
+ (
43
+ 'FoxHound-Dialect-Oracle',
44
+ function()
45
+ {
46
+ suite
47
+ (
48
+ 'Object Sanity',
49
+ function()
50
+ {
51
+ test
52
+ (
53
+ 'initialize should build a happy little object',
54
+ function()
55
+ {
56
+ var testFoxHound = libFoxHound.new(_Fable).setDialect('Oracle');
57
+ Expect(testFoxHound.dialect.name)
58
+ .to.equal('Oracle');
59
+ Expect(testFoxHound)
60
+ .to.be.an('object', 'FoxHound with Oracle should initialize as an object directly from the require statement.');
61
+ }
62
+ );
63
+ }
64
+ );
65
+
66
+ suite
67
+ (
68
+ 'Create Query Generation',
69
+ function()
70
+ {
71
+ test
72
+ (
73
+ 'Create Query without schema parameterizes every column and has no RETURNING',
74
+ function()
75
+ {
76
+ var tmpQuery = libFoxHound.new(_Fable)
77
+ .setDialect('Oracle')
78
+ .setScope('Animal')
79
+ .addRecord({IDAnimal:null, Name:'Foo Foo', Age:15});
80
+ tmpQuery.buildCreateQuery();
81
+ Expect(tmpQuery.query.body)
82
+ .to.equal("INSERT INTO Animal ( IDAnimal, Name, Age) VALUES ( :IDAnimal_0, :Name_1, :Age_2)");
83
+ }
84
+ );
85
+ test
86
+ (
87
+ 'Create Query with schema skips AutoIdentity and appends RETURNING INTO',
88
+ function()
89
+ {
90
+ var tmpQuery = libFoxHound.new(_Fable)
91
+ .setDialect('Oracle')
92
+ .setScope('Animal')
93
+ .addRecord({IDAnimal:null, Name:'Foo Foo', Age:15});
94
+ tmpQuery.query.schema = _AnimalSchema;
95
+ tmpQuery.buildCreateQuery();
96
+ Expect(tmpQuery.query.body)
97
+ .to.equal("INSERT INTO Animal ( Name, Age) VALUES ( :Name_0, :Age_1) RETURNING IDAnimal INTO :RETURNING_ID");
98
+ }
99
+ );
100
+ test
101
+ (
102
+ 'Bad Create Query returns false',
103
+ function()
104
+ {
105
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle');
106
+ tmpQuery.buildCreateQuery();
107
+ tmpQuery.addRecord({});
108
+ tmpQuery.buildCreateQuery();
109
+ Expect(tmpQuery.query.body)
110
+ .to.equal(false);
111
+ }
112
+ );
113
+ }
114
+ );
115
+
116
+ suite
117
+ (
118
+ 'Read Query Generation',
119
+ function()
120
+ {
121
+ test
122
+ (
123
+ 'Simple Read Query (no quoting)',
124
+ function()
125
+ {
126
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle').setScope('Animal');
127
+ tmpQuery.addSort({Column:'Cost',Direction:'Descending'});
128
+ tmpQuery.buildReadQuery();
129
+ Expect(tmpQuery.query.body)
130
+ .to.equal('SELECT Animal.* FROM Animal ORDER BY Cost DESC');
131
+ }
132
+ );
133
+ test
134
+ (
135
+ 'Read Query with named binds and OFFSET/FETCH pagination',
136
+ function()
137
+ {
138
+ var tmpQuery = libFoxHound.new(_Fable)
139
+ .setDialect('Oracle')
140
+ .setScope('Animal')
141
+ .setDataElements(['Name', 'Age'])
142
+ .setCap(10)
143
+ .setBegin(0)
144
+ .addFilter('Age', '15')
145
+ .addSort('Age');
146
+ tmpQuery.buildReadQuery();
147
+ Expect(tmpQuery.query.body)
148
+ .to.equal('SELECT Name, Age FROM Animal WHERE Age = :Age_w0 ORDER BY Age OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY');
149
+ }
150
+ );
151
+ test
152
+ (
153
+ 'Complex Read Query expands IN lists into discrete binds',
154
+ function()
155
+ {
156
+ var tmpQuery = libFoxHound.new(_Fable)
157
+ .setDialect('Oracle')
158
+ .setScope('Animal')
159
+ .setDataElements(['Name', 'Age', 'Cost'])
160
+ .setCap(100)
161
+ .addFilter('Age', '25')
162
+ .addFilter('', '', '(')
163
+ .addFilter('Color', 'Red')
164
+ .addFilter('Color', 'Green', '=', 'OR')
165
+ .addFilter('', '', ')')
166
+ .addFilter('Description', '', 'IS NOT NULL')
167
+ .addFilter('IDOffice', [10, 11, 15, 18, 22], 'IN');
168
+ tmpQuery.addSort('Age');
169
+ tmpQuery.buildReadQuery();
170
+ Expect(tmpQuery.query.body)
171
+ .to.equal('SELECT Name, Age, Cost FROM Animal WHERE Age = :Age_w0 AND ( Color = :Color_w2 OR Color = :Color_w3 ) AND Description IS NOT NULL AND IDOffice IN (:IDOffice_w6_0, :IDOffice_w6_1, :IDOffice_w6_2, :IDOffice_w6_3, :IDOffice_w6_4) ORDER BY Age OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY');
172
+ }
173
+ );
174
+ test
175
+ (
176
+ 'Read Query with schema injects soft-delete filter and PK ORDER BY',
177
+ function()
178
+ {
179
+ var tmpQuery = libFoxHound.new(_Fable)
180
+ .setDialect('Oracle')
181
+ .setScope('Animal')
182
+ .setDataElements(['Name', 'Age', 'Cost'])
183
+ .setCap(100);
184
+ tmpQuery.query.schema = _AnimalSchema;
185
+ tmpQuery.buildReadQuery();
186
+ Expect(tmpQuery.query.body)
187
+ .to.equal('SELECT Name, Age, Cost FROM Animal WHERE Deleted = :Deleted_w0 ORDER BY IDAnimal OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY');
188
+ }
189
+ );
190
+ test
191
+ (
192
+ 'Read Query with quoteIdentifiers wraps identifiers in double quotes',
193
+ function()
194
+ {
195
+ var tmpQuery = libFoxHound.new(_Fable)
196
+ .setDialect('Oracle')
197
+ .setScope('Animal')
198
+ .setDataElements(['Name', 'Age'])
199
+ .addSort('Age');
200
+ tmpQuery.parameters.quoteIdentifiers = true;
201
+ tmpQuery.buildReadQuery();
202
+ Expect(tmpQuery.query.body)
203
+ .to.equal('SELECT "Name", "Age" FROM "Animal" ORDER BY "Age"');
204
+ }
205
+ );
206
+ test
207
+ (
208
+ 'Custom Read Query honors the query override template',
209
+ function()
210
+ {
211
+ var tmpQuery = libFoxHound.new(_Fable)
212
+ .setDialect('Oracle')
213
+ .setScope('Animal')
214
+ .setCap(10)
215
+ .setBegin(0)
216
+ .setDataElements(['Name', 'Age', 'Cost'])
217
+ .setFilter({Column:'Age',Operator:'=',Value:'15',Connector:'AND',Parameter:'Age'});
218
+ tmpQuery.parameters.queryOverride = 'SELECT Name, Age * 5, Cost FROM <%= TableName %> <%= Where %> <%= Limit %>';
219
+ tmpQuery.buildReadQuery();
220
+ Expect(tmpQuery.query.body)
221
+ .to.equal('SELECT Name, Age * 5, Cost FROM Animal WHERE Age = :Age_w0 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY');
222
+ }
223
+ );
224
+ }
225
+ );
226
+
227
+ suite
228
+ (
229
+ 'Legacy Pagination (ROWNUM double-subquery)',
230
+ function()
231
+ {
232
+ test
233
+ (
234
+ 'legacyPagination wraps the query in a ROWNUM subquery (caller sort)',
235
+ function()
236
+ {
237
+ var tmpQuery = libFoxHound.new(_Fable)
238
+ .setDialect('Oracle')
239
+ .setScope('Animal')
240
+ .setDataElements(['Name', 'Age', 'Cost'])
241
+ .setCap(10)
242
+ .setBegin(20)
243
+ .addSort('Age');
244
+ tmpQuery.query.schema = _AnimalSchema;
245
+ tmpQuery.parameters.legacyPagination = true;
246
+ tmpQuery.buildReadQuery();
247
+ Expect(tmpQuery.query.body)
248
+ .to.equal('SELECT Name, Age, Cost FROM (SELECT meadow_inner.*, ROWNUM AS "_RowNum" FROM (SELECT Name, Age, Cost FROM Animal WHERE Deleted = :Deleted_w0 ORDER BY Age) meadow_inner WHERE ROWNUM <= 30) WHERE "_RowNum" > 20');
249
+ }
250
+ );
251
+ test
252
+ (
253
+ 'legacyPagination injects PK ORDER BY when caller omits sort',
254
+ function()
255
+ {
256
+ var tmpQuery = libFoxHound.new(_Fable)
257
+ .setDialect('Oracle')
258
+ .setScope('Animal')
259
+ .setDataElements(['Name', 'Age', 'Cost'])
260
+ .setCap(10);
261
+ tmpQuery.query.schema = _AnimalSchema;
262
+ tmpQuery.parameters.legacyPagination = true;
263
+ tmpQuery.buildReadQuery();
264
+ Expect(tmpQuery.query.body)
265
+ .to.equal('SELECT Name, Age, Cost FROM (SELECT meadow_inner.*, ROWNUM AS "_RowNum" FROM (SELECT Name, Age, Cost FROM Animal WHERE Deleted = :Deleted_w0 ORDER BY IDAnimal) meadow_inner WHERE ROWNUM <= 10) WHERE "_RowNum" > 0');
266
+ }
267
+ );
268
+ test
269
+ (
270
+ 'legacyPagination is inert without a cap',
271
+ function()
272
+ {
273
+ var tmpQuery = libFoxHound.new(_Fable)
274
+ .setDialect('Oracle')
275
+ .setScope('Animal')
276
+ .setDataElements(['Name']);
277
+ tmpQuery.parameters.legacyPagination = true;
278
+ tmpQuery.buildReadQuery();
279
+ Expect(tmpQuery.query.body)
280
+ .to.equal('SELECT Name FROM Animal');
281
+ }
282
+ );
283
+ }
284
+ );
285
+
286
+ suite
287
+ (
288
+ 'Update / Delete / Undelete Query Generation',
289
+ function()
290
+ {
291
+ test
292
+ (
293
+ 'Update Query auto-stamps UpdateDate with SYS_EXTRACT_UTC',
294
+ function()
295
+ {
296
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle')
297
+ .setScope('Animal')
298
+ .addFilter('IDAnimal', 9)
299
+ .addRecord({
300
+ IDAnimal:82,
301
+ GUIDAnimal:'1111-2222-3333-4444-5555-6666-7777',
302
+ CreateDate:false,
303
+ CreatingIDUser:false,
304
+ UpdateDate:false,
305
+ UpdatingIDUser:false,
306
+ Name:'Froo Froo',
307
+ Age:18
308
+ });
309
+ tmpQuery.query.schema = _AnimalSchemaWithoutDeleted;
310
+ tmpQuery.buildUpdateQuery();
311
+ Expect(tmpQuery.query.body)
312
+ .to.equal('UPDATE Animal SET GUIDAnimal = :GUIDAnimal_0, UpdateDate = SYS_EXTRACT_UTC(SYSTIMESTAMP), UpdatingIDUser = :UpdatingIDUser_2, Name = :Name_3, Age = :Age_4 WHERE IDAnimal = :IDAnimal_w0');
313
+ }
314
+ );
315
+ test
316
+ (
317
+ 'Update Query with disabled stamps parameterizes UpdateDate',
318
+ function()
319
+ {
320
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle')
321
+ .setScope('Animal')
322
+ .addFilter('IDAnimal', 9)
323
+ .setDisableAutoUserStamp(true)
324
+ .setDisableAutoDateStamp(true)
325
+ .addRecord({
326
+ IDAnimal:82,
327
+ GUIDAnimal:'1111-2222-3333-4444-5555-6666-7777',
328
+ CreateDate:false,
329
+ CreatingIDUser:false,
330
+ UpdateDate:false,
331
+ UpdatingIDUser:false,
332
+ Name:'Froo Froo',
333
+ Age:18
334
+ });
335
+ tmpQuery.query.schema = _AnimalSchemaWithoutDeleted;
336
+ tmpQuery.buildUpdateQuery();
337
+ Expect(tmpQuery.query.body)
338
+ .to.equal('UPDATE Animal SET GUIDAnimal = :GUIDAnimal_0, UpdateDate = :MANUAL_UpdateDate, Name = :Name_2, Age = :Age_3 WHERE IDAnimal = :IDAnimal_w0');
339
+ }
340
+ );
341
+ test
342
+ (
343
+ 'Delete Query converts to soft-delete UPDATE when a Deleted column exists',
344
+ function()
345
+ {
346
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle')
347
+ .setScope('Animal')
348
+ .addFilter('IDAnimal', 10);
349
+ tmpQuery.query.schema = _AnimalSchema;
350
+ tmpQuery.buildDeleteQuery();
351
+ Expect(tmpQuery.query.body)
352
+ .to.equal('UPDATE Animal SET UpdateDate = SYS_EXTRACT_UTC(SYSTIMESTAMP), Deleted = 1, DeletingIDUser = :DeletingIDUser_2, DeleteDate = SYS_EXTRACT_UTC(SYSTIMESTAMP) WHERE IDAnimal = :IDAnimal_w0 AND Deleted = :Deleted_w1');
353
+ }
354
+ );
355
+ test
356
+ (
357
+ 'Delete Query is a hard DELETE when there is no Deleted column',
358
+ function()
359
+ {
360
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle')
361
+ .setScope('Animal')
362
+ .addFilter('IDAnimal', 9)
363
+ .addRecord({IDAnimal:82, GUIDAnimal:'1111-2222'});
364
+ tmpQuery.query.schema = _AnimalSchemaWithoutDeleted;
365
+ tmpQuery.buildDeleteQuery();
366
+ Expect(tmpQuery.query.body)
367
+ .to.equal('DELETE FROM Animal WHERE IDAnimal = :IDAnimal_w0');
368
+ }
369
+ );
370
+ test
371
+ (
372
+ 'Delete Query is a hard DELETE when delete tracking is disabled',
373
+ function()
374
+ {
375
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle')
376
+ .setScope('Animal')
377
+ .setDisableDeleteTracking(true)
378
+ .addFilter('IDAnimal', 9)
379
+ .addRecord({IDAnimal:82, GUIDAnimal:'1111-2222'});
380
+ tmpQuery.query.schema = _AnimalSchema;
381
+ tmpQuery.buildDeleteQuery();
382
+ Expect(tmpQuery.query.body)
383
+ .to.equal('DELETE FROM Animal WHERE IDAnimal = :IDAnimal_w0');
384
+ }
385
+ );
386
+ test
387
+ (
388
+ 'Undelete Query clears the Deleted bit',
389
+ function()
390
+ {
391
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle')
392
+ .setScope('Animal')
393
+ .addFilter('IDAnimal', 10);
394
+ tmpQuery.query.schema = _AnimalSchema;
395
+ tmpQuery.buildUndeleteQuery();
396
+ Expect(tmpQuery.query.body)
397
+ .to.equal('UPDATE Animal SET UpdateDate = SYS_EXTRACT_UTC(SYSTIMESTAMP), UpdatingIDUser = :UpdatingIDUser_1, Deleted = 0 WHERE IDAnimal = :IDAnimal_w0');
398
+ }
399
+ );
400
+ }
401
+ );
402
+
403
+ suite
404
+ (
405
+ 'Count Query Generation',
406
+ function()
407
+ {
408
+ test
409
+ (
410
+ 'Count Query injects soft-delete filter and aliases as RowCount',
411
+ function()
412
+ {
413
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle').setScope('Animal');
414
+ tmpQuery.query.schema = _AnimalSchema;
415
+ tmpQuery.buildCountQuery();
416
+ Expect(tmpQuery.query.body)
417
+ .to.equal('SELECT COUNT(*) AS RowCount FROM Animal WHERE Deleted = :Deleted_w0');
418
+ }
419
+ );
420
+ test
421
+ (
422
+ 'Count Query honors DISTINCT on the selected field',
423
+ function()
424
+ {
425
+ var tmpQuery = libFoxHound.new(_Fable).setDialect('Oracle')
426
+ .setScope('Animal')
427
+ .setDataElements(['Name'])
428
+ .setDistinct(true);
429
+ tmpQuery.query.schema = _AnimalSchema;
430
+ tmpQuery.buildCountQuery();
431
+ Expect(tmpQuery.query.body)
432
+ .to.equal('SELECT COUNT(DISTINCT Name) AS RowCount FROM Animal WHERE Deleted = :Deleted_w0');
433
+ }
434
+ );
435
+ }
436
+ );
437
+
438
+ suite
439
+ (
440
+ 'Oracle Bind Type Tracking',
441
+ function()
442
+ {
443
+ test
444
+ (
445
+ 'parameterTypes maps schema types to oracledb type strings',
446
+ function()
447
+ {
448
+ var tmpQuery = libFoxHound.new(_Fable)
449
+ .setDialect('Oracle')
450
+ .setScope('Animal')
451
+ .addFilter('Age', 15)
452
+ .addFilter('Name', 'Foo');
453
+ tmpQuery.query.schema = _AnimalSchema;
454
+ tmpQuery.buildReadQuery();
455
+ Expect(tmpQuery.query.parameterTypes.Age_w0).to.equal('NUMBER');
456
+ Expect(tmpQuery.query.parameterTypes.Name_w1).to.equal('STRING');
457
+ Expect(tmpQuery.query.parameterTypes.Deleted_w2).to.equal('NUMBER');
458
+ }
459
+ );
460
+ }
461
+ );
462
+ }
463
+ );