1
+ import { render , screen , fireEvent , waitFor , act } from "@testing-library/react" ;
2
+ import "@testing-library/jest-dom" ;
3
+ import { describe , it , beforeEach , jest } from "@jest/globals" ;
4
+ import AuthDebugger from "../AuthDebugger" ;
5
+ import { TooltipProvider } from "@/components/ui/tooltip" ;
6
+
7
+ // Mock OAuth data that matches the schemas
8
+ const mockOAuthTokens = {
9
+ access_token : "test_access_token" ,
10
+ token_type : "Bearer" ,
11
+ expires_in : 3600 ,
12
+ refresh_token : "test_refresh_token" ,
13
+ scope : "test_scope"
14
+ } ;
15
+
16
+ const mockOAuthMetadata = {
17
+ issuer : "https://oauth.example.com" ,
18
+ authorization_endpoint : "https://oauth.example.com/authorize" ,
19
+ token_endpoint : "https://oauth.example.com/token" ,
20
+ response_types_supported : [ "code" ] ,
21
+ grant_types_supported : [ "authorization_code" ] ,
22
+ } ;
23
+
24
+ const mockOAuthClientInfo = {
25
+ client_id : "test_client_id" ,
26
+ client_secret : "test_client_secret" ,
27
+ redirect_uris : [ "http://localhost:3000/oauth/callback/debug" ] ,
28
+ } ;
29
+
30
+ // Mock the toast hook
31
+ const mockToast = jest . fn ( ) ;
32
+ jest . mock ( "@/hooks/use-toast" , ( ) => ( {
33
+ useToast : ( ) => ( {
34
+ toast : mockToast ,
35
+ } ) ,
36
+ } ) ) ;
37
+
38
+ // Mock MCP SDK functions
39
+ jest . mock ( "@modelcontextprotocol/sdk/client/auth.js" , ( ) => ( {
40
+ auth : jest . fn ( ) ,
41
+ discoverOAuthMetadata : jest . fn ( ) ,
42
+ registerClient : jest . fn ( ) ,
43
+ startAuthorization : jest . fn ( ) ,
44
+ exchangeAuthorization : jest . fn ( ) ,
45
+ } ) ) ;
46
+
47
+ // Import mocked functions
48
+ import {
49
+ auth as mockAuth ,
50
+ discoverOAuthMetadata as mockDiscoverOAuthMetadata ,
51
+ registerClient as mockRegisterClient ,
52
+ startAuthorization as mockStartAuthorization ,
53
+ exchangeAuthorization as mockExchangeAuthorization ,
54
+ } from "@modelcontextprotocol/sdk/client/auth.js" ;
55
+
56
+ // Mock Session Storage
57
+ const sessionStorageMock = {
58
+ getItem : jest . fn ( ) ,
59
+ setItem : jest . fn ( ) ,
60
+ removeItem : jest . fn ( ) ,
61
+ clear : jest . fn ( ) ,
62
+ } ;
63
+ Object . defineProperty ( window , 'sessionStorage' , {
64
+ value : sessionStorageMock ,
65
+ } ) ;
66
+
67
+ // Mock the location.origin
68
+ Object . defineProperty ( window , 'location' , {
69
+ value : {
70
+ origin : 'http://localhost:3000' ,
71
+ } ,
72
+ } ) ;
73
+
74
+ describe ( "AuthDebugger" , ( ) => {
75
+ const defaultProps = {
76
+ sseUrl : "https://example.com" ,
77
+ onBack : jest . fn ( ) ,
78
+ } ;
79
+
80
+ beforeEach ( ( ) => {
81
+ jest . clearAllMocks ( ) ;
82
+ sessionStorageMock . getItem . mockReturnValue ( null ) ;
83
+ ( mockDiscoverOAuthMetadata as jest . Mock ) . mockResolvedValue ( mockOAuthMetadata ) ;
84
+ ( mockRegisterClient as jest . Mock ) . mockResolvedValue ( mockOAuthClientInfo ) ;
85
+ ( mockStartAuthorization as jest . Mock ) . mockResolvedValue ( {
86
+ authorizationUrl : new URL ( "https://oauth.example.com/authorize" ) ,
87
+ codeVerifier : "test_verifier"
88
+ } ) ;
89
+ ( mockExchangeAuthorization as jest . Mock ) . mockResolvedValue ( mockOAuthTokens ) ;
90
+ } ) ;
91
+
92
+ const renderAuthDebugger = ( props = { } ) => {
93
+ return render (
94
+ < TooltipProvider >
95
+ < AuthDebugger { ...defaultProps } { ...props } />
96
+ </ TooltipProvider >
97
+ ) ;
98
+ } ;
99
+
100
+ describe ( "Initial Rendering" , ( ) => {
101
+ it ( "should render the component with correct title" , async ( ) => {
102
+ await act ( async ( ) => {
103
+ renderAuthDebugger ( ) ;
104
+ } ) ;
105
+ expect ( screen . getByText ( "Authentication Settings" ) ) . toBeInTheDocument ( ) ;
106
+ } ) ;
107
+
108
+ it ( "should call onBack when Back button is clicked" , async ( ) => {
109
+ const onBack = jest . fn ( ) ;
110
+ await act ( async ( ) => {
111
+ renderAuthDebugger ( { onBack } ) ;
112
+ } ) ;
113
+ fireEvent . click ( screen . getByText ( "Back to Connect" ) ) ;
114
+ expect ( onBack ) . toHaveBeenCalled ( ) ;
115
+ } ) ;
116
+ } ) ;
117
+
118
+ describe ( "OAuth Flow" , ( ) => {
119
+ it ( "should start OAuth flow when 'Guided OAuth Flow' is clicked" , async ( ) => {
120
+ await act ( async ( ) => {
121
+ renderAuthDebugger ( ) ;
122
+ } ) ;
123
+
124
+ await act ( async ( ) => {
125
+ fireEvent . click ( screen . getByText ( "Guided OAuth Flow" ) ) ;
126
+ } ) ;
127
+
128
+ expect ( screen . getByText ( "OAuth Flow Progress" ) ) . toBeInTheDocument ( ) ;
129
+ } ) ;
130
+
131
+ it ( "should show error when OAuth flow is started without sseUrl" , async ( ) => {
132
+ await act ( async ( ) => {
133
+ renderAuthDebugger ( { sseUrl : "" } ) ;
134
+ } ) ;
135
+
136
+ await act ( async ( ) => {
137
+ fireEvent . click ( screen . getByText ( "Guided OAuth Flow" ) ) ;
138
+ } ) ;
139
+
140
+ expect ( mockToast ) . toHaveBeenCalledWith ( {
141
+ title : "Error" ,
142
+ description : "Please enter a server URL in the sidebar before authenticating" ,
143
+ variant : "destructive" ,
144
+ } ) ;
145
+ } ) ;
146
+ } ) ;
147
+
148
+ describe ( "Session Storage Integration" , ( ) => {
149
+ it ( "should load OAuth tokens from session storage" , async ( ) => {
150
+ // Mock the specific key for tokens with server URL
151
+ sessionStorageMock . getItem . mockImplementation ( ( key ) => {
152
+ if ( key === "[https://example.com] mcp_tokens" ) {
153
+ return JSON . stringify ( mockOAuthTokens ) ;
154
+ }
155
+ return null ;
156
+ } ) ;
157
+
158
+ await act ( async ( ) => {
159
+ renderAuthDebugger ( ) ;
160
+ } ) ;
161
+
162
+ await waitFor ( ( ) => {
163
+ expect ( screen . getByText ( / A c c e s s T o k e n : / ) ) . toBeInTheDocument ( ) ;
164
+ } ) ;
165
+ } ) ;
166
+
167
+ it ( "should handle errors loading OAuth tokens from session storage" , async ( ) => {
168
+ // Mock console to avoid cluttering test output
169
+ const originalError = console . error ;
170
+ console . error = jest . fn ( ) ;
171
+
172
+ // Mock getItem to return invalid JSON for tokens
173
+ sessionStorageMock . getItem . mockImplementation ( ( key ) => {
174
+ if ( key === "[https://example.com] mcp_tokens" ) {
175
+ return "invalid json" ;
176
+ }
177
+ return null ;
178
+ } ) ;
179
+
180
+ await act ( async ( ) => {
181
+ renderAuthDebugger ( ) ;
182
+ } ) ;
183
+
184
+ // Component should still render despite the error
185
+ expect ( screen . getByText ( "Authentication Settings" ) ) . toBeInTheDocument ( ) ;
186
+
187
+ // Restore console.error
188
+ console . error = originalError ;
189
+ } ) ;
190
+ } ) ;
191
+
192
+ describe ( "OAuth State Management" , ( ) => {
193
+ it ( "should clear OAuth state when Clear button is clicked" , async ( ) => {
194
+ // Mock the session storage to return tokens for the specific key
195
+ sessionStorageMock . getItem . mockImplementation ( ( key ) => {
196
+ if ( key === "[https://example.com] mcp_tokens" ) {
197
+ return JSON . stringify ( mockOAuthTokens ) ;
198
+ }
199
+ return null ;
200
+ } ) ;
201
+
202
+ await act ( async ( ) => {
203
+ renderAuthDebugger ( ) ;
204
+ } ) ;
205
+
206
+ await act ( async ( ) => {
207
+ fireEvent . click ( screen . getByText ( "Clear OAuth State" ) ) ;
208
+ } ) ;
209
+
210
+ expect ( mockToast ) . toHaveBeenCalledWith ( {
211
+ title : "Success" ,
212
+ description : "OAuth tokens cleared successfully" ,
213
+ } ) ;
214
+
215
+ // Verify session storage was cleared
216
+ expect ( sessionStorageMock . removeItem ) . toHaveBeenCalled ( ) ;
217
+ } ) ;
218
+ } ) ;
219
+
220
+ describe ( "OAuth Flow Steps" , ( ) => {
221
+ it ( "should handle OAuth flow step progression" , async ( ) => {
222
+ await act ( async ( ) => {
223
+ renderAuthDebugger ( ) ;
224
+ } ) ;
225
+
226
+ // Start guided flow
227
+ await act ( async ( ) => {
228
+ fireEvent . click ( screen . getByText ( "Guided OAuth Flow" ) ) ;
229
+ } ) ;
230
+
231
+ // Verify metadata discovery step
232
+ expect ( screen . getByText ( "Metadata Discovery" ) ) . toBeInTheDocument ( ) ;
233
+
234
+ // Click Continue - this should trigger metadata discovery
235
+ await act ( async ( ) => {
236
+ fireEvent . click ( screen . getByText ( "Continue" ) ) ;
237
+ } ) ;
238
+
239
+ expect ( mockDiscoverOAuthMetadata ) . toHaveBeenCalledWith ( "https://example.com" ) ;
240
+ } ) ;
241
+ } ) ;
242
+ } ) ;
0 commit comments