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