@@ -5,13 +5,28 @@ import { TEST_API_KEY } from '../setup';
55
66// Helper to create mock Headers object
77function createMockHeaders ( entries : [ string , string ] [ ] ) : any {
8- const map = new Map ( entries ) ;
8+ const map = new Map ( entries . map ( ( [ k , v ] ) => [ k . toLowerCase ( ) , v ] ) ) ;
99 return {
1010 get : ( key : string ) => map . get ( key . toLowerCase ( ) ) || null ,
1111 has : ( key : string ) => map . has ( key . toLowerCase ( ) ) ,
1212 entries : ( ) => map . entries ( ) ,
1313 keys : ( ) => map . keys ( ) ,
1414 values : ( ) => map . values ( ) ,
15+ forEach : ( callback : ( value : string , key : string ) => void ) => {
16+ map . forEach ( ( value , key ) => callback ( value , key ) ) ;
17+ } ,
18+ } ;
19+ }
20+
21+ // Helper to create mock error Response
22+ function createMockErrorResponse ( status : number , statusText : string , errorData : any ) : any {
23+ return {
24+ ok : false ,
25+ status,
26+ statusText,
27+ headers : createMockHeaders ( [ [ 'content-type' , 'application/json' ] ] ) ,
28+ json : async ( ) => errorData ,
29+ text : async ( ) => JSON . stringify ( errorData ) ,
1530 } ;
1631}
1732
@@ -198,13 +213,9 @@ describe('HttpClient', () => {
198213 } ) ;
199214
200215 it ( 'should throw AuthenticationError on 401' , async ( ) => {
201- fetchMock . mockResolvedValue ( {
202- ok : false ,
203- status : 401 ,
204- statusText : 'Unauthorized' ,
205- headers : createMockHeaders ( [ [ 'content-type' , 'application/json' ] ] ) ,
206- json : async ( ) => ( { error : 'Invalid API key' } ) ,
207- } ) ;
216+ fetchMock . mockResolvedValue (
217+ createMockErrorResponse ( 401 , 'Unauthorized' , { error : 'Invalid API key' } )
218+ ) ;
208219
209220 // 401 errors should not retry
210221 await expect ( httpClient . get ( '/test' ) ) . rejects . toMatchObject ( {
@@ -219,16 +230,12 @@ describe('HttpClient', () => {
219230
220231 describe ( 'Error Handling' , ( ) => {
221232 it ( 'should throw ValidationError on 400' , async ( ) => {
222- fetchMock . mockResolvedValue ( {
223- ok : false ,
224- status : 400 ,
225- statusText : 'Bad Request' ,
226- headers : createMockHeaders ( [ [ 'content-type' , 'application/json' ] ] ) ,
227- json : async ( ) => ( {
233+ fetchMock . mockResolvedValue (
234+ createMockErrorResponse ( 400 , 'Bad Request' , {
228235 error : 'Validation failed' ,
229236 details : { field : 'required' } ,
230- } ) ,
231- } ) ;
237+ } )
238+ ) ;
232239
233240 // 400 errors should not retry
234241 await expect ( httpClient . get ( '/test' ) ) . rejects . toMatchObject ( {
@@ -240,13 +247,9 @@ describe('HttpClient', () => {
240247 } ) ;
241248
242249 it ( 'should throw NotFoundError on 404' , async ( ) => {
243- fetchMock . mockResolvedValue ( {
244- ok : false ,
245- status : 404 ,
246- statusText : 'Not Found' ,
247- headers : createMockHeaders ( [ [ 'content-type' , 'application/json' ] ] ) ,
248- json : async ( ) => ( { error : 'Resource not found' } ) ,
249- } ) ;
250+ fetchMock . mockResolvedValue (
251+ createMockErrorResponse ( 404 , 'Not Found' , { error : 'Resource not found' } )
252+ ) ;
250253
251254 // 404 errors should not retry
252255 await expect ( httpClient . get ( '/test' ) ) . rejects . toMatchObject ( {
@@ -259,16 +262,14 @@ describe('HttpClient', () => {
259262
260263 it ( 'should throw RateLimitError on 429 after retries' , async ( ) => {
261264 // Always return 429
262- fetchMock . mockResolvedValue ( {
263- ok : false ,
264- status : 429 ,
265- statusText : 'Too Many Requests' ,
266- headers : new Map ( [
267- [ 'content-type' , 'application/json' ] ,
268- [ 'retry-after' , '60' ] ,
269- ] ) ,
270- json : async ( ) => ( { error : 'Rate limit exceeded' } ) ,
271- } ) ;
265+ const errorResponse = createMockErrorResponse ( 429 , 'Too Many Requests' , { error : 'Rate limit exceeded' } ) ;
266+ // Add retry-after header
267+ errorResponse . headers . get = ( key : string ) => {
268+ if ( key . toLowerCase ( ) === 'retry-after' ) return '60' ;
269+ if ( key . toLowerCase ( ) === 'content-type' ) return 'application/json' ;
270+ return null ;
271+ } ;
272+ fetchMock . mockResolvedValue ( errorResponse ) ;
272273
273274 const promise = httpClient . get ( '/test' ) ;
274275
@@ -284,13 +285,9 @@ describe('HttpClient', () => {
284285
285286 it ( 'should throw ServerError on 500 after retries' , async ( ) => {
286287 // Always return 500
287- fetchMock . mockResolvedValue ( {
288- ok : false ,
289- status : 500 ,
290- statusText : 'Internal Server Error' ,
291- headers : createMockHeaders ( [ [ 'content-type' , 'application/json' ] ] ) ,
292- json : async ( ) => ( { error : 'Server error' } ) ,
293- } ) ;
288+ fetchMock . mockResolvedValue (
289+ createMockErrorResponse ( 500 , 'Internal Server Error' , { error : 'Server error' } )
290+ ) ;
294291
295292 const promise = httpClient . get ( '/test' ) ;
296293
@@ -386,13 +383,9 @@ describe('HttpClient', () => {
386383 } ) ;
387384
388385 it ( 'should not retry on 400 Bad Request' , async ( ) => {
389- fetchMock . mockResolvedValue ( {
390- ok : false ,
391- status : 400 ,
392- statusText : 'Bad Request' ,
393- headers : createMockHeaders ( [ [ 'content-type' , 'application/json' ] ] ) ,
394- json : async ( ) => ( { error : 'Invalid input' } ) ,
395- } ) ;
386+ fetchMock . mockResolvedValue (
387+ createMockErrorResponse ( 400 , 'Bad Request' , { error : 'Invalid input' } )
388+ ) ;
396389
397390 const promise = httpClient . get ( '/test' ) ;
398391
@@ -401,13 +394,9 @@ describe('HttpClient', () => {
401394 } ) ;
402395
403396 it ( 'should respect maxRetries limit' , async ( ) => {
404- fetchMock . mockResolvedValue ( {
405- ok : false ,
406- status : 503 ,
407- statusText : 'Service Unavailable' ,
408- headers : createMockHeaders ( [ [ 'content-type' , 'application/json' ] ] ) ,
409- json : async ( ) => ( { error : 'Unavailable' } ) ,
410- } ) ;
397+ fetchMock . mockResolvedValue (
398+ createMockErrorResponse ( 503 , 'Service Unavailable' , { error : 'Unavailable' } )
399+ ) ;
411400
412401 const promise = httpClient . get ( '/test' ) ;
413402
0 commit comments