Files
mcphub/tests/controllers/openApiController.test.ts

248 lines
6.8 KiB
TypeScript

import { convertParametersToTypes } from '../../src/utils/parameterConversion.js';
// Integration tests for OpenAPI controller's parameter type conversion
describe('OpenAPI Controller - Parameter Type Conversion Integration', () => {
test('should convert integer parameters correctly', () => {
const queryParams = {
limit: '5',
offset: '10',
name: 'test'
};
const inputSchema = {
type: 'object',
properties: {
limit: { type: 'integer' },
offset: { type: 'integer' },
name: { type: 'string' }
}
};
const result = convertParametersToTypes(queryParams, inputSchema);
expect(result).toEqual({
limit: 5, // Converted to integer
offset: 10, // Converted to integer
name: 'test' // Remains string
});
});
test('should convert number parameters correctly', () => {
const queryParams = {
price: '19.99',
discount: '0.15'
};
const inputSchema = {
type: 'object',
properties: {
price: { type: 'number' },
discount: { type: 'number' }
}
};
const result = convertParametersToTypes(queryParams, inputSchema);
expect(result).toEqual({
price: 19.99,
discount: 0.15
});
});
test('should convert boolean parameters correctly', () => {
const queryParams = {
enabled: 'true',
disabled: 'false',
active: '1',
inactive: '0'
};
const inputSchema = {
type: 'object',
properties: {
enabled: { type: 'boolean' },
disabled: { type: 'boolean' },
active: { type: 'boolean' },
inactive: { type: 'boolean' }
}
};
const result = convertParametersToTypes(queryParams, inputSchema);
expect(result).toEqual({
enabled: true,
disabled: false,
active: true,
inactive: false
});
});
test('should convert array parameters correctly', () => {
const queryParams = {
tags: 'tag1,tag2,tag3',
ids: '1,2,3'
};
const inputSchema = {
type: 'object',
properties: {
tags: { type: 'array' },
ids: { type: 'array' }
}
};
const result = convertParametersToTypes(queryParams, inputSchema);
expect(result).toEqual({
tags: ['tag1', 'tag2', 'tag3'],
ids: ['1', '2', '3']
});
});
test('should handle missing schema gracefully', () => {
const queryParams = {
limit: '5',
name: 'test'
};
const result = convertParametersToTypes(queryParams, {});
expect(result).toEqual({
limit: '5', // Should remain as string
name: 'test' // Should remain as string
});
});
test('should handle properties not in schema', () => {
const queryParams = {
limit: '5',
unknownParam: 'value'
};
const inputSchema = {
type: 'object',
properties: {
limit: { type: 'integer' }
}
};
const result = convertParametersToTypes(queryParams, inputSchema);
expect(result).toEqual({
limit: 5, // Converted based on schema
unknownParam: 'value' // Kept as is (no schema)
});
});
test('should handle invalid number conversion gracefully', () => {
const queryParams = {
limit: 'not-a-number',
price: 'invalid'
};
const inputSchema = {
type: 'object',
properties: {
limit: { type: 'integer' },
price: { type: 'number' }
}
};
const result = convertParametersToTypes(queryParams, inputSchema);
expect(result).toEqual({
limit: 'not-a-number', // Should remain as string when conversion fails
price: 'invalid' // Should remain as string when conversion fails
});
});
});
// Test the new OpenAPI endpoints functionality
describe('OpenAPI Granular Endpoints', () => {
// Mock the required services
const mockGetAvailableServers = jest.fn();
const mockGenerateOpenAPISpec = jest.fn();
const mockGetGroupByIdOrName = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
});
test('should generate server-specific OpenAPI spec', async () => {
// Mock available servers
mockGetAvailableServers.mockResolvedValue(['server1', 'server2']);
// Mock OpenAPI spec generation
const mockSpec = { openapi: '3.0.3', info: { title: 'server1 MCP API' } };
mockGenerateOpenAPISpec.mockResolvedValue(mockSpec);
// Test server spec generation options
const expectedOptions = {
title: 'server1 MCP API',
description: 'OpenAPI specification for server1 MCP server tools',
serverFilter: ['server1']
};
// Verify that the correct options would be passed
expect(expectedOptions.serverFilter).toEqual(['server1']);
expect(expectedOptions.title).toBe('server1 MCP API');
});
test('should generate group-specific OpenAPI spec', async () => {
// Mock group data
const mockGroup = {
id: 'group1',
name: 'webtools',
servers: [
{ name: 'server1', tools: 'all' },
{ name: 'server2', tools: ['tool1', 'tool2'] }
]
};
mockGetGroupByIdOrName.mockReturnValue(mockGroup);
// Mock OpenAPI spec generation
const mockSpec = { openapi: '3.0.3', info: { title: 'webtools Group MCP API' } };
mockGenerateOpenAPISpec.mockResolvedValue(mockSpec);
// Test group spec generation options
const expectedOptions = {
title: 'webtools Group MCP API',
description: 'OpenAPI specification for webtools group tools',
groupFilter: 'webtools'
};
// Verify that the correct options would be passed
expect(expectedOptions.groupFilter).toBe('webtools');
expect(expectedOptions.title).toBe('webtools Group MCP API');
});
test('should handle non-existent server', async () => {
// Mock available servers (not including 'nonexistent')
mockGetAvailableServers.mockResolvedValue(['server1', 'server2']);
// Verify error handling for non-existent server
const serverExists = ['server1', 'server2'].includes('nonexistent');
expect(serverExists).toBe(false);
});
test('should handle non-existent group', async () => {
// Mock group lookup returning null
mockGetGroupByIdOrName.mockReturnValue(null);
// Verify error handling for non-existent group
const group = mockGetGroupByIdOrName('nonexistent');
expect(group).toBeNull();
});
test('should decode URL-encoded server and tool names with slashes', () => {
// Test that URL-encoded names with slashes are properly decoded
const encodedServerName = 'com.atlassian%2Fatlassian-mcp-server';
const encodedToolName = 'atlassianUserInfo';
const decodedServerName = decodeURIComponent(encodedServerName);
const decodedToolName = decodeURIComponent(encodedToolName);
expect(decodedServerName).toBe('com.atlassian/atlassian-mcp-server');
expect(decodedToolName).toBe('atlassianUserInfo');
});
});