-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathoauth-utils.test.ts
More file actions
149 lines (125 loc) · 5.55 KB
/
oauth-utils.test.ts
File metadata and controls
149 lines (125 loc) · 5.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
* Unit tests for OAuth utility functions
*/
import { discoverTokenEndpoint } from '../../../../src/lib/auth/oauth-utils.js';
import * as proxyModule from '../../../../src/lib/proxy.js';
// Helper to create a mock fetch Response
function mockResponse(body: object | null, ok = true): Response {
return {
ok,
json: () => Promise.resolve(body),
} as unknown as Response;
}
describe('discoverTokenEndpoint', () => {
let fetchSpy: jest.SpyInstance;
beforeEach(() => {
fetchSpy = jest.spyOn(proxyModule, 'proxyFetch');
});
afterEach(() => {
fetchSpy.mockRestore();
});
it('returns token endpoint from path-based oauth-authorization-server', async () => {
fetchSpy.mockImplementation((url: string) => {
if (url === 'https://example.com/mcp/.well-known/oauth-authorization-server') {
return Promise.resolve(mockResponse({ token_endpoint: 'https://example.com/token' }));
}
return Promise.resolve(mockResponse(null, false));
});
const result = await discoverTokenEndpoint('https://example.com/mcp');
expect(result).toBe('https://example.com/token');
});
it('falls back to path-based openid-configuration when oauth-authorization-server has no token_endpoint', async () => {
fetchSpy.mockImplementation((url: string) => {
if (url === 'https://example.com/.well-known/oauth-authorization-server') {
return Promise.resolve(mockResponse({})); // no token_endpoint
}
if (url === 'https://example.com/.well-known/openid-configuration') {
return Promise.resolve(mockResponse({ token_endpoint: 'https://example.com/oidc/token' }));
}
return Promise.resolve(mockResponse(null, false));
});
const result = await discoverTokenEndpoint('https://example.com');
expect(result).toBe('https://example.com/oidc/token');
});
it('falls back to root-based discovery when path-based URLs return no token_endpoint', async () => {
fetchSpy.mockImplementation((url: string) => {
if (url === 'https://example.com/.well-known/oauth-authorization-server') {
return Promise.resolve(mockResponse({ token_endpoint: 'https://example.com/token' }));
}
return Promise.resolve(mockResponse(null, false));
});
const result = await discoverTokenEndpoint('https://example.com/mcp');
expect(result).toBe('https://example.com/token');
});
it('returns undefined when no discovery URL returns a token endpoint', async () => {
fetchSpy.mockResolvedValue(mockResponse(null, false));
const result = await discoverTokenEndpoint('https://example.com/mcp');
expect(result).toBeUndefined();
});
it('handles fetch errors gracefully and continues to next URL', async () => {
fetchSpy.mockImplementation((url: string) => {
if (url === 'https://example.com/mcp/.well-known/oauth-authorization-server') {
return Promise.reject(new Error('Network error'));
}
if (url === 'https://example.com/mcp/.well-known/openid-configuration') {
return Promise.resolve(mockResponse({ token_endpoint: 'https://example.com/token' }));
}
return Promise.resolve(mockResponse(null, false));
});
const result = await discoverTokenEndpoint('https://example.com/mcp');
expect(result).toBe('https://example.com/token');
});
it('trims trailing slashes from serverUrl before building discovery URLs', async () => {
const expectedUrls = [
'https://example.com/mcp/.well-known/oauth-authorization-server',
'https://example.com/mcp/.well-known/openid-configuration',
'https://example.com/.well-known/oauth-authorization-server',
'https://example.com/.well-known/openid-configuration',
];
for (const trailingSlashes of ['/', '///']) {
const calledUrls: string[] = [];
fetchSpy.mockImplementation((url: string) => {
calledUrls.push(url);
return Promise.resolve(mockResponse(null, false));
});
await discoverTokenEndpoint(`https://example.com/mcp${trailingSlashes}`);
expect(calledUrls).toEqual(expectedUrls);
}
});
it('does not add duplicate root-based URLs when serverUrl is already root', async () => {
const calledUrls: string[] = [];
fetchSpy.mockImplementation((url: string) => {
calledUrls.push(url);
return Promise.resolve(mockResponse(null, false));
});
await discoverTokenEndpoint('https://example.com');
expect(calledUrls).toHaveLength(2);
expect(calledUrls).toEqual([
'https://example.com/.well-known/oauth-authorization-server',
'https://example.com/.well-known/openid-configuration',
]);
});
it('does not add duplicate root-based URLs when serverUrl has trailing slash only', async () => {
const calledUrls: string[] = [];
fetchSpy.mockImplementation((url: string) => {
calledUrls.push(url);
return Promise.resolve(mockResponse(null, false));
});
await discoverTokenEndpoint('https://example.com/');
expect(calledUrls).toHaveLength(2);
});
it('tries all 4 discovery URLs for a path-based serverUrl', async () => {
const calledUrls: string[] = [];
fetchSpy.mockImplementation((url: string) => {
calledUrls.push(url);
return Promise.resolve(mockResponse(null, false));
});
await discoverTokenEndpoint('https://example.com/mcp');
expect(calledUrls).toEqual([
'https://example.com/mcp/.well-known/oauth-authorization-server',
'https://example.com/mcp/.well-known/openid-configuration',
'https://example.com/.well-known/oauth-authorization-server',
'https://example.com/.well-known/openid-configuration',
]);
});
});