@@ -9,8 +9,8 @@ import "@testing-library/jest-dom";
99import { describe , it , beforeEach , jest } from "@jest/globals" ;
1010import AuthDebugger , { AuthDebuggerProps } from "../AuthDebugger" ;
1111import { TooltipProvider } from "@/components/ui/tooltip" ;
12+ import { SESSION_KEYS } from "@/lib/constants" ;
1213
13- // Mock OAuth data that matches the schemas
1414const mockOAuthTokens = {
1515 access_token : "test_access_token" ,
1616 token_type : "Bearer" ,
@@ -49,6 +49,7 @@ import {
4949 startAuthorization ,
5050 exchangeAuthorization ,
5151} from "@modelcontextprotocol/sdk/client/auth.js" ;
52+ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js" ;
5253
5354// Type the mocked functions properly
5455const mockDiscoverOAuthMetadata = discoverOAuthMetadata as jest . MockedFunction <
@@ -64,7 +65,6 @@ const mockExchangeAuthorization = exchangeAuthorization as jest.MockedFunction<
6465 typeof exchangeAuthorization
6566> ;
6667
67- // Mock Session Storage
6868const sessionStorageMock = {
6969 getItem : jest . fn ( ) ,
7070 setItem : jest . fn ( ) ,
@@ -75,7 +75,6 @@ Object.defineProperty(window, "sessionStorage", {
7575 value : sessionStorageMock ,
7676} ) ;
7777
78- // Mock the location.origin
7978Object . defineProperty ( window , "location" , {
8079 value : {
8180 origin : "http://localhost:3000" ,
@@ -108,12 +107,19 @@ describe("AuthDebugger", () => {
108107 jest . clearAllMocks ( ) ;
109108 sessionStorageMock . getItem . mockReturnValue ( null ) ;
110109
111- // Set up mock implementations
112110 mockDiscoverOAuthMetadata . mockResolvedValue ( mockOAuthMetadata ) ;
113111 mockRegisterClient . mockResolvedValue ( mockOAuthClientInfo ) ;
114- mockStartAuthorization . mockResolvedValue ( {
115- authorizationUrl : new URL ( "https://oauth.example.com/authorize" ) ,
116- codeVerifier : "test_verifier" ,
112+ mockStartAuthorization . mockImplementation ( async ( _sseUrl , options ) => {
113+ const authUrl = new URL ( "https://oauth.example.com/authorize" ) ;
114+
115+ if ( options . scope ) {
116+ authUrl . searchParams . set ( "scope" , options . scope ) ;
117+ }
118+
119+ return {
120+ authorizationUrl : authUrl ,
121+ codeVerifier : "test_verifier" ,
122+ } ;
117123 } ) ;
118124 mockExchangeAuthorization . mockResolvedValue ( mockOAuthTokens ) ;
119125 } ) ;
@@ -300,5 +306,76 @@ describe("AuthDebugger", () => {
300306 "https://example.com" ,
301307 ) ;
302308 } ) ;
309+
310+ // Setup helper for OAuth authorization tests
311+ const setupAuthorizationUrlTest = async ( metadata : OAuthMetadata ) => {
312+ const updateAuthState = jest . fn ( ) ;
313+
314+ // Mock the session storage to return metadata
315+ sessionStorageMock . getItem . mockImplementation ( ( key ) => {
316+ if ( key === `[https://example.com] ${ SESSION_KEYS . SERVER_METADATA } ` ) {
317+ return JSON . stringify ( metadata ) ;
318+ }
319+ if (
320+ key === `[https://example.com] ${ SESSION_KEYS . CLIENT_INFORMATION } `
321+ ) {
322+ return JSON . stringify ( mockOAuthClientInfo ) ;
323+ }
324+ return null ;
325+ } ) ;
326+
327+ await act ( async ( ) => {
328+ renderAuthDebugger ( {
329+ updateAuthState,
330+ authState : {
331+ ...defaultAuthState ,
332+ isInitiatingAuth : false ,
333+ oauthStep : "client_registration" ,
334+ oauthMetadata : metadata ,
335+ oauthClientInfo : mockOAuthClientInfo ,
336+ } ,
337+ } ) ;
338+ } ) ;
339+
340+ // Click Continue to trigger authorization
341+ await act ( async ( ) => {
342+ fireEvent . click ( screen . getByText ( "Continue" ) ) ;
343+ } ) ;
344+
345+ return updateAuthState ;
346+ } ;
347+
348+ it ( "should include scope in authorization URL when scopes_supported is present" , async ( ) => {
349+ const metadataWithScopes = {
350+ ...mockOAuthMetadata ,
351+ scopes_supported : [ "read" , "write" , "admin" ] ,
352+ } ;
353+
354+ const updateAuthState =
355+ await setupAuthorizationUrlTest ( metadataWithScopes ) ;
356+
357+ // Wait for the updateAuthState to be called
358+ await waitFor ( ( ) => {
359+ expect ( updateAuthState ) . toHaveBeenCalledWith (
360+ expect . objectContaining ( {
361+ authorizationUrl : expect . stringContaining ( "scope=" ) ,
362+ } ) ,
363+ ) ;
364+ } ) ;
365+ } ) ;
366+
367+ it ( "should not include scope in authorization URL when scopes_supported is not present" , async ( ) => {
368+ const updateAuthState =
369+ await setupAuthorizationUrlTest ( mockOAuthMetadata ) ;
370+
371+ // Wait for the updateAuthState to be called
372+ await waitFor ( ( ) => {
373+ expect ( updateAuthState ) . toHaveBeenCalledWith (
374+ expect . objectContaining ( {
375+ authorizationUrl : expect . not . stringContaining ( "scope=" ) ,
376+ } ) ,
377+ ) ;
378+ } ) ;
379+ } ) ;
303380 } ) ;
304381} ) ;
0 commit comments