@reldens/storage 0.117.0 → 0.119.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 (78) 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 -680
  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 +317 -108
  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 +8 -8
  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 +10 -1
  52. package/tests/fixtures/expected-entities/entities-translations.js +2 -0
  53. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +13 -8
  54. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +30 -10
  55. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +15 -14
  56. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
  57. package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +3 -1
  58. package/tests/fixtures/product-details-fixtures.js +4 -1
  59. package/tests/fixtures/reviews-fixtures.js +2 -1
  60. package/tests/fixtures/sql/test-schema.sql +11 -2
  61. package/tests/fixtures/table-columns-fixtures.js +97 -0
  62. package/tests/integration/test-cross-driver-equivalence.js +286 -0
  63. package/tests/integration/test-nested-filters.js +265 -408
  64. package/tests/integration/test-relations.js +135 -0
  65. package/tests/integration/test-reldens-sample-data.js +255 -0
  66. package/tests/integration/test-reldens-shape-relations.js +292 -0
  67. package/tests/run-tests.js +111 -65
  68. package/tests/unit/test-drivers.js +7 -1
  69. package/tests/unit/test-models-generation.js +28 -18
  70. package/tests/utils/column-value-assert.js +139 -0
  71. package/tests/utils/entity-projection.js +111 -0
  72. package/tests/utils/observed-value.js +41 -0
  73. package/tests/utils/projection-difference.js +151 -0
  74. package/tests/utils/relations-shape-support.js +104 -0
  75. package/tests/utils/reldens-schema-loader.js +274 -0
  76. package/tests/utils/test-helpers.js +143 -13
  77. package/tests/utils/test-runner.js +26 -5
  78. 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,6 +11,9 @@ 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');
@@ -31,6 +34,7 @@ class RunTests
31
34
  constructor()
32
35
  {
33
36
  this.allCounts = {total: 0, passed: 0, failed: 0};
37
+ this.driverBenchmarks = {};
34
38
  this.filter = null;
35
39
  this.suite = null;
36
40
  this.driver = null;
@@ -78,8 +82,8 @@ class RunTests
78
82
  TestHelpers.cleanupGeneratedFiles();
79
83
  }
80
84
  await this.runPreFlightChecks(config);
81
- let hasIntegrationTests = !this.suite || this.suite === 'integration';
82
- let hasUnitTests = !this.suite || this.suite === 'unit';
85
+ let hasIntegrationTests = !this.suite || 'integration' === this.suite;
86
+ let hasUnitTests = !this.suite || 'unit' === this.suite;
83
87
  if(this.filter){
84
88
  if(this.filter.includes('integration')){
85
89
  hasUnitTests = false;
@@ -107,6 +111,7 @@ class RunTests
107
111
  Logger.info('Tests passed: '+this.allCounts.passed);
108
112
  Logger.info('Tests failed: '+this.allCounts.failed);
109
113
  Logger.info('='.repeat(60));
114
+ this.logDriverBenchmarks();
110
115
  if(hasIntegrationTests){
111
116
  await this.driverRegistry.cleanup();
112
117
  }
@@ -122,83 +127,122 @@ class RunTests
122
127
  if(this.driver){
123
128
  driverNames = [this.driver];
124
129
  }
130
+ await this.runCrossDriverTests(driverNames);
125
131
  for(let driverName of driverNames){
126
132
  let dataServer = this.driverRegistry.getDriver(driverName);
127
133
  let repos = this.driverRegistry.getRepos(driverName);
128
134
  if(!dataServer){
129
- 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++;
130
138
  continue;
131
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()){
132
179
  try {
133
- let driversTest = new DriversTest(dataServer, repos, driverName);
134
- let driversResult = await driversTest.run();
135
- this.allCounts.total += driversResult.total;
136
- this.allCounts.passed += driversResult.passed;
137
- this.allCounts.failed += driversResult.failed;
138
- } catch(error) {
139
- Logger.critical('Driver '+driverName+' tests crashed: '+error.message);
140
- Logger.critical(error.stack);
141
- }
142
- try {
143
- let nestedFiltersTest = new NestedFiltersTest(dataServer, repos, driverName);
144
- let nestedFiltersResult = await nestedFiltersTest.run();
145
- this.allCounts.total += nestedFiltersResult.total;
146
- this.allCounts.passed += nestedFiltersResult.passed;
147
- this.allCounts.failed += nestedFiltersResult.failed;
148
- } catch(error) {
149
- Logger.critical('Driver '+driverName+' nested filters tests crashed: '+error.message);
150
- Logger.critical(error.stack);
151
- }
152
- try {
153
- let relationsTest = new RelationsTest(dataServer, repos, driverName);
154
- let relationsResult = await relationsTest.run();
155
- this.allCounts.total += relationsResult.total;
156
- this.allCounts.passed += relationsResult.passed;
157
- this.allCounts.failed += relationsResult.failed;
158
- } catch(error) {
159
- Logger.critical('Driver '+driverName+' relations tests crashed: '+error.message);
160
- Logger.critical(error.stack);
161
- }
162
- try {
163
- let rawQueriesTest = new RawQueriesTest(dataServer, repos, driverName);
164
- let rawQueriesResult = await rawQueriesTest.run();
165
- this.allCounts.total += rawQueriesResult.total;
166
- this.allCounts.passed += rawQueriesResult.passed;
167
- this.allCounts.failed += rawQueriesResult.failed;
180
+ let testInstance = new testDefinition.testClass(dataServer, repos, driverName);
181
+ this.appendCounts(await testInstance.run(), testDefinition.isBenchmarked ? driverName : false);
168
182
  } catch(error) {
169
- Logger.critical('Driver '+driverName+' raw queries tests crashed: '+error.message);
183
+ Logger.critical(
184
+ 'Driver '+driverName+' '+testDefinition.label+' tests crashed: '+error.message
185
+ );
170
186
  Logger.critical(error.stack);
187
+ this.allCounts.total++;
188
+ this.allCounts.failed++;
171
189
  }
172
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;
173
230
  }
174
231
 
175
232
  async runUnitTests()
176
233
  {
177
- let entityManagerTest = new EntityManagerTest();
178
- let entityManagerResult = await entityManagerTest.run();
179
- this.allCounts.total += entityManagerResult.total;
180
- this.allCounts.passed += entityManagerResult.passed;
181
- this.allCounts.failed += entityManagerResult.failed;
182
- let typeMapperTest = new TypeMapperTest();
183
- let typeMapperResult = await typeMapperTest.run();
184
- this.allCounts.total += typeMapperResult.total;
185
- this.allCounts.passed += typeMapperResult.passed;
186
- this.allCounts.failed += typeMapperResult.failed;
187
- let driversUnitTest = new DriversUnitTest();
188
- let driversUnitResult = await driversUnitTest.run();
189
- this.allCounts.total += driversUnitResult.total;
190
- this.allCounts.passed += driversUnitResult.passed;
191
- this.allCounts.failed += driversUnitResult.failed;
192
- let entitiesGenerationTest = new EntitiesGenerationTest();
193
- let entitiesGenerationResult = await entitiesGenerationTest.run();
194
- this.allCounts.total += entitiesGenerationResult.total;
195
- this.allCounts.passed += entitiesGenerationResult.passed;
196
- this.allCounts.failed += entitiesGenerationResult.failed;
197
- let modelsGenerationTest = new ModelsGenerationTest();
198
- let modelsGenerationResult = await modelsGenerationTest.run();
199
- this.allCounts.total += modelsGenerationResult.total;
200
- this.allCounts.passed += modelsGenerationResult.passed;
201
- this.allCounts.failed += modelsGenerationResult.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;
202
246
  }
203
247
 
204
248
  async runPreFlightChecks(config)
@@ -228,7 +272,9 @@ process.on('uncaughtException', (error) => {
228
272
  });
229
273
 
230
274
  let runner = new RunTests();
231
- runner.run().catch(error => {
275
+ runner.run().then(counts => {
276
+ process.exit(0 < counts.failed ? 1 : 0);
277
+ }).catch(error => {
232
278
  Logger.info('CATASTROPHIC ERROR: Test runner failed completely\n');
233
279
  Logger.info('Error: '+error.message+'\n');
234
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});
@@ -5,7 +5,8 @@
5
5
  */
6
6
 
7
7
  const { TestRunner, assert } = require('../utils/test-runner');
8
- const { ModelsGeneration } = require('../../lib/generators/models-generation');
8
+ const { RelationsDetection } = require('../../lib/generators/relations-detection');
9
+ const { MikroOrmModelsGeneration } = require('../../lib/mikro-orm/mikro-orm-models-generation');
9
10
 
10
11
  class ModelsGenerationTest
11
12
  {
@@ -70,46 +71,52 @@ class ModelsGenerationTest
70
71
  };
71
72
  }
72
73
 
74
+ createRelationsDetection()
75
+ {
76
+ let relationsDetection = new RelationsDetection({});
77
+ relationsDetection.allTablesData = this.createTables();
78
+ return relationsDetection;
79
+ }
80
+
73
81
  createGeneration()
74
82
  {
75
- let generation = new ModelsGeneration({});
76
- generation.setAllTablesData(this.createTables());
77
- return generation;
83
+ return new MikroOrmModelsGeneration(this.createRelationsDetection());
78
84
  }
79
85
 
80
86
  mikroOrmDefinition(generation, tableName)
81
87
  {
82
- let tableData = generation.allTablesData[tableName];
83
- return generation.getEntityPropertiesDefinition(tableData.columns, 'mikro-orm', tableName, tableData);
88
+ let tableData = generation.relationsDetection.allTablesData[tableName];
89
+ return generation.generateEntityProperties(tableData.columns, tableName, tableData);
84
90
  }
85
91
 
86
92
  async run()
87
93
  {
88
94
  this.runner.suite('ModelsGeneration');
89
- await this.testDetermineMikroOrmForwardKind();
95
+ await this.testForwardRelationKind();
90
96
  await this.testMikroOrmOneToOneDefinitions();
91
97
  await this.testObjectionJsRelationTypes();
92
98
  return this.runner.getResults();
93
99
  }
94
100
 
95
- async testDetermineMikroOrmForwardKind()
101
+ async testForwardRelationKind()
96
102
  {
97
- this.runner.group('determineMikroOrmForwardKind');
103
+ this.runner.group('forwardRelationKind');
98
104
  let generation = this.createGeneration();
99
105
  await this.runner.test('should return 1:1 for a unique foreign key column', async () => {
100
- let kind = generation.determineMikroOrmForwardKind(this.createColumn({key: 'UNI'}));
106
+ let kind = generation.forwardRelationKind(this.createColumn({key: 'UNI'}));
101
107
  assert.strictEqual(kind, '1:1');
102
108
  });
103
109
  await this.runner.test('should return 1:1 for a primary foreign key column', async () => {
104
- let kind = generation.determineMikroOrmForwardKind(this.createColumn({key: 'PRI'}));
110
+ let kind = generation.forwardRelationKind(this.createColumn({key: 'PRI'}));
105
111
  assert.strictEqual(kind, '1:1');
106
112
  });
107
113
  await this.runner.test('should return m:1 for an indexed foreign key column', async () => {
108
- let kind = generation.determineMikroOrmForwardKind(this.createColumn({key: 'MUL'}));
114
+ let kind = generation.forwardRelationKind(this.createColumn({key: 'MUL'}));
115
+ // @possible-hallucinated-undefined-method
109
116
  assert.strictEqual(kind, 'm:1');
110
117
  });
111
118
  await this.runner.test('should return m:1 for a non indexed foreign key column', async () => {
112
- let kind = generation.determineMikroOrmForwardKind(this.createColumn({key: ''}));
119
+ let kind = generation.forwardRelationKind(this.createColumn({key: ''}));
113
120
  assert.strictEqual(kind, 'm:1');
114
121
  });
115
122
  }
@@ -143,18 +150,21 @@ class ModelsGenerationTest
143
150
  async testObjectionJsRelationTypes()
144
151
  {
145
152
  this.runner.group('ObjectionJS relation types for unique foreign keys');
146
- let generation = this.createGeneration();
147
- let tables = generation.allTablesData;
153
+ let relationsDetection = this.createRelationsDetection();
154
+ let tables = relationsDetection.allTablesData;
148
155
  await this.runner.test('should keep BelongsToOneRelation on the owning side', async () => {
149
- let relations = generation.detectObjectionJsRelations('test_product_details', tables.test_product_details);
156
+ let relations = relationsDetection.detectForwardRelations(
157
+ 'test_product_details',
158
+ tables.test_product_details
159
+ );
150
160
  assert.strictEqual(relations.related_test_products.relationType, 'BelongsToOneRelation');
151
161
  });
152
162
  await this.runner.test('should use HasOneRelation on the referenced side of a unique foreign key', async () => {
153
- let reverseRelations = generation.detectReverseObjectionJsRelations('test_products');
163
+ let reverseRelations = relationsDetection.detectReverseRelations('test_products');
154
164
  assert.strictEqual(reverseRelations.related_test_product_details.relationType, 'HasOneRelation');
155
165
  });
156
166
  await this.runner.test('should use HasManyRelation on the referenced side of a non unique foreign key', async () => {
157
- let reverseRelations = generation.detectReverseObjectionJsRelations('test_products');
167
+ let reverseRelations = relationsDetection.detectReverseRelations('test_products');
158
168
  assert.strictEqual(reverseRelations.related_test_reviews.relationType, 'HasManyRelation');
159
169
  });
160
170
  }