UNPKG

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
"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;