@reldens/storage
Version:
886 lines (714 loc) • 28.9 kB
Markdown
# Test Suite Architecture - @reldens/storage
## Critical Patterns and Why They Exist
### Why Dynamic Model Creation Instead of Generated Models?
**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.
**Solution:** Create dynamic models in-memory without requiring the package:
```javascript
// ❌ WRONG - Would require @reldens/storage → loads Prisma → fails
const { TestCategoriesModel } = require('.test-entities/...');
// ✅ CORRECT - Create models dynamically without package dependency
const { Model } = require('objection');
class DynamicModel extends Model {
static get tableName(){ return tableName; }
}
```
### Prisma Client Loading Pattern
**Problem:** Cannot use `require('@prisma/client')` at top-level because the client doesn't exist until generated by tests.
**Solution:** Generate client first, then load from specific path using PrismaClientLoader:
```javascript
// 1. Generate Prisma client (subprocess)
await generatePrismaSchema(config);
await generatePrismaClient();
// 2. Load client from generated path (NOT '@prisma/client')
const { PrismaClientLoader } = require('../../lib/prisma/prisma-client-loader');
let prismaClient = PrismaClientLoader.load(process.cwd(), null, config);
// 3. Pass loaded client to DataServer
serverConfig.prismaClient = prismaClient;
let dataServer = new PrismaDataServer(serverConfig);
```
**This pattern is used in:**
- `reldens-cms` installer (`bin/reldens-cms.js:87-108`)
- `reldens` main application
- Test suite setup
**Key Points:**
- Always load Prisma client from explicit path, never default `@prisma/client`
- Generate client via subprocess before requiring PrismaDataServer
- Use `PrismaClientLoader.load()` to handle path resolution and connection config
- Pass client instance to DataServer via `prismaClient` prop
---
## Critical Test Execution Flow
### **THE GOLDEN RULE: Connect Once, Test Many**
**CRITICAL:** Database connections, table creation, and entity generation happen **ONCE per driver**, NOT per test.
```
Per Driver Suite:
1. Connect to database ← ONCE in before() hook
2. Create tables via SQL ← ONCE in before() hook
3. Generate entities ← ONCE in before() hook
4. Run all tests ← Many times
5. Cleanup and disconnect ← ONCE in after() hook
Per Test:
1. Delete data from tables ← ONLY this in beforeEach() hook
2. Run test ← Test execution
```
---
## Integration Test Lifecycle Hooks
### ✅ CORRECT Pattern (Current Implementation)
```javascript
describe('Driver: '+driverName, () => {
let dataServer;
let categoriesRepo;
let productsRepo;
let reviewsRepo;
let schemaPath = FileHandler.joinPaths(__dirname, '..', 'fixtures', 'sql', 'test-schema.sql');
// RUNS ONCE - Setup everything
before(async function(){
this.timeout(30000);
let rawEntities = {};
// Step 1: Connect to database
dataServer = await TestHelpers.setupDriver(driverName, rawEntities);
if(!dataServer){
throw new Error('Failed to setup driver: '+driverName);
}
// Step 2: Create tables via raw SQL
let schemaSql = FileHandler.readFile(schemaPath);
await TestHelpers.executeRawSQL(dataServer, schemaSql);
// Step 3: Generate entities (introspects database, creates models)
let entitiesGenerated = await TestHelpers.generateTestEntities(dataServer, driverName);
if(!entitiesGenerated){
throw new Error('Failed to generate test entities for '+driverName);
}
// Step 4: Get repository references
categoriesRepo = dataServer.getEntity('testCategories');
productsRepo = dataServer.getEntity('testProducts');
reviewsRepo = dataServer.getEntity('testReviews');
});
// RUNS BEFORE EACH TEST - Only delete data
beforeEach(async () => {
await TestHelpers.cleanDatabase(dataServer);
});
// RUNS ONCE - Final cleanup
after(async () => {
if(dataServer){
await TestHelpers.dropTestTables(dataServer);
await TestHelpers.teardownDriver(dataServer);
}
});
describe('CREATE Operations', () => {
it('should create single record', async () => {
// Test code here
});
});
});
```
### ❌ WRONG Pattern (What Was Causing Issues)
```javascript
describe('Driver: '+driverName, () => {
// ❌ WRONG - This reconnects to database before EVERY test
beforeEach(async () => {
dataServer = await TestHelpers.setupDriver(driverName, rawEntities);
let schemaSql = FileHandler.readFile(schemaPath);
await TestHelpers.executeRawSQL(dataServer, schemaSql);
await TestHelpers.generateTestEntities(dataServer, driverName);
categoriesRepo = dataServer.getEntity('testCategories');
});
// ❌ WRONG - This disconnects after EVERY test
afterEach(async () => {
await TestHelpers.teardownDriver(dataServer);
});
it('should create single record', async () => {
// This single test just did:
// 1. Connect to database
// 2. Drop all tables
// 3. Create all tables
// 4. Introspect database schema
// 5. Generate entity models
// 6. (For Prisma: Generate schema file + Generate client + Reconnect)
// 7. Run the actual test
// 8. Disconnect
// Result: Tests take FOREVER!!!
});
});
```
**Why this is catastrophically wrong:**
- Creates/drops tables hundreds of times
- Reconnects to database hundreds of times
- For Prisma: Generates schema + client + reconnects hundreds of times
- Makes tests 100x slower
- Creates terrible indentation in test output
- Pollutes logs with connection messages
---
## DataServer Architecture
### Flow: Connection → Tables → Entity Generation → Repositories
All three drivers follow this pattern:
```
1. new DataServer({config, rawEntities})
↓
2. await dataServer.connect()
- Establishes database connection
- Sets this.initialized = Date.now()
↓
3. await executeRawSQL(dataServer, schemaSql)
- Creates tables in database
- Tables must exist before next step!
↓
4. await dataServer.generateEntities()
- Introspects database schema
- Creates driver instances (repositories)
- Registers entities in EntityManager
↓
5. dataServer.getEntity('entityName')
- Returns repository with all 31 BaseDriver methods
```
### ObjectionJS DataServer
**File:** `lib/objection-js/objection-js-data-server.js`
```javascript
// Step 1: Connect (lines 26-48)
async connect() {
// Creates Knex instance
this.knex = Knex({
client: this.client,
connection: this.config
});
// Binds Knex to Objection Model globally
Model.knex(this.knex);
this.initialized = Date.now();
}
// Step 2: Generate entities (lines 59-76)
generateEntities() {
for(let i of Object.keys(this.rawEntities)){
let rawEntity = this.rawEntities[i];
// Creates ObjectionJsDriver for each entity
this.entities[i] = new ObjectionJsDriver({
rawModel: rawEntity,
id: i,
name: i,
config: this.config,
knex: this.knex
});
}
this.entityManager.setEntities(this.entities);
}
// Step 3: Fetch database schema (lines 89-95)
async fetchEntitiesFromDatabase() {
return await MySQLTablesProvider.fetchTables(this);
}
```
**Key Points:**
- ✅ No pre-generation required
- ✅ Models can be plain classes extending Objection Model
- ✅ Relation mappings defined in static relationMappings getter
- ✅ Fast and reliable
### MikroORM DataServer
**File:** `lib/mikro-orm/mikro-orm-data-server.js`
```javascript
// Step 1: Connect (lines 31-55)
async connect() {
this.orm = await MikroORM.init({
driver: this.clientsMapped[(this.client || 'mongodb')],
dbName: this.config.database,
clientUrl: this.connectString,
entities: Object.values(this.rawEntities),
allowGlobalContext: true
});
this.initialized = Date.now();
}
// Step 2: Generate entities (lines 66-90)
generateEntities() {
for(let i of Object.keys(this.rawEntities)){
let rawEntity = this.rawEntities[i];
// Creates MikroOrmDriver for each entity
this.entities[i] = new MikroOrmDriver({
rawModel: rawEntity,
id: i,
name: i,
config: this.orm.driver.connection.options,
orm: this.orm
});
}
this.entityManager.setEntities(this.entities);
}
// Step 3: Fetch database schema (lines 111-137)
async fetchEntitiesFromDatabase() {
if('mysql' === this.client){
return await MySQLTablesProvider.fetchTables(this);
}
if('mongodb' === this.client){
// MongoDB specific introspection
let collections = await this.orm.em.getDriver()
.getConnection().db().listCollections().toArray();
// ... returns collection metadata
}
}
```
**Key Points:**
- ✅ No pre-generation required
- ✅ Supports MySQL and MongoDB
- ✅ Entity metadata via decorators
- ✅ Dynamic entity discovery
### Prisma DataServer
**File:** `lib/prisma/prisma-data-server.js`
```javascript
// Step 1: Connect (lines 25-50)
async connect() {
// Checks if PrismaClient exists
if(!this.prisma && this.defaultClientIsGenerated()){
this.prisma = new PrismaClient({
datasources: {db: {url: this.connectString}}
});
}
if(!this.prisma){
Logger.error('Prisma client could not be initialized.');
return false;
}
await this.prisma.$connect();
this.initialized = Date.now();
}
// Step 2: Generate entities (lines 66-99)
generateEntities() {
this.collectAllRelationMappings();
for(let i of Object.keys(this.rawEntities)){
let rawEntity = this.rawEntities[i];
let tableName = rawEntity.tableName;
// REQUIRES: Prisma model must exist in PrismaClient
let prismaModel = this.prisma[tableName];
if(!prismaModel){
Logger.critical('No matching Prisma model found for "'+tableName+'".');
continue;
}
// Creates PrismaDriver for each entity
this.entities[i] = new PrismaDriver({
rawModel: rawEntity,
id: i,
name: i,
config: this.config,
prisma: this.prisma,
model: prismaModel,
allRelationMappings: this.allRelationMappings
});
}
this.entityManager.setEntities(this.entities);
}
// Step 3: Fetch database schema (lines 240-252)
async fetchEntitiesFromDatabase() {
return await MySQLTablesProvider.fetchTables(this);
}
```
**Key Points:**
- ❌ REQUIRES pre-generation: PrismaClient must exist BEFORE connect()
- ❌ Schema file must exist: `prisma/schema.prisma`
- ❌ Client must be generated: `npx prisma generate`
- ⚠️ Requires subprocess execution in tests
---
## Test Helpers Entity Generation
### Critical: Tables Must Exist First!
**File:** `tests/utils/test-helpers.js`
```javascript
static async generateTestEntities(dataServer, driverName)
{
// Prisma only: generate schema + client if not already present
if('prisma' === driverName){
let config = this.getTestDbConfig();
config.client = 'mysql';
let schemaPath = FileHandler.joinPaths(process.cwd(), 'prisma', 'schema.prisma');
if(!FileHandler.exists(schemaPath)){
if(!await this.generatePrismaSchema(config)){
throw new Error('Failed to generate Prisma schema');
}
if(!await this.generatePrismaClient()){
throw new Error('Failed to generate Prisma client');
}
await dataServer.disconnect();
delete require.cache[require.resolve(FileHandler.joinPaths(process.cwd(), 'prisma', 'client'))];
await dataServer.connect();
}
}
// Run EntitiesGenerator — introspects DB, writes entity + model files to generated-entities/
await this.runEntitiesGenerator(dataServer, driverName);
this.fixGeneratedRequirePaths();
this.compareGeneratedWithExpected(driverName, 'entities');
this.compareGeneratedWithExpected(driverName, 'models/'+driverName);
if('objection-js' === driverName){
this.compareGeneratedWithExpected(driverName, 'entities-config.js');
this.compareGeneratedWithExpected(driverName, 'entities-translations.js');
}
// Load generated registered-models file and populate entity manager
await this.loadGeneratedEntities(dataServer, driverName);
return true;
}
```
### Entity Generation Flow
`runEntitiesGenerator()` creates an `EntitiesGenerator` instance pointed at the connected `dataServer`, calls `generator.generate()`, which internally calls `dataServer.fetchEntitiesFromDatabase()` to read real table metadata from `information_schema`. This is why tables must exist before calling `generateTestEntities()` — without tables, introspection returns empty and generation fails.
After generation, `loadGeneratedEntities()` loads the `generated-entities/models/[driver]/registered-models-[driver].js` file, sets `dataServer.rawEntities`, and calls `dataServer.generateEntities()` to register all driver instances in the entity manager.
**Why tables must exist first:**
- `fetchEntitiesFromDatabase()` queries MySQL `information_schema`
- Returns actual table structures with columns, types, foreign keys
- Without tables, this returns empty/null
- Entity generation fails without table metadata
---
## Test Database Operations
### cleanDatabase() - DELETE Data Only
**File:** `tests/utils/test-helpers.js` (lines 183-198)
```javascript
static async cleanDatabase(dataServer)
{
try {
// Disable foreign key checks
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
// DELETE data (fast, keeps tables intact)
await dataServer.rawQuery('DELETE FROM test_reviews;');
await dataServer.rawQuery('DELETE FROM test_products;');
await dataServer.rawQuery('DELETE FROM test_categories;');
// Re-enable foreign key checks
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
return true;
} catch(error) {
try {
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
} catch(e) {}
return false;
}
}
```
**Used in:** `beforeEach()` hook - runs before every test
**Purpose:** Clean slate for each test without recreating tables
**Speed:** Very fast (milliseconds)
### dropTestTables() - DROP Tables
**File:** `tests/utils/test-helpers.js` (lines 200-215)
```javascript
static async dropTestTables(dataServer)
{
try {
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
// DROP tables completely
await dataServer.rawQuery('DROP TABLE IF EXISTS test_reviews;');
await dataServer.rawQuery('DROP TABLE IF EXISTS test_products;');
await dataServer.rawQuery('DROP TABLE IF EXISTS test_categories;');
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
return true;
} catch(error) {
try {
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
} catch(e) {}
return false;
}
}
```
**Used in:** `after()` hook - runs once after all tests
**Purpose:** Complete cleanup, remove tables from database
**Speed:** Fast but slower than DELETE
### executeRawSQL() - Create Tables
**File:** `tests/utils/test-helpers.js` (lines 140-159)
```javascript
static async executeRawSQL(dataServer, sql)
{
try {
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
// Split SQL into statements
let statements = sql.split(';').filter(stmt => stmt.trim().length > 0);
// Execute each statement
for(let i = 0; i < statements.length; i++){
let statement = statements[i].trim();
if(!statement){
continue;
}
await dataServer.rawQuery(statement+';');
}
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
return true;
} catch(error) {
try {
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
} catch(e) {}
throw error;
}
}
```
**Used in:** `before()` hook - runs once before all tests
**Purpose:** Create all tables from SQL schema file
**Speed:** Slow (can take seconds), only runs once
---
## Prisma Special Handling
### Why Prisma is Different
**All other ORMs:**
- Models are code (classes/objects)
- Can be defined at runtime
- Database can change independently
**Prisma:**
- Models are code GENERATED from schema file
- Schema defines data model (not code)
- PrismaClient is generated code (not dynamic)
- Changes require regeneration
### Prisma Test Flow
```
1. Connect to database
↓
2. Create tables via SQL
↓
3. Introspect database schema
↓
4. Generate schema.prisma file ← SUBPROCESS (npx reldens-storage-prisma)
↓
5. Generate PrismaClient ← SUBPROCESS (npx prisma generate)
↓
6. Disconnect old client
↓
7. Clear require cache ← delete require.cache[...]
↓
8. Reconnect with new client
↓
9. Generate entities (now PrismaClient has models)
↓
10. Get repositories
```
### Prisma Generation Commands
**Generate Schema from Database:**
```bash
npx reldens-storage-prisma \
--host=localhost \
--port=3306 \
--user=test_user \
--password=test_password \
--database=reldens_storage_test \
--client=mysql
```
**Generate Prisma Client:**
```bash
npx prisma generate --schema=prisma/schema.prisma
```
**In Tests (lines 306-327):**
```javascript
static async generatePrismaSchema(config){
let cmd = 'npx reldens-storage-prisma'
+' --host='+config.host
+' --port='+config.port
+' --user='+config.user
+' --password='+config.password
+' --database='+config.database
+' --client='+config.client;
try {
let { stdout, stderr } = await execAsync(cmd);
return true;
} catch(error) {
Logger.critical('Failed to generate Prisma schema: '+error.message);
return false;
}
}
static async generatePrismaClient(){
let cmd = 'npx prisma generate --schema='+schemaPath;
try {
let { stdout, stderr } = await execAsync(cmd);
return true;
} catch(error) {
Logger.critical('Failed to generate Prisma client: '+error.message);
return false;
}
}
```
---
## Test Execution Order
### Per Driver Test Suite
**1. Initialization — runs once per driver (`DriverRegistry.initialize()`):**
- `setupDriver()` — connect to database
- `executeRawSQL()` — create tables from SQL schema
- `generateTestEntities()` — introspect DB, run EntitiesGenerator, load entities
- `dataServer.getEntity()` — obtain repository references
**2. Test execution — per group method in each test class:**
Each test class (DriversTest, NestedFiltersTest, RelationsTest, RawQueriesTest) has group methods. Each group method begins with `await TestHelpers.cleanDatabase(this.dataServer)` to DELETE all rows, then runs its tests via `runner.test()`.
Example:
```javascript
async testCreateOperations() {
this.runner.group('CREATE Operations');
await TestHelpers.cleanDatabase(this.dataServer);
// insert fixture data, then call runner.test() for each assertion
}
```
**3. Teardown — runs once per driver (`DriverRegistry.cleanup()`):**
- `dropTestTables()` — DROP all test tables
- `teardownDriver()` — disconnect from database
### All Drivers Execution
**Driver: objection-js**
- Initialize: connect, create tables, generate entities
- Run all tests (cleanDatabase at start of each group)
- Teardown: drop tables, disconnect
**Driver: mikro-orm**
- Initialize: connect, create tables, generate entities
- Run all tests (cleanDatabase at start of each group)
- Teardown: drop tables, disconnect
**Driver: prisma**
- Initialize: connect, create tables, generate Prisma schema (subprocess), generate Prisma client (subprocess), reconnect, generate entities
- Run all tests (cleanDatabase at start of each group)
- Teardown: drop tables, disconnect
**Total connections:** 3 (one per driver)
**Total table creations:** 3 (one per driver)
**Total entity generations:** 3 (one per driver)
---
## Common Issues and Solutions
### Issue 1: Tests Taking Forever
**Symptoms:**
- Each test takes 1000+ milliseconds
- Connection logs appear between tests
- Test output has bad indentation
**Cause:**
- Using `beforeEach()` for setup instead of `before()`
- Reconnecting to database before every test
- Recreating tables before every test
- Regenerating entities before every test
**Solution:**
- Move connection/tables/entities to `before()` hook
- Use `beforeEach()` only for data cleanup (DELETE)
- Use `after()` for final teardown (DROP tables, disconnect)
### Issue 2: Tests "Cancelled" Instead of Failed
**Symptoms:**
- Test output shows: `ℹ tests 147, ℹ suites 0, ℹ pass 0, ℹ fail 0, ℹ cancelled 147`
- No error messages shown
**Cause:**
- `before()` hook fails silently
- Helper methods return `false` instead of throwing errors
- Node.js test runner marks all tests as "cancelled" when `before()` fails
**Solution:**
- Make all helper methods throw errors instead of returning false
- Add explicit error checking: `if(!result){ throw new Error(...) }`
- Use try/catch in `before()` hook to log errors before re-throwing
### Issue 3: Tables Not Found During Entity Generation
**Symptoms:**
- `fetchEntitiesFromDatabase()` returns empty/null
- `EntitiesGenerator.generate()` fails or produces no entities
- Entity generation fails with "No entities generated"
**Cause:**
- Calling `generateTestEntities()` BEFORE creating tables
- Tables must exist before introspection
**Solution:**
- Ensure execution order: connect → create tables → generate entities
- Never call `generateTestEntities()` before `executeRawSQL()`
### Issue 4: Prisma Client Not Found
**Symptoms:**
- `connect()` fails with "Prisma client could not be initialized"
- Error: "Cannot find module '@prisma/client'"
**Cause:**
- PrismaClient not generated
- Schema file missing
- Generated client cleared but not regenerated
**Solution:**
- Run `npx reldens-storage-prisma` to generate schema
- Run `npx prisma generate` to generate client
- Ensure `generateTestEntities()` handles full Prisma flow
- Clear cache and reconnect after generation
### Issue 5: Foreign Key Constraint Violations
**Symptoms:**
- DELETE fails with "Cannot delete or update a parent row"
- DROP TABLE fails with foreign key references
**Cause:**
- MySQL enforces foreign key constraints
- Can't delete parent record with child records
- Can't drop table referenced by other tables
**Solution:**
- Always wrap DELETE/DROP operations:
```javascript
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
// ... DELETE or DROP operations
await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
```
- Delete in correct order (children before parents)
- Drop in correct order (children before parents)
---
## Summary: The Complete Flow
**For each driver (objection-js, mikro-orm, prisma):**
**Initialization — once per driver:**
1. Connect to database
- ObjectionJS: create Knex instance
- MikroORM: initialize MikroORM
- Prisma: create PrismaClient (if already generated)
2. Create tables from SQL file
- SET FOREIGN_KEY_CHECKS=0
- DROP TABLE IF EXISTS (cleanup from previous run)
- CREATE TABLE (with all constraints)
- SET FOREIGN_KEY_CHECKS=1
3. Generate entities from database
- Run EntitiesGenerator (introspects DB, writes files to generated-entities/)
- Prisma only: generate schema.prisma (subprocess), generate PrismaClient (subprocess), reconnect
- Load registered-models file, call dataServer.generateEntities()
**Per test group — cleanDatabase() at start of each group method:**
- SET FOREIGN_KEY_CHECKS=0
- DELETE FROM test_reviews
- DELETE FROM test_products
- DELETE FROM test_categories
- SET FOREIGN_KEY_CHECKS=1
**Per test:** create test data, perform operations, assert results.
**Teardown — once per driver:**
1. Drop all tables
- SET FOREIGN_KEY_CHECKS=0
- DROP TABLE IF EXISTS test_reviews
- DROP TABLE IF EXISTS test_products
- DROP TABLE IF EXISTS test_categories
- SET FOREIGN_KEY_CHECKS=1
2. Disconnect from database
- ObjectionJS: `await knex.destroy()`
- MikroORM: `await orm.close()`
- Prisma: `await prisma.$disconnect()`
---
## Files Modified to Fix Issues
### Integration Test Files (All Fixed)
**test-drivers.js:**
- ✅ Changed `beforeEach` → `before` for setup
- ✅ Added `beforeEach` for data cleanup only
- ✅ Changed `afterEach` → `after` for teardown
- ✅ Added error checking for entity generation
**test-nested-filters.js:**
- ✅ Changed `beforeEach` → `before` for setup
- ✅ Added `beforeEach` for data cleanup only
- ✅ Changed `afterEach` → `after` for teardown
- ✅ Added error checking for entity generation
**test-relations.js:**
- ✅ Changed `beforeEach` → `before` for setup
- ✅ Added `beforeEach` for data cleanup only
- ✅ Changed `afterEach` → `after` for teardown
- ✅ Added error checking for entity generation
**test-raw-queries.js:**
- ✅ Added — covers rawQuery with single and multiple SQL statements
### Test Helpers (All Fixed)
**test-helpers.js:**
- ✅ Removed ALL verbose logging (process.stderr.write statements)
- ✅ Changed `cleanDatabase()` to use DELETE instead of DROP
- ✅ Created `dropTestTables()` for final cleanup
- ✅ Changed `generateTestEntities()` to throw errors instead of returning false
- ✅ Replaced `createModelsFromDatabase()` with `runEntitiesGenerator()` + `loadGeneratedEntities()`
- ✅ Removed logging from `setupDriver()`
- ✅ Removed logging from `executeRawSQL()`
**test-runner.js:**
- ✅ Added Logger import: `const { Logger } = require('@reldens/utils');`
- ✅ Replaced `process.stderr.write()` with Logger methods (lines 24, 30, 41, 45, 46)
- ✅ Uses `Logger.info()` for test progress (suite, group, passed tests)
- ✅ Uses `Logger.error()` for test failures and error messages
- ✅ Follows Rule #29 from ai-coding-rules.md (no direct console/stderr output)
---
## Performance Comparison
### Before Fixes (Wrong Pattern)
Each test reconnected, recreated tables, and regenerated entities independently:
- Connect: ~500ms
- Create tables: ~1000ms
- Generate entities: ~500ms
- Run test: ~10ms
- Teardown: ~100ms
- **Total per test: ~2110ms**
- Total for 100 tests, 3 drivers: ~10+ minutes
### After Fixes (Correct Pattern)
Setup and teardown happen once per driver. Only data cleanup runs between tests:
- Setup once: connect + create tables + generate entities = ~2 seconds
- Each test: DELETE data (~5ms) + run test (~10ms) = ~15ms
- Teardown once: drop tables + disconnect = ~200ms
- **Total for 100 tests, 1 driver: ~3.7 seconds**
- Total for 3 drivers: ~10-15 seconds (includes Prisma subprocess generation)
**Speed improvement: ~60-90x faster**
---
## Conclusion
**The test suite architecture is now correct:**
- ✅ Connects once per driver
- ✅ Creates tables once per driver
- ✅ Generates entities once per driver
- ✅ Cleans data between tests (fast DELETE)
- ✅ Disconnects once per driver
- ✅ Handles Prisma subprocess correctly
- ✅ Fast execution (seconds instead of minutes)
- ✅ Clean output (no connection spam)
- ✅ Proper error handling (throws instead of silent fails)
**DO NOT CHANGE THIS PATTERN** - It is the correct and only way to structure integration tests for this package.