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
154 lines (153 loc) • 5.44 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PgTestConnector = void 0;
const logger_1 = require("@pgpmjs/logger");
const pg_1 = require("pg");
const pg_env_1 = require("pg-env");
const admin_1 = require("./admin");
const test_client_1 = require("./test-client");
const log = new logger_1.Logger('test-connector');
const SYS_EVENTS = ['SIGTERM'];
const end = async (pool) => {
try {
if (pool.ended || pool.ending) {
log.warn('⚠️ pg pool already ended or ending');
return;
}
await pool.end();
}
catch (err) {
log.error('❌ pg pool termination error:', err);
}
};
class PgTestConnector {
static instance;
clients = new Set();
pgPools = new Map();
seenDbConfigs = new Map();
pendingConnects = new Set();
config;
verbose = false;
shuttingDown = false;
constructor(config, verbose = false) {
this.verbose = verbose;
this.config = config;
SYS_EVENTS.forEach((event) => {
process.on(event, () => {
log.info(`⏹ Received ${event}, closing all connections...`);
this.closeAll();
});
});
}
static getInstance(config, verbose = false) {
if (!PgTestConnector.instance) {
PgTestConnector.instance = new PgTestConnector(config, verbose);
}
return PgTestConnector.instance;
}
poolKey(config) {
return `${config.user}@${config.host}:${config.port}/${config.database}`;
}
dbKey(config) {
return `${config.host}:${config.port}/${config.database}`;
}
beginTeardown() {
this.shuttingDown = true;
}
registerConnect(p) {
this.pendingConnects.add(p);
p.finally(() => this.pendingConnects.delete(p));
}
async awaitPendingConnects() {
const arr = Array.from(this.pendingConnects);
if (arr.length) {
await Promise.allSettled(arr);
}
}
getPool(config) {
const key = this.poolKey(config);
if (!this.pgPools.has(key)) {
const pool = new pg_1.Pool(config);
this.pgPools.set(key, pool);
log.info(`📘 Created new pg pool: ${key}`);
}
return this.pgPools.get(key);
}
getClient(config, opts = {}) {
if (this.shuttingDown) {
throw new Error('PgTestConnector is shutting down; no new clients allowed');
}
const client = new test_client_1.PgTestClient(config, {
trackConnect: (p) => this.registerConnect(p),
...opts
});
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(opts = {}) {
const { keepDb = false } = opts;
this.beginTeardown();
await this.awaitPendingConnects();
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...');
const poolTasks = [];
for (const [key, pool] of this.pgPools.entries()) {
log.debug(`🧯 Disposing pg pool [${key}]`);
poolTasks.push(end(pool));
}
this.pgPools.clear();
await Promise.allSettled(poolTasks);
if (keepDb) {
log.info('📦 Keeping databases (keepDb=true)...');
const dbNames = Array.from(this.seenDbConfigs.values()).map(c => c.database);
log.info(`📦 Preserved databases: ${dbNames.join(', ')}`);
}
else {
log.info('🗑️ Dropping seen databases...');
await Promise.all(Array.from(this.seenDbConfigs.values()).map(async (config) => {
try {
const rootPg = (0, pg_env_1.getPgEnvOptions)(this.config);
const admin = new admin_1.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();
const action = keepDb ? 'preserved' : 'dropped';
log.success(`✅ All PgTestClients closed, pools disposed, databases ${action}.`);
this.pendingConnects.clear();
this.shuttingDown = false;
}
close() {
this.closeAll();
}
drop(config) {
const key = this.dbKey(config);
const admin = new admin_1.DbAdmin(config, this.verbose);
admin.drop();
log.warn(`🧨 Dropped database: ${config.database}`);
this.seenDbConfigs.delete(key);
}
async kill(client) {
await client.close();
this.drop(client.config);
}
}
exports.PgTestConnector = PgTestConnector;