@@ -106,7 +106,9 @@ describe("AuthDebugger", () => {
106
106
107
107
mockDiscoverOAuthMetadata . mockResolvedValue ( mockOAuthMetadata ) ;
108
108
mockRegisterClient . mockResolvedValue ( mockOAuthClientInfo ) ;
109
- mockDiscoverOAuthProtectedResourceMetadata . mockResolvedValue ( null ) ;
109
+ mockDiscoverOAuthProtectedResourceMetadata . mockRejectedValue (
110
+ new Error ( "No protected resource metadata found" )
111
+ ) ;
110
112
mockStartAuthorization . mockImplementation ( async ( _sseUrl , options ) => {
111
113
const authUrl = new URL ( "https://oauth.example.com/authorize" ) ;
112
114
@@ -448,7 +450,7 @@ describe("AuthDebugger", () => {
448
450
await act ( async ( ) => {
449
451
renderAuthDebugger ( {
450
452
updateAuthState,
451
- authState : { ...defaultAuthState , loading : false } ,
453
+ authState : { ...defaultAuthState } ,
452
454
} ) ;
453
455
} ) ;
454
456
@@ -471,7 +473,7 @@ describe("AuthDebugger", () => {
471
473
) . mock . calls . find ( ( call ) => call [ 0 ] === SESSION_KEYS . AUTH_DEBUGGER_STATE ) ;
472
474
473
475
expect ( storedStateCall ) . toBeDefined ( ) ;
474
- const storedState = JSON . parse ( storedStateCall ! [ 1 ] ) ;
476
+ const storedState = JSON . parse ( storedStateCall ! [ 1 ] as string ) ;
475
477
476
478
expect ( storedState ) . toMatchObject ( {
477
479
oauthStep : "authorization_code" ,
@@ -487,4 +489,119 @@ describe("AuthDebugger", () => {
487
489
} ) ;
488
490
} ) ;
489
491
} ) ;
492
+
493
+ describe ( "OAuth Protected Resource Metadata" , ( ) => {
494
+ it ( "should successfully fetch and display protected resource metadata" , async ( ) => {
495
+ const updateAuthState = jest . fn ( ) ;
496
+ const mockResourceMetadata = {
497
+ resource : "https://example.com/api" ,
498
+ authorization_servers : [ "https://custom-auth.example.com" ] ,
499
+ bearer_methods_supported : [ "header" , "body" ] ,
500
+ resource_documentation : "https://example.com/api/docs" ,
501
+ resource_policy_uri : "https://example.com/api/policy" ,
502
+ } ;
503
+
504
+ // Mock successful metadata discovery
505
+ mockDiscoverOAuthProtectedResourceMetadata . mockResolvedValue (
506
+ mockResourceMetadata
507
+ ) ;
508
+ mockDiscoverOAuthMetadata . mockResolvedValue ( mockOAuthMetadata ) ;
509
+
510
+ await act ( async ( ) => {
511
+ renderAuthDebugger ( {
512
+ updateAuthState,
513
+ authState : { ...defaultAuthState } ,
514
+ } ) ;
515
+ } ) ;
516
+
517
+ // Click Guided OAuth Flow to start the process
518
+ await act ( async ( ) => {
519
+ fireEvent . click ( screen . getByText ( "Guided OAuth Flow" ) ) ;
520
+ } ) ;
521
+
522
+ // Verify that the flow started with metadata discovery
523
+ expect ( updateAuthState ) . toHaveBeenCalledWith ( {
524
+ oauthStep : "metadata_discovery" ,
525
+ authorizationUrl : null ,
526
+ statusMessage : null ,
527
+ latestError : null ,
528
+ } ) ;
529
+
530
+ // Click Continue to trigger metadata discovery
531
+ const continueButton = await screen . findByText ( "Continue" ) ;
532
+ await act ( async ( ) => {
533
+ fireEvent . click ( continueButton ) ;
534
+ } ) ;
535
+
536
+ // Wait for the metadata to be fetched
537
+ await waitFor ( ( ) => {
538
+ expect ( mockDiscoverOAuthProtectedResourceMetadata ) . toHaveBeenCalledWith (
539
+ "https://example.com"
540
+ ) ;
541
+ } ) ;
542
+
543
+ // Verify the state was updated with the resource metadata
544
+ await waitFor ( ( ) => {
545
+ expect ( updateAuthState ) . toHaveBeenCalledWith (
546
+ expect . objectContaining ( {
547
+ resourceMetadata : mockResourceMetadata ,
548
+ authServerUrl : new URL ( "https://custom-auth.example.com" ) ,
549
+ oauthStep : "client_registration" ,
550
+ } )
551
+ ) ;
552
+ } ) ;
553
+ } ) ;
554
+
555
+ it ( "should handle protected resource metadata fetch failure gracefully" , async ( ) => {
556
+ const updateAuthState = jest . fn ( ) ;
557
+ const mockError = new Error ( "Failed to fetch resource metadata" ) ;
558
+
559
+ // Mock failed metadata discovery
560
+ mockDiscoverOAuthProtectedResourceMetadata . mockRejectedValue ( mockError ) ;
561
+ // But OAuth metadata should still work with the original URL
562
+ mockDiscoverOAuthMetadata . mockResolvedValue ( mockOAuthMetadata ) ;
563
+
564
+ await act ( async ( ) => {
565
+ renderAuthDebugger ( {
566
+ updateAuthState,
567
+ authState : { ...defaultAuthState } ,
568
+ } ) ;
569
+ } ) ;
570
+
571
+ // Click Guided OAuth Flow
572
+ await act ( async ( ) => {
573
+ fireEvent . click ( screen . getByText ( "Guided OAuth Flow" ) ) ;
574
+ } ) ;
575
+
576
+ // Click Continue to trigger metadata discovery
577
+ const continueButton = await screen . findByText ( "Continue" ) ;
578
+ await act ( async ( ) => {
579
+ fireEvent . click ( continueButton ) ;
580
+ } ) ;
581
+
582
+ // Wait for the metadata fetch to fail
583
+ await waitFor ( ( ) => {
584
+ expect ( mockDiscoverOAuthProtectedResourceMetadata ) . toHaveBeenCalledWith (
585
+ "https://example.com"
586
+ ) ;
587
+ } ) ;
588
+
589
+ // Verify the flow continues despite the error
590
+ await waitFor ( ( ) => {
591
+ expect ( updateAuthState ) . toHaveBeenCalledWith (
592
+ expect . objectContaining ( {
593
+ resourceMetadataError : mockError ,
594
+ // Should use the original server URL as fallback
595
+ authServerUrl : new URL ( "https://example.com" ) ,
596
+ oauthStep : "client_registration" ,
597
+ } )
598
+ ) ;
599
+ } ) ;
600
+
601
+ // Verify that regular OAuth metadata discovery was still called
602
+ expect ( mockDiscoverOAuthMetadata ) . toHaveBeenCalledWith (
603
+ new URL ( "https://example.com" )
604
+ ) ;
605
+ } ) ;
606
+ } ) ;
490
607
} ) ;
0 commit comments