@@ -11,6 +11,10 @@ type HttpAssertionOptions = {
1111
1212type JsonValidator = ( value : unknown ) => void ;
1313
14+ export const HTTP_FETCH_TIMEOUT_MS = 20000 ;
15+ export const CURL_PROCESS_TIMEOUT_MS = 30000 ;
16+ const HTTP_POLL_INTERVAL_MS = 5000 ;
17+
1418function curlResolveArgs ( url : string , resolveIp ?: string ) : string [ ] {
1519 if ( ! resolveIp ) {
1620 return [ ] ;
@@ -29,21 +33,43 @@ export function parseCurlHttpBodyResult(stdout: string, body: string): { status:
2933 return { status, body } ;
3034}
3135
36+ async function withFetchTimeout < T > ( task : ( signal : AbortSignal ) => Promise < T > ) : Promise < T > {
37+ const controller = new AbortController ( ) ;
38+ const timer = setTimeout ( ( ) => controller . abort ( ) , HTTP_FETCH_TIMEOUT_MS ) ;
39+ try {
40+ return await task ( controller . signal ) ;
41+ } finally {
42+ clearTimeout ( timer ) ;
43+ }
44+ }
45+
46+ async function fetchWithTimeout ( url : string , init : RequestInit ) : Promise < Response > {
47+ return await withFetchTimeout ( ( signal ) => fetch ( url , { ...init , signal } ) ) ;
48+ }
49+
50+ async function fetchTextWithTimeout ( url : string , init : RequestInit ) : Promise < { response : Response ; body : string } > {
51+ return await withFetchTimeout ( async ( signal ) => {
52+ const response = await fetch ( url , { ...init , signal } ) ;
53+ const body = await response . text ( ) ;
54+ return { response, body } ;
55+ } ) ;
56+ }
57+
3258/** Polls an HTTP endpoint until it returns one of the expected status codes. */
3359export async function waitForHttpStatus ( url : string , expectedStatuses : number [ ] , timeoutMs = 180000 ) : Promise < Response > {
3460 const deadline = Date . now ( ) + timeoutMs ;
3561 let lastResponse : Response | null = null ;
3662 while ( Date . now ( ) < deadline ) {
3763 try {
38- const response = await fetch ( url , { redirect : "manual" } ) ;
64+ const response = await fetchWithTimeout ( url , { redirect : "manual" } ) ;
3965 lastResponse = response ;
4066 if ( expectedStatuses . includes ( response . status ) ) {
4167 return response ;
4268 }
4369 } catch {
4470 // Ignore transient DNS/TLS startup errors while services converge.
4571 }
46- await Bun . sleep ( 5000 ) ;
72+ await Bun . sleep ( HTTP_POLL_INTERVAL_MS ) ;
4773 }
4874 throw new Error ( `timed out waiting for ${ url } to return one of [${ expectedStatuses . join ( ", " ) } ], last status: ${ lastResponse ?. status ?? "none" } ` ) ;
4975}
@@ -70,7 +96,7 @@ async function readHttpsBody(url: string, options: HttpAssertionOptions = {}): P
7096 ...( options . insecure ? [ "-k" ] : [ ] ) ,
7197 ...curlResolveArgs ( url , options . resolveIp ) ,
7298 url
73- ] ) ;
99+ ] , { timeoutMs : CURL_PROCESS_TIMEOUT_MS } ) ;
74100 if ( result . exitCode !== 0 ) {
75101 throw new Error ( result . stderr . trim ( ) || result . stdout . trim ( ) || `failed to fetch ${ url } ` ) ;
76102 }
@@ -123,7 +149,7 @@ export async function waitForHttpStatusResolved(
123149 ...( insecure ? [ "-k" ] : [ ] ) ,
124150 ...curlResolveArgs ( url , resolveIp ) ,
125151 url
126- ] ) ;
152+ ] , { timeoutMs : CURL_PROCESS_TIMEOUT_MS } ) ;
127153 const status = ( result . stdout || "" ) . trim ( ) ;
128154 lastStatus = status || lastStatus ;
129155 lastError = result . stderr . trim ( ) || result . stdout . trim ( ) || lastError ;
@@ -135,7 +161,7 @@ export async function waitForHttpStatusResolved(
135161 }
136162 }
137163
138- await Bun . sleep ( 5000 ) ;
164+ await Bun . sleep ( HTTP_POLL_INTERVAL_MS ) ;
139165 }
140166
141167 throw new Error (
@@ -168,8 +194,7 @@ export async function expectHttpBodyContains(
168194 return ;
169195 }
170196 } else {
171- const response = await fetch ( url , { redirect : "follow" } ) ;
172- const body = await response . text ( ) ;
197+ const { response, body } = await fetchTextWithTimeout ( url , { redirect : "follow" } ) ;
173198 lastStatus = String ( response . status ) ;
174199 lastBody = body ;
175200 if ( body . includes ( needle ) ) {
@@ -180,7 +205,7 @@ export async function expectHttpBodyContains(
180205 lastBody = String ( error ) ;
181206 }
182207
183- await Bun . sleep ( 5000 ) ;
208+ await Bun . sleep ( HTTP_POLL_INTERVAL_MS ) ;
184209 }
185210
186211 const bodySnippet = lastBody . replace ( / \s + / g, " " ) . trim ( ) . slice ( 0 , 400 ) ;
@@ -217,7 +242,7 @@ export async function expectHttpsJson(
217242 lastError = error instanceof Error ? error . message : String ( error ) ;
218243 }
219244
220- await Bun . sleep ( 5000 ) ;
245+ await Bun . sleep ( HTTP_POLL_INTERVAL_MS ) ;
221246 }
222247
223248 const bodySnippet = lastBody . replace ( / \s + / g, " " ) . trim ( ) . slice ( 0 , 400 ) ;
0 commit comments