mythix-orm 1.0.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.
Files changed (124) hide show
  1. package/.eslintrc.js +94 -0
  2. package/.vscode/launch.json +34 -0
  3. package/.vscode/settings.json +10 -0
  4. package/LICENSE +21 -0
  5. package/README.md +15 -0
  6. package/lib/connection/connection-base.js +877 -0
  7. package/lib/connection/index.js +11 -0
  8. package/lib/connection/literals/average-literal.js +14 -0
  9. package/lib/connection/literals/count-literal.js +18 -0
  10. package/lib/connection/literals/distinct-literal.js +14 -0
  11. package/lib/connection/literals/index.js +23 -0
  12. package/lib/connection/literals/literal-base.js +62 -0
  13. package/lib/connection/literals/literal-field-base.js +45 -0
  14. package/lib/connection/literals/literal.js +11 -0
  15. package/lib/connection/literals/max-literal.js +14 -0
  16. package/lib/connection/literals/min-literal.js +14 -0
  17. package/lib/connection/literals/sum-literal.js +14 -0
  18. package/lib/connection/query-generator-base.js +864 -0
  19. package/lib/errors/base-error.js +14 -0
  20. package/lib/errors/connection/access-denied-error.js +13 -0
  21. package/lib/errors/connection/connection-acquire-timeout-error.js +13 -0
  22. package/lib/errors/connection/connection-refused-error.js +13 -0
  23. package/lib/errors/connection/connection-timed-out-error.js +13 -0
  24. package/lib/errors/connection/host-not-found-error.js +13 -0
  25. package/lib/errors/connection/host-not-reachable-error.js +13 -0
  26. package/lib/errors/connection/index.js +19 -0
  27. package/lib/errors/connection/invalid-connection-error.js +13 -0
  28. package/lib/errors/connection-base-error.js +13 -0
  29. package/lib/errors/database/exclusion-constraint-error.js +13 -0
  30. package/lib/errors/database/foreign-key-constraint-error.js +13 -0
  31. package/lib/errors/database/index.js +13 -0
  32. package/lib/errors/database/timeout-error.js +13 -0
  33. package/lib/errors/database/unknown-constraint-error.js +13 -0
  34. package/lib/errors/database-base-error.js +17 -0
  35. package/lib/errors/index.js +44 -0
  36. package/lib/field.js +18 -0
  37. package/lib/index.js +43 -0
  38. package/lib/model.js +1374 -0
  39. package/lib/proxy-class/index.js +3 -0
  40. package/lib/proxy-class/proxy-class.js +269 -0
  41. package/lib/query-engine/field-scope.js +99 -0
  42. package/lib/query-engine/index.js +13 -0
  43. package/lib/query-engine/model-scope.js +198 -0
  44. package/lib/query-engine/query-engine-base.js +325 -0
  45. package/lib/query-engine/query-engine.js +212 -0
  46. package/lib/types/concrete/bigint-type.js +62 -0
  47. package/lib/types/concrete/blob-type.js +46 -0
  48. package/lib/types/concrete/boolean-type.js +41 -0
  49. package/lib/types/concrete/char-type.js +39 -0
  50. package/lib/types/concrete/date-type.js +87 -0
  51. package/lib/types/concrete/datetime-type.js +92 -0
  52. package/lib/types/concrete/float-type.js +47 -0
  53. package/lib/types/concrete/foreign-key-type.js +208 -0
  54. package/lib/types/concrete/index.js +53 -0
  55. package/lib/types/concrete/integer-type.js +51 -0
  56. package/lib/types/concrete/string-type.js +44 -0
  57. package/lib/types/concrete/text-type.js +44 -0
  58. package/lib/types/concrete/uuid-base.js +77 -0
  59. package/lib/types/concrete/uuid-v1-type.js +99 -0
  60. package/lib/types/concrete/uuid-v3-type.js +108 -0
  61. package/lib/types/concrete/uuid-v4-type.js +90 -0
  62. package/lib/types/concrete/uuid-v5-type.js +108 -0
  63. package/lib/types/concrete/xid-type.js +58 -0
  64. package/lib/types/helpers/default-helpers.js +127 -0
  65. package/lib/types/helpers/index.js +35 -0
  66. package/lib/types/index.js +94 -0
  67. package/lib/types/type.js +244 -0
  68. package/lib/types/virtual/index.js +14 -0
  69. package/lib/types/virtual/model-type.js +141 -0
  70. package/lib/types/virtual/models-type.js +264 -0
  71. package/lib/types/virtual/relational-type-base.js +323 -0
  72. package/lib/utils/index.js +65 -0
  73. package/lib/utils/misc-utils.js +73 -0
  74. package/lib/utils/model-utils.js +704 -0
  75. package/lib/utils/query-utils.js +186 -0
  76. package/package.json +63 -0
  77. package/playground/test01.js +15 -0
  78. package/spec/connection/connection-base-spec.js +126 -0
  79. package/spec/connection/literals/average-literal-spec.js +45 -0
  80. package/spec/connection/literals/count-literal-spec.js +42 -0
  81. package/spec/connection/literals/distinct-literal-spec.js +42 -0
  82. package/spec/connection/literals/literal-spec.js +26 -0
  83. package/spec/connection/literals/max-literal-spec.js +42 -0
  84. package/spec/connection/literals/min-literal-spec.js +42 -0
  85. package/spec/connection/literals/sum-literal-spec.js +42 -0
  86. package/spec/helpers/default-helpers-spec.js +108 -0
  87. package/spec/model-spec.js +736 -0
  88. package/spec/proxy-class/proxy-class-spec.js +173 -0
  89. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_chain_query_conditions-001.snapshot +94 -0
  90. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_call-001.snapshot +35 -0
  91. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_sub-001.snapshot +35 -0
  92. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_set_a_default_scope_on_a_model-001.snapshot +57 -0
  93. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_unscope_default_scope_on_a_model-001.snapshot +35 -0
  94. package/spec/query-engine/query-engine-spec.js +99 -0
  95. package/spec/support/jasmine.json +13 -0
  96. package/spec/support/models/blob-test-model.js +19 -0
  97. package/spec/support/models/extended-user-model.js +38 -0
  98. package/spec/support/models/index.js +27 -0
  99. package/spec/support/models/number-model.js +24 -0
  100. package/spec/support/models/role-model.js +26 -0
  101. package/spec/support/models/role-thing-model.js +41 -0
  102. package/spec/support/models/scoped-user-model.js +13 -0
  103. package/spec/support/models/time-model.js +36 -0
  104. package/spec/support/models/user-model.js +70 -0
  105. package/spec/support/models/user-role-model.js +36 -0
  106. package/spec/support/models/user-thing-model.js +46 -0
  107. package/spec/support/models/validation-test-model.js +40 -0
  108. package/spec/support/snapshots.js +293 -0
  109. package/spec/support/test-helpers.js +13 -0
  110. package/spec/types/concrete/bigint-type-spec.js +84 -0
  111. package/spec/types/concrete/boolean-type-spec.js +83 -0
  112. package/spec/types/concrete/date-type-spec.js +85 -0
  113. package/spec/types/concrete/datetime-type-spec.js +87 -0
  114. package/spec/types/concrete/float-type-spec.js +71 -0
  115. package/spec/types/concrete/foreign-key-type-spec.js +64 -0
  116. package/spec/types/concrete/integer-type-spec.js +71 -0
  117. package/spec/types/concrete/string-type-spec.js +91 -0
  118. package/spec/types/concrete/uuid-v1-type-spec.js +73 -0
  119. package/spec/types/concrete/uuid-v4-type-spec.js +65 -0
  120. package/spec/types/type-spec.js +101 -0
  121. package/spec/types/virtual/model-types-spec.js +401 -0
  122. package/spec/utils/misc-utils-spec.js +61 -0
  123. package/spec/utils/model-utils-spec.js +55 -0
  124. package/spec/utils/query-utils-spec.js +105 -0
@@ -0,0 +1,736 @@
1
+ /* eslint-disable max-classes-per-file */
2
+ /* eslint-disable no-magic-numbers */
3
+
4
+ 'use strict';
5
+
6
+ /* global describe, it, expect, spyOn, beforeAll */
7
+
8
+ const Nife = require('nife');
9
+ const { Model, Types, ConnectionBase } = require('../lib');
10
+
11
+ class User extends Model {
12
+ static fields = {
13
+ 'id': {
14
+ type: Types.BIGINT,
15
+ allowNull: false,
16
+ primaryKey: true,
17
+ },
18
+ 'firstName': {
19
+ type: Types.STRING(64),
20
+ allowNull: true,
21
+ index: true,
22
+ },
23
+ 'lastName': {
24
+ type: Types.STRING(64),
25
+ allowNull: true,
26
+ index: true,
27
+ },
28
+ 'isOver21': {
29
+ type: Types.BOOLEAN,
30
+ allowNull: true,
31
+ index: true,
32
+ },
33
+ };
34
+ }
35
+
36
+ function onSaveDefaultValue() {
37
+ return 'saved';
38
+ }
39
+
40
+ class DefaultValuesModel extends Model {
41
+ static fields = {
42
+ 'id': {
43
+ fieldName: 'id',
44
+ type: Types.BIGINT,
45
+ allowNull: false,
46
+ primaryKey: true,
47
+ defaultValue: () => {
48
+ return 654321;
49
+ },
50
+ },
51
+ 'name': {
52
+ fieldName: 'name',
53
+ type: Types.STRING,
54
+ allowNull: false,
55
+ primaryKey: true,
56
+ defaultValue: 'derp',
57
+ },
58
+ 'onlyOnSave': {
59
+ fieldName: 'onlyOnSave',
60
+ type: Types.STRING,
61
+ allowNull: false,
62
+ primaryKey: true,
63
+ defaultValue: Types.Helpers.defaultValueFlags(onSaveDefaultValue, { onStore: true }),
64
+ },
65
+ };
66
+ }
67
+
68
+ class ArrayFieldsModel extends Model {
69
+ static fields = [
70
+ {
71
+ type: Types.BIGINT,
72
+ fieldName: 'id',
73
+ allowNull: false,
74
+ primaryKey: true,
75
+ },
76
+ {
77
+ type: Types.STRING(64),
78
+ fieldName: 'test',
79
+ allowNull: true,
80
+ index: true,
81
+ },
82
+ ];
83
+ }
84
+
85
+ class NoFieldsModel extends Model {
86
+ }
87
+
88
+ describe('Model', () => {
89
+ let connection;
90
+
91
+ beforeAll(async () => {
92
+ connection = new ConnectionBase({
93
+ bindModels: false,
94
+ models: {
95
+ User,
96
+ DefaultValuesModel,
97
+ ArrayFieldsModel,
98
+ NoFieldsModel,
99
+ },
100
+ });
101
+ });
102
+
103
+ const instanceAndStaticTests = (Klass, callback) => {
104
+ callback(Klass, 'static');
105
+ callback(new Klass(), 'instance');
106
+ };
107
+
108
+ describe('mergeFields', () => {
109
+ it('should be able to clone object fields', () => {
110
+ let clonedFields = DefaultValuesModel.mergeFields();
111
+ expect(typeof clonedFields).toEqual('object');
112
+
113
+ let keys = Object.keys(clonedFields).sort();
114
+ expect(keys.length).toEqual(3);
115
+ expect(keys).toEqual([ 'id', 'name', 'onlyOnSave' ]);
116
+
117
+ expect(clonedFields.id).not.toBe(DefaultValuesModel.fields['id']);
118
+ expect(clonedFields.name).not.toBe(DefaultValuesModel.fields['name']);
119
+ expect(clonedFields.onlyOnSave).not.toBe(DefaultValuesModel.fields['onlyOnSave']);
120
+
121
+ expect(clonedFields.id.fieldName).toEqual(DefaultValuesModel.fields['id'].fieldName);
122
+ expect(clonedFields.name.fieldName).toEqual(DefaultValuesModel.fields['name'].fieldName);
123
+ expect(clonedFields.onlyOnSave.fieldName).toEqual(DefaultValuesModel.fields['onlyOnSave'].fieldName);
124
+ });
125
+
126
+ it('should be able to clone array fields', () => {
127
+ let clonedFields = ArrayFieldsModel.mergeFields();
128
+ expect(Array.isArray(clonedFields)).toEqual(true);
129
+
130
+ expect(clonedFields.length).toEqual(2);
131
+ expect(Nife.pluck('fieldName', clonedFields)).toEqual([ 'id', 'test' ]);
132
+
133
+ expect(typeof clonedFields[0]).toEqual('object');
134
+ expect(typeof clonedFields[1]).toEqual('object');
135
+
136
+ expect(clonedFields[0]).not.toBe(ArrayFieldsModel.fields[0]);
137
+ expect(clonedFields[1]).not.toBe(ArrayFieldsModel.fields[1]);
138
+
139
+ expect(clonedFields[0].fieldName).toEqual(ArrayFieldsModel.fields[0].fieldName);
140
+ expect(clonedFields[1].fieldName).toEqual(ArrayFieldsModel.fields[1].fieldName);
141
+ });
142
+
143
+ it('should be able to clone object fields adding extra fields', () => {
144
+ let clonedFields = DefaultValuesModel.mergeFields({
145
+ 'derp': {
146
+ fieldName: 'derp',
147
+ type: Types.INTEGER,
148
+ },
149
+ });
150
+
151
+ expect(typeof clonedFields).toEqual('object');
152
+
153
+ let keys = Object.keys(clonedFields).sort();
154
+ expect(Object.keys(DefaultValuesModel.fields).length).toEqual(3);
155
+ expect(keys.length).toEqual(4);
156
+ expect(keys).toEqual([ 'derp', 'id', 'name', 'onlyOnSave' ]);
157
+
158
+ expect(clonedFields.id).not.toBe(DefaultValuesModel.fields['id']);
159
+ expect(clonedFields.name).not.toBe(DefaultValuesModel.fields['name']);
160
+ expect(clonedFields.onlyOnSave).not.toBe(DefaultValuesModel.fields['onlyOnSave']);
161
+ expect(typeof clonedFields.derp).toEqual('object');
162
+
163
+ expect(clonedFields.id.fieldName).toEqual(DefaultValuesModel.fields['id'].fieldName);
164
+ expect(clonedFields.name.fieldName).toEqual(DefaultValuesModel.fields['name'].fieldName);
165
+ expect(clonedFields.onlyOnSave.fieldName).toEqual(DefaultValuesModel.fields['onlyOnSave'].fieldName);
166
+ expect(clonedFields.derp.fieldName).toEqual('derp');
167
+ });
168
+
169
+ it('should be able to clone array fields adding extra fields', () => {
170
+ let clonedFields = ArrayFieldsModel.mergeFields([
171
+ {
172
+ fieldName: 'derp',
173
+ type: Types.STRING(128),
174
+ },
175
+ {
176
+ fieldName: 'id',
177
+ type: Types.INTEGER,
178
+ allowNull: true,
179
+ hello: 'world',
180
+ },
181
+ ]);
182
+
183
+ expect(Array.isArray(clonedFields)).toEqual(true);
184
+
185
+ expect(clonedFields.length).toEqual(3);
186
+ expect(Nife.pluck('fieldName', clonedFields)).toEqual([ 'id', 'test', 'derp' ]);
187
+
188
+ expect(typeof clonedFields[0]).toEqual('object');
189
+ expect(typeof clonedFields[1]).toEqual('object');
190
+ expect(typeof clonedFields[2]).toEqual('object');
191
+
192
+ expect(clonedFields[0]).not.toBe(ArrayFieldsModel.fields[0]);
193
+ expect(clonedFields[1]).not.toBe(ArrayFieldsModel.fields[1]);
194
+
195
+ expect(clonedFields[0].fieldName).toEqual(ArrayFieldsModel.fields[0].fieldName);
196
+ expect(clonedFields[1].fieldName).toEqual(ArrayFieldsModel.fields[1].fieldName);
197
+ expect(clonedFields[2].fieldName).toEqual('derp');
198
+
199
+ expect(clonedFields[0].fieldName).toEqual('id');
200
+ expect(clonedFields[0].hello).toEqual('world');
201
+ expect(clonedFields[0].allowNull).toEqual(true);
202
+ });
203
+ });
204
+
205
+ describe('getModelName', () => {
206
+ instanceAndStaticTests(User, (target, type) => {
207
+ it(`should be able to get the models name (${type})`, () => {
208
+ expect(target.getModelName()).toEqual('User');
209
+ });
210
+ });
211
+ });
212
+
213
+ describe('getSingularName', () => {
214
+ instanceAndStaticTests(User, (target, type) => {
215
+ it(`should be able to get the models name (${type})`, () => {
216
+ expect(target.getSingularName()).toEqual('User');
217
+ });
218
+ });
219
+ });
220
+
221
+ describe('getPluralModelName', () => {
222
+ instanceAndStaticTests(User, (target, type) => {
223
+ it(`should be able to get the models name (${type})`, () => {
224
+ expect(target.getPluralModelName()).toEqual('Users');
225
+ });
226
+ });
227
+ });
228
+
229
+ describe('getFields', () => {
230
+ instanceAndStaticTests(User, (target, type) => {
231
+ it(`should be able to get the models fields (${type})`, () => {
232
+ let fields = target.getFields();
233
+ expect(typeof fields).toEqual('object');
234
+ expect(Object.keys(fields).sort()).toEqual([
235
+ 'firstName',
236
+ 'id',
237
+ 'isOver21',
238
+ 'lastName',
239
+ ]);
240
+ });
241
+ });
242
+
243
+ instanceAndStaticTests(User, (target, type) => {
244
+ it(`should be able to get the models fields by name (${type})`, () => {
245
+ let fields = target.getFields([ 'id', 'firstName' ]);
246
+ expect(fields).toBeInstanceOf(Array);
247
+ expect(fields.map((field) => field.fieldName).sort()).toEqual([
248
+ 'firstName',
249
+ 'id',
250
+ ]);
251
+ });
252
+ });
253
+
254
+ instanceAndStaticTests(ArrayFieldsModel, (target, type) => {
255
+ it(`should be able to get the models fields as an array (${type})`, () => {
256
+ let fields = target.getFields();
257
+
258
+ expect(Array.isArray(fields)).toEqual(true);
259
+ expect(fields.length).toEqual(2);
260
+
261
+ expect(Nife.pluck('fieldName', fields)).toEqual([
262
+ 'id',
263
+ 'test',
264
+ ]);
265
+ });
266
+ });
267
+ });
268
+
269
+ describe('iterateFields', () => {
270
+ instanceAndStaticTests(User, (target, type) => {
271
+ it(`should be able to iterate model fields (${type})`, () => {
272
+ let fieldNames = target.iterateFields(({ field, fieldName, stop }) => {
273
+ expect(typeof stop).toEqual('function');
274
+ expect(field.fieldName).toEqual(fieldName);
275
+ return fieldName;
276
+ });
277
+
278
+ expect(fieldNames.sort()).toEqual([
279
+ 'firstName',
280
+ 'id',
281
+ 'isOver21',
282
+ 'lastName',
283
+ ]);
284
+ });
285
+
286
+ it(`should be able to stop early while iterating model fields (${type})`, () => {
287
+ let fieldNames = target.iterateFields(({ fieldName, stop, index }) => {
288
+ if (index > 1) {
289
+ stop();
290
+ return;
291
+ }
292
+
293
+ return fieldName;
294
+ });
295
+
296
+ expect(fieldNames.sort()).toEqual([
297
+ 'firstName',
298
+ 'id',
299
+ ]);
300
+ });
301
+
302
+ it(`should return immediately if callback is not a function (${type})`, () => {
303
+ expect(target.iterateFields()).toEqual([]);
304
+ expect(target.iterateFields({})).toEqual([]);
305
+ expect(target.iterateFields(null)).toEqual([]);
306
+ });
307
+ });
308
+
309
+ instanceAndStaticTests(NoFieldsModel, (target, type) => {
310
+ it(`should return immediately if fields are blank (${type})`, () => {
311
+ let wasCalled = false;
312
+ let fieldNames = target.iterateFields(() => {
313
+ wasCalled = true;
314
+ });
315
+
316
+ expect(wasCalled).toEqual(false);
317
+ expect(fieldNames).toEqual([]);
318
+ });
319
+ });
320
+
321
+ class BadTypeModel extends Model {
322
+ static fields = {
323
+ 'name': {
324
+ fieldName: 'name',
325
+ allowNull: false,
326
+ },
327
+ };
328
+ }
329
+
330
+ it('should throw an error if "type" is empty (static)', () => {
331
+ expect(() => BadTypeModel.iterateFields(() => {})).toThrow(new Error('BadTypeModel::initializeFields: "type" not found on "BadTypeModel.name". "type" is required for all fields.'));
332
+ });
333
+
334
+ it('should throw an error if "type" is empty (instance)', () => {
335
+ expect(() => new BadTypeModel()).toThrow(new Error('BadTypeModel::initializeFields: "type" not found on "BadTypeModel.name". "type" is required for all fields.'));
336
+ });
337
+
338
+ it('should throw an error with no "fieldName" when fields are an array', () => {
339
+ class BadModel extends Model {
340
+ static fields = [
341
+ {
342
+ type: Types.UUIDV4,
343
+ allowNull: false,
344
+ primaryKey: true,
345
+ },
346
+ ];
347
+ }
348
+
349
+ expect(() => {
350
+ BadModel.iterateFields(() => {});
351
+ }).toThrow(new Error('BadModel::initializeFields: "fieldName" is missing on field index 0.'));
352
+
353
+ expect(() => {
354
+ new BadModel();
355
+ }).toThrow(new Error('BadModel::initializeFields: "fieldName" is missing on field index 0.'));
356
+ });
357
+ });
358
+
359
+ describe('getField', () => {
360
+ instanceAndStaticTests(User, (target, type) => {
361
+ it(`should be able to get a model field (${type})`, () => {
362
+ let field = target.getField('id');
363
+ expect(field.fieldName).toEqual('id');
364
+
365
+ field = target.getField('firstName');
366
+ expect(field.fieldName).toEqual('firstName');
367
+ });
368
+
369
+ it(`should gacefully fail to get model field (${type})`, () => {
370
+ let field = target.getField('_unknown');
371
+ expect(field).toBe(undefined);
372
+ });
373
+ });
374
+
375
+ instanceAndStaticTests(ArrayFieldsModel, (target, type) => {
376
+ it(`should be able to get a model field with fields defined as an array (${type})`, () => {
377
+ let field = target.getField('id');
378
+ expect(field.fieldName).toEqual('id');
379
+
380
+ field = target.getField('test');
381
+ expect(field.fieldName).toEqual('test');
382
+ });
383
+ });
384
+
385
+ instanceAndStaticTests(NoFieldsModel, (target, type) => {
386
+ it(`should gacefully fail to get model field when there are no fields (${type})`, () => {
387
+ let field = target.getField('id');
388
+ expect(field).toBe(undefined);
389
+ });
390
+ });
391
+ });
392
+
393
+ describe('hasField', () => {
394
+ instanceAndStaticTests(User, (target, type) => {
395
+ it(`should be able to check if a field exists on a model (${type})`, () => {
396
+ expect(target.hasField('id')).toEqual(true);
397
+ expect(target.hasField('firstName')).toEqual(true);
398
+ expect(target.hasField('_unknown')).toEqual(false);
399
+ });
400
+ });
401
+
402
+ instanceAndStaticTests(ArrayFieldsModel, (target, type) => {
403
+ it(`should be able to check if a field exists on a model with fields as an array (${type})`, () => {
404
+ expect(target.hasField('id')).toEqual(true);
405
+ expect(target.hasField('test')).toEqual(true);
406
+ expect(target.hasField('_unknown')).toEqual(false);
407
+ });
408
+ });
409
+
410
+ instanceAndStaticTests(NoFieldsModel, (target, type) => {
411
+ it(`should gacefully fail to check if a field exists on a model when there are no fields (${type})`, () => {
412
+ expect(target.hasField('id')).toBe(false);
413
+ });
414
+ });
415
+ });
416
+
417
+ describe('getPrimaryKeyField', () => {
418
+ instanceAndStaticTests(User, (target, type) => {
419
+ it(`should be able to fetch the primary key field of a model (${type})`, () => {
420
+ let field = target.getPrimaryKeyField();
421
+ expect(field.fieldName).toEqual('id');
422
+ expect(field.primaryKey).toEqual(true);
423
+ });
424
+ });
425
+ });
426
+
427
+ describe('getPrimaryKeyFieldName', () => {
428
+ instanceAndStaticTests(User, (target, type) => {
429
+ it(`should be able to fetch the primary key field name of a model (${type})`, () => {
430
+ expect(target.getPrimaryKeyFieldName()).toEqual('id');
431
+ });
432
+ });
433
+ });
434
+
435
+ describe('getTableName', () => {
436
+ instanceAndStaticTests(User, (target, type) => {
437
+ it(`should be able to get the table name for the model (${type})`, () => {
438
+ expect(target.getTableName()).toEqual('users');
439
+ });
440
+ });
441
+
442
+ class OtherUser extends User {
443
+ static getTableName() {
444
+ return 'tableOTHERUsers';
445
+ }
446
+ }
447
+
448
+ instanceAndStaticTests(OtherUser, (target, type) => {
449
+ it(`should be able to get the table name for the model (${type})`, () => {
450
+ expect(target.getTableName()).toEqual('tableOTHERUsers');
451
+ });
452
+ });
453
+ });
454
+
455
+ describe('getConcreteFieldCount', () => {
456
+ it('should fetch concrete field count', () => {
457
+ expect(User.getConcreteFieldCount()).toEqual(4);
458
+ });
459
+ });
460
+
461
+ describe('_castFieldValue', () => {
462
+ it('should be able cast a value', () => {
463
+ let user = new User(null, { connection });
464
+ expect(user._castFieldValue({ type: { castToType: () => 'derp' } }, 'hello')).toEqual('derp');
465
+ });
466
+
467
+ it('should return value if type is empty', () => {
468
+ let user = new User(null, { connection });
469
+ expect(user._castFieldValue({ type: null }, null)).toBe(null);
470
+ expect(user._castFieldValue({ type: null }, undefined)).toBe(undefined);
471
+ expect(user._castFieldValue({ type: null }, 'derp')).toBe('derp');
472
+ });
473
+ });
474
+
475
+ describe('getDataValue', () => {
476
+ it('should be able get a value', () => {
477
+ let user = new User({ firstName: 'Bob', lastName: 'Pickle' }, { connection });
478
+ expect(user.getDataValue('firstName')).toEqual('Bob');
479
+ });
480
+
481
+ it('should be able get a dirty value', () => {
482
+ let user = new User({ firstName: 'Bob', lastName: 'Pickle' }, { connection });
483
+ user.clearDirty();
484
+
485
+ expect(user.getDataValue('lastName')).toEqual('Pickle');
486
+ expect(user._fieldData.lastName).toEqual('Pickle');
487
+ expect(user._dirtyFieldData.lastName).toBe(undefined);
488
+
489
+ user.lastName = 'Mustard';
490
+
491
+ expect(user._fieldData.lastName).toEqual('Pickle');
492
+ expect(user._dirtyFieldData.lastName).toBe('Mustard');
493
+ expect(user.getDataValue('lastName')).toEqual('Mustard');
494
+ expect(user.lastName).toEqual('Mustard');
495
+ expect(user.changes).toEqual({
496
+ lastName: { previous: 'Pickle', current: 'Mustard' },
497
+ });
498
+ });
499
+ });
500
+
501
+ describe('setDataValue', () => {
502
+ it('should be able set a value', () => {
503
+ let user = new User({ firstName: 'Bob', lastName: 'Pickle' }, { connection });
504
+
505
+ user.clearDirty();
506
+ expect(user.firstName).toEqual('Bob');
507
+
508
+ user.setDataValue('firstName', 'Booger');
509
+
510
+ expect(user.firstName).toEqual('Booger');
511
+ expect(user._fieldData.firstName).toEqual('Bob');
512
+ expect(user._dirtyFieldData.firstName).toBe('Booger');
513
+ });
514
+
515
+ it('should throw an error if the field does not exist', () => {
516
+ let user = new User({ firstName: 'Bob', lastName: 'Pickle' }, { connection });
517
+ expect(() => user.setDataValue('invalid', 'Booger')).toThrow(new Error('User::setDataValue: Unable to find field named "invalid".'));
518
+ });
519
+
520
+ it('should clear dirty state if field is set back to original value', () => {
521
+ let user = new User({ firstName: 'Bob', lastName: 'Pickle' }, { connection });
522
+ user.clearDirty();
523
+
524
+ user.firstName = 'Booger';
525
+
526
+ expect(user.firstName).toEqual('Booger');
527
+ expect(user._fieldData.firstName).toEqual('Bob');
528
+ expect(user._dirtyFieldData.firstName).toBe('Booger');
529
+
530
+ user.firstName = 'Bob';
531
+
532
+ expect(user.firstName).toEqual('Bob');
533
+ expect(user._fieldData.firstName).toEqual('Bob');
534
+ expect(user._dirtyFieldData.firstName).toBe(undefined);
535
+ });
536
+ });
537
+
538
+ describe('misc', () => {
539
+ it('can define schema', () => {
540
+ let user = new User(null, { connection });
541
+ expect(Object.prototype.hasOwnProperty.call(user, 'id')).toEqual(true);
542
+ expect(Object.prototype.hasOwnProperty.call(user, 'firstName')).toEqual(true);
543
+ expect(Object.prototype.hasOwnProperty.call(user, 'lastName')).toEqual(true);
544
+ expect(Object.prototype.hasOwnProperty.call(user, 'isOver21')).toEqual(true);
545
+ });
546
+
547
+ it('can get/set field values', () => {
548
+ let user = new User(null, { connection });
549
+
550
+ user.id = 1234;
551
+ user.firstName = 'Test';
552
+ user.lastName = 'User';
553
+ user.isOver21 = true;
554
+
555
+ expect(user.id).toEqual(1234);
556
+ expect(user.firstName).toEqual('Test');
557
+ expect(user.lastName).toEqual('User');
558
+ expect(user.isOver21).toEqual(true);
559
+
560
+ expect(Object.prototype.hasOwnProperty.call(user._fieldData, 'id')).toEqual(true);
561
+ expect(Object.prototype.hasOwnProperty.call(user._fieldData, 'firstName')).toEqual(true);
562
+ expect(Object.prototype.hasOwnProperty.call(user._fieldData, 'lastName')).toEqual(true);
563
+ expect(Object.prototype.hasOwnProperty.call(user._fieldData, 'isOver21')).toEqual(true);
564
+
565
+ expect(user._fieldData.id).toEqual(undefined);
566
+ expect(user._fieldData.firstName).toEqual(undefined);
567
+ expect(user._fieldData.lastName).toEqual(undefined);
568
+ expect(user._fieldData.isOver21).toEqual(undefined);
569
+
570
+ expect(user.isDirty()).toEqual(true);
571
+ expect(user.changes).toEqual({
572
+ id: { previous: undefined, current: 1234 },
573
+ firstName: { previous: undefined, current: 'Test' },
574
+ lastName: { previous: undefined, current: 'User' },
575
+ isOver21: { previous: undefined, current: true },
576
+ });
577
+ });
578
+
579
+ it('can set field values from constructor', () => {
580
+ let user = new User({
581
+ id: 1234,
582
+ firstName: 'Test',
583
+ lastName: 'User',
584
+ isOver21: true,
585
+ }, { connection });
586
+
587
+ expect(user.id).toEqual(1234);
588
+ expect(user.firstName).toEqual('Test');
589
+ expect(user.lastName).toEqual('User');
590
+ expect(user.isOver21).toEqual(true);
591
+
592
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'id')).toEqual(true);
593
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'firstName')).toEqual(true);
594
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'lastName')).toEqual(true);
595
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'isOver21')).toEqual(true);
596
+
597
+ expect(user._dirtyFieldData.id).toEqual(1234);
598
+ expect(user._dirtyFieldData.firstName).toEqual('Test');
599
+ expect(user._dirtyFieldData.lastName).toEqual('User');
600
+ expect(user._dirtyFieldData.isOver21).toEqual(true);
601
+
602
+ expect(user.isDirty()).toEqual(true);
603
+ expect(user.changes).toEqual({
604
+ id: { previous: undefined, current: 1234 },
605
+ firstName: { previous: undefined, current: 'Test' },
606
+ isOver21: { previous: undefined, current: true },
607
+ lastName: { previous: undefined, current: 'User' },
608
+ });
609
+ });
610
+
611
+ it('can cast field values from constructor', () => {
612
+ let user = new User({
613
+ id: '1234',
614
+ firstName: 'Test',
615
+ lastName: 'User',
616
+ isOver21: 'true',
617
+ }, { connection });
618
+
619
+ expect(user.id).toEqual(1234);
620
+ expect(user.firstName).toEqual('Test');
621
+ expect(user.lastName).toEqual('User');
622
+ expect(user.isOver21).toEqual(true);
623
+
624
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'id')).toEqual(true);
625
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'firstName')).toEqual(true);
626
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'lastName')).toEqual(true);
627
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'isOver21')).toEqual(true);
628
+
629
+ expect(user._dirtyFieldData.id).toEqual(1234);
630
+ expect(user._dirtyFieldData.firstName).toEqual('Test');
631
+ expect(user._dirtyFieldData.lastName).toEqual('User');
632
+ expect(user._dirtyFieldData.isOver21).toEqual(true);
633
+
634
+ expect(user.isDirty()).toEqual(true);
635
+ expect(user.changes).toEqual({
636
+ id: { previous: undefined, current: 1234 },
637
+ firstName: { previous: undefined, current: 'Test' },
638
+ isOver21: { previous: undefined, current: true },
639
+ lastName: { previous: undefined, current: 'User' },
640
+ });
641
+ });
642
+
643
+ it('field values from a constructor that are methods should be called automatically', () => {
644
+ let user = new User({
645
+ id: () => 1234,
646
+ firstName: () => 'Test',
647
+ lastName: () => 'User',
648
+ isOver21: () => true,
649
+ }, { connection });
650
+
651
+ expect(user.id).toEqual(1234);
652
+ expect(user.firstName).toEqual('Test');
653
+ expect(user.lastName).toEqual('User');
654
+ expect(user.isOver21).toEqual(true);
655
+
656
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'id')).toEqual(true);
657
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'firstName')).toEqual(true);
658
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'lastName')).toEqual(true);
659
+ expect(Object.prototype.hasOwnProperty.call(user._dirtyFieldData, 'isOver21')).toEqual(true);
660
+
661
+ expect(user._dirtyFieldData.id).toEqual(1234);
662
+ expect(user._dirtyFieldData.firstName).toEqual('Test');
663
+ expect(user._dirtyFieldData.lastName).toEqual('User');
664
+ expect(user._dirtyFieldData.isOver21).toEqual(true);
665
+
666
+ expect(user.isDirty()).toEqual(true);
667
+ expect(user.changes).toEqual({
668
+ id: { previous: undefined, current: 1234 },
669
+ firstName: { previous: undefined, current: 'Test' },
670
+ isOver21: { previous: undefined, current: true },
671
+ lastName: { previous: undefined, current: 'User' },
672
+ });
673
+ });
674
+
675
+ it('can have default values for fields', () => {
676
+ let model = new DefaultValuesModel(null, { connection });
677
+ expect(model.id).toEqual(654321);
678
+ expect(model.name).toEqual('derp');
679
+ expect(model.onlyOnSave).toEqual(undefined);
680
+ });
681
+
682
+ it('can use getters and setters', () => {
683
+ class TestModel extends Model {
684
+ static fields = {
685
+ 'test': {
686
+ type: Types.STRING(128),
687
+ get() {
688
+ return this.getDataValue('test') || 'derp';
689
+ },
690
+ set({ value }) {
691
+ this.setDataValue('test', value);
692
+ },
693
+ },
694
+ };
695
+ }
696
+
697
+ let model = new TestModel(null, { connection });
698
+
699
+ spyOn(TestModel.fields.test, 'get').and.callThrough();
700
+ spyOn(TestModel.fields.test, 'set').and.callThrough();
701
+
702
+ expect(model.test).toEqual('derp');
703
+ expect(TestModel.fields.test.get).toHaveBeenCalled();
704
+ expect(TestModel.fields.test.set).not.toHaveBeenCalled();
705
+
706
+ model.test = 'hello';
707
+ expect(TestModel.fields.test.set).toHaveBeenCalled();
708
+ expect(model.test).toEqual('hello');
709
+ });
710
+
711
+ it('has injected methods from a virtual field', () => {
712
+ class VirtualFieldTest extends Model {
713
+ static fields = {
714
+ 'id': {
715
+ type: Types.BIGINT,
716
+ allowNull: false,
717
+ primaryKey: true,
718
+ },
719
+ 'role': {
720
+ type: Types.Model('Role', ({ Role }) => {
721
+ return Role.where;
722
+ }),
723
+ },
724
+ };
725
+ }
726
+
727
+ let instance = new VirtualFieldTest(null, { connection });
728
+ expect(instance.createRole).toBeInstanceOf(Function);
729
+ expect(instance.getRole).toBeInstanceOf(Function);
730
+ expect(instance.updateRole).toBeInstanceOf(Function);
731
+ expect(instance.destroyRole).toBeInstanceOf(Function);
732
+ });
733
+ });
734
+
735
+ // TODO: Need to be able to serialize/deserialize models
736
+ });