Skip to main content
Glama

documcp

by tosin2013
test-local-deployment.test.ts19.3 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