@reldens/storage 0.88.0 → 0.89.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/driver-methods-complete-analysis.md +1978 -0
- package/.claude/test-architecture.md +973 -0
- package/CLAUDE.md +121 -1
- package/bin/reldens-storage.js +1 -1
- package/lib/base-driver.js +8 -0
- package/lib/entities-generator.js +1 -3
- package/lib/entity-templates/mikro-orm-model.template +2 -1
- package/lib/generators/models-generation.js +126 -5
- package/lib/mikro-orm/mikro-orm-data-server.js +24 -10
- package/lib/mikro-orm/mikro-orm-driver.js +184 -116
- package/lib/objection-js/objection-js-driver.js +111 -19
- package/lib/prisma/prisma-data-server.js +3 -2
- package/lib/prisma/prisma-driver.js +12 -10
- package/lib/prisma/prisma-filter-processor.js +193 -146
- package/lib/prisma/prisma-metadata-loader.js +0 -4
- package/lib/prisma/prisma-relation-resolver.js +292 -267
- package/lib/prisma/prisma-type-caster.js +280 -260
- package/package.json +15 -6
- package/tests/.env.test.example +7 -0
- package/tests/fixtures/categories-fixtures.js +164 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities/test-categories-entity.js +70 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities/test-products-entity.js +95 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities/test-reviews-entity.js +84 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities-config.js +17 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities-translations.js +13 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +23 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +57 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +79 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +70 -0
- package/tests/fixtures/expected-entities-objection-js/entities/test-categories-entity.js +70 -0
- package/tests/fixtures/expected-entities-objection-js/entities/test-products-entity.js +95 -0
- package/tests/fixtures/expected-entities-objection-js/entities/test-reviews-entity.js +84 -0
- package/tests/fixtures/expected-entities-objection-js/entities-config.js +17 -0
- package/tests/fixtures/expected-entities-objection-js/entities-translations.js +13 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +23 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-categories-model.js +33 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +42 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-reviews-model.js +33 -0
- package/tests/fixtures/expected-entities-prisma/entities/test-categories-entity.js +70 -0
- package/tests/fixtures/expected-entities-prisma/entities/test-products-entity.js +95 -0
- package/tests/fixtures/expected-entities-prisma/entities/test-reviews-entity.js +84 -0
- package/tests/fixtures/expected-entities-prisma/entities-config.js +17 -0
- package/tests/fixtures/expected-entities-prisma/entities-translations.js +13 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +23 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-categories-model.js +43 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +50 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-reviews-model.js +46 -0
- package/tests/fixtures/products-fixtures.js +119 -0
- package/tests/fixtures/reviews-fixtures.js +42 -0
- package/tests/fixtures/sql/test-schema.sql +59 -0
- package/tests/integration/test-drivers.js +257 -0
- package/tests/integration/test-nested-filters.js +408 -0
- package/tests/integration/test-relations.js +349 -0
- package/tests/run-tests.js +213 -0
- package/tests/unit/test-drivers.js +89 -0
- package/tests/unit/test-entities-generator.js +533 -0
- package/tests/unit/test-entity-manager.js +128 -0
- package/tests/unit/test-type-mapper.js +232 -0
- package/tests/utils/custom-reporter.js +73 -0
- package/tests/utils/driver-registry.js +144 -0
- package/tests/utils/prisma-subprocess-worker.js +150 -0
- package/tests/utils/test-helpers.js +726 -0
- package/tests/utils/test-runner.js +63 -0
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
# Test Suite Architecture - @reldens/storage
|
|
2
|
+
|
|
3
|
+
## Critical Patterns and Why They Exist
|
|
4
|
+
|
|
5
|
+
### Why Dynamic Model Creation Instead of Generated Models?
|
|
6
|
+
|
|
7
|
+
**Problem:** Generated models in `.test-entities/` use `const { ObjectionJsRawModel } = require('@reldens/storage')` which loads the package index.js, which requires PrismaDataServer, which requires `@prisma/client` at top-level, which doesn't exist yet.
|
|
8
|
+
|
|
9
|
+
**Solution:** Create dynamic models in-memory without requiring the package:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// ❌ WRONG - Would require @reldens/storage → loads Prisma → fails
|
|
13
|
+
const { TestCategoriesModel } = require('.test-entities/...');
|
|
14
|
+
|
|
15
|
+
// ✅ CORRECT - Create models dynamically without package dependency
|
|
16
|
+
const { Model } = require('objection');
|
|
17
|
+
class DynamicModel extends Model {
|
|
18
|
+
static get tableName(){ return tableName; }
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Prisma Client Loading Pattern
|
|
23
|
+
|
|
24
|
+
**Problem:** Cannot use `require('@prisma/client')` at top-level because the client doesn't exist until generated by tests.
|
|
25
|
+
|
|
26
|
+
**Solution:** Generate client first, then load from specific path using PrismaClientLoader:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
// 1. Generate Prisma client (subprocess)
|
|
30
|
+
await generatePrismaSchema(config);
|
|
31
|
+
await generatePrismaClient();
|
|
32
|
+
|
|
33
|
+
// 2. Load client from generated path (NOT '@prisma/client')
|
|
34
|
+
const { PrismaClientLoader } = require('../../lib/prisma/prisma-client-loader');
|
|
35
|
+
let prismaClient = PrismaClientLoader.load(process.cwd(), null, config);
|
|
36
|
+
|
|
37
|
+
// 3. Pass loaded client to DataServer
|
|
38
|
+
serverConfig.prismaClient = prismaClient;
|
|
39
|
+
let dataServer = new PrismaDataServer(serverConfig);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**This pattern is used in:**
|
|
43
|
+
- `reldens-cms` installer (`bin/reldens-cms.js:87-108`)
|
|
44
|
+
- `reldens` main application
|
|
45
|
+
- Test suite setup
|
|
46
|
+
|
|
47
|
+
**Key Points:**
|
|
48
|
+
- Always load Prisma client from explicit path, never default `@prisma/client`
|
|
49
|
+
- Generate client via subprocess before requiring PrismaDataServer
|
|
50
|
+
- Use `PrismaClientLoader.load()` to handle path resolution and connection config
|
|
51
|
+
- Pass client instance to DataServer via `prismaClient` prop
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Critical Test Execution Flow
|
|
56
|
+
|
|
57
|
+
### **THE GOLDEN RULE: Connect Once, Test Many**
|
|
58
|
+
|
|
59
|
+
**CRITICAL:** Database connections, table creation, and entity generation happen **ONCE per driver**, NOT per test.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Per Driver Suite:
|
|
63
|
+
1. Connect to database ← ONCE in before() hook
|
|
64
|
+
2. Create tables via SQL ← ONCE in before() hook
|
|
65
|
+
3. Generate entities ← ONCE in before() hook
|
|
66
|
+
4. Run all tests ← Many times
|
|
67
|
+
5. Cleanup and disconnect ← ONCE in after() hook
|
|
68
|
+
|
|
69
|
+
Per Test:
|
|
70
|
+
1. Delete data from tables ← ONLY this in beforeEach() hook
|
|
71
|
+
2. Run test ← Test execution
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Integration Test Lifecycle Hooks
|
|
77
|
+
|
|
78
|
+
### ✅ CORRECT Pattern (Current Implementation)
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
describe('Driver: '+driverName, () => {
|
|
82
|
+
let dataServer;
|
|
83
|
+
let categoriesRepo;
|
|
84
|
+
let productsRepo;
|
|
85
|
+
let reviewsRepo;
|
|
86
|
+
let schemaPath = FileHandler.joinPaths(__dirname, '..', 'fixtures', 'sql', 'test-schema.sql');
|
|
87
|
+
|
|
88
|
+
// RUNS ONCE - Setup everything
|
|
89
|
+
before(async function(){
|
|
90
|
+
this.timeout(30000);
|
|
91
|
+
let rawEntities = {};
|
|
92
|
+
|
|
93
|
+
// Step 1: Connect to database
|
|
94
|
+
dataServer = await TestHelpers.setupDriver(driverName, rawEntities);
|
|
95
|
+
if(!dataServer){
|
|
96
|
+
throw new Error('Failed to setup driver: '+driverName);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Step 2: Create tables via raw SQL
|
|
100
|
+
let schemaSql = FileHandler.readFile(schemaPath);
|
|
101
|
+
await TestHelpers.executeRawSQL(dataServer, schemaSql);
|
|
102
|
+
|
|
103
|
+
// Step 3: Generate entities (introspects database, creates models)
|
|
104
|
+
let entitiesGenerated = await TestHelpers.generateTestEntities(dataServer, driverName);
|
|
105
|
+
if(!entitiesGenerated){
|
|
106
|
+
throw new Error('Failed to generate test entities for '+driverName);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Step 4: Get repository references
|
|
110
|
+
categoriesRepo = dataServer.getEntity('testCategories');
|
|
111
|
+
productsRepo = dataServer.getEntity('testProducts');
|
|
112
|
+
reviewsRepo = dataServer.getEntity('testReviews');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// RUNS BEFORE EACH TEST - Only delete data
|
|
116
|
+
beforeEach(async () => {
|
|
117
|
+
await TestHelpers.cleanDatabase(dataServer);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// RUNS ONCE - Final cleanup
|
|
121
|
+
after(async () => {
|
|
122
|
+
if(dataServer){
|
|
123
|
+
await TestHelpers.dropTestTables(dataServer);
|
|
124
|
+
await TestHelpers.teardownDriver(dataServer);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('CREATE Operations', () => {
|
|
129
|
+
it('should create single record', async () => {
|
|
130
|
+
// Test code here
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### ❌ WRONG Pattern (What Was Causing Issues)
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
describe('Driver: '+driverName, () => {
|
|
140
|
+
// ❌ WRONG - This reconnects to database before EVERY test
|
|
141
|
+
beforeEach(async () => {
|
|
142
|
+
dataServer = await TestHelpers.setupDriver(driverName, rawEntities);
|
|
143
|
+
let schemaSql = FileHandler.readFile(schemaPath);
|
|
144
|
+
await TestHelpers.executeRawSQL(dataServer, schemaSql);
|
|
145
|
+
await TestHelpers.generateTestEntities(dataServer, driverName);
|
|
146
|
+
categoriesRepo = dataServer.getEntity('testCategories');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ❌ WRONG - This disconnects after EVERY test
|
|
150
|
+
afterEach(async () => {
|
|
151
|
+
await TestHelpers.teardownDriver(dataServer);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should create single record', async () => {
|
|
155
|
+
// This single test just did:
|
|
156
|
+
// 1. Connect to database
|
|
157
|
+
// 2. Drop all tables
|
|
158
|
+
// 3. Create all tables
|
|
159
|
+
// 4. Introspect database schema
|
|
160
|
+
// 5. Generate entity models
|
|
161
|
+
// 6. (For Prisma: Generate schema file + Generate client + Reconnect)
|
|
162
|
+
// 7. Run the actual test
|
|
163
|
+
// 8. Disconnect
|
|
164
|
+
// Result: Tests take FOREVER!!!
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Why this is catastrophically wrong:**
|
|
170
|
+
- Creates/drops tables hundreds of times
|
|
171
|
+
- Reconnects to database hundreds of times
|
|
172
|
+
- For Prisma: Generates schema + client + reconnects hundreds of times
|
|
173
|
+
- Makes tests 100x slower
|
|
174
|
+
- Creates terrible indentation in test output
|
|
175
|
+
- Pollutes logs with connection messages
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## DataServer Architecture
|
|
180
|
+
|
|
181
|
+
### Flow: Connection → Tables → Entity Generation → Repositories
|
|
182
|
+
|
|
183
|
+
All three drivers follow this pattern:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
1. new DataServer({config, rawEntities})
|
|
187
|
+
↓
|
|
188
|
+
2. await dataServer.connect()
|
|
189
|
+
- Establishes database connection
|
|
190
|
+
- Sets this.initialized = Date.now()
|
|
191
|
+
↓
|
|
192
|
+
3. await executeRawSQL(dataServer, schemaSql)
|
|
193
|
+
- Creates tables in database
|
|
194
|
+
- Tables must exist before next step!
|
|
195
|
+
↓
|
|
196
|
+
4. await dataServer.generateEntities()
|
|
197
|
+
- Introspects database schema
|
|
198
|
+
- Creates driver instances (repositories)
|
|
199
|
+
- Registers entities in EntityManager
|
|
200
|
+
↓
|
|
201
|
+
5. dataServer.getEntity('entityName')
|
|
202
|
+
- Returns repository with all 31 BaseDriver methods
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### ObjectionJS DataServer
|
|
206
|
+
|
|
207
|
+
**File:** `lib/objection-js/objection-js-data-server.js`
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
// Step 1: Connect (lines 26-48)
|
|
211
|
+
async connect() {
|
|
212
|
+
// Creates Knex instance
|
|
213
|
+
this.knex = Knex({
|
|
214
|
+
client: this.client,
|
|
215
|
+
connection: this.config
|
|
216
|
+
});
|
|
217
|
+
// Binds Knex to Objection Model globally
|
|
218
|
+
Model.knex(this.knex);
|
|
219
|
+
this.initialized = Date.now();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Step 2: Generate entities (lines 59-76)
|
|
223
|
+
generateEntities() {
|
|
224
|
+
for(let i of Object.keys(this.rawEntities)){
|
|
225
|
+
let rawEntity = this.rawEntities[i];
|
|
226
|
+
// Creates ObjectionJsDriver for each entity
|
|
227
|
+
this.entities[i] = new ObjectionJsDriver({
|
|
228
|
+
rawModel: rawEntity,
|
|
229
|
+
id: i,
|
|
230
|
+
name: i,
|
|
231
|
+
config: this.config,
|
|
232
|
+
knex: this.knex
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
this.entityManager.setEntities(this.entities);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Step 3: Fetch database schema (lines 89-95)
|
|
239
|
+
async fetchEntitiesFromDatabase() {
|
|
240
|
+
return await MySQLTablesProvider.fetchTables(this);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Key Points:**
|
|
245
|
+
- ✅ No pre-generation required
|
|
246
|
+
- ✅ Models can be plain classes extending Objection Model
|
|
247
|
+
- ✅ Relation mappings defined in static relationMappings getter
|
|
248
|
+
- ✅ Fast and reliable
|
|
249
|
+
|
|
250
|
+
### MikroORM DataServer
|
|
251
|
+
|
|
252
|
+
**File:** `lib/mikro-orm/mikro-orm-data-server.js`
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
// Step 1: Connect (lines 31-55)
|
|
256
|
+
async connect() {
|
|
257
|
+
this.orm = await MikroORM.init({
|
|
258
|
+
driver: this.clientsMapped[(this.client || 'mongodb')],
|
|
259
|
+
dbName: this.config.database,
|
|
260
|
+
clientUrl: this.connectString,
|
|
261
|
+
entities: Object.values(this.rawEntities),
|
|
262
|
+
allowGlobalContext: true
|
|
263
|
+
});
|
|
264
|
+
this.initialized = Date.now();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Step 2: Generate entities (lines 66-90)
|
|
268
|
+
generateEntities() {
|
|
269
|
+
for(let i of Object.keys(this.rawEntities)){
|
|
270
|
+
let rawEntity = this.rawEntities[i];
|
|
271
|
+
// Creates MikroOrmDriver for each entity
|
|
272
|
+
this.entities[i] = new MikroOrmDriver({
|
|
273
|
+
rawModel: rawEntity,
|
|
274
|
+
id: i,
|
|
275
|
+
name: i,
|
|
276
|
+
config: this.orm.driver.connection.options,
|
|
277
|
+
orm: this.orm
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
this.entityManager.setEntities(this.entities);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Step 3: Fetch database schema (lines 111-137)
|
|
284
|
+
async fetchEntitiesFromDatabase() {
|
|
285
|
+
if('mysql' === this.client){
|
|
286
|
+
return await MySQLTablesProvider.fetchTables(this);
|
|
287
|
+
}
|
|
288
|
+
if('mongodb' === this.client){
|
|
289
|
+
// MongoDB specific introspection
|
|
290
|
+
let collections = await this.orm.em.getDriver()
|
|
291
|
+
.getConnection().db().listCollections().toArray();
|
|
292
|
+
// ... returns collection metadata
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Key Points:**
|
|
298
|
+
- ✅ No pre-generation required
|
|
299
|
+
- ✅ Supports MySQL and MongoDB
|
|
300
|
+
- ✅ Entity metadata via decorators
|
|
301
|
+
- ✅ Dynamic entity discovery
|
|
302
|
+
|
|
303
|
+
### Prisma DataServer
|
|
304
|
+
|
|
305
|
+
**File:** `lib/prisma/prisma-data-server.js`
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
// Step 1: Connect (lines 25-50)
|
|
309
|
+
async connect() {
|
|
310
|
+
// Checks if PrismaClient exists
|
|
311
|
+
if(!this.prisma && this.defaultClientIsGenerated()){
|
|
312
|
+
this.prisma = new PrismaClient({
|
|
313
|
+
datasources: {db: {url: this.connectString}}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if(!this.prisma){
|
|
317
|
+
Logger.error('Prisma client could not be initialized.');
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
await this.prisma.$connect();
|
|
321
|
+
this.initialized = Date.now();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Step 2: Generate entities (lines 66-99)
|
|
325
|
+
generateEntities() {
|
|
326
|
+
this.collectAllRelationMappings();
|
|
327
|
+
for(let i of Object.keys(this.rawEntities)){
|
|
328
|
+
let rawEntity = this.rawEntities[i];
|
|
329
|
+
let tableName = rawEntity.tableName;
|
|
330
|
+
// REQUIRES: Prisma model must exist in PrismaClient
|
|
331
|
+
let prismaModel = this.prisma[tableName];
|
|
332
|
+
if(!prismaModel){
|
|
333
|
+
Logger.critical('No matching Prisma model found for "'+tableName+'".');
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
// Creates PrismaDriver for each entity
|
|
337
|
+
this.entities[i] = new PrismaDriver({
|
|
338
|
+
rawModel: rawEntity,
|
|
339
|
+
id: i,
|
|
340
|
+
name: i,
|
|
341
|
+
config: this.config,
|
|
342
|
+
prisma: this.prisma,
|
|
343
|
+
model: prismaModel,
|
|
344
|
+
allRelationMappings: this.allRelationMappings
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
this.entityManager.setEntities(this.entities);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Step 3: Fetch database schema (lines 240-252)
|
|
351
|
+
async fetchEntitiesFromDatabase() {
|
|
352
|
+
return await MySQLTablesProvider.fetchTables(this);
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Key Points:**
|
|
357
|
+
- ❌ REQUIRES pre-generation: PrismaClient must exist BEFORE connect()
|
|
358
|
+
- ❌ Schema file must exist: `prisma/schema.prisma`
|
|
359
|
+
- ❌ Client must be generated: `npx prisma generate`
|
|
360
|
+
- ⚠️ Requires subprocess execution in tests
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Test Helpers Entity Generation
|
|
365
|
+
|
|
366
|
+
### Critical: Tables Must Exist First!
|
|
367
|
+
|
|
368
|
+
**File:** `tests/utils/test-helpers.js` (lines 389-416)
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
static async generateTestEntities(dataServer, driverName)
|
|
372
|
+
{
|
|
373
|
+
// Step 1: Introspect database (REQUIRES tables to exist!)
|
|
374
|
+
let models = await this.createModelsFromDatabase(dataServer, driverName);
|
|
375
|
+
if(!models || 0 === Object.keys(models).length){
|
|
376
|
+
throw new Error('Failed to create models from database for '+driverName);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Step 2: PRISMA ONLY - Generate schema + client
|
|
380
|
+
if('prisma' === driverName){
|
|
381
|
+
let config = this.getTestDbConfig();
|
|
382
|
+
config.client = 'mysql';
|
|
383
|
+
|
|
384
|
+
// Subprocess 1: Generate schema.prisma from database
|
|
385
|
+
if(!await this.generatePrismaSchema(config)){
|
|
386
|
+
throw new Error('Failed to generate Prisma schema');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Subprocess 2: Generate PrismaClient code
|
|
390
|
+
if(!await this.generatePrismaClient()){
|
|
391
|
+
throw new Error('Failed to generate Prisma client');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Step 3: Disconnect old client
|
|
395
|
+
await dataServer.disconnect();
|
|
396
|
+
|
|
397
|
+
// Step 4: Clear Node.js module cache
|
|
398
|
+
delete require.cache[require.resolve('@prisma/client')];
|
|
399
|
+
|
|
400
|
+
// Step 5: Reconnect with new client
|
|
401
|
+
if(!await dataServer.connect()){
|
|
402
|
+
throw new Error('Failed to reconnect Prisma client');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Step 3: Set rawEntities and generate driver instances
|
|
407
|
+
dataServer.rawEntities = models;
|
|
408
|
+
let result = await dataServer.generateEntities();
|
|
409
|
+
if(0 === Object.keys(dataServer.entities).length){
|
|
410
|
+
throw new Error('No entities were generated for '+driverName);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### createModelsFromDatabase Flow
|
|
418
|
+
|
|
419
|
+
**Lines 31-69:**
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
static async createModelsFromDatabase(dataServer, driverName)
|
|
423
|
+
{
|
|
424
|
+
// CRITICAL: This calls fetchEntitiesFromDatabase which reads actual tables
|
|
425
|
+
let tables = await dataServer.fetchEntitiesFromDatabase();
|
|
426
|
+
if(!tables){
|
|
427
|
+
return {};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
let models = {};
|
|
431
|
+
|
|
432
|
+
if('objection-js' === driverName){
|
|
433
|
+
for(let tableName of Object.keys(tables)){
|
|
434
|
+
let entityName = sc.camelCase(tableName.replace('test_', ''));
|
|
435
|
+
// Creates dynamic Model class extending Objection Model
|
|
436
|
+
class DynamicModel extends Model {
|
|
437
|
+
static get tableName(){
|
|
438
|
+
return tableName;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if(dataServer.knex){
|
|
442
|
+
DynamicModel.knex(dataServer.knex);
|
|
443
|
+
}
|
|
444
|
+
let modelKey = 'test'+sc.capitalizedCamelCase(entityName);
|
|
445
|
+
models[modelKey] = DynamicModel;
|
|
446
|
+
}
|
|
447
|
+
return models;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if('mikro-orm' === driverName){
|
|
451
|
+
for(let tableName of Object.keys(tables)){
|
|
452
|
+
let entityName = sc.camelCase(tableName.replace('test_', ''));
|
|
453
|
+
models['test'+sc.capitalizedCamelCase(entityName)] = {tableName: tableName};
|
|
454
|
+
}
|
|
455
|
+
return models;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if('prisma' === driverName){
|
|
459
|
+
for(let tableName of Object.keys(tables)){
|
|
460
|
+
let entityName = sc.camelCase(tableName.replace('test_', ''));
|
|
461
|
+
models['test'+sc.capitalizedCamelCase(entityName)] = {tableName: tableName};
|
|
462
|
+
}
|
|
463
|
+
return models;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {};
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Why tables must exist first:**
|
|
471
|
+
- `fetchEntitiesFromDatabase()` queries MySQL `information_schema`
|
|
472
|
+
- Returns actual table structures with columns, types, foreign keys
|
|
473
|
+
- Without tables, this returns empty/null
|
|
474
|
+
- Entity generation fails without table metadata
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Test Database Operations
|
|
479
|
+
|
|
480
|
+
### cleanDatabase() - DELETE Data Only
|
|
481
|
+
|
|
482
|
+
**File:** `tests/utils/test-helpers.js` (lines 183-198)
|
|
483
|
+
|
|
484
|
+
```javascript
|
|
485
|
+
static async cleanDatabase(dataServer)
|
|
486
|
+
{
|
|
487
|
+
try {
|
|
488
|
+
// Disable foreign key checks
|
|
489
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
|
|
490
|
+
|
|
491
|
+
// DELETE data (fast, keeps tables intact)
|
|
492
|
+
await dataServer.rawQuery('DELETE FROM test_reviews;');
|
|
493
|
+
await dataServer.rawQuery('DELETE FROM test_products;');
|
|
494
|
+
await dataServer.rawQuery('DELETE FROM test_categories;');
|
|
495
|
+
|
|
496
|
+
// Re-enable foreign key checks
|
|
497
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
|
|
498
|
+
return true;
|
|
499
|
+
} catch(error) {
|
|
500
|
+
try {
|
|
501
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
|
|
502
|
+
} catch(e) {}
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**Used in:** `beforeEach()` hook - runs before every test
|
|
509
|
+
**Purpose:** Clean slate for each test without recreating tables
|
|
510
|
+
**Speed:** Very fast (milliseconds)
|
|
511
|
+
|
|
512
|
+
### dropTestTables() - DROP Tables
|
|
513
|
+
|
|
514
|
+
**File:** `tests/utils/test-helpers.js` (lines 200-215)
|
|
515
|
+
|
|
516
|
+
```javascript
|
|
517
|
+
static async dropTestTables(dataServer)
|
|
518
|
+
{
|
|
519
|
+
try {
|
|
520
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
|
|
521
|
+
|
|
522
|
+
// DROP tables completely
|
|
523
|
+
await dataServer.rawQuery('DROP TABLE IF EXISTS test_reviews;');
|
|
524
|
+
await dataServer.rawQuery('DROP TABLE IF EXISTS test_products;');
|
|
525
|
+
await dataServer.rawQuery('DROP TABLE IF EXISTS test_categories;');
|
|
526
|
+
|
|
527
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
|
|
528
|
+
return true;
|
|
529
|
+
} catch(error) {
|
|
530
|
+
try {
|
|
531
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
|
|
532
|
+
} catch(e) {}
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Used in:** `after()` hook - runs once after all tests
|
|
539
|
+
**Purpose:** Complete cleanup, remove tables from database
|
|
540
|
+
**Speed:** Fast but slower than DELETE
|
|
541
|
+
|
|
542
|
+
### executeRawSQL() - Create Tables
|
|
543
|
+
|
|
544
|
+
**File:** `tests/utils/test-helpers.js` (lines 140-159)
|
|
545
|
+
|
|
546
|
+
```javascript
|
|
547
|
+
static async executeRawSQL(dataServer, sql)
|
|
548
|
+
{
|
|
549
|
+
try {
|
|
550
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
|
|
551
|
+
|
|
552
|
+
// Split SQL into statements
|
|
553
|
+
let statements = sql.split(';').filter(stmt => stmt.trim().length > 0);
|
|
554
|
+
|
|
555
|
+
// Execute each statement
|
|
556
|
+
for(let i = 0; i < statements.length; i++){
|
|
557
|
+
let statement = statements[i].trim();
|
|
558
|
+
if(!statement){
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
await dataServer.rawQuery(statement+';');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
|
|
565
|
+
return true;
|
|
566
|
+
} catch(error) {
|
|
567
|
+
try {
|
|
568
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
|
|
569
|
+
} catch(e) {}
|
|
570
|
+
throw error;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
**Used in:** `before()` hook - runs once before all tests
|
|
576
|
+
**Purpose:** Create all tables from SQL schema file
|
|
577
|
+
**Speed:** Slow (can take seconds), only runs once
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Prisma Special Handling
|
|
582
|
+
|
|
583
|
+
### Why Prisma is Different
|
|
584
|
+
|
|
585
|
+
**All other ORMs:**
|
|
586
|
+
- Models are code (classes/objects)
|
|
587
|
+
- Can be defined at runtime
|
|
588
|
+
- Database can change independently
|
|
589
|
+
|
|
590
|
+
**Prisma:**
|
|
591
|
+
- Models are code GENERATED from schema file
|
|
592
|
+
- Schema defines data model (not code)
|
|
593
|
+
- PrismaClient is generated code (not dynamic)
|
|
594
|
+
- Changes require regeneration
|
|
595
|
+
|
|
596
|
+
### Prisma Test Flow
|
|
597
|
+
|
|
598
|
+
```
|
|
599
|
+
1. Connect to database
|
|
600
|
+
↓
|
|
601
|
+
2. Create tables via SQL
|
|
602
|
+
↓
|
|
603
|
+
3. Introspect database schema
|
|
604
|
+
↓
|
|
605
|
+
4. Generate schema.prisma file ← SUBPROCESS (npx reldens-storage-prisma)
|
|
606
|
+
↓
|
|
607
|
+
5. Generate PrismaClient ← SUBPROCESS (npx prisma generate)
|
|
608
|
+
↓
|
|
609
|
+
6. Disconnect old client
|
|
610
|
+
↓
|
|
611
|
+
7. Clear require cache ← delete require.cache[...]
|
|
612
|
+
↓
|
|
613
|
+
8. Reconnect with new client
|
|
614
|
+
↓
|
|
615
|
+
9. Generate entities (now PrismaClient has models)
|
|
616
|
+
↓
|
|
617
|
+
10. Get repositories
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Prisma Generation Commands
|
|
621
|
+
|
|
622
|
+
**Generate Schema from Database:**
|
|
623
|
+
```bash
|
|
624
|
+
npx reldens-storage-prisma \
|
|
625
|
+
--host=localhost \
|
|
626
|
+
--port=3306 \
|
|
627
|
+
--user=test_user \
|
|
628
|
+
--password=test_password \
|
|
629
|
+
--database=reldens_storage_test \
|
|
630
|
+
--client=mysql
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**Generate Prisma Client:**
|
|
634
|
+
```bash
|
|
635
|
+
npx prisma generate --schema=prisma/schema.prisma
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**In Tests (lines 306-327):**
|
|
639
|
+
```javascript
|
|
640
|
+
static async generatePrismaSchema(config){
|
|
641
|
+
let cmd = 'npx reldens-storage-prisma'
|
|
642
|
+
+' --host='+config.host
|
|
643
|
+
+' --port='+config.port
|
|
644
|
+
+' --user='+config.user
|
|
645
|
+
+' --password='+config.password
|
|
646
|
+
+' --database='+config.database
|
|
647
|
+
+' --client='+config.client;
|
|
648
|
+
|
|
649
|
+
try {
|
|
650
|
+
let { stdout, stderr } = await execAsync(cmd);
|
|
651
|
+
return true;
|
|
652
|
+
} catch(error) {
|
|
653
|
+
Logger.critical('Failed to generate Prisma schema: '+error.message);
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
static async generatePrismaClient(){
|
|
659
|
+
let cmd = 'npx prisma generate --schema='+schemaPath;
|
|
660
|
+
try {
|
|
661
|
+
let { stdout, stderr } = await execAsync(cmd);
|
|
662
|
+
return true;
|
|
663
|
+
} catch(error) {
|
|
664
|
+
Logger.critical('Failed to generate Prisma client: '+error.message);
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## Test Execution Order
|
|
673
|
+
|
|
674
|
+
### Per Driver Test Suite
|
|
675
|
+
|
|
676
|
+
```
|
|
677
|
+
1. before() hook - RUNS ONCE
|
|
678
|
+
├─ setupDriver (connect to database)
|
|
679
|
+
├─ executeRawSQL (create tables)
|
|
680
|
+
├─ generateTestEntities (introspect + generate models)
|
|
681
|
+
└─ getEntity (get repository references)
|
|
682
|
+
|
|
683
|
+
2. Test Suite Execution
|
|
684
|
+
│
|
|
685
|
+
├─ describe('CREATE Operations')
|
|
686
|
+
│ ├─ beforeEach() → cleanDatabase (DELETE data)
|
|
687
|
+
│ ├─ it('should create single record')
|
|
688
|
+
│ ├─ beforeEach() → cleanDatabase (DELETE data)
|
|
689
|
+
│ ├─ it('should create with JSON field')
|
|
690
|
+
│ └─ ...
|
|
691
|
+
│
|
|
692
|
+
├─ describe('UPDATE Operations')
|
|
693
|
+
│ ├─ beforeEach() → cleanDatabase (DELETE data)
|
|
694
|
+
│ ├─ it('should update by ID')
|
|
695
|
+
│ └─ ...
|
|
696
|
+
│
|
|
697
|
+
├─ describe('QUERY Operations')
|
|
698
|
+
│ ├─ beforeEach() → cleanDatabase (DELETE data)
|
|
699
|
+
│ ├─ it('should load all records')
|
|
700
|
+
│ └─ ...
|
|
701
|
+
│
|
|
702
|
+
└─ describe('DELETE Operations')
|
|
703
|
+
├─ beforeEach() → cleanDatabase (DELETE data)
|
|
704
|
+
├─ it('should delete by ID')
|
|
705
|
+
└─ ...
|
|
706
|
+
|
|
707
|
+
3. after() hook - RUNS ONCE
|
|
708
|
+
├─ dropTestTables (DROP all tables)
|
|
709
|
+
└─ teardownDriver (disconnect from database)
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### All Drivers Execution
|
|
713
|
+
|
|
714
|
+
```
|
|
715
|
+
Driver: objection-js
|
|
716
|
+
├─ before() → Connect, Create Tables, Generate Entities
|
|
717
|
+
├─ Run 147 tests (with beforeEach DELETE between each)
|
|
718
|
+
└─ after() → Drop Tables, Disconnect
|
|
719
|
+
|
|
720
|
+
Driver: mikro-orm
|
|
721
|
+
├─ before() → Connect, Create Tables, Generate Entities
|
|
722
|
+
├─ Run 147 tests (with beforeEach DELETE between each)
|
|
723
|
+
└─ after() → Drop Tables, Disconnect
|
|
724
|
+
|
|
725
|
+
Driver: prisma
|
|
726
|
+
├─ before() → Connect, Create Tables, Generate Schema, Generate Client, Reconnect, Generate Entities
|
|
727
|
+
├─ Run 147 tests (with beforeEach DELETE between each)
|
|
728
|
+
└─ after() → Drop Tables, Disconnect
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
**Total connections:** 3 (one per driver)
|
|
732
|
+
**Total table creations:** 3 (one per driver)
|
|
733
|
+
**Total entity generations:** 3 (one per driver)
|
|
734
|
+
**Total test executions:** 441 (147 tests × 3 drivers)
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
## Common Issues and Solutions
|
|
739
|
+
|
|
740
|
+
### Issue 1: Tests Taking Forever
|
|
741
|
+
|
|
742
|
+
**Symptoms:**
|
|
743
|
+
- Each test takes 1000+ milliseconds
|
|
744
|
+
- Connection logs appear between tests
|
|
745
|
+
- Test output has bad indentation
|
|
746
|
+
|
|
747
|
+
**Cause:**
|
|
748
|
+
- Using `beforeEach()` for setup instead of `before()`
|
|
749
|
+
- Reconnecting to database before every test
|
|
750
|
+
- Recreating tables before every test
|
|
751
|
+
- Regenerating entities before every test
|
|
752
|
+
|
|
753
|
+
**Solution:**
|
|
754
|
+
- Move connection/tables/entities to `before()` hook
|
|
755
|
+
- Use `beforeEach()` only for data cleanup (DELETE)
|
|
756
|
+
- Use `after()` for final teardown (DROP tables, disconnect)
|
|
757
|
+
|
|
758
|
+
### Issue 2: Tests "Cancelled" Instead of Failed
|
|
759
|
+
|
|
760
|
+
**Symptoms:**
|
|
761
|
+
- Test output shows: `ℹ tests 147, ℹ suites 0, ℹ pass 0, ℹ fail 0, ℹ cancelled 147`
|
|
762
|
+
- No error messages shown
|
|
763
|
+
|
|
764
|
+
**Cause:**
|
|
765
|
+
- `before()` hook fails silently
|
|
766
|
+
- Helper methods return `false` instead of throwing errors
|
|
767
|
+
- Node.js test runner marks all tests as "cancelled" when `before()` fails
|
|
768
|
+
|
|
769
|
+
**Solution:**
|
|
770
|
+
- Make all helper methods throw errors instead of returning false
|
|
771
|
+
- Add explicit error checking: `if(!result){ throw new Error(...) }`
|
|
772
|
+
- Use try/catch in `before()` hook to log errors before re-throwing
|
|
773
|
+
|
|
774
|
+
### Issue 3: Tables Not Found During Entity Generation
|
|
775
|
+
|
|
776
|
+
**Symptoms:**
|
|
777
|
+
- `fetchEntitiesFromDatabase()` returns empty/null
|
|
778
|
+
- `createModelsFromDatabase()` returns empty object
|
|
779
|
+
- Entity generation fails with "No entities generated"
|
|
780
|
+
|
|
781
|
+
**Cause:**
|
|
782
|
+
- Calling `generateTestEntities()` BEFORE creating tables
|
|
783
|
+
- Tables must exist before introspection
|
|
784
|
+
|
|
785
|
+
**Solution:**
|
|
786
|
+
- Ensure execution order: connect → create tables → generate entities
|
|
787
|
+
- Never call `generateTestEntities()` before `executeRawSQL()`
|
|
788
|
+
|
|
789
|
+
### Issue 4: Prisma Client Not Found
|
|
790
|
+
|
|
791
|
+
**Symptoms:**
|
|
792
|
+
- `connect()` fails with "Prisma client could not be initialized"
|
|
793
|
+
- Error: "Cannot find module '@prisma/client'"
|
|
794
|
+
|
|
795
|
+
**Cause:**
|
|
796
|
+
- PrismaClient not generated
|
|
797
|
+
- Schema file missing
|
|
798
|
+
- Generated client cleared but not regenerated
|
|
799
|
+
|
|
800
|
+
**Solution:**
|
|
801
|
+
- Run `npx reldens-storage-prisma` to generate schema
|
|
802
|
+
- Run `npx prisma generate` to generate client
|
|
803
|
+
- Ensure `generateTestEntities()` handles full Prisma flow
|
|
804
|
+
- Clear cache and reconnect after generation
|
|
805
|
+
|
|
806
|
+
### Issue 5: Foreign Key Constraint Violations
|
|
807
|
+
|
|
808
|
+
**Symptoms:**
|
|
809
|
+
- DELETE fails with "Cannot delete or update a parent row"
|
|
810
|
+
- DROP TABLE fails with foreign key references
|
|
811
|
+
|
|
812
|
+
**Cause:**
|
|
813
|
+
- MySQL enforces foreign key constraints
|
|
814
|
+
- Can't delete parent record with child records
|
|
815
|
+
- Can't drop table referenced by other tables
|
|
816
|
+
|
|
817
|
+
**Solution:**
|
|
818
|
+
- Always wrap DELETE/DROP operations:
|
|
819
|
+
```javascript
|
|
820
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
|
|
821
|
+
// ... DELETE or DROP operations
|
|
822
|
+
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
|
|
823
|
+
```
|
|
824
|
+
- Delete in correct order (children before parents)
|
|
825
|
+
- Drop in correct order (children before parents)
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
## Summary: The Complete Flow
|
|
830
|
+
|
|
831
|
+
```
|
|
832
|
+
SESSION START
|
|
833
|
+
↓
|
|
834
|
+
FOR EACH DRIVER (objection-js, mikro-orm, prisma):
|
|
835
|
+
↓
|
|
836
|
+
before() hook - RUNS ONCE:
|
|
837
|
+
1. Connect to database
|
|
838
|
+
├─ ObjectionJS: Create Knex instance
|
|
839
|
+
├─ MikroORM: Initialize MikroORM
|
|
840
|
+
└─ Prisma: Create PrismaClient (if generated)
|
|
841
|
+
|
|
842
|
+
2. Create tables from SQL file
|
|
843
|
+
├─ SET FOREIGN_KEY_CHECKS=0
|
|
844
|
+
├─ DROP TABLE IF EXISTS (in order)
|
|
845
|
+
├─ CREATE TABLE (with constraints)
|
|
846
|
+
└─ SET FOREIGN_KEY_CHECKS=1
|
|
847
|
+
|
|
848
|
+
3. Generate entities from database
|
|
849
|
+
├─ Introspect database schema
|
|
850
|
+
├─ Create model classes/objects
|
|
851
|
+
├─ [Prisma only]: Generate schema → Generate client → Reconnect
|
|
852
|
+
├─ Call dataServer.generateEntities()
|
|
853
|
+
└─ Get repository references
|
|
854
|
+
|
|
855
|
+
FOR EACH TEST:
|
|
856
|
+
beforeEach() hook:
|
|
857
|
+
├─ SET FOREIGN_KEY_CHECKS=0
|
|
858
|
+
├─ DELETE FROM test_reviews
|
|
859
|
+
├─ DELETE FROM test_products
|
|
860
|
+
├─ DELETE FROM test_categories
|
|
861
|
+
└─ SET FOREIGN_KEY_CHECKS=1
|
|
862
|
+
|
|
863
|
+
Run test:
|
|
864
|
+
├─ Create test data
|
|
865
|
+
├─ Perform operations
|
|
866
|
+
├─ Assert results
|
|
867
|
+
└─ (Data will be cleaned in next beforeEach)
|
|
868
|
+
|
|
869
|
+
after() hook - RUNS ONCE:
|
|
870
|
+
1. Drop all tables
|
|
871
|
+
├─ SET FOREIGN_KEY_CHECKS=0
|
|
872
|
+
├─ DROP TABLE IF EXISTS test_reviews
|
|
873
|
+
├─ DROP TABLE IF EXISTS test_products
|
|
874
|
+
├─ DROP TABLE IF EXISTS test_categories
|
|
875
|
+
└─ SET FOREIGN_KEY_CHECKS=1
|
|
876
|
+
|
|
877
|
+
2. Disconnect from database
|
|
878
|
+
├─ ObjectionJS: await knex.destroy()
|
|
879
|
+
├─ MikroORM: await orm.close()
|
|
880
|
+
└─ Prisma: await prisma.$disconnect()
|
|
881
|
+
|
|
882
|
+
SESSION END
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
---
|
|
886
|
+
|
|
887
|
+
## Files Modified to Fix Issues
|
|
888
|
+
|
|
889
|
+
### Integration Test Files (All Fixed)
|
|
890
|
+
|
|
891
|
+
**test-cross-driver-compatibility.js:**
|
|
892
|
+
- ✅ Changed `beforeEach` → `before` for setup
|
|
893
|
+
- ✅ Added `beforeEach` for data cleanup only
|
|
894
|
+
- ✅ Changed `afterEach` → `after` for teardown
|
|
895
|
+
- ✅ Added error checking for entity generation
|
|
896
|
+
|
|
897
|
+
**test-nested-filters.js:**
|
|
898
|
+
- ✅ Changed `beforeEach` → `before` for setup
|
|
899
|
+
- ✅ Added `beforeEach` for data cleanup only
|
|
900
|
+
- ✅ Changed `afterEach` → `after` for teardown
|
|
901
|
+
- ✅ Added error checking for entity generation
|
|
902
|
+
|
|
903
|
+
**test-relations.js:**
|
|
904
|
+
- ✅ Changed `beforeEach` → `before` for setup
|
|
905
|
+
- ✅ Added `beforeEach` for data cleanup only
|
|
906
|
+
- ✅ Changed `afterEach` → `after` for teardown
|
|
907
|
+
- ✅ Added error checking for entity generation
|
|
908
|
+
|
|
909
|
+
### Test Helpers (All Fixed)
|
|
910
|
+
|
|
911
|
+
**test-helpers.js:**
|
|
912
|
+
- ✅ Removed ALL verbose logging (process.stderr.write statements)
|
|
913
|
+
- ✅ Changed `cleanDatabase()` to use DELETE instead of DROP
|
|
914
|
+
- ✅ Created `dropTestTables()` for final cleanup
|
|
915
|
+
- ✅ Changed `generateTestEntities()` to throw errors instead of returning false
|
|
916
|
+
- ✅ Removed logging from `createModelsFromDatabase()`
|
|
917
|
+
- ✅ Removed logging from `setupDriver()`
|
|
918
|
+
- ✅ Removed logging from `executeRawSQL()`
|
|
919
|
+
|
|
920
|
+
**test-runner.js:**
|
|
921
|
+
- ✅ Added Logger import: `const { Logger } = require('@reldens/utils');`
|
|
922
|
+
- ✅ Replaced `process.stderr.write()` with Logger methods (lines 24, 30, 41, 45, 46)
|
|
923
|
+
- ✅ Uses `Logger.info()` for test progress (suite, group, passed tests)
|
|
924
|
+
- ✅ Uses `Logger.error()` for test failures and error messages
|
|
925
|
+
- ✅ Follows Rule #29 from ai-coding-rules.md (no direct console/stderr output)
|
|
926
|
+
|
|
927
|
+
---
|
|
928
|
+
|
|
929
|
+
## Performance Comparison
|
|
930
|
+
|
|
931
|
+
### Before Fixes (Wrong Pattern)
|
|
932
|
+
|
|
933
|
+
```
|
|
934
|
+
Driver: objection-js
|
|
935
|
+
├─ Test 1: Connect (500ms) + Create Tables (1000ms) + Generate Entities (500ms) + Run (10ms) + Teardown (100ms) = 2110ms
|
|
936
|
+
├─ Test 2: Connect (500ms) + Create Tables (1000ms) + Generate Entities (500ms) + Run (10ms) + Teardown (100ms) = 2110ms
|
|
937
|
+
└─ ... × 147 tests = 310,170ms (5+ minutes per driver)
|
|
938
|
+
|
|
939
|
+
Total for 3 drivers: ~15+ minutes
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
### After Fixes (Correct Pattern)
|
|
943
|
+
|
|
944
|
+
```
|
|
945
|
+
Driver: objection-js
|
|
946
|
+
├─ before(): Connect (500ms) + Create Tables (1000ms) + Generate Entities (500ms) = 2000ms
|
|
947
|
+
├─ Test 1: DELETE (5ms) + Run (10ms) = 15ms
|
|
948
|
+
├─ Test 2: DELETE (5ms) + Run (10ms) = 15ms
|
|
949
|
+
├─ ... × 147 tests = 2205ms
|
|
950
|
+
└─ after(): Drop Tables (100ms) + Teardown (100ms) = 200ms
|
|
951
|
+
|
|
952
|
+
Total for 1 driver: ~2.4 seconds
|
|
953
|
+
Total for 3 drivers: ~7-10 seconds (includes Prisma subprocess)
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
**Speed improvement: 90x faster** 🚀
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
## Conclusion
|
|
961
|
+
|
|
962
|
+
**The test suite architecture is now correct:**
|
|
963
|
+
- ✅ Connects once per driver
|
|
964
|
+
- ✅ Creates tables once per driver
|
|
965
|
+
- ✅ Generates entities once per driver
|
|
966
|
+
- ✅ Cleans data between tests (fast DELETE)
|
|
967
|
+
- ✅ Disconnects once per driver
|
|
968
|
+
- ✅ Handles Prisma subprocess correctly
|
|
969
|
+
- ✅ Fast execution (seconds instead of minutes)
|
|
970
|
+
- ✅ Clean output (no connection spam)
|
|
971
|
+
- ✅ Proper error handling (throws instead of silent fails)
|
|
972
|
+
|
|
973
|
+
**DO NOT CHANGE THIS PATTERN** - It is the correct and only way to structure integration tests for this package.
|