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

212 lines (206 loc) 10.7 kB
"use strict"; /** * Unit tests for Get Components Guide Tool */ Object.defineProperty(exports, "__esModule", { value: true }); const getComponentsGuide_1 = require("../tools/getComponentsGuide"); const gitlabClient_1 = require("../services/gitlabClient"); // Mock the GitLab client jest.mock('../services/gitlabClient', () => ({ fetchGitLabFileContentSafe: jest.fn(), GitLabError: class GitLabError extends Error { statusCode; url; constructor(message, statusCode, url) { super(message); this.statusCode = statusCode; this.url = url; this.name = 'GitLabError'; } } })); // Mock the config jest.mock('../config', () => ({ config: { urls: { gitlabBase: 'https://gitlab.example.com/repo/-/raw/main/' } } })); const gitlabClient_2 = require("../services/gitlabClient"); const mockFetchGitLabFileContentSafe = gitlabClient_2.fetchGitLabFileContentSafe; describe('Get Components Guide Tool', () => { beforeEach(() => { jest.clearAllMocks(); jest.spyOn(console, 'log').mockImplementation(() => { }); jest.spyOn(console, 'error').mockImplementation(() => { }); }); afterEach(() => { jest.restoreAllMocks(); }); describe('resolveComponentGuidePath', () => { it('should return absolute URLs as-is', () => { const absoluteUrl = 'https://example.com/path/to/file.txt'; const result = (0, getComponentsGuide_1.resolveComponentGuidePath)(absoluteUrl); expect(result).toBe(absoluteUrl); }); it('should resolve relative paths with base URL', () => { const relativePath = 'yeepay/dynamicpassword/llms.txt'; const result = (0, getComponentsGuide_1.resolveComponentGuidePath)(relativePath); expect(result).toBe('https://gitlab.example.com/repo/-/raw/main/yeepay/dynamicpassword/llms.txt'); }); it('should handle paths starting with slash', () => { const pathWithSlash = '/yeepay/dynamicpassword/llms.txt'; const result = (0, getComponentsGuide_1.resolveComponentGuidePath)(pathWithSlash); expect(result).toBe('https://gitlab.example.com/repo/-/raw/main/yeepay/dynamicpassword/llms.txt'); }); it('should handle base URL without trailing slash', () => { // This test would need to mock config differently, but the logic is covered const relativePath = 'component/guide.txt'; const result = (0, getComponentsGuide_1.resolveComponentGuidePath)(relativePath); expect(result).toBe('https://gitlab.example.com/repo/-/raw/main/component/guide.txt'); }); }); describe('isValidComponentGuidePath', () => { it('should accept valid relative paths', () => { const validPaths = [ 'yeepay/dynamicpassword/llms.txt', 'component-name/guide.md', 'simple-file.txt', 'deep/nested/path/file.txt', 'file_with_underscores.txt', 'file-with-hyphens.txt' ]; validPaths.forEach(path => { expect((0, getComponentsGuide_1.isValidComponentGuidePath)(path)).toBe(true); }); }); it('should accept valid absolute URLs', () => { const validUrls = [ 'https://example.com/file.txt', 'http://gitlab.com/repo/-/raw/main/file.txt', 'https://gitlab.yeepay.com/awesome/awesome-components/-/raw/main/llms.txt' ]; validUrls.forEach(url => { expect((0, getComponentsGuide_1.isValidComponentGuidePath)(url)).toBe(true); }); }); it('should reject invalid paths', () => { const invalidPaths = [ '', ' ', '../../../etc/passwd', 'path/with/../traversal', 'path<script>alert(1)</script>', 'path"with"quotes', "path'with'quotes", 'path&with&ampersands', 'path|with|pipes', 'path;with;semicolons' ]; invalidPaths.forEach(path => { expect((0, getComponentsGuide_1.isValidComponentGuidePath)(path)).toBe(false); }); }); it('should reject malformed URLs', () => { const malformedUrls = [ 'http://', 'https://', 'not-a-url-but-starts-with-http://invalid' ]; malformedUrls.forEach(url => { expect((0, getComponentsGuide_1.isValidComponentGuidePath)(url)).toBe(false); }); }); }); describe('formatGuideContent', () => { it('should format content with title from first line', () => { const content = 'Dynamic Password Component\n\nThis is a guide...'; const path = 'yeepay/dynamicpassword/llms.txt'; const result = (0, getComponentsGuide_1.formatGuideContent)(content, path); expect(result).toContain('# Component Guide: Dynamic Password Component'); expect(result).toContain('**Source Path:** `yeepay/dynamicpassword/llms.txt`'); expect(result).toContain('**Content Length:** 42 characters'); expect(result).toContain('This is a guide...'); }); it('should format content with title from markdown header', () => { const content = '# My Component Guide\n\nContent here...'; const path = 'component/guide.txt'; const result = (0, getComponentsGuide_1.formatGuideContent)(content, path); expect(result).toContain('# Component Guide: My Component Guide'); expect(result).toContain('**Source Path:** `component/guide.txt`'); }); it('should use path as title when no clear title found', () => { const content = '\n\n\nSome content without clear title...'; const path = 'component/guide.txt'; const result = (0, getComponentsGuide_1.formatGuideContent)(content, path); expect(result).toContain('# Component Guide: component/guide.txt'); }); it('should handle empty content', () => { const content = ''; const path = 'empty/file.txt'; const result = (0, getComponentsGuide_1.formatGuideContent)(content, path); expect(result).toContain('# Component Guide: empty/file.txt'); expect(result).toContain('**Content Length:** 0 characters'); }); }); describe('getComponentsGuideTool', () => { const mockGuideContent = `# Dynamic Password Component This component provides secure dynamic password generation functionality. ## Installation npm install @yeepay/dynamic-password ## Usage \`\`\`javascript import { generatePassword } from '@yeepay/dynamic-password'; const password = generatePassword(); \`\`\``; it('should successfully fetch and format component guide', async () => { mockFetchGitLabFileContentSafe.mockResolvedValueOnce(mockGuideContent); const result = await (0, getComponentsGuide_1.getComponentsGuideTool)({ path: 'yeepay/dynamicpassword/llms.txt' }); expect(result.content[0].text).toContain('# Component Guide: Dynamic Password Component'); expect(result.content[0].text).toContain('**Source Path:** `yeepay/dynamicpassword/llms.txt`'); expect(result.content[0].text).toContain('This component provides secure dynamic password'); expect(result.isError).toBeUndefined(); }); it('should handle absolute URLs', async () => { const absoluteUrl = 'https://example.com/component/guide.txt'; mockFetchGitLabFileContentSafe.mockResolvedValueOnce(mockGuideContent); const result = await (0, getComponentsGuide_1.getComponentsGuideTool)({ path: absoluteUrl }); expect(mockFetchGitLabFileContentSafe).toHaveBeenCalledWith(absoluteUrl); expect(result.content[0].text).toContain('Dynamic Password Component'); expect(result.isError).toBeUndefined(); }); it('should reject invalid paths', async () => { const result = await (0, getComponentsGuide_1.getComponentsGuideTool)({ path: '../../../etc/passwd' }); 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 handle GitLab errors gracefully', async () => { const gitlabError = new gitlabClient_1.GitLabError('File not found', 404, 'https://example.com/file.txt'); mockFetchGitLabFileContentSafe.mockRejectedValueOnce(gitlabError); const result = await (0, getComponentsGuide_1.getComponentsGuideTool)({ path: 'nonexistent/component.txt' }); expect(result.content[0].text).toContain('Error: Component Guide Not Found'); expect(result.content[0].text).toContain('GitLab Error: File not found'); expect(result.content[0].text).toContain('Status Code: 404'); expect(result.content[0].text).toContain('Suggestions:'); expect(result.isError).toBe(true); }); it('should handle general errors gracefully', async () => { mockFetchGitLabFileContentSafe.mockRejectedValueOnce(new Error('Network error')); const result = await (0, getComponentsGuide_1.getComponentsGuideTool)({ path: 'component/guide.txt' }); expect(result.content[0].text).toContain('Error: Failed to Retrieve Component Guide'); expect(result.content[0].text).toContain('Error: Network error'); expect(result.content[0].text).toContain('Suggestions:'); expect(result.isError).toBe(true); }); it('should log the path and resolved URL', async () => { const consoleSpy = jest.spyOn(console, 'log'); mockFetchGitLabFileContentSafe.mockResolvedValueOnce(mockGuideContent); await (0, getComponentsGuide_1.getComponentsGuideTool)({ path: 'yeepay/dynamicpassword/llms.txt' }); expect(consoleSpy).toHaveBeenCalledWith('Getting component guide for path: yeepay/dynamicpassword/llms.txt'); expect(consoleSpy).toHaveBeenCalledWith('Resolved URL: https://gitlab.example.com/repo/-/raw/main/yeepay/dynamicpassword/llms.txt'); }); }); });