Skip to content

Commit 02332f1

Browse files
committed
test(tools): stabilize secure fetch body semantics for retries
1 parent af4e8c6 commit 02332f1

File tree

1 file changed

+45
-25
lines changed

1 file changed

+45
-25
lines changed

apps/sim/tools/index.test.ts

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -315,49 +315,69 @@ function setupSecurityFetchMocks() {
315315

316316
vi.spyOn(securityValidation, 'secureFetchWithPinnedIP').mockImplementation(
317317
async (url, _resolvedIP, options = {}) => {
318-
const fetchResponse = await global.fetch(url, {
319-
method: options.method,
320-
headers: options.headers as HeadersInit,
321-
body: options.body as BodyInit | null | undefined,
322-
})
318+
let fetchResponse: any
319+
try {
320+
fetchResponse = await global.fetch(url, {
321+
method: options.method,
322+
headers: options.headers as HeadersInit,
323+
body: options.body as BodyInit | null | undefined,
324+
})
325+
} catch (error) {
326+
// Keep parity with secure fetch timeout behavior expected by retry logic tests.
327+
if (error instanceof Error && error.name === 'AbortError') {
328+
throw new Error('Request timed out')
329+
}
330+
throw error
331+
}
323332

324333
if (!fetchResponse) {
325334
throw new Error('Mock fetch returned no response')
326335
}
327336

328-
const headersRecord = responseHeadersToRecord((fetchResponse as any).headers)
329-
const status = (fetchResponse as any).status ?? 200
337+
const headersRecord = responseHeadersToRecord(fetchResponse.headers)
338+
const status = fetchResponse.status ?? 200
330339
const statusText = (fetchResponse as any).statusText ?? (status >= 200 ? 'OK' : 'Error')
331-
const ok = (fetchResponse as any).ok ?? (status >= 200 && status < 300)
340+
const ok = fetchResponse.ok ?? (status >= 200 && status < 300)
341+
let cachedBodyText: string | null = null
342+
343+
const getBodyText = async (): Promise<string> => {
344+
if (cachedBodyText !== null) return cachedBodyText
345+
346+
if (typeof fetchResponse.text === 'function') {
347+
cachedBodyText = await fetchResponse.text()
348+
return cachedBodyText
349+
}
350+
351+
if (typeof fetchResponse.json === 'function') {
352+
const jsonData = await fetchResponse.json()
353+
cachedBodyText = typeof jsonData === 'string' ? jsonData : JSON.stringify(jsonData)
354+
return cachedBodyText
355+
}
356+
357+
if (typeof fetchResponse.arrayBuffer === 'function') {
358+
const arr = await fetchResponse.arrayBuffer()
359+
cachedBodyText = new TextDecoder().decode(arr)
360+
return cachedBodyText
361+
}
362+
363+
cachedBodyText = ''
364+
return cachedBodyText
365+
}
332366

333367
return {
334368
ok,
335369
status,
336370
statusText,
337371
headers: new securityValidation.SecureFetchHeaders(headersRecord),
338372
text: async () => {
339-
if (typeof (fetchResponse as any).text === 'function') {
340-
return (fetchResponse as any).text()
341-
}
342-
if (typeof (fetchResponse as any).json === 'function') {
343-
return JSON.stringify(await (fetchResponse as any).json())
344-
}
345-
return ''
373+
return getBodyText()
346374
},
347375
json: async () => {
348-
if (typeof (fetchResponse as any).json === 'function') {
349-
return (fetchResponse as any).json()
350-
}
351-
const rawText =
352-
typeof (fetchResponse as any).text === 'function' ? await (fetchResponse as any).text() : ''
376+
const rawText = await getBodyText()
353377
return rawText ? JSON.parse(rawText) : {}
354378
},
355379
arrayBuffer: async () => {
356-
if (typeof (fetchResponse as any).arrayBuffer === 'function') {
357-
return (fetchResponse as any).arrayBuffer()
358-
}
359-
const rawText =
360-
typeof (fetchResponse as any).text === 'function' ? await (fetchResponse as any).text() : ''
380+
const rawText = await getBodyText()
361381
return new TextEncoder().encode(rawText).buffer
362382
},
363383
}

0 commit comments

Comments
 (0)