Skip to content

Commit af4e8c6

Browse files
committed
test(tools): mock secure fetch path in index suite
1 parent 86f0235 commit af4e8c6

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

apps/sim/tools/index.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,89 @@ function setupEnvVars(variables: Record<string, string>) {
282282
}
283283
}
284284

285+
function responseHeadersToRecord(headers: unknown): Record<string, string> {
286+
const record: Record<string, string> = {}
287+
if (!headers || typeof headers !== 'object') return record
288+
289+
if (headers instanceof Headers) {
290+
headers.forEach((value, key) => {
291+
record[key] = value
292+
})
293+
return record
294+
}
295+
296+
if ('forEach' in headers && typeof (headers as any).forEach === 'function') {
297+
;(headers as any).forEach((value: string, key: string) => {
298+
record[key] = value
299+
})
300+
return record
301+
}
302+
303+
for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {
304+
if (typeof value === 'string') record[key] = value
305+
}
306+
return record
307+
}
308+
309+
function setupSecurityFetchMocks() {
310+
vi.spyOn(securityValidation, 'validateUrlWithDNS').mockResolvedValue({
311+
isValid: true,
312+
resolvedIP: '127.0.0.1',
313+
originalHostname: 'localhost',
314+
})
315+
316+
vi.spyOn(securityValidation, 'secureFetchWithPinnedIP').mockImplementation(
317+
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+
})
323+
324+
if (!fetchResponse) {
325+
throw new Error('Mock fetch returned no response')
326+
}
327+
328+
const headersRecord = responseHeadersToRecord((fetchResponse as any).headers)
329+
const status = (fetchResponse as any).status ?? 200
330+
const statusText = (fetchResponse as any).statusText ?? (status >= 200 ? 'OK' : 'Error')
331+
const ok = (fetchResponse as any).ok ?? (status >= 200 && status < 300)
332+
333+
return {
334+
ok,
335+
status,
336+
statusText,
337+
headers: new securityValidation.SecureFetchHeaders(headersRecord),
338+
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 ''
346+
},
347+
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() : ''
353+
return rawText ? JSON.parse(rawText) : {}
354+
},
355+
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() : ''
361+
return new TextEncoder().encode(rawText).buffer
362+
},
363+
}
364+
}
365+
)
366+
}
367+
285368
describe('Tools Registry', () => {
286369
it('should include all expected built-in tools', () => {
287370
expect(tools.http_request).toBeDefined()
@@ -336,6 +419,7 @@ describe('executeTool Function', () => {
336419
status: 200,
337420
headers: { 'content-type': 'application/json' },
338421
})
422+
setupSecurityFetchMocks()
339423

340424
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
341425
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
@@ -443,6 +527,7 @@ describe('Internal Tool Timeout Behavior', () => {
443527
let cleanupEnvVars: () => void
444528

445529
beforeEach(() => {
530+
setupSecurityFetchMocks()
446531
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
447532
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
448533
})
@@ -550,6 +635,7 @@ describe('Automatic Internal Route Detection', () => {
550635
let cleanupEnvVars: () => void
551636

552637
beforeEach(() => {
638+
setupSecurityFetchMocks()
553639
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
554640
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
555641
})
@@ -805,6 +891,7 @@ describe('Centralized Error Handling', () => {
805891
let cleanupEnvVars: () => void
806892

807893
beforeEach(() => {
894+
setupSecurityFetchMocks()
808895
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
809896
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
810897
})
@@ -1034,6 +1121,7 @@ describe('MCP Tool Execution', () => {
10341121
let cleanupEnvVars: () => void
10351122

10361123
beforeEach(() => {
1124+
setupSecurityFetchMocks()
10371125
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
10381126
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
10391127
})

0 commit comments

Comments
 (0)