Skip to content

Commit 62bc957

Browse files
committed
test(tools): stabilize secure fetch body semantics for retries
1 parent 722f03d commit 62bc957

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
@@ -145,49 +145,69 @@ function setupSecurityFetchMocks() {
145145

146146
vi.spyOn(securityValidation, 'secureFetchWithPinnedIP').mockImplementation(
147147
async (url, _resolvedIP, options = {}) => {
148-
const fetchResponse = await global.fetch(url, {
149-
method: options.method,
150-
headers: options.headers as HeadersInit,
151-
body: options.body as BodyInit | null | undefined,
152-
})
148+
let fetchResponse: any
149+
try {
150+
fetchResponse = await global.fetch(url, {
151+
method: options.method,
152+
headers: options.headers as HeadersInit,
153+
body: options.body as BodyInit | null | undefined,
154+
})
155+
} catch (error) {
156+
// Keep parity with secure fetch timeout behavior expected by retry logic tests.
157+
if (error instanceof Error && error.name === 'AbortError') {
158+
throw new Error('Request timed out')
159+
}
160+
throw error
161+
}
153162

154163
if (!fetchResponse) {
155164
throw new Error('Mock fetch returned no response')
156165
}
157166

158-
const headersRecord = responseHeadersToRecord((fetchResponse as any).headers)
159-
const status = (fetchResponse as any).status ?? 200
167+
const headersRecord = responseHeadersToRecord(fetchResponse.headers)
168+
const status = fetchResponse.status ?? 200
160169
const statusText = (fetchResponse as any).statusText ?? (status >= 200 ? 'OK' : 'Error')
161-
const ok = (fetchResponse as any).ok ?? (status >= 200 && status < 300)
170+
const ok = fetchResponse.ok ?? (status >= 200 && status < 300)
171+
let cachedBodyText: string | null = null
172+
173+
const getBodyText = async (): Promise<string> => {
174+
if (cachedBodyText !== null) return cachedBodyText
175+
176+
if (typeof fetchResponse.text === 'function') {
177+
cachedBodyText = await fetchResponse.text()
178+
return cachedBodyText
179+
}
180+
181+
if (typeof fetchResponse.json === 'function') {
182+
const jsonData = await fetchResponse.json()
183+
cachedBodyText = typeof jsonData === 'string' ? jsonData : JSON.stringify(jsonData)
184+
return cachedBodyText
185+
}
186+
187+
if (typeof fetchResponse.arrayBuffer === 'function') {
188+
const arr = await fetchResponse.arrayBuffer()
189+
cachedBodyText = new TextDecoder().decode(arr)
190+
return cachedBodyText
191+
}
192+
193+
cachedBodyText = ''
194+
return cachedBodyText
195+
}
162196

163197
return {
164198
ok,
165199
status,
166200
statusText,
167201
headers: new securityValidation.SecureFetchHeaders(headersRecord),
168202
text: async () => {
169-
if (typeof (fetchResponse as any).text === 'function') {
170-
return (fetchResponse as any).text()
171-
}
172-
if (typeof (fetchResponse as any).json === 'function') {
173-
return JSON.stringify(await (fetchResponse as any).json())
174-
}
175-
return ''
203+
return getBodyText()
176204
},
177205
json: async () => {
178-
if (typeof (fetchResponse as any).json === 'function') {
179-
return (fetchResponse as any).json()
180-
}
181-
const rawText =
182-
typeof (fetchResponse as any).text === 'function' ? await (fetchResponse as any).text() : ''
206+
const rawText = await getBodyText()
183207
return rawText ? JSON.parse(rawText) : {}
184208
},
185209
arrayBuffer: async () => {
186-
if (typeof (fetchResponse as any).arrayBuffer === 'function') {
187-
return (fetchResponse as any).arrayBuffer()
188-
}
189-
const rawText =
190-
typeof (fetchResponse as any).text === 'function' ? await (fetchResponse as any).text() : ''
210+
const rawText = await getBodyText()
191211
return new TextEncoder().encode(rawText).buffer
192212
},
193213
}

0 commit comments

Comments
 (0)