pgsql-test
Version:
pgsql-test offers isolated, role-aware, and rollback-friendly PostgreSQL environments for integration tests — giving developers realistic test coverage without external state pollution
112 lines (111 loc) • 3.82 kB
JavaScript
import { Pool } from 'pg';
import { DbAdmin } from './admin';
import { getPgEnvOptions } from '@launchql/types';
import { PgTestClient } from './test-client';
import { Logger } from '@launchql/server-utils';
const log = new Logger('test-connector');
const SYS_EVENTS = ['SIGTERM'];
const end = (pool) => {
try {
if (pool.ended || pool.ending) {
log.warn('⚠️ pg pool already ended or ending');
return;
}
pool.end();
}
catch (err) {
log.error('❌ pg pool termination error:', err);
}
};
export class PgTestConnector {
static instance;
clients = new Set();
pgPools = new Map();
seenDbConfigs = new Map();
verbose = false;
constructor(verbose = false) {
this.verbose = verbose;
SYS_EVENTS.forEach((event) => {
process.on(event, () => {
log.info(`⏹ Received ${event}, closing all connections...`);
this.closeAll();
});
});
}
static getInstance(verbose = false) {
if (!PgTestConnector.instance) {
PgTestConnector.instance = new PgTestConnector(verbose);
}
return PgTestConnector.instance;
}
poolKey(config) {
return `${config.user}@${config.host}:${config.port}/${config.database}`;
}
dbKey(config) {
return `${config.host}:${config.port}/${config.database}`;
}
getPool(config) {
const key = this.poolKey(config);
if (!this.pgPools.has(key)) {
const pool = new Pool(config);
this.pgPools.set(key, pool);
log.info(`📘 Created new pg pool: ${key}`);
}
return this.pgPools.get(key);
}
getClient(config) {
const client = new PgTestClient(config);
this.clients.add(client);
const key = this.dbKey(config);
this.seenDbConfigs.set(key, config);
log.info(`🔌 New PgTestClient connected to ${config.database}`);
return client;
}
async closeAll() {
log.info('🧹 Closing all PgTestClients...');
await Promise.all(Array.from(this.clients).map(async (client) => {
try {
await client.close();
log.success(`✅ Closed client for ${client.config.database}`);
}
catch (err) {
log.error(`❌ Error closing PgTestClient for ${client.config.database}:`, err);
}
}));
this.clients.clear();
log.info('🧯 Disposing pg pools...');
for (const [key, pool] of this.pgPools.entries()) {
log.debug(`🧯 Disposing pg pool [${key}]`);
end(pool);
}
this.pgPools.clear();
log.info('🗑️ Dropping seen databases...');
await Promise.all(Array.from(this.seenDbConfigs.values()).map(async (config) => {
try {
const rootPg = getPgEnvOptions();
const admin = new DbAdmin({ ...config, user: rootPg.user, password: rootPg.password }, this.verbose);
admin.drop();
log.warn(`🧨 Dropped database: ${config.database}`);
}
catch (err) {
log.error(`❌ Failed to drop database ${config.database}:`, err);
}
}));
this.seenDbConfigs.clear();
log.success('✅ All PgTestClients closed, pools disposed, databases dropped.');
}
close() {
this.closeAll();
}
drop(config) {
const key = this.dbKey(config);
const admin = new DbAdmin(config, this.verbose);
admin.drop();
log.warn(`🧨 Dropped database: ${config.database}`);
this.seenDbConfigs.delete(key);
}
kill(client) {
client.close();
this.drop(client.config);
}
}