/**
 * Shared utilities for exercising Row-Level Security (RLS) in integration tests.
 *
 * The test pool connects as `postgres`, a superuser with BYPASSRLS, so any query
 * run as postgres bypasses RLS regardless of `FORCE ROW LEVEL SECURITY`. To make
 * Postgres actually evaluate the `tenant_isolation` policies (USING for reads,
 * WITH CHECK for writes) a test must run the queries-under-test as a non-superuser
 * role. These helpers create that role and confine the privilege drop to a
 * SAVEPOINT so the surrounding test transaction stays usable.
 *
 * This is the same boundary the tenant-bound providers rely on at runtime, so
 * future tenant-scoped providers can reuse it rather than re-deriving the
 * role-switch mechanics.
 */
const SCHEMA = 'accounter_schema';
/**
 * Unique role name per worker. `process.pid` alone is not enough: in Vitest's
 * thread pool, parallel workers share one process (and thus one pid), so a random
 * suffix is appended to avoid role-name collisions across concurrent test files.
 */
export const RLS_TEST_ROLE = `rls_test_user_${process.pid}_${Math.random().toString(36).slice(2, 10)}`;
/**
 * Idempotently create the per-process non-superuser RLS role and grant it schema
 * usage plus any requested table privileges.
 *
 * `CREATE ROLE` / `GRANT` are not meant to be rolled back per-test, so this runs
 * on its own connection outside any test transaction. Call from `beforeAll` and
 * pair with {@link dropRlsRole} in `afterAll`.
 */
export async function ensureRlsRole(pool, options = {}) {
    const client = await pool.connect();
    try {
        // CREATE ROLE is not transactional — guard against re-create across workers.
        await client.query(`DO $$ BEGIN
         IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${RLS_TEST_ROLE}') THEN
           CREATE ROLE ${RLS_TEST_ROLE} LOGIN PASSWORD 'unused';
         END IF;
       END $$`);
        await client.query(`GRANT USAGE ON SCHEMA ${SCHEMA} TO ${RLS_TEST_ROLE}`);
        for (const { table, privileges } of options.grants ?? []) {
            await client.query(`GRANT ${privileges} ON ${SCHEMA}.${table} TO ${RLS_TEST_ROLE}`);
        }
    }
    finally {
        client.release();
    }
}
/**
 * Drop the RLS role. Mirror of {@link ensureRlsRole}.
 *
 * A bare `DROP ROLE` would fail while the role still holds the schema/table grants
 * from ensureRlsRole — Postgres tracks them in pg_shdepend and refuses to drop a
 * role other objects depend on. So we first `DROP OWNED BY`, which revokes every
 * privilege granted to the role in the current database, then drop the role. The
 * whole thing is guarded on role existence so a failed setup (role never created)
 * surfaces the real error instead of a "role does not exist" during teardown.
 */
export async function dropRlsRole(pool) {
    const client = await pool.connect();
    try {
        await client.query(`DO $$ BEGIN
         IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${RLS_TEST_ROLE}') THEN
           EXECUTE 'DROP OWNED BY ${RLS_TEST_ROLE}';
           EXECUTE 'DROP ROLE ${RLS_TEST_ROLE}';
         END IF;
       END $$`);
    }
    finally {
        client.release();
    }
}
/**
 * Run `fn` with the connection's role dropped to the non-superuser RLS role, so
 * Postgres evaluates the RLS policies. The role switch is confined to a SAVEPOINT:
 * on success the role is reset and the savepoint released; on any error the
 * savepoint is rolled back (which also restores the original role) and the error
 * is rethrown for the caller to interpret (e.g. `42501` = WITH CHECK rejection).
 *
 * Set the relevant `app.*` session variables BEFORE calling this — as superuser,
 * before privileges are dropped.
 */
export async function runAsRlsRole(client, fn) {
    await client.query(`SAVEPOINT rls_role`);
    try {
        await client.query(`SET LOCAL ROLE ${RLS_TEST_ROLE}`);
        const result = await fn();
        await client.query(`RESET ROLE`);
        await client.query(`RELEASE SAVEPOINT rls_role`);
        return result;
    }
    catch (err) {
        await client.query(`ROLLBACK TO SAVEPOINT rls_role`);
        throw err;
    }
}
//# sourceMappingURL=rls-role.js.map