Skip to main content
Glama

documcp

by tosin2013
tool-error-handling.test.ts16.2 kB
import { promises as fs } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import { analyzeRepository } from '../../src/tools/analyze-repository.js'; import { recommendSSG } from '../../src/tools/recommend-ssg.js'; import { generateConfig } from '../../src/tools/generate-config.js'; import { setupStructure } from '../../src/tools/setup-structure.js'; import { deployPages } from '../../src/tools/deploy-pages.js'; describe('Tool Error Handling and Edge Cases', () => { let tempDir: string; beforeEach(async () => { tempDir = join( tmpdir(), `test-errors-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, ); await fs.mkdir(tempDir, { recursive: true }); }); afterEach(async () => { try { await fs.rm(tempDir, { recursive: true }); } catch { // Ignore cleanup errors } }); describe('Repository Analysis Error Handling', () => { it.skip('should handle non-existent directories gracefully', async () => { const nonExistentPath = join(tempDir, 'non-existent'); await expect(analyzeRepository({ path: nonExistentPath })).rejects.toThrow(); }); it('should handle empty directories', async () => { const result = await analyzeRepository({ path: tempDir }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); }); it.skip('should handle directories with permission issues', async () => { const restrictedDir = join(tempDir, 'restricted'); await fs.mkdir(restrictedDir); try { // Make directory unreadable await fs.chmod(restrictedDir, 0o000); await expect(analyzeRepository({ path: restrictedDir })).rejects.toThrow(); } finally { // Restore permissions for cleanup await fs.chmod(restrictedDir, 0o755); } }); it('should handle malformed package.json files', async () => { await fs.writeFile(join(tempDir, 'package.json'), 'invalid json content'); const result = await analyzeRepository({ path: tempDir }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); }); it('should handle very large directories efficiently', async () => { // Create many files to test performance const promises = []; for (let i = 0; i < 100; i++) { promises.push(fs.writeFile(join(tempDir, `file-${i}.js`), `console.log(${i});`)); } await Promise.all(promises); const startTime = Date.now(); const result = await analyzeRepository({ path: tempDir, depth: 'quick' }); const duration = Date.now() - startTime; expect(result).toBeDefined(); expect(duration).toBeLessThan(2000); // Should complete within 2 seconds }); it.skip('should handle invalid depth parameters', async () => { await fs.writeFile(join(tempDir, 'package.json'), '{"name": "test"}'); // @ts-ignore - Testing invalid parameter const result = await analyzeRepository({ path: tempDir, depth: 'invalid' as any }); expect(result).toBeDefined(); // Should default to 'standard' depth }); }); describe('SSG Recommendation Error Handling', () => { it('should handle missing analysis data', async () => { await expect(recommendSSG({})).rejects.toThrow(); }); it.skip('should handle invalid analysis IDs', async () => { await expect(recommendSSG({ analysisId: 'non-existent-id' })).rejects.toThrow(); }); it.skip('should provide fallback recommendations for edge cases', async () => { // Create minimal valid analysis const minimalAnalysis = { projectType: 'unknown', languages: [], frameworks: [], complexity: 'low' as const, dependencies: [], devDependencies: [], scripts: {}, fileCount: 0, totalSize: 0, }; // Test with various edge case preferences const testCases = [ { ecosystem: 'invalid' as any }, { priority: 'unknown' as any }, { ecosystem: 'javascript', priority: 'performance' }, ]; for (const preferences of testCases) { const result = await recommendSSG({ analysisId: 'test-analysis-id', preferences, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); } }); it.skip('should handle analysis with missing required fields', async () => { const incompleteAnalysis = { projectType: 'javascript', // Missing other required fields }; await expect( recommendSSG({ // @ts-ignore - Testing incomplete data analysisId: undefined, }), ).rejects.toThrow(); }); }); describe('Configuration Generation Error Handling', () => { it('should handle invalid SSG types', async () => { await expect( generateConfig({ ssg: 'invalid-ssg' as any, projectName: 'test', outputPath: tempDir, }), ).rejects.toThrow(); }); it('should handle missing required parameters', async () => { await expect(generateConfig({})).rejects.toThrow(); await expect(generateConfig({ ssg: 'jekyll' })).rejects.toThrow(); await expect(generateConfig({ ssg: 'jekyll', projectName: 'test' })).rejects.toThrow(); }); it('should handle write permission issues', async () => { const readOnlyDir = join(tempDir, 'readonly'); await fs.mkdir(readOnlyDir); try { await fs.chmod(readOnlyDir, 0o444); const result = await generateConfig({ ssg: 'jekyll', projectName: 'test', outputPath: readOnlyDir, }); expect((result as any).isError).toBe(true); expect(result.content).toBeDefined(); expect( result.content.some((item: any) => item.text && item.text.includes('permission denied')), ).toBe(true); } finally { await fs.chmod(readOnlyDir, 0o755); } }); it('should handle extremely long project names', async () => { const longName = 'a'.repeat(1000); const result = await generateConfig({ ssg: 'jekyll', projectName: longName, outputPath: tempDir, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); }); it('should handle special characters in project names', async () => { const specialChars = ['test@project', 'test#project', 'test space project', 'test/project']; for (const projectName of specialChars) { const result = await generateConfig({ ssg: 'jekyll', projectName, outputPath: tempDir, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); } }); it('should validate SSG-specific configuration options', async () => { const ssgTypes = ['jekyll', 'hugo', 'docusaurus', 'mkdocs', 'eleventy']; for (const ssg of ssgTypes) { const result = await generateConfig({ ssg: ssg as any, projectName: `test-${ssg}`, outputPath: tempDir, projectDescription: `Test project for ${ssg}`, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(result.content).toBeDefined(); expect(result.content.length).toBeGreaterThan(0); } }); }); describe('Structure Setup Error Handling', () => { it('should handle invalid output paths', async () => { // Use a path that will definitely fail - a file path instead of directory // First create a file, then try to use it as a directory path const invalidPath = join(tempDir, 'not-a-directory.txt'); await fs.writeFile(invalidPath, 'this is a file, not a directory'); const result = await setupStructure({ path: invalidPath, ssg: 'jekyll', }); expect((result as any).isError).toBe(true); expect(result.content).toBeDefined(); expect( result.content.some( (item: any) => item.text && (item.text.includes('ENOTDIR') || item.text.includes('EEXIST') || item.text.includes('not a directory')), ), ).toBe(true); }); it('should handle missing SSG parameter', async () => { await expect( setupStructure({ path: tempDir, }), ).rejects.toThrow(); }); it('should create structure in existing directories with files', async () => { // Create some existing files await fs.writeFile(join(tempDir, 'existing-file.txt'), 'existing content'); await fs.mkdir(join(tempDir, 'existing-dir')); const result = await setupStructure({ path: tempDir, ssg: 'jekyll', includeExamples: true, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); // Should not overwrite existing files const existingContent = await fs.readFile(join(tempDir, 'existing-file.txt'), 'utf8'); expect(existingContent).toBe('existing content'); }); it('should handle different Diataxis structure options', async () => { const options = [ { includeExamples: true }, { includeExamples: false }, { includeExamples: true, customStructure: { tutorials: ['custom-tutorial'] } }, ]; for (const option of options) { const testDir = join(tempDir, `test-${Math.random().toString(36).substr(2, 5)}`); await fs.mkdir(testDir); const result = await setupStructure({ path: testDir, ssg: 'docusaurus', ...option, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); } }); }); describe('Deployment Setup Error Handling', () => { it('should handle repositories without proper configuration', async () => { const result = await deployPages({ repository: 'invalid/repo/format', ssg: 'jekyll', }); // deployPages actually succeeds with invalid repo format - it just creates workflow expect(result.content).toBeDefined(); expect(result.content[0].text).toContain('invalid/repo/format'); }); it('should handle missing repository parameter', async () => { await expect( deployPages({ ssg: 'jekyll', }), ).rejects.toThrow(); }); it('should handle different branch configurations', async () => { const branchConfigs = [ { branch: 'main' }, { branch: 'master' }, { branch: 'gh-pages' }, { branch: 'custom-branch' }, ]; for (const config of branchConfigs) { const result = await deployPages({ repository: 'user/test-repo', ssg: 'jekyll', ...config, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(result.content[0].text).toContain(config.branch); } }); it('should handle custom domain configurations', async () => { const customDomains = [ 'example.com', 'docs.example.com', 'sub.domain.example.org', 'localhost', // Edge case ]; for (const customDomain of customDomains) { const result = await deployPages({ repository: 'user/test-repo', ssg: 'jekyll', customDomain, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(result.content[0].text).toContain(customDomain); } }); it('should generate workflows for all supported SSGs', async () => { const ssgTypes = ['jekyll', 'hugo', 'docusaurus', 'mkdocs', 'eleventy']; for (const ssg of ssgTypes) { const result = await deployPages({ repository: 'user/test-repo', ssg: ssg as any, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(result.content[0].text).toContain(ssg); expect(result.content).toBeDefined(); } }); }); describe('Input Validation', () => { it('should validate string inputs for XSS and injection attacks', async () => { const maliciousInputs = [ '<script>alert("xss")</script>', '${process.env}', '../../../etc/passwd', 'test`rm -rf /`test', 'test && rm -rf /', 'test; cat /etc/passwd', ]; for (const maliciousInput of maliciousInputs) { // Test with different tools const result = await generateConfig({ ssg: 'jekyll', projectName: maliciousInput, outputPath: tempDir, projectDescription: maliciousInput, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); // Should sanitize or escape malicious content } }); it('should handle Unicode and international characters', async () => { const unicodeInputs = [ 'тест', // Cyrillic '测试', // Chinese '🚀📊', // Emojis 'café', // Accented characters 'مشروع', // Arabic ]; for (const unicodeInput of unicodeInputs) { const result = await generateConfig({ ssg: 'jekyll', projectName: unicodeInput, outputPath: tempDir, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); } }); it('should handle extremely large parameter values', async () => { const largeDescription = 'A'.repeat(10000); const result = await generateConfig({ ssg: 'jekyll', projectName: 'test', outputPath: tempDir, projectDescription: largeDescription, }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); }); }); describe('Concurrent Operations', () => { it('should handle multiple simultaneous tool calls', async () => { // Create test directories const dirs = await Promise.all([ fs.mkdir(join(tempDir, 'test1'), { recursive: true }), fs.mkdir(join(tempDir, 'test2'), { recursive: true }), fs.mkdir(join(tempDir, 'test3'), { recursive: true }), ]); // Run multiple operations concurrently const promises = [ generateConfig({ ssg: 'jekyll', projectName: 'test1', outputPath: join(tempDir, 'test1'), }), generateConfig({ ssg: 'hugo', projectName: 'test2', outputPath: join(tempDir, 'test2'), }), generateConfig({ ssg: 'docusaurus', projectName: 'test3', outputPath: join(tempDir, 'test3'), }), ]; const results = await Promise.all(promises); results.forEach((result) => { expect(result).toBeDefined(); expect(result.content).toBeDefined(); }); }); it('should handle resource contention gracefully', async () => { // Multiple operations on same directory const promises = Array(5) .fill(null) .map((_, i) => setupStructure({ path: join(tempDir, `concurrent-${i}`), ssg: 'jekyll', includeExamples: false, }), ); // Create directories first await Promise.all( promises.map((_, i) => fs.mkdir(join(tempDir, `concurrent-${i}`), { recursive: true })), ); const results = await Promise.allSettled(promises); // All should succeed or fail gracefully results.forEach((result) => { if (result.status === 'fulfilled') { expect(result.value.content).toBeDefined(); } else { expect(result.reason).toBeInstanceOf(Error); } }); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tosin2013/documcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server