From 00456d87e94095fd5795e25926581c132a8a9218 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 9 May 2026 18:22:46 +0000 Subject: [PATCH 1/2] feat(graphile-test): add withTransaction to pgClient in test context In production, @dataplan/pg provides withTransaction on the pgClient. In tests, the raw pg Client lacks it, causing plugins that call pgClient.withTransaction() (e.g. graphile-presigned-url-plugin) to fail unless tests manually monkey-patch the method. This adds a savepoint-based withTransaction implementation to the client inside the withPgClient callback. Savepoints nest cleanly inside the test harness's outer transaction, matching production behavior. Eliminates the need for per-test monkey patches. --- graphile/graphile-test/src/context.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/graphile/graphile-test/src/context.ts b/graphile/graphile-test/src/context.ts index f2fb9ab6f1..eb11971465 100644 --- a/graphile/graphile-test/src/context.ts +++ b/graphile/graphile-test/src/context.ts @@ -270,8 +270,28 @@ export const runGraphQLInContext = async ({ _pgSettings: Record | null, callback: (client: Client) => T | Promise ): Promise => { - // Simply use the test client - it's already in a transaction - // The pgSettings have already been applied above via setContextOnClient + // Augment the client with withTransaction if it doesn't already have it. + // In production, @dataplan/pg provides this method. In tests the raw pg + // Client lacks it, so we implement it via savepoints (which nest cleanly + // inside the test harness's outer transaction). + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const client = pgClient as any; + if (typeof client.withTransaction !== 'function') { + client.withTransaction = async ( + cb: (txClient: Client) => unknown | Promise + ) => { + const sp = `wt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + await client.query(`SAVEPOINT ${sp}`); + try { + const result = await cb(client); + await client.query(`RELEASE SAVEPOINT ${sp}`); + return result; + } catch (err) { + await client.query(`ROLLBACK TO SAVEPOINT ${sp}`); + throw err; + } + }; + } return callback(pgClient); }; From 30fa33f1879f8e4b18260cf53e53d6de3a9897d7 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 10 May 2026 01:38:56 +0000 Subject: [PATCH 2/2] feat(graphile-test): add setUserContext utility for RLS testing Convenience wrapper around db.setContext() with standard constructive JWT claims (role: authenticated, jwt.claims.user_id, jwt.claims.database_id). Eliminates the need to copy-paste the same setContext pattern in every test file. --- graphile/graphile-test/src/index.ts | 1 + graphile/graphile-test/src/utils.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/graphile/graphile-test/src/index.ts b/graphile/graphile-test/src/index.ts index 34de46eb02..b3430d3fa1 100644 --- a/graphile/graphile-test/src/index.ts +++ b/graphile/graphile-test/src/index.ts @@ -21,4 +21,5 @@ export type { GraphQLTestContext, Variables, } from './types'; +export { setUserContext } from './utils'; export { seed, snapshot } from 'pgsql-test'; diff --git a/graphile/graphile-test/src/utils.ts b/graphile/graphile-test/src/utils.ts index acd0b0c21a..52ecd9d50f 100644 --- a/graphile/graphile-test/src/utils.ts +++ b/graphile/graphile-test/src/utils.ts @@ -1 +1,19 @@ +import type { PgTestClient } from 'pgsql-test/test-client'; + export * from 'pgsql-test/utils'; + +/** + * Set authenticated user context for RLS testing. + * Convenience wrapper around db.setContext() with standard constructive JWT claims. + */ +export function setUserContext( + db: PgTestClient, + userId: string, + databaseId: string, +): void { + db.setContext({ + role: 'authenticated', + 'jwt.claims.user_id': userId, + 'jwt.claims.database_id': databaseId, + }); +}