@@ -9,8 +9,8 @@ import "@testing-library/jest-dom";
9
9
import { describe , it , beforeEach , jest } from "@jest/globals" ;
10
10
import AuthDebugger , { AuthDebuggerProps } from "../AuthDebugger" ;
11
11
import { TooltipProvider } from "@/components/ui/tooltip" ;
12
+ import { SESSION_KEYS } from "@/lib/constants" ;
12
13
13
- // Mock OAuth data that matches the schemas
14
14
const mockOAuthTokens = {
15
15
access_token : "test_access_token" ,
16
16
token_type : "Bearer" ,
@@ -49,6 +49,7 @@ import {
49
49
startAuthorization ,
50
50
exchangeAuthorization ,
51
51
} from "@modelcontextprotocol/sdk/client/auth.js" ;
52
+ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js" ;
52
53
53
54
// Type the mocked functions properly
54
55
const mockDiscoverOAuthMetadata = discoverOAuthMetadata as jest . MockedFunction <
@@ -64,7 +65,6 @@ const mockExchangeAuthorization = exchangeAuthorization as jest.MockedFunction<
64
65
typeof exchangeAuthorization
65
66
> ;
66
67
67
- // Mock Session Storage
68
68
const sessionStorageMock = {
69
69
getItem : jest . fn ( ) ,
70
70
setItem : jest . fn ( ) ,
@@ -75,7 +75,6 @@ Object.defineProperty(window, "sessionStorage", {
75
75
value : sessionStorageMock ,
76
76
} ) ;
77
77
78
- // Mock the location.origin
79
78
Object . defineProperty ( window , "location" , {
80
79
value : {
81
80
origin : "http://localhost:3000" ,
@@ -108,12 +107,19 @@ describe("AuthDebugger", () => {
108
107
jest . clearAllMocks ( ) ;
109
108
sessionStorageMock . getItem . mockReturnValue ( null ) ;
110
109
111
- // Set up mock implementations
112
110
mockDiscoverOAuthMetadata . mockResolvedValue ( mockOAuthMetadata ) ;
113
111
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
+ } ;
117
123
} ) ;
118
124
mockExchangeAuthorization . mockResolvedValue ( mockOAuthTokens ) ;
119
125
} ) ;
@@ -300,5 +306,76 @@ describe("AuthDebugger", () => {
300
306
"https://example.com" ,
301
307
) ;
302
308
} ) ;
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
+ } ) ;
303
380
} ) ;
304
381
} ) ;
0 commit comments