@@ -13,6 +13,7 @@ import {
13
13
InputSyntaxError ,
14
14
InvalidInputError ,
15
15
NotNullIntegrityConstraintViolationError ,
16
+ parseDsn ,
16
17
sql ,
17
18
StatementCancelledError ,
18
19
StatementTimeoutError ,
@@ -24,6 +25,7 @@ import {
24
25
import { type TestContextType } from './createTestRunner' ;
25
26
import { type DriverFactory } from '@slonik/driver' ;
26
27
import { type TestFn } from 'ava' ;
28
+ import { randomUUID } from 'node:crypto' ;
27
29
import { setTimeout as delay } from 'node:timers/promises' ;
28
30
import * as sinon from 'sinon' ;
29
31
import { z } from 'zod' ;
@@ -2110,6 +2112,81 @@ export const createIntegrationTests = (
2110
2112
await pool . end ( ) ;
2111
2113
} ) ;
2112
2114
2115
+ test ( 'connections failing auth are not added to the connection pool' , async ( t ) => {
2116
+ const superPool = await createPool ( t . context . dsn , {
2117
+ driverFactory,
2118
+ maximumPoolSize : 1 ,
2119
+ } ) ;
2120
+
2121
+ const connection = parseDsn ( t . context . dsn ) ;
2122
+
2123
+ const testUser = `auth_change_test_${ randomUUID ( ) . split ( '-' ) [ 0 ] } ` ;
2124
+
2125
+ await superPool . query (
2126
+ sql . unsafe `
2127
+ CREATE ROLE ${ sql . identifier ( [ testUser ] ) }
2128
+ WITH LOGIN SUPERUSER
2129
+ PASSWORD 'auth_change_test'
2130
+ ` ,
2131
+ ) ;
2132
+
2133
+ // Connect as the new role
2134
+ const pool = await createPool (
2135
+ `postgres://${ testUser } :auth_change_test@${ connection . host } :${ connection . port } /${ connection . databaseName } ` ,
2136
+ {
2137
+ driverFactory,
2138
+ idleTimeout : 1_000 ,
2139
+ maximumPoolSize : 1 ,
2140
+ } ,
2141
+ ) ;
2142
+
2143
+ await pool . oneFirst ( sql . unsafe `
2144
+ SELECT pg_backend_pid();
2145
+ ` ) ;
2146
+
2147
+ // Change the password
2148
+ await superPool . query (
2149
+ sql . unsafe `
2150
+ ALTER ROLE ${ sql . identifier ( [ testUser ] ) }
2151
+ PASSWORD 'auth_change_test_changed'
2152
+ ` ,
2153
+ ) ;
2154
+
2155
+ // Wait for the idle timeout to expire
2156
+ await delay ( 1_000 ) ;
2157
+
2158
+ // Ensure that there are no longer active connections.
2159
+ t . like ( pool . state ( ) , {
2160
+ acquiredConnections : 0 ,
2161
+ idleConnections : 0 ,
2162
+ pendingDestroyConnections : 0 ,
2163
+ pendingReleaseConnections : 0 ,
2164
+ waitingClients : 0 ,
2165
+ } ) ;
2166
+
2167
+ const error = await t . throwsAsync (
2168
+ pool . oneFirst ( sql . unsafe `
2169
+ SELECT pg_backend_pid();
2170
+ ` ) ,
2171
+ ) ;
2172
+
2173
+ // @ts -expect-error TODO
2174
+ t . is ( error . cause . code , '28P01' ) ;
2175
+
2176
+ // Ensure that the connection was not added to the pool.
2177
+ t . like ( pool . state ( ) , {
2178
+ acquiredConnections : 0 ,
2179
+ idleConnections : 0 ,
2180
+ pendingDestroyConnections : 0 ,
2181
+ pendingReleaseConnections : 0 ,
2182
+ waitingClients : 0 ,
2183
+ } ) ;
2184
+
2185
+ await pool . end ( ) ;
2186
+
2187
+ await superPool . end ( ) ;
2188
+ } ) ;
2189
+
2113
2190
test ( 'retains a minimum number of connections in the pool' , async ( t ) => {
2114
2191
const pool = await createPool ( t . context . dsn , {
2115
2192
driverFactory,
0 commit comments