UNPKG

@yeepay/awesome-components-mcp

Version:

MCP server providing access to awesome-components documentation and integration guides with dual-mode operation: direct fetch and GitLab MCP instruction generation

304 lines (237 loc) 11.1 kB
/** * Integration tests for Get Components Guide MCP Tool * These tests verify the tool's behavior with mocked GitLab responses */ // Mock the GitLab client module const mockFetchGitLabFileContentSafe = jest.fn(); jest.mock('../../services/gitlabClient', () => ({ fetchGitLabFileContentSafe: mockFetchGitLabFileContentSafe, GitLabError: class GitLabError extends Error { constructor(message: string, public statusCode?: number, public url?: string) { super(message); this.name = 'GitLabError'; } } })); import { getComponentsGuideTool } from '../../tools/getComponentsGuide'; // Mock the config jest.mock('../../config', () => ({ config: { urls: { gitlabBase: 'https://gitlab.example.com/repo/-/raw/main/' }, auth: { gitlabToken: 'test-token' }, internalGitlabHost: 'gitlab.example.com', // GitLab MCP settings for relative paths internalGuidesProjectId: 'test-project' } })); describe('Get Components Guide Tool Integration Tests', () => { beforeEach(() => { jest.clearAllMocks(); jest.spyOn(console, 'log').mockImplementation(() => {}); jest.spyOn(console, 'error').mockImplementation(() => {}); // Reset global fetch mock if (global.fetch && jest.isMockFunction(global.fetch)) { (global.fetch as jest.Mock).mockReset(); } }); afterEach(() => { jest.restoreAllMocks(); }); describe('Successful execution scenarios', () => { const mockGuideContent = `# Dynamic Password Component This component provides secure dynamic password generation functionality for payment systems. ## Installation \`\`\`bash npm install @yeepay/dynamic-password \`\`\` ## Basic Usage \`\`\`javascript import { generatePassword } from '@yeepay/dynamic-password'; const password = generatePassword({ length: 12, includeSpecialChars: true }); \`\`\` ## Configuration The component supports various configuration options: - \`length\`: Password length (default: 8) - \`includeSpecialChars\`: Include special characters (default: false) - \`excludeSimilar\`: Exclude similar characters (default: true) ## API Reference ### generatePassword(options) Generates a secure dynamic password. **Parameters:** - \`options\` (Object): Configuration options **Returns:** - \`string\`: Generated password`; it('should generate GitLab MCP instruction for relative path', async () => { const result = await getComponentsGuideTool({ path: 'yeepay/dynamicpassword/llms.txt' }); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); expect(result.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); expect(result.content[0].text).toContain('get_file_contents'); expect(result.content[0].text).toContain('test-project'); expect(result.content[0].text).toContain('yeepay/dynamicpassword/llms.txt'); expect(result.isError).toBeUndefined(); // Should not call GitLab client directly expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); it('should execute successfully with absolute URL', async () => { const absoluteUrl = 'https://external.gitlab.com/project/-/raw/main/component/guide.txt'; // Mock fetch for external URL (since it's external, it should use fetch, not GitLab client) global.fetch = jest.fn().mockResolvedValueOnce({ ok: true, status: 200, text: jest.fn().mockResolvedValueOnce(mockGuideContent) } as any); const result = await getComponentsGuideTool({ path: absoluteUrl }); expect(result.content).toHaveLength(1); expect(result.content[0].text).toContain('# Component Guide: Dynamic Password Component'); expect(result.content[0].text).toContain(`**Source Path:** \`${absoluteUrl}\``); expect(result.isError).toBeUndefined(); expect(global.fetch).toHaveBeenCalledWith(absoluteUrl, { headers: { 'User-Agent': 'awesome-components-mcp/1.0.0' } }); }); it('should generate GitLab MCP instruction for path with leading slash', async () => { const result = await getComponentsGuideTool({ path: '/yeepay/dynamicpassword/llms.txt' }); expect(result.content).toHaveLength(1); expect(result.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); expect(result.content[0].text).toContain('yeepay/dynamicpassword/llms.txt'); expect(result.isError).toBeUndefined(); // Should not call GitLab client directly expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); it('should generate GitLab MCP instruction for markdown file', async () => { const result = await getComponentsGuideTool({ path: 'component/guide.md' }); expect(result.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); expect(result.content[0].text).toContain('component/guide.md'); expect(result.isError).toBeUndefined(); // Should not call GitLab client directly expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); it('should generate GitLab MCP instruction for empty path', async () => { const result = await getComponentsGuideTool({ path: 'empty/component.txt' }); expect(result.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); expect(result.content[0].text).toContain('empty/component.txt'); expect(result.isError).toBeUndefined(); // Should not call GitLab client directly expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); }); describe('Path validation and error scenarios', () => { it('should reject invalid paths with dangerous characters', async () => { const result = await getComponentsGuideTool({ path: '../../../etc/passwd' }); expect(result.content).toHaveLength(1); expect(result.content[0].text).toContain('Error: Invalid Component Guide Path'); expect(result.content[0].text).toContain('invalid characters or format'); expect(result.isError).toBe(true); expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); it('should reject paths with script injection attempts', async () => { const result = await getComponentsGuideTool({ path: 'path<script>alert(1)</script>' }); expect(result.content[0].text).toContain('Error: Invalid Component Guide Path'); expect(result.isError).toBe(true); expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); it('should reject empty paths', async () => { const result = await getComponentsGuideTool({ path: '' }); expect(result.content[0].text).toContain('Error: Invalid Component Guide Path'); expect(result.isError).toBe(true); expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); it('should generate GitLab MCP instruction for nonexistent path', async () => { const result = await getComponentsGuideTool({ path: 'nonexistent/component.txt' }); expect(result.content).toHaveLength(1); expect(result.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); expect(result.content[0].text).toContain('nonexistent/component.txt'); expect(result.isError).toBeUndefined(); // Should not call GitLab client directly expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); it('should generate GitLab MCP instruction for private path', async () => { const result = await getComponentsGuideTool({ path: 'private/component.txt' }); expect(result.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); expect(result.content[0].text).toContain('private/component.txt'); expect(result.isError).toBeUndefined(); // Should not call GitLab client directly expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); it('should generate GitLab MCP instruction for any relative path', async () => { const result = await getComponentsGuideTool({ path: 'component/guide.txt' }); expect(result.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); expect(result.content[0].text).toContain('component/guide.txt'); expect(result.isError).toBeUndefined(); // Should not call GitLab client directly expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); }); }); describe('Tool behavior verification', () => { it('should log the correct path and GitLab MCP instruction generation', async () => { const consoleSpy = jest.spyOn(console, 'log'); await getComponentsGuideTool({ path: 'yeepay/component/llms.txt' }); expect(consoleSpy).toHaveBeenCalledWith('Getting component guide for path: yeepay/component/llms.txt'); expect(consoleSpy).toHaveBeenCalledWith('Relative/internal path detected, generating gitlab-mcp instruction: yeepay/component/llms.txt'); }); it('should return MCP-compliant response structure', async () => { const result = await getComponentsGuideTool({ path: 'test/component.txt' }); // Verify MCP response structure expect(result).toHaveProperty('content'); expect(Array.isArray(result.content)).toBe(true); expect(result.content).toHaveLength(1); expect(result.content[0]).toHaveProperty('type', 'text'); expect(result.content[0]).toHaveProperty('text'); expect(typeof result.content[0].text).toBe('string'); }); it('should handle different path types correctly', async () => { // Test relative path - should generate GitLab MCP instruction const result1 = await getComponentsGuideTool({ path: 'component/guide.txt' }); expect(result1.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalled(); // Test path with leading slash - should generate GitLab MCP instruction const result2 = await getComponentsGuideTool({ path: '/component/guide.txt' }); expect(result2.content[0].text).toContain('# Component Guide - GitLab MCP Instruction'); // Test external URL (should use fetch, not GitLab client) const externalUrl = 'https://external.com/file.txt'; // Mock fetch for external URL global.fetch = jest.fn().mockResolvedValueOnce({ ok: true, status: 200, text: jest.fn().mockResolvedValueOnce('external content') } as any); const result3 = await getComponentsGuideTool({ path: externalUrl }); // Should use fetch, not GitLab client expect(global.fetch).toHaveBeenCalledWith(externalUrl, { headers: { 'User-Agent': 'awesome-components-mcp/1.0.0' } }); expect(result3.content[0].text).toContain('# Component Guide: file'); expect(mockFetchGitLabFileContentSafe).not.toHaveBeenCalledWith(externalUrl); }); }); });