UNPKG

hl-eco-mcp

Version:

Gateway to the HyperLiquid Ecosystem for AI agents.

408 lines 18.3 kB
import { SchemaGenerator } from '../../../src/community/generation/SchemaGenerator.js'; describe('SchemaGenerator', () => { let generator; beforeEach(() => { generator = new SchemaGenerator(); }); const sampleProtocol = { name: 'test-api', version: '1.0.0', description: 'A test API protocol', author: 'Test Author', license: 'MIT', authentication: { type: 'api_key', location: 'header', name: 'X-API-Key', }, rateLimit: { requests: 1000, window: '1h', }, endpoints: [ { name: 'getData', method: 'GET', path: '/api/v1/data', description: 'Retrieve data from the API', parameters: [ { name: 'limit', type: 'number', description: 'Maximum number of items to return', required: false, default: 100, minimum: 1, maximum: 1000, }, { name: 'category', type: 'string', description: 'Filter by category', required: true, enum: ['news', 'sports', 'tech'], }, ], response: { type: 'object', description: 'API response containing data', }, authentication: true, rateLimit: { requests: 100, window: '1m', }, }, { name: 'createItem', method: 'POST', path: '/api/v1/items', description: 'Create a new item', parameters: [ { name: 'title', type: 'string', description: 'Item title', required: true, pattern: '^[a-zA-Z0-9\\s]+$', }, { name: 'metadata', type: 'object', description: 'Additional item metadata', required: false, properties: { tags: { type: 'array', description: 'Array of tags', required: false, items: { type: 'string', description: 'Individual tag', required: true, }, }, }, }, ], response: { type: 'object', description: 'Created item response', }, authentication: true, }, ], }; describe('Schema Generation', () => { it('should generate schema for a protocol', () => { const schema = generator.generateSchema(sampleProtocol); expect(schema).toHaveProperty('tools'); expect(schema).toHaveProperty('metadata'); expect(schema.tools).toHaveLength(2); expect(schema.metadata).toEqual({ protocol: 'test-api', version: '1.0.0', generatedAt: expect.any(Date), endpoints: 2, }); }); it('should generate proper tool names', () => { const schema = generator.generateSchema(sampleProtocol); expect(schema.tools[0]?.name).toBe('testApi_getData'); expect(schema.tools[1]?.name).toBe('testApi_createItem'); }); it('should generate proper tool descriptions', () => { const schema = generator.generateSchema(sampleProtocol); expect(schema.tools[0]?.description).toContain('Retrieve data from the API'); expect(schema.tools[0]?.description).toContain('(Requires authentication: api_key)'); expect(schema.tools[0]?.description).toContain('(Rate limited: 100 requests per 1m)'); expect(schema.tools[0]?.description).toContain('[test-api v1.0.0]'); }); it('should convert parameters to JSON schema format', () => { const schema = generator.generateSchema(sampleProtocol); const getDataTool = schema.tools.find((tool) => tool.name === 'testApi_getData'); expect(getDataTool?.parameters.properties).toHaveProperty('limit'); expect(getDataTool?.parameters.properties.limit).toEqual({ type: 'number', description: 'Maximum number of items to return', default: 100, minimum: 1, maximum: 1000, }); expect(getDataTool?.parameters.properties).toHaveProperty('category'); expect(getDataTool?.parameters.properties.category).toEqual({ type: 'string', description: 'Filter by category', enum: ['news', 'sports', 'tech'], }); expect(getDataTool?.parameters.required).toContain('category'); expect(getDataTool?.parameters.required).not.toContain('limit'); }); it('should handle complex object parameters', () => { const schema = generator.generateSchema(sampleProtocol); const createItemTool = schema.tools.find((tool) => tool.name === 'testApi_createItem'); expect(createItemTool?.parameters.properties).toHaveProperty('metadata'); expect(createItemTool?.parameters.properties.metadata).toEqual({ type: 'object', description: 'Additional item metadata', properties: { tags: { type: 'array', description: 'Array of tags', items: { type: 'string', description: 'Individual tag', }, }, }, }); }); it('should add authentication parameters when required', () => { const schema = generator.generateSchema(sampleProtocol); const getDataTool = schema.tools.find((tool) => tool.name === 'testApi_getData'); expect(getDataTool?.parameters.properties).toHaveProperty('apiKey'); expect(getDataTool?.parameters.properties.apiKey).toEqual({ type: 'string', description: 'API key for authentication', minLength: 1, }); expect(getDataTool?.parameters.required).toContain('apiKey'); }); it('should add common parameters to all tools', () => { const schema = generator.generateSchema(sampleProtocol); schema.tools.forEach((tool) => { expect(tool.parameters.properties).toHaveProperty('timeout'); expect(tool.parameters.properties.timeout).toEqual({ type: 'number', description: 'Request timeout in milliseconds', minimum: 1000, maximum: 60000, default: 10000, }); expect(tool.parameters.properties).toHaveProperty('retries'); expect(tool.parameters.properties.retries).toEqual({ type: 'number', description: 'Number of retry attempts on failure', minimum: 0, maximum: 5, default: 2, }); }); }); }); describe('Authentication Parameter Generation', () => { it('should generate bearer token parameters', () => { const protocolWithBearer = { ...sampleProtocol, authentication: { type: 'bearer_token', }, }; const schema = generator.generateSchema(protocolWithBearer); const tool = schema.tools[0]; expect(tool?.parameters.properties).toHaveProperty('bearerToken'); expect(tool?.parameters.properties?.bearerToken).toEqual({ type: 'string', description: 'Bearer token for authentication', minLength: 1, }); }); it('should generate basic auth parameters', () => { const protocolWithBasic = { ...sampleProtocol, authentication: { type: 'basic', }, }; const schema = generator.generateSchema(protocolWithBasic); const tool = schema.tools[0]; expect(tool?.parameters.properties).toHaveProperty('basicAuth'); expect(tool?.parameters.properties?.basicAuth).toEqual({ type: 'object', description: 'Basic authentication credentials', properties: { username: { type: 'string', description: 'Username for basic authentication', }, password: { type: 'string', description: 'Password for basic authentication', }, }, required: ['username', 'password'], }); }); it('should generate OAuth2 parameters', () => { const protocolWithOAuth = { ...sampleProtocol, authentication: { type: 'oauth2', }, }; const schema = generator.generateSchema(protocolWithOAuth); const tool = schema.tools[0]; expect(tool?.parameters.properties).toHaveProperty('accessToken'); expect(tool?.parameters.properties?.accessToken).toEqual({ type: 'string', description: 'OAuth2 access token', minLength: 1, }); }); }); describe('Documentation Generation', () => { it('should generate schema documentation', () => { const schema = generator.generateSchema(sampleProtocol); const documentation = generator.generateSchemaDocumentation(schema); expect(documentation).toContain('# test-api v1.0.0 - MCP Tools'); expect(documentation).toContain('Total tools: 2'); expect(documentation).toContain('## testApi_getData'); expect(documentation).toContain('## testApi_createItem'); expect(documentation).toContain('### Parameters'); expect(documentation).toContain('| Parameter | Type | Required | Description |'); }); it('should handle tools with no parameters in documentation', () => { const protocolWithNoParams = { ...sampleProtocol, endpoints: [ { name: 'ping', method: 'GET', path: '/ping', description: 'Health check endpoint', response: { type: 'object', description: 'Health status', }, }, ], }; const schema = generator.generateSchema(protocolWithNoParams); const documentation = generator.generateSchemaDocumentation(schema); expect(documentation).toContain('timeout'); }); }); describe('Schema Validation', () => { it('should validate generated schemas', () => { const schema = generator.generateSchema(sampleProtocol); const validation = generator.validateGeneratedSchema(schema); expect(validation.valid).toBe(true); expect(validation.errors).toHaveLength(0); }); it('should detect duplicate tool names', () => { const schema = generator.generateSchema(sampleProtocol); // Manually create duplicate if (schema.tools[0]) { schema.tools.push({ ...schema.tools[0] }); } const validation = generator.validateGeneratedSchema(schema); expect(validation.valid).toBe(false); expect(validation.errors).toContainEqual(expect.stringContaining('Duplicate tool name')); }); it('should detect invalid tool name formats', () => { const schema = generator.generateSchema(sampleProtocol); if (schema.tools[0]) { schema.tools[0].name = '123invalid-name'; } const validation = generator.validateGeneratedSchema(schema); expect(validation.valid).toBe(false); expect(validation.errors).toContainEqual(expect.stringContaining('Invalid tool name format')); }); it('should warn about long tool names', () => { const schema = generator.generateSchema(sampleProtocol); if (schema.tools[0]) { schema.tools[0].name = 'a'.repeat(70); } const validation = generator.validateGeneratedSchema(schema); expect(validation.warnings).toContainEqual(expect.stringContaining('Tool name is very long')); }); it('should warn about short descriptions', () => { const schema = generator.generateSchema(sampleProtocol); if (schema.tools[0]) { schema.tools[0].description = 'Short'; } const validation = generator.validateGeneratedSchema(schema); expect(validation.warnings).toContainEqual(expect.stringContaining('Tool description is very short')); }); it('should validate parameter consistency', () => { const schema = generator.generateSchema(sampleProtocol); // Add required parameter that doesn't exist in properties if (schema.tools[0]?.parameters.required) { schema.tools[0].parameters.required.push('nonExistentParam'); } const validation = generator.validateGeneratedSchema(schema); expect(validation.valid).toBe(false); expect(validation.errors).toContainEqual(expect.stringContaining("Required parameter 'nonExistentParam' not found")); }); it('should detect missing parameter types', () => { const schema = generator.generateSchema(sampleProtocol); if (schema.tools[0]?.parameters.properties?.limit) { delete schema.tools[0].parameters.properties.limit.type; } const validation = generator.validateGeneratedSchema(schema); expect(validation.valid).toBe(false); expect(validation.errors).toContainEqual(expect.stringContaining("Parameter 'limit' missing type")); }); it('should warn about missing parameter descriptions', () => { const schema = generator.generateSchema(sampleProtocol); if (schema.tools[0]?.parameters.properties?.limit) { delete schema.tools[0].parameters.properties.limit.description; } const validation = generator.validateGeneratedSchema(schema); expect(validation.warnings).toContainEqual(expect.stringContaining("Parameter 'limit' missing description")); }); }); describe('Edge Cases', () => { it('should handle empty protocols gracefully', () => { const emptyProtocol = { name: 'empty-protocol', version: '1.0.0', description: 'An empty protocol', author: 'Test Author', license: 'MIT', endpoints: [], }; expect(() => generator.generateSchema(emptyProtocol)).not.toThrow(); }); it('should handle protocols without authentication', () => { const protocolWithoutAuth = { ...sampleProtocol, authentication: undefined, endpoints: sampleProtocol.endpoints.map((endpoint) => ({ ...endpoint, authentication: false, })), }; const schema = generator.generateSchema(protocolWithoutAuth); schema.tools.forEach((tool) => { expect(tool.parameters.properties).not.toHaveProperty('apiKey'); expect(tool.parameters.properties).not.toHaveProperty('bearerToken'); expect(tool.parameters.properties).not.toHaveProperty('basicAuth'); expect(tool.parameters.properties).not.toHaveProperty('accessToken'); }); }); it('should handle protocols with special characters in names', () => { const protocolWithSpecialChars = { ...sampleProtocol, name: 'test-api_v2', }; const schema = generator.generateSchema(protocolWithSpecialChars); expect(schema.tools[0]?.name).toBe('testApiV2_getData'); expect(schema.tools[1]?.name).toBe('testApiV2_createItem'); }); }); describe('Error Handling', () => { it('should handle invalid protocol gracefully', () => { const invalidProtocol = null; expect(() => generator.generateSchema(invalidProtocol)).toThrow(); }); it('should provide meaningful error messages', () => { const protocolWithInvalidEndpoint = { ...sampleProtocol, endpoints: [null], }; expect(() => generator.generateSchema(protocolWithInvalidEndpoint)).toThrow(); }); }); }); //# sourceMappingURL=SchemaGenerator.test.js.map