@@ -63,8 +63,14 @@ vi.mock('../../../src/config', () => ({
6363} ) ) ;
6464
6565describe ( 'PostgreSQL - helper' , async ( ) => {
66- const { connect, query, resetConnection, getSessionStore, ensureSessionStoreReady } =
67- await import ( '../../../src/db/postgres/helper' ) ;
66+ const {
67+ connect,
68+ query,
69+ resetConnection,
70+ getSessionStore,
71+ ensureSessionStoreReady,
72+ withTransaction,
73+ } = await import ( '../../../src/db/postgres/helper' ) ;
6874
6975 beforeEach ( async ( ) => {
7076 vi . clearAllMocks ( ) ;
@@ -288,6 +294,47 @@ describe('PostgreSQL - helper', async () => {
288294 } ) ;
289295 } ) ;
290296
297+ describe ( 'withTransaction' , ( ) => {
298+ it ( 'wraps the callback in BEGIN/COMMIT and releases the client' , async ( ) => {
299+ getDatabaseMock . mockReturnValue ( {
300+ type : 'postgres' ,
301+ enabled : true ,
302+ connectionString : 'postgresql://localhost/x' ,
303+ } ) ;
304+
305+ const result = await withTransaction ( async ( client ) => {
306+ await client . query ( 'SELECT 1' ) ;
307+ return 'ok' ;
308+ } ) ;
309+
310+ expect ( result ) . toBe ( 'ok' ) ;
311+ const statements = mockClientQuery . mock . calls . map ( ( [ sql ] ) => sql ) ;
312+ expect ( statements [ 0 ] ) . toBe ( 'BEGIN' ) ;
313+ expect ( statements ) . toContain ( 'SELECT 1' ) ;
314+ expect ( statements [ statements . length - 1 ] ) . toBe ( 'COMMIT' ) ;
315+ expect ( mockClientRelease ) . toHaveBeenCalledTimes ( 1 ) ;
316+ } ) ;
317+
318+ it ( 'rolls back and rethrows when the callback fails' , async ( ) => {
319+ getDatabaseMock . mockReturnValue ( {
320+ type : 'postgres' ,
321+ enabled : true ,
322+ connectionString : 'postgresql://localhost/x' ,
323+ } ) ;
324+
325+ await expect (
326+ withTransaction ( async ( ) => {
327+ throw new Error ( 'boom' ) ;
328+ } ) ,
329+ ) . rejects . toThrow ( 'boom' ) ;
330+
331+ const statements = mockClientQuery . mock . calls . map ( ( [ sql ] ) => sql ) ;
332+ expect ( statements [ 0 ] ) . toBe ( 'BEGIN' ) ;
333+ expect ( statements [ statements . length - 1 ] ) . toBe ( 'ROLLBACK' ) ;
334+ expect ( mockClientRelease ) . toHaveBeenCalledTimes ( 1 ) ;
335+ } ) ;
336+ } ) ;
337+
291338 describe ( 'pool error handling' , ( ) => {
292339 it ( 'registers an idle-client error listener that logs without crashing' , async ( ) => {
293340 getDatabaseMock . mockReturnValue ( {
0 commit comments