
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
drizzle-orm-test
Advanced tools
Drop-in replacement for pgsql-test that enables Drizzle ORM to work seamlessly with context management and RLS testing
drizzle-orm-test is a Drizzle ORM-optimized version of pgsql-test designed to work work seamlessly with context management and RLS (Row-Level Security) testing. It provides instant, isolated PostgreSQL databases for testing with automatic transaction rollbacks, context switching, and clean seeding β configured for local-first development environment. It's also great for GitHub Actions and CI/CD testing.
Explore a full working example (including GitHub Actions CI/CD) in the drizzle-test-suite repo.
npm install drizzle-orm-test
.setContext().sql files, programmatic seeds, or even load fixturesJest, Mocha, etc.π Learn how to test with Drizzle ORM β
import { drizzle } from 'drizzle-orm/node-postgres';
import { getConnections, PgTestClient } from 'drizzle-orm-test';
import { pgTable, serial, text } from 'drizzle-orm/pg-core';
// Define your schema
const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
userId: text('user_id')
});
let db: PgTestClient;
let pg: PgTestClient;
let teardown: () => Promise<void>;
beforeAll(async () => {
({ db, pg, teardown } = await getConnections());
// Setup schema using pg (superuser)
await pg.query(`
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
user_id TEXT NOT NULL
);
GRANT ALL ON TABLE users TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO authenticated;
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY users_policy ON users
FOR ALL TO authenticated
USING (user_id = current_setting('jwt.claims.user_id', true));
`);
});
afterAll(async () => {
await teardown();
});
beforeEach(async () => {
await db.beforeEach();
});
afterEach(async () => {
await db.afterEach();
});
describe('Drizzle with RLS', () => {
it('should respect authentication context', async () => {
// Set context on db (standard pgsql-test API)
db.setContext({
role: 'authenticated',
'jwt.claims.user_id': '123'
});
// Use standard Drizzle pattern - no wrapper needed!
const drizzleDb = drizzle(db.client);
// All queries will include the context (SET LOCAL statements)
const result = await drizzleDb.select().from(users);
expect(result).toBeDefined();
});
});
import { drizzle } from 'drizzle-orm/node-postgres';
import { getConnections } from 'drizzle-orm-test';
import * as schema from './schema';
const { db, teardown } = await getConnections();
// Set context on db
db.setContext({
role: 'authenticated',
'jwt.claims.user_id': '123'
});
// Use standard Drizzle pattern with schema
const drizzleDb = drizzle(db.client, { schema });
// Now you can use relational queries
const usersWithPosts = await drizzleDb.query.users.findMany({
with: {
posts: true
}
});
Note: For seeding documentation, see the pgsql-test README directly. The seeding API is identical since
drizzle-orm-testuses the samegetConnections()function and seed adapters.
getConnections(cn?, seedAdapters?)Drop-in replacement for pgsql-test's getConnections that patches db.client.query() to automatically apply context before each query.
Parameters:
cn?: GetConnectionOpts - Connection options (same as pgsql-test)seedAdapters?: SeedAdapter[] - Optional seed adapters for test dataReturns: Promise<{ pg, db, admin, teardown, manager }>
pg: PgTestClient - Superuser client for schema setup (bypasses RLS)db: PgTestClient - App-level client with patched client.query() for RLS testingadmin: DbAdmin - CLI operations (createdb, dropdb, etc.)teardown: () => Promise<void> - Cleanup functionmanager: PgTestConnector - Connection pool managerPgTestClientRe-exported from pgsql-test for convenience. See pgsql-test documentation for full API.
Key Methods:
setContext(ctx: Record<string, string>) - Set context variables (role, JWT claims, etc.)auth(options?: AuthOptions) - Set authentication context (shorthand for common JWT claims)clearContext() - Clear all contextbeforeEach() - Start transaction for test isolationafterEach() - Rollback transactionpublish() - Commit data to make visible to other connectionsquery<T>(sql, values?) - Execute query with context appliedimport { drizzle } from 'drizzle-orm/node-postgres';
import { getConnections } from 'drizzle-orm-test';
import { eq } from 'drizzle-orm';
describe('RLS Policies', () => {
let db, pg, teardown;
beforeAll(async () => {
({ db, pg, teardown } = await getConnections());
// Setup schema with RLS using pg (superuser)
await pg.query(`
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
content TEXT NOT NULL
);
GRANT ALL ON TABLE posts TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE posts_id_seq TO authenticated;
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_posts ON posts
FOR ALL TO authenticated
USING (user_id = current_setting('jwt.claims.user_id', true));
-- Seed test data using pg (bypasses RLS)
INSERT INTO posts (user_id, content)
VALUES ('1', 'User 1 Post'), ('2', 'User 2 Post');
`);
});
afterAll(() => teardown());
describe('as user 1', () => {
beforeEach(async () => {
await db.beforeEach();
db.setContext({
role: 'authenticated',
'jwt.claims.user_id': '1'
});
});
afterEach(() => db.afterEach());
it('can only see own posts', async () => {
const drizzleDb = drizzle(db.client);
// Query with Drizzle - should only see user 1's posts
const posts = await drizzleDb.select().from(postsTable);
expect(posts).toHaveLength(1);
expect(posts[0].content).toBe('User 1 Post');
});
it('can insert own posts', async () => {
const drizzleDb = drizzle(db.client);
await drizzleDb.insert(postsTable).values({
userId: '1',
content: 'New Post'
});
const posts = await drizzleDb.select().from(postsTable);
expect(posts).toHaveLength(2);
});
});
describe('as user 2', () => {
beforeEach(async () => {
await db.beforeEach();
db.setContext({
role: 'authenticated',
'jwt.claims.user_id': '2'
});
});
afterEach(() => db.afterEach());
it('can only see own posts', async () => {
const drizzleDb = drizzle(db.client);
const posts = await drizzleDb.select().from(postsTable);
expect(posts).toHaveLength(1);
expect(posts[0].content).toBe('User 2 Post');
});
});
});
import { drizzle } from 'drizzle-orm/node-postgres';
import { getConnections } from 'drizzle-orm-test';
let db1, db2, teardown;
beforeAll(async () => {
const conn1 = await getConnections();
const conn2 = await getConnections();
db1 = conn1.db;
db2 = conn2.db;
teardown = async () => {
await conn1.teardown();
await conn2.teardown();
};
});
afterAll(() => teardown());
it('can test cross-connection visibility', async () => {
const drizzle1 = drizzle(db1.client);
const drizzle2 = drizzle(db2.client);
// Set context for both connections
db1.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
db2.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
// Insert in first connection
await drizzle1.insert(users).values({ name: 'Alice', userId: '1' });
// Not visible to second connection yet (transaction isolation)
let result = await drizzle2.select().from(users);
expect(result).toHaveLength(0);
// Publish to make visible
await db1.publish();
// Now visible to second connection
result = await drizzle2.select().from(users);
expect(result).toHaveLength(1);
});
it('can switch context between queries', async () => {
const drizzleDb = drizzle(db.client);
// Query as user 1
db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
let posts = await drizzleDb.select().from(postsTable);
expect(posts.every(p => p.userId === '1')).toBe(true);
// Switch to user 2
db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '2' });
posts = await drizzleDb.select().from(postsTable);
expect(posts.every(p => p.userId === '2')).toBe(true);
});
When using Drizzle ORM directly with pgsql-test, the context management (SET LOCAL statements for role and JWT claims) doesn't work because Drizzle calls the underlying pg.Client.query() directly, bypassing PgTestClient's ctxQuery() method that applies the context.
// This doesn't work with plain pgsql-test - context is ignored!
import { drizzle } from 'drizzle-orm/node-postgres';
import { getConnections } from 'pgsql-test';
const { db } = await getConnections();
db.setContext({ userId: '123' }); // This context is never applied!
const drizzleDb = drizzle(db.client); // Drizzle bypasses PgTestClient
const result = await drizzleDb.select().from(users); // No RLS context applied β
drizzle-orm-test is a drop-in replacement for pgsql-test that patches db.client.query() to automatically apply context before each query. This allows you to use the standard Drizzle pattern while maintaining full RLS support.
import { drizzle } from 'drizzle-orm/node-postgres';
import { getConnections, PgTestClient } from 'drizzle-orm-test';
const { db } = await getConnections();
db.setContext({ userId: '123' }); // Context is applied!
const drizzleDb = drizzle(db.client); // Standard Drizzle pattern
const result = await drizzleDb.select().from(users); // RLS context applied β
drizzle-orm-test patches db.client.query() to automatically execute SET LOCAL statements before each query. This ensures that:
SET LOCAL before every Drizzle querycurrent_setting('jwt.claims.*')beforeEach/afterEachdrizzle(db.client) directly without wrappersThe implementation avoids infinite recursion by directly executing the context statements using the original query method, rather than calling db.ctxQuery() which would create a circular call.
If you're already using pgsql-test, migration is simple:
// Before (pgsql-test)
import { getConnections, PgTestClient } from 'pgsql-test';
// After (drizzle-orm-test)
import { getConnections, PgTestClient } from 'drizzle-orm-test';
// Everything else stays the same!
// Now you can also use Drizzle ORM with full RLS support
The drizzle-orm-test/utils module provides utilities for sanitizing query results for snapshot testing. These helpers replace dynamic values (IDs, UUIDs, dates, hashes) with stable placeholders, making snapshots deterministic.
import { snapshot } from 'drizzle-orm-test/utils';
const drizzleDb = drizzle(db.client);
const result = await drizzleDb.select().from(users);
expect(snapshot(result)).toMatchSnapshot();
See pgsql-test Snapshot Utilities for the full API reference.
π Quickstart: Getting Up and Running Get started with modular databases in minutes. Install prerequisites and deploy your first module.
π¦ Modular PostgreSQL Development with Database Packages Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.
βοΈ Authoring Database Changes Master the workflow for adding, organizing, and managing database changes with pgpm.
π§ͺ End-to-End PostgreSQL Testing with TypeScript Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.
β‘ Supabase Testing Use TypeScript-first tools to test Supabase projects with realistic RLS, policies, and auth contexts.
π§ Drizzle ORM Testing Run full-stack tests with Drizzle ORM, including database setup, teardown, and RLS enforcement.
π§ Troubleshooting Common issues and solutions for pgpm, PostgreSQL, and testing.
SET LOCAL) into queriesβideal for setting role, jwt.claims, and other session settings.libpg_query, converting SQL into parse trees.π Built by the Constructive team β creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on GitHub.
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
FAQs
Drop-in replacement for pgsql-test that enables Drizzle ORM to work seamlessly with context management and RLS testing
The npm package drizzle-orm-test receives a total of 797 weekly downloads. As such, drizzle-orm-test popularity was classified as not popular.
We found that drizzle-orm-test demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.