From df8938a16f8efdeddeaf937ad50a2643d8de36ba Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 9 May 2026 18:22:46 +0000 Subject: [PATCH] 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 f2fb9ab6f..eb1197146 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); };