@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
JavaScript
;
/**
* 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&ersands',
'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');
});
});
});