@@ -17,6 +17,7 @@ global.fetch = mockFetch;
17
17
describe ( "OAuth Authorization" , ( ) => {
18
18
beforeEach ( ( ) => {
19
19
mockFetch . mockReset ( ) ;
20
+ jest . clearAllMocks ( ) ;
20
21
} ) ;
21
22
22
23
describe ( "extractResourceMetadataUrl" , ( ) => {
@@ -625,6 +626,7 @@ describe("OAuth Authorization", () => {
625
626
} ) ;
626
627
} ) ;
627
628
629
+
628
630
describe ( "registerClient" , ( ) => {
629
631
const validClientMetadata = {
630
632
redirect_uris : [ "http://localhost:3000/callback" ] ,
@@ -712,6 +714,7 @@ describe("OAuth Authorization", () => {
712
714
} ) ;
713
715
} ) ;
714
716
717
+
715
718
describe ( "auth function" , ( ) => {
716
719
const mockProvider : OAuthClientProvider = {
717
720
get redirectUrl ( ) { return "http://localhost:3000/callback" ; } ,
@@ -727,12 +730,79 @@ describe("OAuth Authorization", () => {
727
730
redirectToAuthorization : jest . fn ( ) ,
728
731
saveCodeVerifier : jest . fn ( ) ,
729
732
codeVerifier : jest . fn ( ) ,
733
+ saveClientInformation : jest . fn ( ) ,
734
+ } ;
735
+
736
+ const validMetadata = {
737
+ issuer : "https://auth.example.com" ,
738
+ authorization_endpoint : "https://auth.example.com/authorize" ,
739
+ token_endpoint : "https://auth.example.com/token" ,
740
+ registration_endpoint : "https://auth.example.com/register" ,
741
+ response_types_supported : [ "code" ] ,
742
+ code_challenge_methods_supported : [ "S256" ] ,
743
+ } ;
744
+
745
+ const validClientInfo = {
746
+ client_id : "client123" ,
747
+ client_secret : "secret123" ,
748
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
749
+ client_name : "Test Client" ,
730
750
} ;
731
751
732
752
beforeEach ( ( ) => {
733
753
jest . clearAllMocks ( ) ;
734
754
} ) ;
735
755
756
+
757
+ it ( "uses scope from registered client information if not present in clientMetadata" , async ( ) => {
758
+ // Mock fetch for metadata discovery and registration
759
+ mockFetch
760
+ . mockResolvedValueOnce ( {
761
+ ok : true ,
762
+ status : 200 ,
763
+ json : async ( ) => ( { } ) ,
764
+ } ) // protected resource metadata
765
+ . mockResolvedValueOnce ( {
766
+ ok : true ,
767
+ status : 200 ,
768
+ json : async ( ) => validMetadata ,
769
+ } ) // discovery
770
+ . mockResolvedValueOnce ( {
771
+ ok : true ,
772
+ status : 200 ,
773
+ json : async ( ) => ( {
774
+ ...validClientInfo ,
775
+ scope : "dynamic scope from registration" ,
776
+ } ) ,
777
+ } ) ; // registration
778
+
779
+ // Provider: clientInformation returns undefined first, then fullInformation after registration
780
+ const fullInformation = {
781
+ ...validClientInfo ,
782
+ scope : "dynamic scope from registration" ,
783
+ } ;
784
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
785
+ const clientInformationMock = jest
786
+ . fn ( )
787
+ . mockResolvedValueOnce ( undefined )
788
+ . mockResolvedValueOnce ( fullInformation ) ;
789
+
790
+
791
+
792
+ await auth ( mockProvider , {
793
+ serverUrl : "https://auth.example.com" ,
794
+ } ) ;
795
+
796
+ // Check saveClientInformation was called with fullInformation
797
+ expect ( mockProvider . saveClientInformation ) . toHaveBeenCalledWith ( fullInformation ) ;
798
+
799
+ // Check that redirectToAuthorization was called with a URL containing the correct scope from registration
800
+ expect ( mockProvider . redirectToAuthorization ) . toHaveBeenCalledTimes ( 1 ) ;
801
+ const urlArg = ( mockProvider . redirectToAuthorization as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ;
802
+ expect ( urlArg ) . toBeInstanceOf ( URL ) ;
803
+ expect ( urlArg . searchParams . get ( "scope" ) ) . toBe ( "dynamic scope from registration" ) ;
804
+ } ) ;
805
+
736
806
it ( "falls back to /.well-known/oauth-authorization-server when no protected-resource-metadata" , async ( ) => {
737
807
// Setup: First call to protected resource metadata fails (404)
738
808
// Second call to auth server metadata succeeds
0 commit comments