@reldens/storage 0.116.0 → 0.118.0

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 (86) hide show
  1. package/.claude/test-architecture.md +27 -17
  2. package/CLAUDE.md +67 -11
  3. package/README.md +204 -22
  4. package/bin/reldens-storage.js +43 -10
  5. package/index.js +53 -12
  6. package/lib/drizzle/drizzle-data-server.js +123 -0
  7. package/lib/drizzle/drizzle-driver.js +228 -0
  8. package/lib/drizzle/drizzle-models-generation.js +49 -0
  9. package/lib/drizzle/drizzle-modules-loader.js +32 -0
  10. package/lib/drizzle/drizzle-modules-validator.js +74 -0
  11. package/lib/entities-generator.js +36 -7
  12. package/lib/entity-templates/drizzle-model.template +28 -0
  13. package/lib/entity-templates/query-builder-model.template +17 -0
  14. package/lib/generators/base-generator.js +13 -0
  15. package/lib/generators/entities-generation.js +0 -13
  16. package/lib/generators/models-generation.js +179 -693
  17. package/lib/generators/relations-detection.js +171 -0
  18. package/lib/knex/knex-data-server.js +128 -0
  19. package/lib/knex/knex-driver.js +157 -0
  20. package/lib/knex/knex-modules-loader.js +28 -0
  21. package/lib/knex/knex-modules-validator.js +45 -0
  22. package/lib/kysely/kysely-data-server.js +128 -0
  23. package/lib/kysely/kysely-driver.js +176 -0
  24. package/lib/kysely/kysely-modules-loader.js +28 -0
  25. package/lib/kysely/kysely-modules-validator.js +53 -0
  26. package/lib/mikro-orm/mikro-orm-data-server.js +48 -29
  27. package/lib/mikro-orm/mikro-orm-driver.js +385 -121
  28. package/lib/mikro-orm/mikro-orm-models-generation.js +178 -0
  29. package/lib/mikro-orm/mikro-orm-modules-loader.js +50 -0
  30. package/lib/mikro-orm/mikro-orm-modules-validator.js +46 -0
  31. package/lib/mysql2-connection-config.js +38 -0
  32. package/lib/objection-js/objection-js-data-server.js +71 -125
  33. package/lib/objection-js/objection-js-driver.js +4 -1
  34. package/lib/objection-js/objection-js-models-generation.js +98 -0
  35. package/lib/objection-js/objection-modules-loader.js +28 -0
  36. package/lib/objection-js/objection-modules-validator.js +31 -0
  37. package/lib/package-resolver.js +45 -0
  38. package/lib/prisma/prisma-client-loader.js +11 -1
  39. package/lib/prisma/prisma-driver.js +16 -2
  40. package/lib/prisma/prisma-filter-processor.js +5 -6
  41. package/lib/prisma/prisma-models-generation.js +236 -0
  42. package/lib/prisma/prisma-schema-generator.js +5 -1
  43. package/lib/prisma/prisma-type-caster.js +14 -0
  44. package/lib/query-builder-driver.js +210 -0
  45. package/lib/query-builder-models-generation.js +80 -0
  46. package/lib/relations-loader.js +192 -0
  47. package/lib/type-mapper.js +38 -0
  48. package/package.json +7 -7
  49. package/tests/.env.test.example +6 -0
  50. package/tests/fixtures/categories-fixtures.js +8 -0
  51. package/tests/fixtures/expected-entities/entities/test-product-details-entity.js +72 -0
  52. package/tests/fixtures/expected-entities/entities-config.js +6 -4
  53. package/tests/fixtures/expected-entities/entities-translations.js +25 -14
  54. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +2 -0
  55. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +13 -8
  56. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +80 -0
  57. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +20 -14
  58. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
  59. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +2 -0
  60. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-product-details-model.js +33 -0
  61. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +9 -0
  62. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +2 -0
  63. package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +43 -0
  64. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +2 -0
  65. package/tests/fixtures/product-details-fixtures.js +22 -0
  66. package/tests/fixtures/reviews-fixtures.js +2 -1
  67. package/tests/fixtures/sql/test-schema.sql +23 -1
  68. package/tests/fixtures/table-columns-fixtures.js +97 -0
  69. package/tests/integration/test-cross-driver-equivalence.js +286 -0
  70. package/tests/integration/test-nested-filters.js +265 -408
  71. package/tests/integration/test-relations.js +199 -0
  72. package/tests/integration/test-reldens-sample-data.js +255 -0
  73. package/tests/integration/test-reldens-shape-relations.js +292 -0
  74. package/tests/run-tests.js +112 -60
  75. package/tests/unit/test-drivers.js +7 -1
  76. package/tests/unit/test-models-generation.js +174 -0
  77. package/tests/utils/column-value-assert.js +139 -0
  78. package/tests/utils/driver-registry.js +1 -1
  79. package/tests/utils/entity-projection.js +111 -0
  80. package/tests/utils/observed-value.js +41 -0
  81. package/tests/utils/projection-difference.js +151 -0
  82. package/tests/utils/relations-shape-support.js +104 -0
  83. package/tests/utils/reldens-schema-loader.js +274 -0
  84. package/tests/utils/test-helpers.js +152 -19
  85. package/tests/utils/test-runner.js +26 -5
  86. package/tests/utils/whole-result-assert.js +243 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ *
3
+ * Reldens - Reldens Shape Relations Test
4
+ * Reproduces the real Reldens data shape against the generated test models:
5
+ * test_reviews.product_id (NOT NULL FK) -> test_products.id, plus test_products.category_id (NOT NULL FK)
6
+ * -> test_categories.id, mirroring skills_owners_class_path.class_path_id -> skills_class_path.id and
7
+ * skills_class_path.levels_set_id -> skills_levels_set.id.
8
+ * The relation hint is passed exactly as lib/actions/server/models-manager.js does it: a bare string.
9
+ *
10
+ */
11
+
12
+ const { TestRunner, assert } = require('../utils/test-runner');
13
+ const { ObservedValue } = require('../utils/observed-value');
14
+ const { RelationsShapeSupport } = require('../utils/relations-shape-support');
15
+ const { CategoriesFixtures } = require('../fixtures/categories-fixtures');
16
+ const { ProductsFixtures } = require('../fixtures/products-fixtures');
17
+ const { ReviewsFixtures } = require('../fixtures/reviews-fixtures');
18
+
19
+ class ReldensShapeRelationsTest
20
+ {
21
+
22
+ constructor(dataServer, repos, driverName)
23
+ {
24
+ this.driverName = driverName;
25
+ this.support = new RelationsShapeSupport(dataServer, repos);
26
+ this.runner = new TestRunner();
27
+ this.createdChildId = false;
28
+ this.createdParentId = false;
29
+ this.child = ReviewsFixtures.review_relations_1;
30
+ this.parent = ProductsFixtures.product_relations_1;
31
+ this.otherParent = ProductsFixtures.product_relations_2;
32
+ this.grandParent = CategoriesFixtures.category_relations_1;
33
+ this.otherGrandParent = CategoriesFixtures.category_relations_2;
34
+ this.parentColumns = {sku: this.parent.sku, name: this.parent.name};
35
+ this.grandParentColumns = {name: this.grandParent.name, slug: this.grandParent.slug};
36
+ this.parentKey = 'related_test_products';
37
+ this.grandParentKey = 'related_test_categories';
38
+ }
39
+
40
+ async run()
41
+ {
42
+ this.runner.suite('Reldens Shape Relations - Driver: '+this.driverName);
43
+ await this.prepareShapeFixtures();
44
+ await this.testSingleLevelHints();
45
+ await this.testTwoLevelHints();
46
+ await this.testNonPrimaryColumnForeignKey();
47
+ await this.testForeignKeyScalarWithoutRelations();
48
+ await this.testForeignKeyOnCreate();
49
+ await this.testForeignKeyOnUpdate();
50
+ return this.runner.getResults();
51
+ }
52
+
53
+ async prepareShapeFixtures()
54
+ {
55
+ this.runner.group('Reldens Shape Fixtures');
56
+ await this.support.insertChainFixtures();
57
+ await this.runner.test('should have the child, parent and grandparent rows in place', async () => {
58
+ let childRow = await this.support.cleared('child').loadOneBy('id', this.child.id);
59
+ let parentRow = await this.support.cleared('parent').loadOneBy('id', this.parent.id);
60
+ let grandParentRow = await this.support.cleared('grandParent').loadOneBy('id', this.grandParent.id);
61
+ assert.ok(childRow && parentRow && grandParentRow, 'not every fixture row was inserted');
62
+ this.support.assertNumericColumn(childRow.product_id, this.parent.id, 'child row product_id');
63
+ this.support.assertNumericColumn(parentRow.category_id, this.grandParent.id, 'parent row category_id');
64
+ this.support.assertEntityColumns(grandParentRow, this.grandParentColumns, 'grandparent row');
65
+ });
66
+ }
67
+
68
+ async testSingleLevelHints()
69
+ {
70
+ this.runner.group('Single Level Relation Hint - models-manager.js shape');
71
+ let hintForms = {'a bare string': this.parentKey, 'an array': [this.parentKey]};
72
+ for(let hintLabel of Object.keys(hintForms)){
73
+ await this.runner.test('should hydrate the parent entity from '+hintLabel+' hint', async () => {
74
+ let record = await this.support.loadChildWithHint(hintForms[hintLabel]);
75
+ ObservedValue.log('loadOneByWithRelations('+hintLabel+') result', record);
76
+ assert.ok(record, 'loadOneByWithRelations returned: '+ObservedValue.describe(record));
77
+ this.support.assertEntityColumns(record[this.parentKey], this.parentColumns, 'record.'+this.parentKey);
78
+ this.support.assertNumericColumn(record[this.parentKey].id, this.parent.id, 'the parent entity id');
79
+ this.support.assertNumericColumn(record.product_id, this.parent.id, 'the child product_id scalar');
80
+ });
81
+ }
82
+ await this.runner.test('should hydrate the parent on loadByWithRelations with a bare string hint', async () => {
83
+ let records = await this.support.cleared('child').loadByWithRelations(
84
+ 'product_id',
85
+ this.parent.id,
86
+ this.parentKey
87
+ );
88
+ ObservedValue.log('loadByWithRelations result', records);
89
+ assert.ok(Array.isArray(records), 'loadByWithRelations returned: '+ObservedValue.describe(records));
90
+ assert.strictEqual(records.length, 1, 'records.length observed: '+records.length);
91
+ this.support.assertEntityColumns(records[0][this.parentKey], this.parentColumns, 'records[0] parent');
92
+ });
93
+ await this.runner.test('should hydrate the parent on loadOneWithRelations with an array hint', async () => {
94
+ let record = await this.support.cleared('child').loadOneWithRelations({id: this.child.id}, [this.parentKey]);
95
+ assert.ok(record, 'loadOneWithRelations returned: '+ObservedValue.describe(record));
96
+ this.support.assertEntityColumns(record[this.parentKey], this.parentColumns, 'loadOneWithRelations parent');
97
+ });
98
+ await this.runner.test('should hydrate the parent on loadByIdWithRelations with a bare string hint', async () => {
99
+ let record = await this.support.cleared('child').loadByIdWithRelations(this.child.id, this.parentKey);
100
+ assert.ok(record, 'loadByIdWithRelations returned: '+ObservedValue.describe(record));
101
+ this.support.assertEntityColumns(record[this.parentKey], this.parentColumns, 'loadByIdWithRelations parent');
102
+ });
103
+ }
104
+
105
+ async testTwoLevelHints()
106
+ {
107
+ this.runner.group('Two Level Relation Hint');
108
+ let nestedPath = this.parentKey+'.'+this.grandParentKey;
109
+ let hintForms = {'an array': [nestedPath], 'a bare string': nestedPath};
110
+ for(let hintLabel of Object.keys(hintForms)){
111
+ await this.runner.test('should hydrate the grandparent from '+hintLabel+' two level hint', async () => {
112
+ let record = await this.support.loadChildWithHint(hintForms[hintLabel]);
113
+ assert.ok(record, 'loadOneByWithRelations returned: '+ObservedValue.describe(record));
114
+ let parentValue = record[this.parentKey];
115
+ this.support.assertEntityColumns(parentValue, this.parentColumns, 'record.'+this.parentKey);
116
+ this.support.assertEntityColumns(
117
+ parentValue[this.grandParentKey],
118
+ this.grandParentColumns,
119
+ 'record.'+nestedPath
120
+ );
121
+ this.support.assertNumericColumn(parentValue.category_id, this.grandParent.id, 'parent category_id');
122
+ });
123
+ }
124
+ }
125
+
126
+ async testNonPrimaryColumnForeignKey()
127
+ {
128
+ this.runner.group('Non Primary Column Foreign Key');
129
+ await this.runner.test('should hydrate a parent whose foreign key targets a unique column', async () => {
130
+ let record = await this.support.cleared('child').loadOneByWithRelations(
131
+ 'id',
132
+ this.child.id,
133
+ this.grandParentKey
134
+ );
135
+ assert.ok(record, 'loadOneByWithRelations returned: '+ObservedValue.describe(record));
136
+ this.support.assertEntityColumns(
137
+ record[this.grandParentKey],
138
+ this.grandParentColumns,
139
+ 'record.'+this.grandParentKey
140
+ );
141
+ assert.strictEqual(
142
+ record.category_slug,
143
+ this.grandParent.slug,
144
+ 'record.category_slug observed: '+ObservedValue.describe(record.category_slug)
145
+ );
146
+ });
147
+ }
148
+
149
+ async testForeignKeyScalarWithoutRelations()
150
+ {
151
+ this.runner.group('Foreign Key Scalar Without Relations');
152
+ await this.runner.test('should return the child foreign key scalar on a plain loadOneBy', async () => {
153
+ let record = await this.support.cleared('child').loadOneBy('product_id', this.parent.id);
154
+ ObservedValue.log('plain loadOneBy result', record);
155
+ assert.ok(record, 'loadOneBy returned: '+ObservedValue.describe(record));
156
+ this.support.assertNumericColumn(record.product_id, this.parent.id, 'plain loadOneBy product_id');
157
+ let relationStub = record[this.parentKey];
158
+ let describedStub = ObservedValue.log('plain loadOneBy '+this.parentKey, relationStub);
159
+ assert.ok(
160
+ !relationStub || !relationStub[Object.keys(this.parentColumns)[0]],
161
+ 'the relation was hydrated without being requested, observed: '+describedStub
162
+ );
163
+ });
164
+ await this.runner.test('should return the parent foreign key scalar on a plain loadById', async () => {
165
+ let record = await this.support.cleared('parent').loadById(this.parent.id);
166
+ assert.ok(record, 'loadById returned: '+ObservedValue.describe(record));
167
+ this.support.assertNumericColumn(record.category_id, this.grandParent.id, 'plain loadById category_id');
168
+ });
169
+ await this.runner.test('should return the child foreign key scalar on a plain loadBy', async () => {
170
+ let records = await this.support.cleared('child').loadBy('id', this.child.id);
171
+ assert.ok(Array.isArray(records), 'loadBy returned: '+ObservedValue.describe(records));
172
+ this.support.assertNumericColumn(records[0].product_id, this.parent.id, 'plain loadBy product_id');
173
+ });
174
+ }
175
+
176
+ async testForeignKeyOnCreate()
177
+ {
178
+ this.runner.group('Foreign Key Persisted On Create');
179
+ await this.runner.test('should persist the child not null foreign key column on create', async () => {
180
+ let created = await this.support.repos.child.create({
181
+ product_id: this.otherParent.id,
182
+ reviewer_name: 'Foreign Key Create',
183
+ reviewer_email: 'fk-create@example.com',
184
+ rating: 5,
185
+ title: 'Foreign key create',
186
+ comment: 'Created to verify the foreign key column is persisted',
187
+ is_verified: 1,
188
+ helpful_count: 0
189
+ });
190
+ ObservedValue.log('child create result', created);
191
+ assert.ok(created, 'create returned: '+ObservedValue.describe(created));
192
+ assert.ok(created.id, 'created.id observed: '+ObservedValue.describe(created.id));
193
+ this.createdChildId = created.id;
194
+ ObservedValue.log('product_id on the instance returned by create', created.product_id);
195
+ await this.support.assertStoredColumn(
196
+ 'child',
197
+ created.id,
198
+ 'product_id',
199
+ this.otherParent.id,
200
+ 'created child'
201
+ );
202
+ });
203
+ await this.runner.test('should persist the parent not null foreign key column on create', async () => {
204
+ let created = await this.support.repos.parent.create({
205
+ category_id: this.otherGrandParent.id,
206
+ name: 'Foreign Key Create Product',
207
+ sku: 'FK-CREATE-PROD-1',
208
+ description: 'Created to verify the parent foreign key column is persisted',
209
+ price: 12.34,
210
+ stock_quantity: 3,
211
+ is_featured: 0,
212
+ tags: 'test,fk',
213
+ status: 'draft'
214
+ });
215
+ ObservedValue.log('parent create result', created);
216
+ assert.ok(created, 'create returned: '+ObservedValue.describe(created));
217
+ assert.ok(created.id, 'created.id observed: '+ObservedValue.describe(created.id));
218
+ this.createdParentId = created.id;
219
+ await this.support.assertStoredColumn(
220
+ 'parent',
221
+ created.id,
222
+ 'category_id',
223
+ this.otherGrandParent.id,
224
+ 'created parent'
225
+ );
226
+ });
227
+ }
228
+
229
+ async testForeignKeyOnUpdate()
230
+ {
231
+ this.runner.group('Foreign Key Persisted On Update');
232
+ await this.runner.test('should persist the child foreign key column on updateById', async () => {
233
+ assert.ok(this.createdChildId, 'no created child id is available from the create group');
234
+ let updated = await this.support.repos.child.updateById(this.createdChildId, {product_id: this.parent.id});
235
+ ObservedValue.log('child updateById result', updated);
236
+ await this.support.assertStoredColumn(
237
+ 'child',
238
+ this.createdChildId,
239
+ 'product_id',
240
+ this.parent.id,
241
+ 'child after updateById'
242
+ );
243
+ });
244
+ await this.runner.test('should persist the child foreign key column on updateBy', async () => {
245
+ assert.ok(this.createdChildId, 'no created child id is available from the create group');
246
+ let updated = await this.support.repos.child.updateBy(
247
+ 'id',
248
+ this.createdChildId,
249
+ {product_id: this.otherParent.id}
250
+ );
251
+ ObservedValue.log('child updateBy result', updated);
252
+ await this.support.assertStoredColumn(
253
+ 'child',
254
+ this.createdChildId,
255
+ 'product_id',
256
+ this.otherParent.id,
257
+ 'child after updateBy'
258
+ );
259
+ });
260
+ await this.runner.test('should persist the parent foreign key column on updateById', async () => {
261
+ assert.ok(this.createdParentId, 'no created parent id is available from the create group');
262
+ let updated = await this.support.repos.parent.updateById(
263
+ this.createdParentId,
264
+ {category_id: this.grandParent.id}
265
+ );
266
+ ObservedValue.log('parent updateById result', updated);
267
+ await this.support.assertStoredColumn(
268
+ 'parent',
269
+ this.createdParentId,
270
+ 'category_id',
271
+ this.grandParent.id,
272
+ 'parent after updateById'
273
+ );
274
+ });
275
+ await this.runner.test('should hydrate the reassigned parent after the foreign key update', async () => {
276
+ assert.ok(this.createdChildId, 'no created child id is available from the create group');
277
+ let record = await this.support.cleared('child').loadByIdWithRelations(
278
+ this.createdChildId,
279
+ this.parentKey
280
+ );
281
+ assert.ok(record, 'loadByIdWithRelations returned: '+ObservedValue.describe(record));
282
+ this.support.assertEntityColumns(
283
+ record[this.parentKey],
284
+ {sku: this.otherParent.sku, name: this.otherParent.name},
285
+ 'reassigned record.'+this.parentKey
286
+ );
287
+ });
288
+ }
289
+
290
+ }
291
+
292
+ module.exports = ReldensShapeRelationsTest;
@@ -11,11 +11,15 @@ const { DriverRegistry } = require('./utils/driver-registry');
11
11
  const DriversTest = require('./integration/test-drivers');
12
12
  const NestedFiltersTest = require('./integration/test-nested-filters');
13
13
  const RelationsTest = require('./integration/test-relations');
14
+ const ReldensShapeRelationsTest = require('./integration/test-reldens-shape-relations');
15
+ const ReldensSampleDataTest = require('./integration/test-reldens-sample-data');
16
+ const { CrossDriverEquivalenceTest } = require('./integration/test-cross-driver-equivalence');
14
17
  const RawQueriesTest = require('./integration/test-raw-queries');
15
18
  const EntityManagerTest = require('./unit/test-entity-manager');
16
19
  const TypeMapperTest = require('./unit/test-type-mapper');
17
20
  const DriversUnitTest = require('./unit/test-drivers');
18
21
  const EntitiesGenerationTest = require('./unit/test-entities-generation');
22
+ const ModelsGenerationTest = require('./unit/test-models-generation');
19
23
 
20
24
  if(!process.env.RELDENS_TEST_DB_HOST){
21
25
  let envPath = FileHandler.joinPaths(__dirname, '.env.test');
@@ -30,6 +34,7 @@ class RunTests
30
34
  constructor()
31
35
  {
32
36
  this.allCounts = {total: 0, passed: 0, failed: 0};
37
+ this.driverBenchmarks = {};
33
38
  this.filter = null;
34
39
  this.suite = null;
35
40
  this.driver = null;
@@ -77,8 +82,8 @@ class RunTests
77
82
  TestHelpers.cleanupGeneratedFiles();
78
83
  }
79
84
  await this.runPreFlightChecks(config);
80
- let hasIntegrationTests = !this.suite || this.suite === 'integration';
81
- let hasUnitTests = !this.suite || this.suite === 'unit';
85
+ let hasIntegrationTests = !this.suite || 'integration' === this.suite;
86
+ let hasUnitTests = !this.suite || 'unit' === this.suite;
82
87
  if(this.filter){
83
88
  if(this.filter.includes('integration')){
84
89
  hasUnitTests = false;
@@ -106,6 +111,7 @@ class RunTests
106
111
  Logger.info('Tests passed: '+this.allCounts.passed);
107
112
  Logger.info('Tests failed: '+this.allCounts.failed);
108
113
  Logger.info('='.repeat(60));
114
+ this.logDriverBenchmarks();
109
115
  if(hasIntegrationTests){
110
116
  await this.driverRegistry.cleanup();
111
117
  }
@@ -121,78 +127,122 @@ class RunTests
121
127
  if(this.driver){
122
128
  driverNames = [this.driver];
123
129
  }
130
+ await this.runCrossDriverTests(driverNames);
124
131
  for(let driverName of driverNames){
125
132
  let dataServer = this.driverRegistry.getDriver(driverName);
126
133
  let repos = this.driverRegistry.getRepos(driverName);
127
134
  if(!dataServer){
128
- Logger.warning('Driver '+driverName+' not available, skipping tests');
135
+ Logger.error('Driver '+driverName+' is not available, counted as a failure.');
136
+ this.allCounts.total++;
137
+ this.allCounts.failed++;
129
138
  continue;
130
139
  }
140
+ await this.runDriverTests(driverName, dataServer, repos);
141
+ }
142
+ }
143
+
144
+ async runCrossDriverTests(driverNames)
145
+ {
146
+ if(2 > driverNames.length){
147
+ Logger.info('Cross driver equivalence needs two active drivers, only '+driverNames.join(', ')+' is.');
148
+ return this.allCounts;
149
+ }
150
+ try {
151
+ return this.appendCounts(
152
+ await (new CrossDriverEquivalenceTest(this.driverRegistry, driverNames)).run(),
153
+ false
154
+ );
155
+ } catch(error) {
156
+ Logger.critical('Cross driver equivalence tests crashed: '+error.message);
157
+ Logger.critical(error.stack);
158
+ this.allCounts.total++;
159
+ this.allCounts.failed++;
160
+ }
161
+ return this.allCounts;
162
+ }
163
+
164
+ integrationTestClasses()
165
+ {
166
+ return [
167
+ {label: 'drivers', testClass: DriversTest, isBenchmarked: true},
168
+ {label: 'nested filters', testClass: NestedFiltersTest, isBenchmarked: true},
169
+ {label: 'relations', testClass: RelationsTest, isBenchmarked: true},
170
+ {label: 'raw queries', testClass: RawQueriesTest, isBenchmarked: true},
171
+ {label: 'reldens shape relations', testClass: ReldensShapeRelationsTest, isBenchmarked: false},
172
+ {label: 'reldens sample data', testClass: ReldensSampleDataTest, isBenchmarked: false}
173
+ ];
174
+ }
175
+
176
+ async runDriverTests(driverName, dataServer, repos)
177
+ {
178
+ for(let testDefinition of this.integrationTestClasses()){
131
179
  try {
132
- let driversTest = new DriversTest(dataServer, repos, driverName);
133
- let driversResult = await driversTest.run();
134
- this.allCounts.total += driversResult.total;
135
- this.allCounts.passed += driversResult.passed;
136
- this.allCounts.failed += driversResult.failed;
137
- } catch(error) {
138
- Logger.critical('Driver '+driverName+' tests crashed: '+error.message);
139
- Logger.critical(error.stack);
140
- }
141
- try {
142
- let nestedFiltersTest = new NestedFiltersTest(dataServer, repos, driverName);
143
- let nestedFiltersResult = await nestedFiltersTest.run();
144
- this.allCounts.total += nestedFiltersResult.total;
145
- this.allCounts.passed += nestedFiltersResult.passed;
146
- this.allCounts.failed += nestedFiltersResult.failed;
147
- } catch(error) {
148
- Logger.critical('Driver '+driverName+' nested filters tests crashed: '+error.message);
149
- Logger.critical(error.stack);
150
- }
151
- try {
152
- let relationsTest = new RelationsTest(dataServer, repos, driverName);
153
- let relationsResult = await relationsTest.run();
154
- this.allCounts.total += relationsResult.total;
155
- this.allCounts.passed += relationsResult.passed;
156
- this.allCounts.failed += relationsResult.failed;
157
- } catch(error) {
158
- Logger.critical('Driver '+driverName+' relations tests crashed: '+error.message);
159
- Logger.critical(error.stack);
160
- }
161
- try {
162
- let rawQueriesTest = new RawQueriesTest(dataServer, repos, driverName);
163
- let rawQueriesResult = await rawQueriesTest.run();
164
- this.allCounts.total += rawQueriesResult.total;
165
- this.allCounts.passed += rawQueriesResult.passed;
166
- this.allCounts.failed += rawQueriesResult.failed;
180
+ let testInstance = new testDefinition.testClass(dataServer, repos, driverName);
181
+ this.appendCounts(await testInstance.run(), testDefinition.isBenchmarked ? driverName : false);
167
182
  } catch(error) {
168
- Logger.critical('Driver '+driverName+' raw queries tests crashed: '+error.message);
183
+ Logger.critical(
184
+ 'Driver '+driverName+' '+testDefinition.label+' tests crashed: '+error.message
185
+ );
169
186
  Logger.critical(error.stack);
187
+ this.allCounts.total++;
188
+ this.allCounts.failed++;
170
189
  }
171
190
  }
191
+ return this.driverBenchmarks;
192
+ }
193
+
194
+ appendCounts(result, driverName)
195
+ {
196
+ this.allCounts.total += result.total;
197
+ this.allCounts.passed += result.passed;
198
+ this.allCounts.failed += result.failed;
199
+ if(!driverName){
200
+ return result;
201
+ }
202
+ if(!sc.hasOwn(this.driverBenchmarks, driverName)){
203
+ this.driverBenchmarks[driverName] = {total: 0, duration: 0};
204
+ }
205
+ this.driverBenchmarks[driverName].total += result.total;
206
+ this.driverBenchmarks[driverName].duration += sc.get(result, 'duration', 0);
207
+ return result;
208
+ }
209
+
210
+ logDriverBenchmarks()
211
+ {
212
+ let driverNames = Object.keys(this.driverBenchmarks);
213
+ if(0 === driverNames.length){
214
+ return false;
215
+ }
216
+ Logger.info('='.repeat(60));
217
+ Logger.info('DRIVER BENCHMARKS - INTEGRATION TESTS');
218
+ Logger.info('='.repeat(60));
219
+ for(let driverName of driverNames){
220
+ let benchmark = this.driverBenchmarks[driverName];
221
+ let average = 0 === benchmark.total ? 0 : (benchmark.duration / benchmark.total).toFixed(2);
222
+ Logger.info(
223
+ driverName+' - tests: '+benchmark.total
224
+ +' - total: '+benchmark.duration+'ms'
225
+ +' - average: '+average+'ms'
226
+ );
227
+ }
228
+ Logger.info('='.repeat(60));
229
+ return true;
172
230
  }
173
231
 
174
232
  async runUnitTests()
175
233
  {
176
- let entityManagerTest = new EntityManagerTest();
177
- let entityManagerResult = await entityManagerTest.run();
178
- this.allCounts.total += entityManagerResult.total;
179
- this.allCounts.passed += entityManagerResult.passed;
180
- this.allCounts.failed += entityManagerResult.failed;
181
- let typeMapperTest = new TypeMapperTest();
182
- let typeMapperResult = await typeMapperTest.run();
183
- this.allCounts.total += typeMapperResult.total;
184
- this.allCounts.passed += typeMapperResult.passed;
185
- this.allCounts.failed += typeMapperResult.failed;
186
- let driversUnitTest = new DriversUnitTest();
187
- let driversUnitResult = await driversUnitTest.run();
188
- this.allCounts.total += driversUnitResult.total;
189
- this.allCounts.passed += driversUnitResult.passed;
190
- this.allCounts.failed += driversUnitResult.failed;
191
- let entitiesGenerationTest = new EntitiesGenerationTest();
192
- let entitiesGenerationResult = await entitiesGenerationTest.run();
193
- this.allCounts.total += entitiesGenerationResult.total;
194
- this.allCounts.passed += entitiesGenerationResult.passed;
195
- this.allCounts.failed += entitiesGenerationResult.failed;
234
+ let unitTestClasses = [
235
+ EntityManagerTest,
236
+ TypeMapperTest,
237
+ DriversUnitTest,
238
+ EntitiesGenerationTest,
239
+ ModelsGenerationTest
240
+ ];
241
+ for(let UnitTestClass of unitTestClasses){
242
+ let unitTest = new UnitTestClass();
243
+ this.appendCounts(await unitTest.run(), false);
244
+ }
245
+ return this.allCounts;
196
246
  }
197
247
 
198
248
  async runPreFlightChecks(config)
@@ -222,7 +272,9 @@ process.on('uncaughtException', (error) => {
222
272
  });
223
273
 
224
274
  let runner = new RunTests();
225
- runner.run().catch(error => {
275
+ runner.run().then(counts => {
276
+ process.exit(0 < counts.failed ? 1 : 0);
277
+ }).catch(error => {
226
278
  Logger.info('CATASTROPHIC ERROR: Test runner failed completely\n');
227
279
  Logger.info('Error: '+error.message+'\n');
228
280
  Logger.info(error.stack+'\n');
@@ -10,6 +10,9 @@ const { TestHelpers } = require('../utils/test-helpers');
10
10
  const { ObjectionJsDriver } = require('../../lib/objection-js/objection-js-driver');
11
11
  const { MikroOrmDriver } = require('../../lib/mikro-orm/mikro-orm-driver');
12
12
  const { PrismaDriver } = require('../../lib/prisma/prisma-driver');
13
+ const { KnexDriver } = require('../../lib/knex/knex-driver');
14
+ const { KyselyDriver } = require('../../lib/kysely/kysely-driver');
15
+ const { DrizzleDriver } = require('../../lib/drizzle/drizzle-driver');
13
16
 
14
17
  class DriversUnitTest
15
18
  {
@@ -19,7 +22,10 @@ class DriversUnitTest
19
22
  this.runner = new TestRunner();
20
23
  this.DRIVERS = [
21
24
  {name: 'objection-js', class: ObjectionJsDriver},
22
- {name: 'mikro-orm', class: MikroOrmDriver}
25
+ {name: 'mikro-orm', class: MikroOrmDriver},
26
+ {name: 'knex', class: KnexDriver},
27
+ {name: 'kysely', class: KyselyDriver},
28
+ {name: 'drizzle', class: DrizzleDriver}
23
29
  ];
24
30
  if(TestHelpers.isPrismaEnabled()){
25
31
  this.DRIVERS.push({name: 'prisma', class: PrismaDriver});