Skip to main content
Glama

documcp

by tosin2013
test-local-deployment.test.ts19 kB
import { testLocalDeployment } from '../../src/tools/test-local-deployment.js'; import * as childProcess from 'child_process'; import * as fs from 'fs'; // Create simpler mocking approach describe('testLocalDeployment', () => { const testRepoPath = process.cwd(); afterEach(() => { jest.restoreAllMocks(); }); describe('Input validation', () => { it('should handle invalid SSG parameter', async () => { await expect( testLocalDeployment({ repositoryPath: '/test/path', ssg: 'invalid' as any, }), ).rejects.toThrow(); }); it('should handle missing required parameters', async () => { await expect( testLocalDeployment({ ssg: 'docusaurus', } as any), ).rejects.toThrow(); }); it('should handle unsupported SSG gracefully', async () => { // This should throw a ZodError due to input validation await expect( testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'gatsby' as any, }), ).rejects.toThrow('Invalid enum value'); }); }); describe('Basic functionality', () => { it('should return proper response structure', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', skipBuild: true, }); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); expect(result.content.length).toBeGreaterThan(0); expect(() => JSON.parse(result.content[0].text)).not.toThrow(); }); it('should use default port when not specified', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.port).toBe(3000); }); it('should use custom port when specified', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', port: 4000, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.port).toBe(4000); }); it('should use custom timeout when specified', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', timeout: 120, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.buildSuccess).toBeDefined(); }); }); describe('SSG support', () => { it('should handle all supported SSG types', async () => { const ssgs = ['jekyll', 'hugo', 'docusaurus', 'mkdocs', 'eleventy']; for (const ssg of ssgs) { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: ssg as any, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.ssg).toBe(ssg); expect(parsedResult.buildSuccess).toBeDefined(); } }); it('should generate test script for all SSG types', async () => { const ssgs = ['jekyll', 'hugo', 'docusaurus', 'mkdocs', 'eleventy']; for (const ssg of ssgs) { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: ssg as any, skipBuild: true, port: 4000, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.testScript).toContain(`# Local Deployment Test Script for ${ssg}`); expect(parsedResult.testScript).toContain('http://localhost:4000'); } }); it('should include install commands for Node.js-based SSGs', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'docusaurus', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.testScript).toContain('npm install'); }); it('should not include install commands for non-Node.js SSGs', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.testScript).not.toContain('npm install'); }); }); describe('Configuration handling', () => { it('should provide recommendations when configuration is missing', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'jekyll', // Jekyll config unlikely to exist in this repo skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.recommendations).toEqual( expect.arrayContaining([expect.stringContaining('Missing configuration file')]), ); }); it('should provide next steps for missing configuration', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'jekyll', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.nextSteps).toEqual( expect.arrayContaining([expect.stringContaining('generate_config')]), ); }); }); describe('Error handling', () => { it('should handle general errors gracefully', async () => { jest.spyOn(process, 'chdir').mockImplementation(() => { throw new Error('Permission denied'); }); const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', }); // The tool returns an error response structure instead of throwing const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.success).toBe(false); expect(parsedResult.error.code).toBe('LOCAL_TEST_FAILED'); expect(parsedResult.error.message).toContain('Permission denied'); }); it('should handle non-existent repository path', async () => { const result = await testLocalDeployment({ repositoryPath: '/non/existent/path', ssg: 'hugo', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); // Should still work with skipBuild, but may have warnings expect(parsedResult).toBeDefined(); expect(result.content).toBeDefined(); }); }); describe('Response structure validation', () => { it('should include all required response fields', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult).toHaveProperty('buildSuccess'); expect(parsedResult).toHaveProperty('ssg'); expect(parsedResult).toHaveProperty('port'); expect(parsedResult).toHaveProperty('testScript'); expect(parsedResult).toHaveProperty('recommendations'); expect(parsedResult).toHaveProperty('nextSteps'); }); it('should include tool recommendations in next steps', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(Array.isArray(parsedResult.nextSteps)).toBe(true); expect(parsedResult.nextSteps.length).toBeGreaterThan(0); }); it('should validate test script content structure', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', port: 8080, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); const testScript = parsedResult.testScript; expect(testScript).toContain('# Local Deployment Test Script for hugo'); expect(testScript).toContain('http://localhost:8080'); expect(testScript).toContain('hugo server'); expect(testScript).toContain('--port 8080'); }); it('should handle different timeout values', async () => { const timeouts = [30, 60, 120, 300]; for (const timeout of timeouts) { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', timeout, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.buildSuccess).toBeDefined(); // Timeout is not directly returned in response, but test should pass } }); it('should provide appropriate recommendations for each SSG type', async () => { const ssgConfigs = { jekyll: '_config.yml', hugo: 'config.toml', docusaurus: 'docusaurus.config.js', mkdocs: 'mkdocs.yml', eleventy: '.eleventy.js', }; for (const [ssg, configFile] of Object.entries(ssgConfigs)) { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: ssg as any, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.recommendations).toEqual( expect.arrayContaining([expect.stringContaining(configFile)]), ); } }); it('should include comprehensive next steps', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'jekyll', // Missing config will trigger recommendations skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); const nextSteps = parsedResult.nextSteps; expect(Array.isArray(nextSteps)).toBe(true); expect(nextSteps.length).toBeGreaterThan(0); // Should include generate_config step for missing config expect(nextSteps).toEqual( expect.arrayContaining([expect.stringContaining('generate_config')]), ); }); it('should handle edge case with empty repository path', async () => { const result = await testLocalDeployment({ repositoryPath: '', ssg: 'hugo', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); // Should handle gracefully and provide recommendations expect(parsedResult).toBeDefined(); expect(result.content).toBeDefined(); }); it('should validate port range handling', async () => { const ports = [1000, 3000, 8080, 9000, 65535]; for (const port of ports) { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', port, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.port).toBe(port); expect(parsedResult.testScript).toContain(`http://localhost:${port}`); } }); }); describe('Advanced coverage scenarios', () => { beforeEach(() => { jest.spyOn(process, 'chdir').mockImplementation(() => {}); }); afterEach(() => { jest.restoreAllMocks(); }); describe('Configuration file scenarios', () => { it('should detect existing configuration file for hugo', async () => { // Mock fs.access to succeed for hugo config file const mockFsAccess = jest.spyOn(fs.promises, 'access').mockResolvedValueOnce(undefined); const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); // Should not recommend missing config since file exists expect(parsedResult.recommendations).not.toEqual( expect.arrayContaining([expect.stringContaining('Missing configuration file')]), ); mockFsAccess.mockRestore(); }); it('should detect existing configuration file for jekyll', async () => { // Mock fs.access to succeed for jekyll config file const mockFsAccess = jest.spyOn(fs.promises, 'access').mockResolvedValueOnce(undefined); const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'jekyll', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); // Should not recommend missing config since file exists expect(parsedResult.recommendations).not.toEqual( expect.arrayContaining([expect.stringContaining('Missing configuration file')]), ); mockFsAccess.mockRestore(); }); it('should detect existing configuration file for docusaurus', async () => { // Mock fs.access to succeed for docusaurus config file const mockFsAccess = jest.spyOn(fs.promises, 'access').mockResolvedValueOnce(undefined); const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'docusaurus', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); // Should not recommend missing config since file exists expect(parsedResult.recommendations).not.toEqual( expect.arrayContaining([expect.stringContaining('Missing configuration file')]), ); mockFsAccess.mockRestore(); }); it('should detect existing configuration file for mkdocs', async () => { // Mock fs.access to succeed for mkdocs config file const mockFsAccess = jest.spyOn(fs.promises, 'access').mockResolvedValueOnce(undefined); const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'mkdocs', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); // Should not recommend missing config since file exists expect(parsedResult.recommendations).not.toEqual( expect.arrayContaining([expect.stringContaining('Missing configuration file')]), ); mockFsAccess.mockRestore(); }); it('should detect existing configuration file for eleventy', async () => { // Mock fs.access to succeed for eleventy config file const mockFsAccess = jest.spyOn(fs.promises, 'access').mockResolvedValueOnce(undefined); const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'eleventy', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); // Should not recommend missing config since file exists expect(parsedResult.recommendations).not.toEqual( expect.arrayContaining([expect.stringContaining('Missing configuration file')]), ); mockFsAccess.mockRestore(); }); }); describe('Build scenarios with actual executions', () => { it('should handle successful build for eleventy without skipBuild', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'eleventy', skipBuild: false, timeout: 10, // Short timeout to avoid long waits }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.ssg).toBe('eleventy'); expect(parsedResult.buildSuccess).toBeDefined(); expect(parsedResult.testScript).toContain('npx @11ty/eleventy'); }); it('should handle successful build for mkdocs without skipBuild', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'mkdocs', skipBuild: false, timeout: 10, // Short timeout to avoid long waits }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.ssg).toBe('mkdocs'); expect(parsedResult.buildSuccess).toBeDefined(); expect(parsedResult.testScript).toContain('mkdocs build'); }); it('should exercise server start paths with short timeout', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'hugo', skipBuild: true, timeout: 5, // Very short timeout to trigger timeout path }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.ssg).toBe('hugo'); expect(parsedResult.serverStarted).toBeDefined(); // localUrl may be undefined if server doesn't start quickly enough expect( typeof parsedResult.localUrl === 'string' || parsedResult.localUrl === undefined, ).toBe(true); }); it('should test port customization in serve commands', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'jekyll', port: 4000, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.testScript).toContain('--port 4000'); expect(parsedResult.testScript).toContain('http://localhost:4000'); }); it('should test mkdocs serve command with custom port', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'mkdocs', port: 8000, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.testScript).toContain('--dev-addr localhost:8000'); expect(parsedResult.testScript).toContain('http://localhost:8000'); }); it('should test eleventy serve command with custom port', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'eleventy', port: 3001, skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.testScript).toContain('--port 3001'); expect(parsedResult.testScript).toContain('http://localhost:3001'); }); it('should provide correct next steps recommendations', async () => { const result = await testLocalDeployment({ repositoryPath: testRepoPath, ssg: 'docusaurus', skipBuild: true, }); const parsedResult = JSON.parse(result.content[0].text); expect(parsedResult.nextSteps).toBeDefined(); expect(Array.isArray(parsedResult.nextSteps)).toBe(true); expect(parsedResult.nextSteps.length).toBeGreaterThan(0); }); }); }); });

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