@@ -2,15 +2,16 @@ import { expect, test, describe } from 'vitest'
22import { app } from './utils'
33import { pgMeta } from '../lib/utils'
44
5- const TIMEOUT = ( Number ( process . env . PG_QUERY_TIMEOUT_SECS ) ?? 10 ) + 2
6- const STATEMENT_TIMEOUT = ( Number ( process . env . PG_QUERY_TIMEOUT_SECS ) ?? 10 ) + 1
5+ const PG_QUERY_TIMEOUT = Number ( process . env . PG_QUERY_TIMEOUT_SECS ) ?? 10
6+ const TIMEOUT = PG_QUERY_TIMEOUT + 2
7+ const STATEMENT_TIMEOUT = PG_QUERY_TIMEOUT + 1
8+ const CUSTOM_QUERY_TIMEOUT = 2
79
810describe ( 'test query timeout' , ( ) => {
911 test (
10- `query timeout after ${ TIMEOUT } s and connection cleanup` ,
12+ `pool timeout after ${ TIMEOUT } s with statementTimeoutSecs and connection cleanup` ,
1113 async ( ) => {
1214 const query = `SELECT pg_sleep(${ TIMEOUT + 10 } );`
13- // Execute a query that will sleep for 10 seconds
1415 const res = await app . inject ( {
1516 method : 'POST' ,
1617 path : '/query' ,
@@ -20,30 +21,25 @@ describe('test query timeout', () => {
2021 } ,
2122 } )
2223
23- // Check that we get the proper timeout error response
24- expect ( res . statusCode ) . toBe ( 408 ) // Request Timeout
24+ expect ( res . statusCode ) . toBe ( 408 )
2525 expect ( res . json ( ) ) . toMatchObject ( {
2626 error : expect . stringContaining ( 'Query read timeout' ) ,
2727 } )
28- // wait one second for the statement timeout to take effect
2928 await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) )
3029
31- // Verify that the connection has been cleaned up by checking active connections
3230 const connectionsRes = await pgMeta . query ( `
3331 SELECT * FROM pg_stat_activity where application_name = 'postgres-meta 0.0.0-automated' and query ILIKE '%${ query } %';
3432 ` )
3533
36- // Should have no active connections except for our current query
3734 expect ( connectionsRes . data ) . toHaveLength ( 0 )
3835 } ,
3936 TIMEOUT * 1000
4037 )
4138
4239 test (
43- 'query without timeout parameter should not have timeout' ,
40+ 'absent queryTimeoutSecs uses default pool timeout' ,
4441 async ( ) => {
4542 const query = `SELECT pg_sleep(${ TIMEOUT + 10 } );`
46- // Execute a query that will sleep for 10 seconds without specifying timeout
4743 const res = await app . inject ( {
4844 method : 'POST' ,
4945 path : '/query' ,
@@ -52,22 +48,79 @@ describe('test query timeout', () => {
5248 } ,
5349 } )
5450
55- // Check that we get the proper timeout error response
56- expect ( res . statusCode ) . toBe ( 408 ) // Request Timeout
51+ expect ( res . statusCode ) . toBe ( 408 )
5752 expect ( res . json ( ) ) . toMatchObject ( {
5853 error : expect . stringContaining ( 'Query read timeout' ) ,
5954 } )
60- // wait one second
6155 await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) )
6256
63- // Verify that the connection has not been cleaned up sinice there is no statementTimetout
57+ // No statementTimeout was set, so the PG-side query is still running
6458 const connectionsRes = await pgMeta . query ( `
6559 SELECT * FROM pg_stat_activity where application_name = 'postgres-meta 0.0.0-automated' and query ILIKE '%${ query } %';
6660 ` )
6761
68- // Should have no active connections except for our current query
6962 expect ( connectionsRes . data ) . toHaveLength ( 1 )
7063 } ,
7164 TIMEOUT * 1000
7265 )
66+
67+ test (
68+ 'queryTimeoutSecs=0 disables pool-level timeout' ,
69+ async ( ) => {
70+ const sleepSecs = PG_QUERY_TIMEOUT + 1
71+ const res = await app . inject ( {
72+ method : 'POST' ,
73+ path : '/query' ,
74+ query : 'queryTimeoutSecs=0' ,
75+ payload : {
76+ query : `SELECT pg_sleep(${ sleepSecs } );` ,
77+ } ,
78+ } )
79+
80+ expect ( res . statusCode ) . toBe ( 200 )
81+ expect ( res . json ( ) ) . toEqual ( [ { pg_sleep : '' } ] )
82+ } ,
83+ ( PG_QUERY_TIMEOUT + 5 ) * 1000
84+ )
85+
86+ test (
87+ 'custom queryTimeoutSecs overrides default pool timeout' ,
88+ async ( ) => {
89+ const res = await app . inject ( {
90+ method : 'POST' ,
91+ path : '/query' ,
92+ query : `queryTimeoutSecs=${ CUSTOM_QUERY_TIMEOUT } ` ,
93+ payload : {
94+ query : `SELECT pg_sleep(${ PG_QUERY_TIMEOUT + 1 } );` ,
95+ } ,
96+ } )
97+
98+ expect ( res . statusCode ) . toBe ( 408 )
99+ expect ( res . json ( ) ) . toMatchObject ( {
100+ error : expect . stringContaining ( 'Query read timeout' ) ,
101+ } )
102+ } ,
103+ ( CUSTOM_QUERY_TIMEOUT + 5 ) * 1000
104+ )
105+
106+ test (
107+ 'queryTimeoutSecs=0 with statementTimeoutSecs still enforces statement timeout' ,
108+ async ( ) => {
109+ const res = await app . inject ( {
110+ method : 'POST' ,
111+ path : '/query' ,
112+ query : `queryTimeoutSecs=0&statementTimeoutSecs=${ STATEMENT_TIMEOUT } ` ,
113+ payload : {
114+ query : `SELECT pg_sleep(${ STATEMENT_TIMEOUT + 5 } );` ,
115+ } ,
116+ } )
117+
118+ // Statement timeout fires (not pool timeout), producing a DatabaseError
119+ expect ( res . statusCode ) . toBe ( 400 )
120+ expect ( res . json ( ) ) . toMatchObject ( {
121+ error : expect . stringContaining ( 'canceling statement due to statement timeout' ) ,
122+ } )
123+ } ,
124+ ( STATEMENT_TIMEOUT + 5 ) * 1000
125+ )
73126} )
0 commit comments