3
3
startAuthorization ,
4
4
exchangeAuthorization ,
5
5
refreshAuthorization ,
6
- registerClient ,
7
6
discoverOAuthProtectedResourceMetadata ,
8
7
extractResourceMetadataUrl ,
9
8
auth ,
@@ -17,6 +16,7 @@ global.fetch = mockFetch;
17
16
describe ( "OAuth Authorization" , ( ) => {
18
17
beforeEach ( ( ) => {
19
18
mockFetch . mockReset ( ) ;
19
+ jest . clearAllMocks ( ) ;
20
20
} ) ;
21
21
22
22
describe ( "extractResourceMetadataUrl" , ( ) => {
@@ -626,89 +626,93 @@ describe("OAuth Authorization", () => {
626
626
} ) ;
627
627
628
628
describe ( "registerClient" , ( ) => {
629
- const validClientMetadata = {
630
- redirect_uris : [ "http://localhost:3000/callback" ] ,
631
- client_name : "Test Client" ,
629
+ // ...existing tests...
630
+ } ) ;
631
+
632
+ describe ( "auth" , ( ) => {
633
+ // Patch pkce-challenge to return deterministic values
634
+ jest . mock ( "pkce-challenge" , ( ) => ( ) => ( {
635
+ code_verifier : "test_verifier" ,
636
+ code_challenge : "test_challenge" ,
637
+ } ) ) ;
638
+
639
+ const validMetadata = {
640
+ issuer : "https://auth.example.com" ,
641
+ authorization_endpoint : "https://auth.example.com/authorize" ,
642
+ token_endpoint : "https://auth.example.com/token" ,
643
+ registration_endpoint : "https://auth.example.com/register" ,
644
+ response_types_supported : [ "code" ] ,
645
+ code_challenge_methods_supported : [ "S256" ] ,
632
646
} ;
633
647
634
648
const validClientInfo = {
635
649
client_id : "client123" ,
636
650
client_secret : "secret123" ,
637
- client_id_issued_at : 1612137600 ,
638
- client_secret_expires_at : 1612224000 ,
639
- ...validClientMetadata ,
651
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
652
+ client_name : "Test Client" ,
640
653
} ;
641
654
642
- it ( "registers client and returns client information" , async ( ) => {
643
- mockFetch . mockResolvedValueOnce ( {
644
- ok : true ,
645
- status : 200 ,
646
- json : async ( ) => validClientInfo ,
647
- } ) ;
648
-
649
- const clientInfo = await registerClient ( "https://auth.example.com" , {
650
- clientMetadata : validClientMetadata ,
651
- } ) ;
652
-
653
- expect ( clientInfo ) . toEqual ( validClientInfo ) ;
654
- expect ( mockFetch ) . toHaveBeenCalledWith (
655
- expect . objectContaining ( {
656
- href : "https://auth.example.com/register" ,
657
- } ) ,
658
- expect . objectContaining ( {
659
- method : "POST" ,
660
- headers : {
661
- "Content-Type" : "application/json" ,
662
- } ,
663
- body : JSON . stringify ( validClientMetadata ) ,
664
- } )
665
- ) ;
666
- } ) ;
667
-
668
- it ( "validates client information response schema" , async ( ) => {
669
- mockFetch . mockResolvedValueOnce ( {
670
- ok : true ,
671
- status : 200 ,
672
- json : async ( ) => ( {
673
- // Missing required fields
674
- client_secret : "secret123" ,
675
- } ) ,
676
- } ) ;
677
-
678
- await expect (
679
- registerClient ( "https://auth.example.com" , {
680
- clientMetadata : validClientMetadata ,
681
- } )
682
- ) . rejects . toThrow ( ) ;
683
- } ) ;
684
-
685
- it ( "throws when registration endpoint not available in metadata" , async ( ) => {
686
- const metadata = {
687
- issuer : "https://auth.example.com" ,
688
- authorization_endpoint : "https://auth.example.com/authorize" ,
689
- token_endpoint : "https://auth.example.com/token" ,
690
- response_types_supported : [ "code" ] ,
655
+ it ( "uses scope from registered client information if not present in clientMetadata" , async ( ) => {
656
+ // Mock fetch for metadata discovery and registration
657
+ mockFetch
658
+ . mockResolvedValueOnce ( {
659
+ ok : true ,
660
+ status : 200 ,
661
+ json : async ( ) => ( { } ) ,
662
+ } ) // protected resource metadata
663
+ . mockResolvedValueOnce ( {
664
+ ok : true ,
665
+ status : 200 ,
666
+ json : async ( ) => validMetadata ,
667
+ } ) // discovery
668
+ . mockResolvedValueOnce ( {
669
+ ok : true ,
670
+ status : 200 ,
671
+ json : async ( ) => ( {
672
+ ...validClientInfo ,
673
+ scope : "dynamic scope from registration" ,
674
+ } ) ,
675
+ } ) ; // registration
676
+
677
+ // Provider: clientInformation returns undefined first, then fullInformation after registration
678
+ const fullInformation = {
679
+ ...validClientInfo ,
680
+ scope : "dynamic scope from registration" ,
681
+ } ;
682
+ const clientInformationMock = jest
683
+ . fn ( )
684
+ . mockResolvedValueOnce ( undefined )
685
+ . mockResolvedValueOnce ( fullInformation ) ;
686
+
687
+ const provider = {
688
+ get redirectUrl ( ) {
689
+ return "http://localhost:3000/callback" ;
690
+ } ,
691
+ get clientMetadata ( ) {
692
+ // No scope here!
693
+ return validClientInfo ;
694
+ } ,
695
+ clientInformation : clientInformationMock ,
696
+ tokens : jest . fn ( ) . mockResolvedValue ( undefined ) ,
697
+ saveTokens : jest . fn ( ) ,
698
+ saveCodeVerifier : jest . fn ( ) ,
699
+ codeVerifier : jest . fn ( ) ,
700
+ redirectToAuthorization : jest . fn ( ) ,
701
+ saveClientInformation : jest . fn ( ) ,
691
702
} ;
692
703
693
- await expect (
694
- registerClient ( "https://auth.example.com" , {
695
- metadata,
696
- clientMetadata : validClientMetadata ,
697
- } )
698
- ) . rejects . toThrow ( / d o e s n o t s u p p o r t d y n a m i c c l i e n t r e g i s t r a t i o n / ) ;
699
- } ) ;
700
-
701
- it ( "throws on error response" , async ( ) => {
702
- mockFetch . mockResolvedValueOnce ( {
703
- ok : false ,
704
- status : 400 ,
704
+ await auth ( provider , {
705
+ serverUrl : "https://auth.example.com" ,
705
706
} ) ;
706
707
707
- await expect (
708
- registerClient ( "https://auth.example.com" , {
709
- clientMetadata : validClientMetadata ,
710
- } )
711
- ) . rejects . toThrow ( "Dynamic client registration failed" ) ;
708
+ // Check saveClientInformation was called with fullInformation
709
+ expect ( provider . saveClientInformation ) . toHaveBeenCalledWith ( fullInformation ) ;
710
+
711
+ // Check that redirectToAuthorization was called with a URL containing the correct scope from registration
712
+ expect ( provider . redirectToAuthorization ) . toHaveBeenCalledTimes ( 1 ) ;
713
+ const urlArg = provider . redirectToAuthorization . mock . calls [ 0 ] [ 0 ] ;
714
+ expect ( urlArg ) . toBeInstanceOf ( URL ) ;
715
+ expect ( urlArg . searchParams . get ( "scope" ) ) . toBe ( "dynamic scope from registration" ) ;
712
716
} ) ;
713
717
} ) ;
714
718
0 commit comments