Migrate to vitest from jest (#1074)

Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
This commit is contained in:
Matt 2025-11-14 14:08:00 +00:00 • committed by GitHub
commit 98df118a4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -15,9 +15,18 @@ import {
} from './auth.js';
import { ServerError } from '../server/auth/errors.js';
import { AuthorizationServerMetadata } from '../shared/auth.js';
import { expect, vi, type Mock } from 'vitest';
// Mock pkce-challenge
vi.mock('pkce-challenge', () => ({
default: () => ({
code_verifier: 'test_verifier',
code_challenge: 'test_challenge'
})
}));
// Mock fetch globally
const mockFetch = jest.fn();
const mockFetch = vi.fn();
global.fetch = mockFetch;
describe('OAuth Authorization', () => {
@ -30,7 +39,7 @@ describe('OAuth Authorization', () => {
const resourceUrl = 'https://resource.example.com/.well-known/oauth-protected-resource';
const mockResponse = {
headers: {
get: jest.fn(name => (name === 'WWW-Authenticate' ? `Bearer realm="mcp", resource_metadata="${resourceUrl}"` : null))
get: vi.fn(name => (name === 'WWW-Authenticate' ? `Bearer realm="mcp", resource_metadata="${resourceUrl}"` : null))
}
} as unknown as Response;
@ -41,7 +50,7 @@ describe('OAuth Authorization', () => {
const scope = 'read';
const mockResponse = {
headers: {
get: jest.fn(name => (name === 'WWW-Authenticate' ? `Bearer realm="mcp", scope="${scope}"` : null))
get: vi.fn(name => (name === 'WWW-Authenticate' ? `Bearer realm="mcp", scope="${scope}"` : null))
}
} as unknown as Response;
@ -53,7 +62,7 @@ describe('OAuth Authorization', () => {
const scope = 'read';
const mockResponse = {
headers: {
get: jest.fn(name =>
get: vi.fn(name =>
name === 'WWW-Authenticate' ? `Basic realm="mcp", resource_metadata="${resourceUrl}", scope="${scope}"` : null
)
}
@ -65,7 +74,7 @@ describe('OAuth Authorization', () => {
it('returns empty object if resource_metadata and scope not present', async () => {
const mockResponse = {
headers: {
get: jest.fn(name => (name === 'WWW-Authenticate' ? `Bearer realm="mcp"` : null))
get: vi.fn(name => (name === 'WWW-Authenticate' ? `Bearer realm="mcp"` : null))
}
} as unknown as Response;
@ -77,7 +86,7 @@ describe('OAuth Authorization', () => {
const scope = 'read';
const mockResponse = {
headers: {
get: jest.fn(name =>
get: vi.fn(name =>
name === 'WWW-Authenticate' ? `Bearer realm="mcp", resource_metadata="${resourceUrl}", scope="${scope}"` : null
)
}
@ -393,7 +402,7 @@ describe('OAuth Authorization', () => {
authorization_servers: ['https://auth.example.com']
};
const customFetch = jest.fn().mockResolvedValue({
const customFetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => validMetadata
@ -689,7 +698,7 @@ describe('OAuth Authorization', () => {
code_challenge_methods_supported: ['S256']
};
const customFetch = jest.fn().mockResolvedValue({
const customFetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => validMetadata
@ -851,7 +860,7 @@ describe('OAuth Authorization', () => {
});
it('supports custom fetch function', async () => {
const customFetch = jest.fn().mockResolvedValue({
const customFetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => validOAuthMetadata
@ -1115,14 +1124,16 @@ describe('OAuth Authorization', () => {
href: 'https://auth.example.com/token'
}),
expect.objectContaining({
method: 'POST',
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
method: 'POST'
})
);
const body = mockFetch.mock.calls[0][1].body as URLSearchParams;
const options = mockFetch.mock.calls[0][1];
expect(options.headers).toBeInstanceOf(Headers);
expect(options.headers.get('Content-Type')).toBe('application/x-www-form-urlencoded');
expect(options.body).toBeInstanceOf(URLSearchParams);
const body = options.body as URLSearchParams;
expect(body.get('grant_type')).toBe('authorization_code');
expect(body.get('code')).toBe('code123');
expect(body.get('code_verifier')).toBe('verifier123');
@ -1217,7 +1228,7 @@ describe('OAuth Authorization', () => {
});
it('supports overriding the fetch function used for requests', async () => {
const customFetch = jest.fn().mockResolvedValue({
const customFetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => validTokens
@ -1506,16 +1517,16 @@ describe('OAuth Authorization', () => {
client_name: 'Test Client'
};
},
clientInformation: jest.fn(),
tokens: jest.fn(),
saveTokens: jest.fn(),
redirectToAuthorization: jest.fn(),
saveCodeVerifier: jest.fn(),
codeVerifier: jest.fn()
clientInformation: vi.fn(),
tokens: vi.fn(),
saveTokens: vi.fn(),
redirectToAuthorization: vi.fn(),
saveCodeVerifier: vi.fn(),
codeVerifier: vi.fn()
};
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('falls back to /.well-known/oauth-authorization-server when no protected-resource-metadata', async () => {
@ -1567,9 +1578,9 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods
(mockProvider.clientInformation as jest.Mock).mockResolvedValue(undefined);
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
mockProvider.saveClientInformation = jest.fn();
(mockProvider.clientInformation as Mock).mockResolvedValue(undefined);
(mockProvider.tokens as Mock).mockResolvedValue(undefined);
mockProvider.saveClientInformation = vi.fn();
// Call the auth function
const result = await auth(mockProvider, {
@ -1639,9 +1650,9 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods
(mockProvider.clientInformation as jest.Mock).mockResolvedValue(undefined);
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
mockProvider.saveClientInformation = jest.fn();
(mockProvider.clientInformation as Mock).mockResolvedValue(undefined);
(mockProvider.tokens as Mock).mockResolvedValue(undefined);
mockProvider.saveClientInformation = vi.fn();
// Call the auth function with a server URL that has a path
const result = await auth(mockProvider, {
@ -1657,7 +1668,7 @@ describe('OAuth Authorization', () => {
call[0].toString().includes('/.well-known/oauth-authorization-server')
);
expect(authServerCall).toBeDefined();
expect(authServerCall[0].toString()).toBe('https://resource.example.com/.well-known/oauth-authorization-server');
expect(authServerCall![0].toString()).toBe('https://resource.example.com/.well-known/oauth-authorization-server');
});
it('passes resource parameter through authorization flow', async () => {
@ -1690,13 +1701,13 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods for authorization flow
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
(mockProvider.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
(mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
(mockProvider.redirectToAuthorization as jest.Mock).mockResolvedValue(undefined);
(mockProvider.tokens as Mock).mockResolvedValue(undefined);
(mockProvider.saveCodeVerifier as Mock).mockResolvedValue(undefined);
(mockProvider.redirectToAuthorization as Mock).mockResolvedValue(undefined);
// Call auth without authorization code (should trigger redirect)
const result = await auth(mockProvider, {
@ -1712,7 +1723,7 @@ describe('OAuth Authorization', () => {
})
);
const redirectCall = (mockProvider.redirectToAuthorization as jest.Mock).mock.calls[0];
const redirectCall = (mockProvider.redirectToAuthorization as Mock).mock.calls[0];
const authUrl: URL = redirectCall[0];
expect(authUrl.searchParams.get('resource')).toBe('https://api.example.com/mcp-server');
});
@ -1760,12 +1771,12 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods for token exchange
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
(mockProvider.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(mockProvider.codeVerifier as jest.Mock).mockResolvedValue('test-verifier');
(mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined);
(mockProvider.codeVerifier as Mock).mockResolvedValue('test-verifier');
(mockProvider.saveTokens as Mock).mockResolvedValue(undefined);
// Call auth with authorization code
const result = await auth(mockProvider, {
@ -1826,15 +1837,15 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods for token refresh
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
(mockProvider.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(mockProvider.tokens as jest.Mock).mockResolvedValue({
(mockProvider.tokens as Mock).mockResolvedValue({
access_token: 'old-access',
refresh_token: 'refresh123'
});
(mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined);
(mockProvider.saveTokens as Mock).mockResolvedValue(undefined);
// Call auth with existing tokens (should trigger refresh)
const result = await auth(mockProvider, {
@ -1854,7 +1865,7 @@ describe('OAuth Authorization', () => {
});
it('skips default PRM resource validation when custom validateResourceURL is provided', async () => {
const mockValidateResourceURL = jest.fn().mockResolvedValue(undefined);
const mockValidateResourceURL = vi.fn().mockResolvedValue(undefined);
const providerWithCustomValidation = {
...mockProvider,
validateResourceURL: mockValidateResourceURL
@ -1892,13 +1903,13 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods
(providerWithCustomValidation.clientInformation as jest.Mock).mockResolvedValue({
(providerWithCustomValidation.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(providerWithCustomValidation.tokens as jest.Mock).mockResolvedValue(undefined);
(providerWithCustomValidation.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
(providerWithCustomValidation.redirectToAuthorization as jest.Mock).mockResolvedValue(undefined);
(providerWithCustomValidation.tokens as Mock).mockResolvedValue(undefined);
(providerWithCustomValidation.saveCodeVerifier as Mock).mockResolvedValue(undefined);
(providerWithCustomValidation.redirectToAuthorization as Mock).mockResolvedValue(undefined);
// Call auth - should succeed despite resource mismatch because custom validation overrides default
const result = await auth(providerWithCustomValidation, {
@ -1947,13 +1958,13 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
(mockProvider.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
(mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
(mockProvider.redirectToAuthorization as jest.Mock).mockResolvedValue(undefined);
(mockProvider.tokens as Mock).mockResolvedValue(undefined);
(mockProvider.saveCodeVerifier as Mock).mockResolvedValue(undefined);
(mockProvider.redirectToAuthorization as Mock).mockResolvedValue(undefined);
// Call auth with a URL that has the resource as prefix
const result = await auth(mockProvider, {
@ -1969,7 +1980,7 @@ describe('OAuth Authorization', () => {
})
);
const redirectCall = (mockProvider.redirectToAuthorization as jest.Mock).mock.calls[0];
const redirectCall = (mockProvider.redirectToAuthorization as Mock).mock.calls[0];
const authUrl: URL = redirectCall[0];
// Should use the PRM's resource value, not the full requested URL
expect(authUrl.searchParams.get('resource')).toBe('https://api.example.com/');
@ -2005,13 +2016,13 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
(mockProvider.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
(mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
(mockProvider.redirectToAuthorization as jest.Mock).mockResolvedValue(undefined);
(mockProvider.tokens as Mock).mockResolvedValue(undefined);
(mockProvider.saveCodeVerifier as Mock).mockResolvedValue(undefined);
(mockProvider.redirectToAuthorization as Mock).mockResolvedValue(undefined);
// Call auth - should not include resource parameter
const result = await auth(mockProvider, {
@ -2027,7 +2038,7 @@ describe('OAuth Authorization', () => {
})
);
const redirectCall = (mockProvider.redirectToAuthorization as jest.Mock).mock.calls[0];
const redirectCall = (mockProvider.redirectToAuthorization as Mock).mock.calls[0];
const authUrl: URL = redirectCall[0];
// Resource parameter should not be present when PRM is not available
expect(authUrl.searchParams.has('resource')).toBe(false);
@ -2072,12 +2083,12 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods for token exchange
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
(mockProvider.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(mockProvider.codeVerifier as jest.Mock).mockResolvedValue('test-verifier');
(mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined);
(mockProvider.codeVerifier as Mock).mockResolvedValue('test-verifier');
(mockProvider.saveTokens as Mock).mockResolvedValue(undefined);
// Call auth with authorization code
const result = await auth(mockProvider, {
@ -2135,15 +2146,15 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods for token refresh
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
(mockProvider.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(mockProvider.tokens as jest.Mock).mockResolvedValue({
(mockProvider.tokens as Mock).mockResolvedValue({
access_token: 'old-access',
refresh_token: 'refresh123'
});
(mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined);
(mockProvider.saveTokens as Mock).mockResolvedValue(undefined);
// Call auth with existing tokens (should trigger refresh)
const result = await auth(mockProvider, {
@ -2196,13 +2207,13 @@ describe('OAuth Authorization', () => {
});
// Mock provider methods
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
(mockProvider.clientInformation as Mock).mockResolvedValue({
client_id: 'test-client',
client_secret: 'test-secret'
});
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
(mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
(mockProvider.redirectToAuthorization as jest.Mock).mockResolvedValue(undefined);
(mockProvider.tokens as Mock).mockResolvedValue(undefined);
(mockProvider.saveCodeVerifier as Mock).mockResolvedValue(undefined);
(mockProvider.redirectToAuthorization as Mock).mockResolvedValue(undefined);
// Call auth with serverUrl that has a path
const result = await auth(mockProvider, {
@ -2222,7 +2233,7 @@ describe('OAuth Authorization', () => {
});
it('supports overriding the fetch function used for requests', async () => {
const customFetch = jest.fn();
const customFetch = vi.fn();
// Mock PRM discovery
customFetch.mockResolvedValueOnce({
@ -2258,15 +2269,15 @@ describe('OAuth Authorization', () => {
redirect_uris: ['http://localhost:3000/callback']
};
},
clientInformation: jest.fn().mockResolvedValue({
clientInformation: vi.fn().mockResolvedValue({
client_id: 'client123',
client_secret: 'secret123'
}),
tokens: jest.fn().mockResolvedValue(undefined),
saveTokens: jest.fn(),
redirectToAuthorization: jest.fn(),
saveCodeVerifier: jest.fn(),
codeVerifier: jest.fn().mockResolvedValue('verifier123')
tokens: vi.fn().mockResolvedValue(undefined),
saveTokens: vi.fn(),
redirectToAuthorization: vi.fn(),
saveCodeVerifier: vi.fn(),
codeVerifier: vi.fn().mockResolvedValue('verifier123')
};
const result = await auth(mockProvider, {