@@ -543,18 +543,14 @@ describe("Credentials", () => {
543543 const apiTokenIssuer = "issuer.fga.example" ;
544544 const expectedBaseUrl = "https://issuer.fga.example" ;
545545 const expectedPath = `/${ DEFAULT_TOKEN_ENDPOINT_PATH } ` ;
546- let tokenRequestCount = 0 ;
547546
548547 const scope = nock ( expectedBaseUrl )
549548 . post ( expectedPath )
550549 . once ( )
551550 . delay ( 20 )
552- . reply ( ( ) => {
553- tokenRequestCount += 1 ;
554- return [ 200 , {
555- access_token : "shared-token" ,
556- expires_in : 300 ,
557- } ] ;
551+ . reply ( 200 , {
552+ access_token : "shared-token" ,
553+ expires_in : 300 ,
558554 } ) ;
559555
560556 const credentials = new Credentials (
@@ -578,7 +574,56 @@ describe("Credentials", () => {
578574 headers . forEach ( header => {
579575 expect ( header ?. value ) . toBe ( "Bearer shared-token" ) ;
580576 } ) ;
581- expect ( tokenRequestCount ) . toBe ( 1 ) ;
577+ expect ( scope . isDone ( ) ) . toBe ( true ) ;
578+ } ) ;
579+
580+ test ( "should clear shared refresh promise after failure and retry on the next call" , async ( ) => {
581+ const apiTokenIssuer = "issuer.fga.example" ;
582+ const expectedBaseUrl = "https://issuer.fga.example" ;
583+ const expectedPath = `/${ DEFAULT_TOKEN_ENDPOINT_PATH } ` ;
584+
585+ const scope = nock ( expectedBaseUrl )
586+ . post ( expectedPath )
587+ . once ( )
588+ . reply ( 404 , {
589+ code : "not_found" ,
590+ message : "token exchange failed" ,
591+ } )
592+ . post ( expectedPath )
593+ . once ( )
594+ . reply ( 200 , {
595+ access_token : "recovered-token" ,
596+ expires_in : 300 ,
597+ } ) ;
598+
599+ const credentials = new Credentials (
600+ {
601+ method : CredentialsMethod . ClientCredentials ,
602+ config : {
603+ apiTokenIssuer,
604+ apiAudience : OPENFGA_API_AUDIENCE ,
605+ clientId : OPENFGA_CLIENT_ID ,
606+ clientSecret : OPENFGA_CLIENT_SECRET ,
607+ } ,
608+ } as AuthCredentialsConfig ,
609+ undefined ,
610+ mockTelemetryConfig ,
611+ ) ;
612+
613+ const results = await Promise . allSettled (
614+ Array . from ( { length : 5 } , ( ) => credentials . getAccessTokenHeader ( ) )
615+ ) ;
616+ const rejected = results . filter ( ( result ) : result is PromiseRejectedResult => result . status === "rejected" ) ;
617+
618+ expect ( rejected ) . toHaveLength ( 5 ) ;
619+ expect ( rejected [ 0 ] . reason ) . toBe ( rejected [ 1 ] . reason ) ;
620+ expect ( rejected [ 1 ] . reason ) . toBe ( rejected [ 2 ] . reason ) ;
621+ expect ( rejected [ 2 ] . reason ) . toBe ( rejected [ 3 ] . reason ) ;
622+ expect ( rejected [ 3 ] . reason ) . toBe ( rejected [ 4 ] . reason ) ;
623+
624+ const header = await credentials . getAccessTokenHeader ( ) ;
625+
626+ expect ( header ?. value ) . toBe ( "Bearer recovered-token" ) ;
582627 expect ( scope . isDone ( ) ) . toBe ( true ) ;
583628 } ) ;
584629
0 commit comments