Files
mcphub/tests/models/oauth.test.ts
2025-11-21 13:25:02 +08:00

237 lines
6.6 KiB
TypeScript

import {
createOAuthClient,
findOAuthClientById,
updateOAuthClient,
deleteOAuthClient,
saveAuthorizationCode,
getAuthorizationCode,
revokeAuthorizationCode,
saveToken,
getToken,
revokeToken,
} from '../../src/models/OAuth.js';
// Mock the config module to use in-memory storage for tests
let mockSettings = { mcpServers: {}, users: [], oauthClients: [] };
jest.mock('../../src/config/index.js', () => ({
loadSettings: jest.fn(() => ({ ...mockSettings })),
saveSettings: jest.fn((settings: any) => {
mockSettings = { ...settings };
return true;
}),
loadOriginalSettings: jest.fn(() => ({ ...mockSettings })),
}));
describe('OAuth Model', () => {
beforeEach(() => {
jest.clearAllMocks();
// Reset mock settings before each test
mockSettings = { mcpServers: {}, users: [], oauthClients: [] };
});
describe('OAuth Client Management', () => {
test('should create a new OAuth client', () => {
const client = {
clientId: 'test-client',
clientSecret: 'test-secret',
name: 'Test Client',
redirectUris: ['http://localhost:3000/callback'],
grants: ['authorization_code', 'refresh_token'],
scopes: ['read', 'write'],
};
const created = createOAuthClient(client);
expect(created).toEqual(client);
const found = findOAuthClientById('test-client');
expect(found).toEqual(client);
});
test('should not create duplicate OAuth client', () => {
const client = {
clientId: 'test-client',
clientSecret: 'test-secret',
name: 'Test Client',
redirectUris: ['http://localhost:3000/callback'],
grants: ['authorization_code'],
scopes: ['read'],
};
createOAuthClient(client);
expect(() => createOAuthClient(client)).toThrow();
});
test('should update an OAuth client', () => {
const client = {
clientId: 'test-client',
clientSecret: 'test-secret',
name: 'Test Client',
redirectUris: ['http://localhost:3000/callback'],
grants: ['authorization_code'],
scopes: ['read'],
};
createOAuthClient(client);
const updated = updateOAuthClient('test-client', {
name: 'Updated Client',
scopes: ['read', 'write'],
});
expect(updated?.name).toBe('Updated Client');
expect(updated?.scopes).toEqual(['read', 'write']);
});
test('should delete an OAuth client', () => {
const client = {
clientId: 'test-client',
clientSecret: 'test-secret',
name: 'Test Client',
redirectUris: ['http://localhost:3000/callback'],
grants: ['authorization_code'],
scopes: ['read'],
};
createOAuthClient(client);
expect(findOAuthClientById('test-client')).toBeDefined();
const deleted = deleteOAuthClient('test-client');
expect(deleted).toBe(true);
expect(findOAuthClientById('test-client')).toBeUndefined();
});
});
describe('Authorization Code Management', () => {
test('should save and retrieve authorization code', () => {
const code = saveAuthorizationCode({
redirectUri: 'http://localhost:3000/callback',
scope: 'read write',
clientId: 'test-client',
username: 'testuser',
codeChallenge: 'test-challenge',
codeChallengeMethod: 'S256',
});
expect(code).toBeDefined();
expect(typeof code).toBe('string');
const retrieved = getAuthorizationCode(code);
expect(retrieved).toBeDefined();
expect(retrieved?.redirectUri).toBe('http://localhost:3000/callback');
expect(retrieved?.clientId).toBe('test-client');
expect(retrieved?.username).toBe('testuser');
});
test('should not retrieve expired authorization code', async () => {
const code = saveAuthorizationCode(
{
redirectUri: 'http://localhost:3000/callback',
scope: 'read',
clientId: 'test-client',
username: 'testuser',
},
-1, // Expired
);
// Wait a bit to ensure expiration
await new Promise((resolve) => setTimeout(resolve, 100));
const retrieved = getAuthorizationCode(code);
expect(retrieved).toBeUndefined();
});
test('should revoke authorization code', () => {
const code = saveAuthorizationCode({
redirectUri: 'http://localhost:3000/callback',
scope: 'read',
clientId: 'test-client',
username: 'testuser',
});
expect(getAuthorizationCode(code)).toBeDefined();
revokeAuthorizationCode(code);
expect(getAuthorizationCode(code)).toBeUndefined();
});
});
describe('Token Management', () => {
test('should save and retrieve token', () => {
const token = saveToken(
{
scope: 'read write',
clientId: 'test-client',
username: 'testuser',
},
3600, // accessTokenLifetime
86400, // refreshTokenLifetime
);
expect(token.accessToken).toBeDefined();
expect(token.refreshToken).toBeDefined();
expect(token.accessTokenExpiresAt).toBeInstanceOf(Date);
const retrieved = getToken(token.accessToken);
expect(retrieved).toBeDefined();
expect(retrieved?.clientId).toBe('test-client');
expect(retrieved?.username).toBe('testuser');
});
test('should retrieve token by refresh token', () => {
const token = saveToken(
{
scope: 'read',
clientId: 'test-client',
username: 'testuser',
},
3600,
86400,
);
expect(token.refreshToken).toBeDefined();
const retrieved = getToken(token.refreshToken!);
expect(retrieved).toBeDefined();
expect(retrieved?.accessToken).toBe(token.accessToken);
});
test('should not retrieve expired access token', async () => {
const token = saveToken(
{
scope: 'read',
clientId: 'test-client',
username: 'testuser',
},
-1, // Expired
);
await new Promise((resolve) => setTimeout(resolve, 100));
const retrieved = getToken(token.accessToken);
expect(retrieved).toBeUndefined();
});
test('should revoke token', () => {
const token = saveToken(
{
scope: 'read',
clientId: 'test-client',
username: 'testuser',
},
3600,
86400,
);
expect(getToken(token.accessToken)).toBeDefined();
revokeToken(token.accessToken);
expect(getToken(token.accessToken)).toBeUndefined();
if (token.refreshToken) {
expect(getToken(token.refreshToken)).toBeUndefined();
}
});
});
});