@@ -311,6 +311,108 @@ describe('setupClerkTestingToken', () => {
311311 warnSpy . mockRestore ( ) ;
312312 } ) ;
313313
314+ it ( 'logs response diagnostics after exhausting retries on retryable status' , async ( ) => {
315+ const { context, getRouteHandler } = createMockContext ( ) ;
316+ await setupClerkTestingToken ( { context } ) ;
317+
318+ const warnSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
319+
320+ const route = {
321+ request : ( ) => ( { url : ( ) => 'https://clerk.example.com/v1/client' } ) ,
322+ fetch : vi . fn ( ( ) =>
323+ Promise . resolve ( {
324+ status : ( ) => 429 ,
325+ headers : ( ) => ( {
326+ 'cf-ray' : '8f7a2b3c4d5e6f70-IAD' ,
327+ 'retry-after' : '12' ,
328+ 'content-type' : 'application/json' ,
329+ } ) ,
330+ text : ( ) => Promise . resolve ( '{"errors":[{"code":"too_many_requests"}]}' ) ,
331+ json : ( ) => Promise . resolve ( { } ) ,
332+ } ) ,
333+ ) ,
334+ fulfill : vi . fn ( ( ) => Promise . resolve ( ) ) ,
335+ continue : vi . fn ( ( ) => Promise . resolve ( ) ) ,
336+ } as unknown as Route ;
337+
338+ const handlerPromise = getRouteHandler ( ) ! ( route ) ;
339+ await vi . advanceTimersByTimeAsync ( 60_000 ) ;
340+ await handlerPromise ;
341+
342+ expect ( route . fulfill ) . toHaveBeenCalledTimes ( 1 ) ;
343+ expect ( warnSpy ) . toHaveBeenCalledTimes ( 1 ) ;
344+ const warning = warnSpy . mock . calls [ 0 ] [ 0 ] as string ;
345+ expect ( warning ) . toContain ( 'cf-ray: 8f7a2b3c4d5e6f70-IAD' ) ;
346+ expect ( warning ) . toContain ( 'retry-after: 12' ) ;
347+ expect ( warning ) . toContain ( 'content-type: application/json' ) ;
348+ expect ( warning ) . toContain ( 'body: {"errors":[{"code":"too_many_requests"}]}' ) ;
349+
350+ warnSpy . mockRestore ( ) ;
351+ } ) ;
352+
353+ it ( 'truncates long response bodies in diagnostics' , async ( ) => {
354+ const { context, getRouteHandler } = createMockContext ( ) ;
355+ await setupClerkTestingToken ( { context } ) ;
356+
357+ const warnSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
358+
359+ const route = {
360+ request : ( ) => ( { url : ( ) => 'https://clerk.example.com/v1/client' } ) ,
361+ fetch : vi . fn ( ( ) =>
362+ Promise . resolve ( {
363+ status : ( ) => 429 ,
364+ headers : ( ) => ( { } ) ,
365+ text : ( ) => Promise . resolve ( 'x' . repeat ( 2000 ) ) ,
366+ json : ( ) => Promise . resolve ( { } ) ,
367+ } ) ,
368+ ) ,
369+ fulfill : vi . fn ( ( ) => Promise . resolve ( ) ) ,
370+ continue : vi . fn ( ( ) => Promise . resolve ( ) ) ,
371+ } as unknown as Route ;
372+
373+ const handlerPromise = getRouteHandler ( ) ! ( route ) ;
374+ await vi . advanceTimersByTimeAsync ( 60_000 ) ;
375+ await handlerPromise ;
376+
377+ expect ( warnSpy ) . toHaveBeenCalledTimes ( 1 ) ;
378+ const warning = warnSpy . mock . calls [ 0 ] [ 0 ] as string ;
379+ expect ( warning ) . toContain ( 'cf-ray: n/a' ) ;
380+ expect ( warning ) . toContain ( `body: ${ 'x' . repeat ( 500 ) } ` ) ;
381+ expect ( warning ) . not . toContain ( 'x' . repeat ( 501 ) ) ;
382+
383+ warnSpy . mockRestore ( ) ;
384+ } ) ;
385+
386+ it ( 'still fulfills the response when diagnostics cannot be read' , async ( ) => {
387+ const { context, getRouteHandler } = createMockContext ( ) ;
388+ await setupClerkTestingToken ( { context } ) ;
389+
390+ const warnSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
391+
392+ // No headers()/text() on the response: diagnostics are best-effort and must not break fulfillment.
393+ const route = {
394+ request : ( ) => ( { url : ( ) => 'https://clerk.example.com/v1/client' } ) ,
395+ fetch : vi . fn ( ( ) =>
396+ Promise . resolve ( {
397+ status : ( ) => 429 ,
398+ json : ( ) => Promise . resolve ( { } ) ,
399+ } ) ,
400+ ) ,
401+ fulfill : vi . fn ( ( ) => Promise . resolve ( ) ) ,
402+ continue : vi . fn ( ( ) => Promise . resolve ( ) ) ,
403+ } as unknown as Route ;
404+
405+ const handlerPromise = getRouteHandler ( ) ! ( route ) ;
406+ await vi . advanceTimersByTimeAsync ( 60_000 ) ;
407+ await handlerPromise ;
408+
409+ expect ( route . fulfill ) . toHaveBeenCalledTimes ( 1 ) ;
410+ expect ( route . continue ) . not . toHaveBeenCalled ( ) ;
411+ expect ( warnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( 'failed with status 429 after 4 attempts' ) ) ;
412+
413+ warnSpy . mockRestore ( ) ;
414+ } ) ;
415+
314416 it ( 'retries on thrown errors and warns after exhausting retries' , async ( ) => {
315417 const { context, getRouteHandler } = createMockContext ( ) ;
316418 await setupClerkTestingToken ( { context } ) ;
@@ -332,7 +434,10 @@ describe('setupClerkTestingToken', () => {
332434
333435 expect ( route . fetch ) . toHaveBeenCalledTimes ( 4 ) ;
334436 expect ( route . continue ) . toHaveBeenCalledTimes ( 1 ) ;
335- expect ( warnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( 'failed after 4 attempts' ) , networkError ) ;
437+ expect ( warnSpy ) . toHaveBeenCalledTimes ( 1 ) ;
438+ const warning = warnSpy . mock . calls [ 0 ] [ 0 ] as string ;
439+ expect ( warning ) . toContain ( 'failed after 4 attempts' ) ;
440+ expect ( warning ) . toContain ( 'net::ERR_CONNECTION_REFUSED' ) ;
336441
337442 warnSpy . mockRestore ( ) ;
338443 errorSpy . mockRestore ( ) ;
0 commit comments