@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.
- package/.claude/test-architecture.md +27 -17
- package/CLAUDE.md +67 -11
- package/README.md +204 -22
- package/bin/reldens-storage.js +43 -10
- package/index.js +53 -12
- package/lib/drizzle/drizzle-data-server.js +123 -0
- package/lib/drizzle/drizzle-driver.js +228 -0
- package/lib/drizzle/drizzle-models-generation.js +49 -0
- package/lib/drizzle/drizzle-modules-loader.js +32 -0
- package/lib/drizzle/drizzle-modules-validator.js +74 -0
- package/lib/entities-generator.js +36 -7
- package/lib/entity-templates/drizzle-model.template +28 -0
- package/lib/entity-templates/query-builder-model.template +17 -0
- package/lib/generators/base-generator.js +13 -0
- package/lib/generators/entities-generation.js +0 -13
- package/lib/generators/models-generation.js +179 -680
- package/lib/generators/relations-detection.js +171 -0
- package/lib/knex/knex-data-server.js +128 -0
- package/lib/knex/knex-driver.js +157 -0
- package/lib/knex/knex-modules-loader.js +28 -0
- package/lib/knex/knex-modules-validator.js +45 -0
- package/lib/kysely/kysely-data-server.js +128 -0
- package/lib/kysely/kysely-driver.js +176 -0
- package/lib/kysely/kysely-modules-loader.js +28 -0
- package/lib/kysely/kysely-modules-validator.js +53 -0
- package/lib/mikro-orm/mikro-orm-data-server.js +48 -29
- package/lib/mikro-orm/mikro-orm-driver.js +317 -108
- package/lib/mikro-orm/mikro-orm-models-generation.js +178 -0
- package/lib/mikro-orm/mikro-orm-modules-loader.js +50 -0
- package/lib/mikro-orm/mikro-orm-modules-validator.js +46 -0
- package/lib/mysql2-connection-config.js +38 -0
- package/lib/objection-js/objection-js-data-server.js +71 -125
- package/lib/objection-js/objection-js-driver.js +4 -1
- package/lib/objection-js/objection-js-models-generation.js +98 -0
- package/lib/objection-js/objection-modules-loader.js +28 -0
- package/lib/objection-js/objection-modules-validator.js +31 -0
- package/lib/package-resolver.js +45 -0
- package/lib/prisma/prisma-client-loader.js +11 -1
- package/lib/prisma/prisma-driver.js +16 -2
- package/lib/prisma/prisma-filter-processor.js +5 -6
- package/lib/prisma/prisma-models-generation.js +236 -0
- package/lib/prisma/prisma-schema-generator.js +5 -1
- package/lib/prisma/prisma-type-caster.js +14 -0
- package/lib/query-builder-driver.js +210 -0
- package/lib/query-builder-models-generation.js +80 -0
- package/lib/relations-loader.js +192 -0
- package/lib/type-mapper.js +38 -0
- package/package.json +8 -8
- package/tests/.env.test.example +6 -0
- package/tests/fixtures/categories-fixtures.js +8 -0
- package/tests/fixtures/expected-entities/entities/test-product-details-entity.js +10 -1
- package/tests/fixtures/expected-entities/entities-translations.js +2 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +13 -8
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +30 -10
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +15 -14
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +3 -1
- package/tests/fixtures/product-details-fixtures.js +4 -1
- package/tests/fixtures/reviews-fixtures.js +2 -1
- package/tests/fixtures/sql/test-schema.sql +11 -2
- package/tests/fixtures/table-columns-fixtures.js +97 -0
- package/tests/integration/test-cross-driver-equivalence.js +286 -0
- package/tests/integration/test-nested-filters.js +265 -408
- package/tests/integration/test-relations.js +135 -0
- package/tests/integration/test-reldens-sample-data.js +255 -0
- package/tests/integration/test-reldens-shape-relations.js +292 -0
- package/tests/run-tests.js +111 -65
- package/tests/unit/test-drivers.js +7 -1
- package/tests/unit/test-models-generation.js +28 -18
- package/tests/utils/column-value-assert.js +139 -0
- package/tests/utils/entity-projection.js +111 -0
- package/tests/utils/observed-value.js +41 -0
- package/tests/utils/projection-difference.js +151 -0
- package/tests/utils/relations-shape-support.js +104 -0
- package/tests/utils/reldens-schema-loader.js +274 -0
- package/tests/utils/test-helpers.js +143 -13
- package/tests/utils/test-runner.js +26 -5
- 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;
|
package/tests/run-tests.js
CHANGED
|
@@ -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
|
|
82
|
-
let hasUnitTests = !this.suite || this.suite
|
|
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.
|
|
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
|
|
134
|
-
|
|
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(
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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().
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
101
|
+
async testForwardRelationKind()
|
|
96
102
|
{
|
|
97
|
-
this.runner.group('
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
147
|
-
let tables =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
167
|
+
let reverseRelations = relationsDetection.detectReverseRelations('test_products');
|
|
158
168
|
assert.strictEqual(reverseRelations.related_test_reviews.relationType, 'HasManyRelation');
|
|
159
169
|
});
|
|
160
170
|
}
|