Skip to content

Commit 03dc094

Browse files
committed
adding oauth proxy tests
1 parent 7b1d296 commit 03dc094

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/**
2+
* Tests for OAuth Proxy Utilities
3+
*/
4+
5+
import {
6+
discoverAuthorizationServerMetadataViaProxy,
7+
discoverOAuthProtectedResourceMetadataViaProxy,
8+
registerClientViaProxy,
9+
exchangeAuthorizationViaProxy,
10+
} from "../oauth-proxy";
11+
import { InspectorConfig } from "../configurationTypes";
12+
13+
// Mock the config utils
14+
jest.mock("@/utils/configUtils", () => ({
15+
getMCPProxyAddress: jest.fn(() => "http://localhost:6277"),
16+
getMCPProxyAuthToken: jest.fn(() => ({
17+
token: "test-token",
18+
header: "x-mcp-proxy-auth",
19+
})),
20+
}));
21+
22+
// Mock global fetch
23+
global.fetch = jest.fn();
24+
25+
const mockConfig: InspectorConfig = {
26+
MCP_SERVER_REQUEST_TIMEOUT: {
27+
label: "Request Timeout",
28+
description: "Timeout for MCP requests",
29+
value: 30000,
30+
is_session_item: false,
31+
},
32+
MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: {
33+
label: "Reset Timeout on Progress",
34+
description: "Reset timeout on progress notifications",
35+
value: true,
36+
is_session_item: false,
37+
},
38+
MCP_REQUEST_MAX_TOTAL_TIMEOUT: {
39+
label: "Max Total Timeout",
40+
description: "Maximum total timeout",
41+
value: 300000,
42+
is_session_item: false,
43+
},
44+
MCP_PROXY_FULL_ADDRESS: {
45+
label: "Proxy Address",
46+
description: "Full address of the MCP proxy",
47+
value: "http://localhost:6277",
48+
is_session_item: false,
49+
},
50+
MCP_PROXY_AUTH_TOKEN: {
51+
label: "Proxy Auth Token",
52+
description: "Authentication token for the proxy",
53+
value: "test-token",
54+
is_session_item: false,
55+
},
56+
};
57+
58+
describe("OAuth Proxy Utilities", () => {
59+
beforeEach(() => {
60+
jest.clearAllMocks();
61+
});
62+
63+
describe("discoverAuthorizationServerMetadataViaProxy", () => {
64+
it("should successfully fetch metadata through proxy", async () => {
65+
const mockMetadata = {
66+
issuer: "https://auth.example.com",
67+
authorization_endpoint: "https://auth.example.com/authorize",
68+
token_endpoint: "https://auth.example.com/token",
69+
response_types_supported: ["code"],
70+
grant_types_supported: ["authorization_code"],
71+
};
72+
73+
(global.fetch as jest.Mock).mockResolvedValueOnce({
74+
ok: true,
75+
json: async () => mockMetadata,
76+
});
77+
78+
const result = await discoverAuthorizationServerMetadataViaProxy(
79+
new URL("https://auth.example.com"),
80+
mockConfig,
81+
);
82+
83+
expect(result).toEqual(mockMetadata);
84+
expect(global.fetch).toHaveBeenCalledWith(
85+
"http://localhost:6277/oauth/metadata?authServerUrl=https%3A%2F%2Fauth.example.com%2F",
86+
{
87+
method: "GET",
88+
headers: {
89+
"Content-Type": "application/json",
90+
"x-mcp-proxy-auth": "Bearer test-token",
91+
},
92+
},
93+
);
94+
});
95+
96+
it("should handle network errors", async () => {
97+
(global.fetch as jest.Mock).mockRejectedValueOnce(
98+
new Error("Network error"),
99+
);
100+
101+
await expect(
102+
discoverAuthorizationServerMetadataViaProxy(
103+
new URL("https://auth.example.com"),
104+
mockConfig,
105+
),
106+
).rejects.toThrow("Network error");
107+
});
108+
109+
it("should handle non-ok responses", async () => {
110+
(global.fetch as jest.Mock).mockResolvedValueOnce({
111+
ok: false,
112+
statusText: "Not Found",
113+
json: async () => ({ error: "Metadata not found" }),
114+
});
115+
116+
await expect(
117+
discoverAuthorizationServerMetadataViaProxy(
118+
new URL("https://auth.example.com"),
119+
mockConfig,
120+
),
121+
).rejects.toThrow(
122+
"Failed to discover OAuth metadata: Metadata not found",
123+
);
124+
});
125+
126+
it("should handle responses without error details", async () => {
127+
(global.fetch as jest.Mock).mockResolvedValueOnce({
128+
ok: false,
129+
statusText: "Internal Server Error",
130+
json: async () => {
131+
throw new Error("Invalid JSON");
132+
},
133+
});
134+
135+
await expect(
136+
discoverAuthorizationServerMetadataViaProxy(
137+
new URL("https://auth.example.com"),
138+
mockConfig,
139+
),
140+
).rejects.toThrow(
141+
"Failed to discover OAuth metadata: Internal Server Error",
142+
);
143+
});
144+
});
145+
146+
describe("discoverOAuthProtectedResourceMetadataViaProxy", () => {
147+
it("should successfully fetch resource metadata through proxy", async () => {
148+
const mockMetadata = {
149+
resource: "https://api.example.com",
150+
authorization_servers: ["https://auth.example.com"],
151+
scopes_supported: ["read", "write"],
152+
};
153+
154+
(global.fetch as jest.Mock).mockResolvedValueOnce({
155+
ok: true,
156+
json: async () => mockMetadata,
157+
});
158+
159+
const result = await discoverOAuthProtectedResourceMetadataViaProxy(
160+
"https://api.example.com",
161+
mockConfig,
162+
);
163+
164+
expect(result).toEqual(mockMetadata);
165+
expect(global.fetch).toHaveBeenCalledWith(
166+
"http://localhost:6277/oauth/resource-metadata?serverUrl=https%3A%2F%2Fapi.example.com",
167+
{
168+
method: "GET",
169+
headers: {
170+
"Content-Type": "application/json",
171+
"x-mcp-proxy-auth": "Bearer test-token",
172+
},
173+
},
174+
);
175+
});
176+
177+
it("should handle errors", async () => {
178+
(global.fetch as jest.Mock).mockResolvedValueOnce({
179+
ok: false,
180+
statusText: "Not Found",
181+
json: async () => ({ error: "Resource metadata not found" }),
182+
});
183+
184+
await expect(
185+
discoverOAuthProtectedResourceMetadataViaProxy(
186+
"https://api.example.com",
187+
mockConfig,
188+
),
189+
).rejects.toThrow(
190+
"Failed to discover resource metadata: Resource metadata not found",
191+
);
192+
});
193+
});
194+
195+
describe("registerClientViaProxy", () => {
196+
it("should successfully register client through proxy", async () => {
197+
const clientMetadata = {
198+
client_name: "Test Client",
199+
redirect_uris: ["http://localhost:6274/oauth/callback"],
200+
grant_types: ["authorization_code"],
201+
};
202+
203+
const mockClientInformation = {
204+
client_id: "test-client-id",
205+
client_secret: "test-client-secret",
206+
...clientMetadata,
207+
};
208+
209+
(global.fetch as jest.Mock).mockResolvedValueOnce({
210+
ok: true,
211+
json: async () => mockClientInformation,
212+
});
213+
214+
const result = await registerClientViaProxy(
215+
"https://auth.example.com/register",
216+
clientMetadata,
217+
mockConfig,
218+
);
219+
220+
expect(result).toEqual(mockClientInformation);
221+
expect(global.fetch).toHaveBeenCalledWith(
222+
"http://localhost:6277/oauth/register",
223+
{
224+
method: "POST",
225+
headers: {
226+
"Content-Type": "application/json",
227+
"x-mcp-proxy-auth": "Bearer test-token",
228+
},
229+
body: JSON.stringify({
230+
registrationEndpoint: "https://auth.example.com/register",
231+
clientMetadata,
232+
}),
233+
},
234+
);
235+
});
236+
237+
it("should handle registration errors", async () => {
238+
(global.fetch as jest.Mock).mockResolvedValueOnce({
239+
ok: false,
240+
statusText: "Bad Request",
241+
json: async () => ({ error: "Invalid client metadata" }),
242+
});
243+
244+
await expect(
245+
registerClientViaProxy(
246+
"https://auth.example.com/register",
247+
{
248+
client_name: "Test",
249+
redirect_uris: ["http://localhost:6274/oauth/callback"],
250+
},
251+
mockConfig,
252+
),
253+
).rejects.toThrow("Failed to register client: Invalid client metadata");
254+
});
255+
});
256+
257+
describe("exchangeAuthorizationViaProxy", () => {
258+
it("should successfully exchange authorization code through proxy", async () => {
259+
const params = {
260+
grant_type: "authorization_code",
261+
code: "test-auth-code",
262+
redirect_uri: "http://localhost:6274/oauth/callback",
263+
code_verifier: "test-verifier",
264+
client_id: "test-client-id",
265+
};
266+
267+
const mockTokens = {
268+
access_token: "test-access-token",
269+
token_type: "Bearer",
270+
expires_in: 3600,
271+
};
272+
273+
(global.fetch as jest.Mock).mockResolvedValueOnce({
274+
ok: true,
275+
json: async () => mockTokens,
276+
});
277+
278+
const result = await exchangeAuthorizationViaProxy(
279+
"https://auth.example.com/token",
280+
params,
281+
mockConfig,
282+
);
283+
284+
expect(result).toEqual(mockTokens);
285+
expect(global.fetch).toHaveBeenCalledWith(
286+
"http://localhost:6277/oauth/token",
287+
{
288+
method: "POST",
289+
headers: {
290+
"Content-Type": "application/json",
291+
"x-mcp-proxy-auth": "Bearer test-token",
292+
},
293+
body: JSON.stringify({
294+
tokenEndpoint: "https://auth.example.com/token",
295+
params,
296+
}),
297+
},
298+
);
299+
});
300+
301+
it("should handle token exchange errors", async () => {
302+
(global.fetch as jest.Mock).mockResolvedValueOnce({
303+
ok: false,
304+
statusText: "Unauthorized",
305+
json: async () => ({ error: "invalid_grant" }),
306+
});
307+
308+
await expect(
309+
exchangeAuthorizationViaProxy(
310+
"https://auth.example.com/token",
311+
{ grant_type: "authorization_code", code: "invalid" },
312+
mockConfig,
313+
),
314+
).rejects.toThrow("Failed to exchange authorization code: invalid_grant");
315+
});
316+
317+
it("should handle network failures", async () => {
318+
(global.fetch as jest.Mock).mockRejectedValueOnce(
319+
new Error("Connection refused"),
320+
);
321+
322+
await expect(
323+
exchangeAuthorizationViaProxy(
324+
"https://auth.example.com/token",
325+
{ grant_type: "authorization_code", code: "test" },
326+
mockConfig,
327+
),
328+
).rejects.toThrow("Connection refused");
329+
});
330+
});
331+
});

0 commit comments

Comments
 (0)