@@ -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+
285368describe ( '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