supabase-test
Version:
supabase-test offers isolated, role-aware, and rollback-friendly PostgreSQL environments for integration tests — giving developers realistic test coverage without external state pollution
155 lines (154 loc) • 5.08 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PgTestClient = void 0;
const pg_1 = require("pg");
const roles_1 = require("./roles");
class PgTestClient {
config;
client;
opts;
ctxStmts = '';
contextSettings = {};
_ended = false;
connectPromise = null;
constructor(config, opts = {}) {
this.opts = opts;
this.config = config;
this.client = new pg_1.Client({
host: this.config.host,
port: this.config.port,
database: this.config.database,
user: this.config.user,
password: this.config.password
});
if (!opts.deferConnect) {
this.connectPromise = this.client.connect();
if (opts.trackConnect)
opts.trackConnect(this.connectPromise);
}
}
async ensureConnected() {
if (this.connectPromise) {
try {
await this.connectPromise;
}
catch { }
}
}
async close() {
if (!this._ended) {
this._ended = true;
await this.ensureConnected();
this.client.end();
}
}
async begin() {
await this.client.query('BEGIN;');
}
async savepoint(name = 'lqlsavepoint') {
await this.client.query(`SAVEPOINT "${name}";`);
}
async rollback(name = 'lqlsavepoint') {
await this.client.query(`ROLLBACK TO SAVEPOINT "${name}";`);
}
async commit() {
await this.client.query('COMMIT;');
}
async beforeEach() {
await this.begin();
await this.savepoint();
}
async afterEach() {
await this.rollback();
await this.commit();
}
setContext(ctx) {
Object.assign(this.contextSettings, ctx);
this.ctxStmts = Object.entries(this.contextSettings)
.map(([key, val]) => val === null
? `SELECT set_config('${key}', NULL, true);`
: `SELECT set_config('${key}', '${val}', true);`)
.join('\n');
}
/**
* Set authentication context for the current session.
* Configures role and user ID using cascading defaults from options → opts.auth → RoleMapping.
*/
auth(options = {}) {
const role = options.role ?? this.opts.auth?.role ?? (0, roles_1.getRoleName)('authenticated', this.opts);
const userIdKey = options.userIdKey ?? this.opts.auth?.userIdKey ?? 'jwt.claims.user_id';
const userId = options.userId ?? this.opts.auth?.userId ?? null;
this.setContext({
role,
[userIdKey]: userId !== null ? String(userId) : null
});
}
/**
* Commit current transaction to make data visible to other connections, then start fresh transaction.
* Maintains test isolation by creating a savepoint and reapplying session context.
*/
async publish() {
await this.commit(); // make data visible to other sessions
await this.begin(); // fresh tx
await this.savepoint(); // keep rollback harness
await this.ctxQuery(); // reapply all setContext()
}
/**
* Clear all session context variables and reset to default anonymous role.
*/
clearContext() {
const defaultRole = (0, roles_1.getRoleName)('anonymous', this.opts);
const nulledSettings = {};
Object.keys(this.contextSettings).forEach(key => {
nulledSettings[key] = null;
});
nulledSettings.role = defaultRole;
this.ctxStmts = Object.entries(nulledSettings)
.map(([key, val]) => val === null
? `SELECT set_config('${key}', NULL, true);`
: `SELECT set_config('${key}', '${val}', true);`)
.join('\n');
this.contextSettings = { role: defaultRole };
}
async any(query, values) {
const result = await this.query(query, values);
return result.rows;
}
async one(query, values) {
const rows = await this.any(query, values);
if (rows.length !== 1) {
throw new Error('Expected exactly one result');
}
return rows[0];
}
async oneOrNone(query, values) {
const rows = await this.any(query, values);
return rows[0] || null;
}
async many(query, values) {
const rows = await this.any(query, values);
if (rows.length === 0)
throw new Error('Expected many rows, got none');
return rows;
}
async manyOrNone(query, values) {
return this.any(query, values);
}
async none(query, values) {
await this.query(query, values);
}
async result(query, values) {
return this.query(query, values);
}
async query(query, values) {
await this.ctxQuery();
const result = await this.client.query(query, values);
return result;
}
async ctxQuery() {
if (this.ctxStmts) {
await this.client.query(this.ctxStmts);
}
}
}
exports.PgTestClient = PgTestClient;