Files
gridpilot.gg/tests/integration/harness/database-manager.ts
2026-01-08 16:52:37 +01:00

199 lines
4.7 KiB
TypeScript

/**
* Database Manager for Integration Tests
* Handles database connections, migrations, seeding, and cleanup
*/
import { Pool, PoolClient, QueryResult } from 'pg';
import { setTimeout } from 'timers/promises';
export interface DatabaseConfig {
host: string;
port: number;
database: string;
user: string;
password: string;
}
export class DatabaseManager {
private pool: Pool;
private client: PoolClient | null = null;
constructor(config: DatabaseConfig) {
this.pool = new Pool({
host: config.host,
port: config.port,
database: config.database,
user: config.user,
password: config.password,
max: 1,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
});
}
/**
* Wait for database to be ready
*/
async waitForReady(timeout: number = 30000): Promise<void> {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
try {
const client = await this.pool.connect();
await client.query('SELECT 1');
client.release();
console.log('[DatabaseManager] ✓ Database is ready');
return;
} catch (error) {
await setTimeout(1000);
}
}
throw new Error('Database failed to become ready');
}
/**
* Get a client for transactions
*/
async getClient(): Promise<PoolClient> {
if (!this.client) {
this.client = await this.pool.connect();
}
return this.client;
}
/**
* Execute query with automatic client management
*/
async query(text: string, params?: any[]): Promise<QueryResult> {
const client = await this.getClient();
return client.query(text, params);
}
/**
* Begin transaction
*/
async begin(): Promise<void> {
const client = await this.getClient();
await client.query('BEGIN');
}
/**
* Commit transaction
*/
async commit(): Promise<void> {
if (this.client) {
await this.client.query('COMMIT');
}
}
/**
* Rollback transaction
*/
async rollback(): Promise<void> {
if (this.client) {
await this.client.query('ROLLBACK');
}
}
/**
* Truncate all tables (for cleanup between tests)
*/
async truncateAllTables(): Promise<void> {
const client = await this.getClient();
// Get all table names
const result = await client.query(`
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
AND tablename NOT LIKE 'pg_%'
AND tablename NOT LIKE 'sql_%'
`);
if (result.rows.length === 0) return;
// Disable triggers temporarily to allow truncation
await client.query('SET session_replication_role = replica');
const tableNames = result.rows.map(r => r.tablename).join(', ');
try {
await client.query(`TRUNCATE TABLE ${tableNames} CASCADE`);
console.log(`[DatabaseManager] ✓ Truncated tables: ${tableNames}`);
} finally {
await client.query('SET session_replication_role = DEFAULT');
}
}
/**
* Run database migrations
*/
async runMigrations(): Promise<void> {
// This would typically run TypeORM migrations
// For now, we'll assume the API handles this on startup
console.log('[DatabaseManager] Migrations handled by API startup');
}
/**
* Seed minimal test data
*/
async seedMinimalData(): Promise<void> {
const client = await this.getClient();
// Insert minimal required data for tests
// This will be extended based on test requirements
console.log('[DatabaseManager] ✓ Minimal test data seeded');
}
/**
* Check for constraint violations in recent operations
*/
async getRecentConstraintErrors(since: Date): Promise<string[]> {
const client = await this.getClient();
const result = await client.query(`
SELECT
sqlstate,
message,
detail,
constraint_name
FROM pg_last_error_log()
WHERE sqlstate IN ('23505', '23503', '23514')
AND log_time > $1
ORDER BY log_time DESC
`, [since]);
return result.rows.map(r => r.message);
}
/**
* Get table constraints
*/
async getTableConstraints(tableName: string): Promise<any[]> {
const client = await this.getClient();
const result = await client.query(`
SELECT
conname as constraint_name,
contype as constraint_type,
pg_get_constraintdef(oid) as definition
FROM pg_constraint
WHERE conrelid = $1::regclass
ORDER BY contype
`, [tableName]);
return result.rows;
}
/**
* Close connection pool
*/
async close(): Promise<void> {
if (this.client) {
this.client.release();
this.client = null;
}
await this.pool.end();
}
}