Skip to content

Commit be22205

Browse files
committed
draft test
1 parent d11f2db commit be22205

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
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(/Access Token:/)).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

Comments
 (0)