@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.
- 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 -693
- 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 +385 -121
- 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 +7 -7
- 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 +72 -0
- package/tests/fixtures/expected-entities/entities-config.js +6 -4
- package/tests/fixtures/expected-entities/entities-translations.js +25 -14
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.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 +80 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +20 -14
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +2 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-product-details-model.js +33 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +9 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +2 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +43 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +2 -0
- package/tests/fixtures/product-details-fixtures.js +22 -0
- package/tests/fixtures/reviews-fixtures.js +2 -1
- package/tests/fixtures/sql/test-schema.sql +23 -1
- 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 +199 -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 +112 -60
- package/tests/unit/test-drivers.js +7 -1
- package/tests/unit/test-models-generation.js +174 -0
- package/tests/utils/column-value-assert.js +139 -0
- package/tests/utils/driver-registry.js +1 -1
- 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 +152 -19
- 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,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
|
|
81
|
-
let hasUnitTests = !this.suite || this.suite
|
|
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.
|
|
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
|
|
133
|
-
|
|
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(
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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().
|
|
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});
|