Skip to content

Commit 722f03d

Browse files
committed
test(tools): mock secure fetch path in index suite
1 parent ad8fde1 commit 722f03d

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
@@ -112,6 +112,89 @@ function setupEnvVars(variables: Record<string, string>) {
112112
}
113113
}
114114

115+
function responseHeadersToRecord(headers: unknown): Record<string, string> {
116+
const record: Record<string, string> = {}
117+
if (!headers || typeof headers !== 'object') return record
118+
119+
if (headers instanceof Headers) {
120+
headers.forEach((value, key) => {
121+
record[key] = value
122+
})
123+
return record
124+
}
125+
126+
if ('forEach' in headers && typeof (headers as any).forEach === 'function') {
127+
;(headers as any).forEach((value: string, key: string) => {
128+
record[key] = value
129+
})
130+
return record
131+
}
132+
133+
for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {
134+
if (typeof value === 'string') record[key] = value
135+
}
136+
return record
137+
}
138+
139+
function setupSecurityFetchMocks() {
140+
vi.spyOn(securityValidation, 'validateUrlWithDNS').mockResolvedValue({
141+
isValid: true,
142+
resolvedIP: '127.0.0.1',
143+
originalHostname: 'localhost',
144+
})
145+
146+
vi.spyOn(securityValidation, 'secureFetchWithPinnedIP').mockImplementation(
147+
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+
})
153+
154+
if (!fetchResponse) {
155+
throw new Error('Mock fetch returned no response')
156+
}
157+
158+
const headersRecord = responseHeadersToRecord((fetchResponse as any).headers)
159+
const status = (fetchResponse as any).status ?? 200
160+
const statusText = (fetchResponse as any).statusText ?? (status >= 200 ? 'OK' : 'Error')
161+
const ok = (fetchResponse as any).ok ?? (status >= 200 && status < 300)
162+
163+
return {
164+
ok,
165+
status,
166+
statusText,
167+
headers: new securityValidation.SecureFetchHeaders(headersRecord),
168+
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 ''
176+
},
177+
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() : ''
183+
return rawText ? JSON.parse(rawText) : {}
184+
},
185+
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() : ''
191+
return new TextEncoder().encode(rawText).buffer
192+
},
193+
}
194+
}
195+
)
196+
}
197+
115198
describe('Tools Registry', () => {
116199
it('should include all expected built-in tools', () => {
117200
expect(Object.keys(tools).length).toBeGreaterThan(10)
@@ -168,6 +251,7 @@ describe('executeTool Function', () => {
168251
status: 200,
169252
headers: { 'content-type': 'application/json' },
170253
})
254+
setupSecurityFetchMocks()
171255

172256
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
173257
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
@@ -275,6 +359,7 @@ describe('Internal Tool Timeout Behavior', () => {
275359
let cleanupEnvVars: () => void
276360

277361
beforeEach(() => {
362+
setupSecurityFetchMocks()
278363
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
279364
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
280365
})
@@ -382,6 +467,7 @@ describe('Automatic Internal Route Detection', () => {
382467
let cleanupEnvVars: () => void
383468

384469
beforeEach(() => {
470+
setupSecurityFetchMocks()
385471
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
386472
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
387473
})
@@ -637,6 +723,7 @@ describe('Centralized Error Handling', () => {
637723
let cleanupEnvVars: () => void
638724

639725
beforeEach(() => {
726+
setupSecurityFetchMocks()
640727
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
641728
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
642729
})
@@ -866,6 +953,7 @@ describe('MCP Tool Execution', () => {
866953
let cleanupEnvVars: () => void
867954

868955
beforeEach(() => {
956+
setupSecurityFetchMocks()
869957
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
870958
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
871959
})

0 commit comments

Comments
 (0)