Skip to main content
Glama
MUSE-CODE-SPACE

Vibe Coding Documentation MCP (MUSE)

git.test.js11.6 kB
import { gitTool } from '../tools/git.js'; import { parseStatusPorcelainV2, parseLogOutput, parseDiffStat, } from '../utils/gitParsers.js'; import * as path from 'path'; import * as os from 'os'; // Test with the actual vibe-coding-mcp repo const TEST_REPO_PATH = path.resolve(process.cwd()); describe('gitTool', () => { describe('status action', () => { it('should return repository status', async () => { const result = await gitTool({ action: 'status', repoPath: TEST_REPO_PATH, }); expect(result.success).toBe(true); expect(result.action).toBe('status'); expect(result.status).toBeDefined(); expect(result.status?.branch).toBeDefined(); expect(typeof result.status?.isClean).toBe('boolean'); expect(Array.isArray(result.status?.staged)).toBe(true); expect(Array.isArray(result.status?.unstaged)).toBe(true); }); it('should include untracked files by default', async () => { const result = await gitTool({ action: 'status', repoPath: TEST_REPO_PATH, includeUntracked: true, }); expect(result.success).toBe(true); expect(Array.isArray(result.status?.untracked)).toBe(true); }); }); describe('log action', () => { it('should return commit history', async () => { const result = await gitTool({ action: 'log', repoPath: TEST_REPO_PATH, limit: 5, }); expect(result.success).toBe(true); expect(result.action).toBe('log'); expect(result.commits).toBeDefined(); expect(Array.isArray(result.commits)).toBe(true); expect(result.commits.length).toBeLessThanOrEqual(5); if (result.commits.length > 0) { const commit = result.commits[0]; expect(commit.hash).toBeDefined(); expect(commit.shortHash).toBeDefined(); expect(commit.author).toBeDefined(); expect(commit.date).toBeDefined(); expect(commit.message).toBeDefined(); } }); it('should support oneline format', async () => { const result = await gitTool({ action: 'log', repoPath: TEST_REPO_PATH, limit: 3, oneline: true, }); expect(result.success).toBe(true); expect(result.commits).toBeDefined(); }); it('should filter by grep', async () => { const result = await gitTool({ action: 'log', repoPath: TEST_REPO_PATH, limit: 10, grep: 'feat', }); expect(result.success).toBe(true); // All returned commits should contain 'feat' in message if (result.commits && result.commits.length > 0) { result.commits.forEach((commit) => { expect(commit.message.toLowerCase()).toContain('feat'); }); } }); }); describe('diff action', () => { it('should return diff information', async () => { const result = await gitTool({ action: 'diff', repoPath: TEST_REPO_PATH, diffType: 'all', stat: true, }); expect(result.success).toBe(true); expect(result.action).toBe('diff'); expect(result.diff).toBeDefined(); expect(typeof result.diff?.totalAdditions).toBe('number'); expect(typeof result.diff?.totalDeletions).toBe('number'); expect(Array.isArray(result.diff?.files)).toBe(true); }); it('should support staged diff', async () => { const result = await gitTool({ action: 'diff', repoPath: TEST_REPO_PATH, diffType: 'staged', }); expect(result.success).toBe(true); expect(result.diff).toBeDefined(); }); }); describe('branch action', () => { it('should return branch information', async () => { const result = await gitTool({ action: 'branch', repoPath: TEST_REPO_PATH, includeRemote: true, }); expect(result.success).toBe(true); expect(result.action).toBe('branch'); expect(result.branch).toBeDefined(); expect(result.branch?.current).toBeDefined(); expect(Array.isArray(result.branch?.local)).toBe(true); }); it('should include remote branches', async () => { const result = await gitTool({ action: 'branch', repoPath: TEST_REPO_PATH, includeRemote: true, }); expect(result.success).toBe(true); expect(Array.isArray(result.branch?.remote)).toBe(true); }); }); describe('snapshot action', () => { it('should capture complete git snapshot', async () => { const result = await gitTool({ action: 'snapshot', repoPath: TEST_REPO_PATH, includeDiff: true, includeLog: true, logLimit: 5, }); expect(result.success).toBe(true); expect(result.action).toBe('snapshot'); expect(result.snapshot).toBeDefined(); expect(result.snapshot?.timestamp).toBeDefined(); expect(result.snapshot?.repository).toBeDefined(); expect(result.snapshot?.status).toBeDefined(); expect(result.snapshot?.branch).toBeDefined(); expect(Array.isArray(result.snapshot?.recentCommits)).toBe(true); }); it('should include stash when requested', async () => { const result = await gitTool({ action: 'snapshot', repoPath: TEST_REPO_PATH, includeStash: true, }); expect(result.success).toBe(true); // Stashes may or may not exist if (result.snapshot?.stashes) { expect(Array.isArray(result.snapshot.stashes)).toBe(true); } }); }); describe('extractDecisions action', () => { it('should extract design decisions from commits', async () => { const result = await gitTool({ action: 'extractDecisions', repoPath: TEST_REPO_PATH, limit: 50, }); expect(result.success).toBe(true); expect(result.action).toBe('extractDecisions'); expect(result.decisions).toBeDefined(); expect(Array.isArray(result.decisions)).toBe(true); if (result.decisions && result.decisions.length > 0) { const decision = result.decisions[0]; expect(decision.commitHash).toBeDefined(); expect(decision.title).toBeDefined(); expect(decision.category).toBeDefined(); expect(['high', 'medium', 'low']).toContain(decision.importance); } }); it('should filter by since date', async () => { const result = await gitTool({ action: 'extractDecisions', repoPath: TEST_REPO_PATH, since: '1 month ago', limit: 20, }); expect(result.success).toBe(true); expect(result.decisions).toBeDefined(); }); }); describe('error handling', () => { it('should handle non-git directory gracefully', async () => { const nonGitPath = os.tmpdir(); const result = await gitTool({ action: 'status', repoPath: nonGitPath, }); expect(result.success).toBe(false); expect(result.error).toContain('Not a git repository'); }); it('should handle non-existent path', async () => { const result = await gitTool({ action: 'status', repoPath: '/non/existent/path', }); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('should require sessionId for linkToSession', async () => { const result = await gitTool({ action: 'linkToSession', repoPath: TEST_REPO_PATH, }); expect(result.success).toBe(false); expect(result.error).toContain('sessionId is required'); }); }); }); describe('gitParsers', () => { describe('parseStatusPorcelainV2', () => { it('should parse branch info', () => { const output = `# branch.oid abc123 # branch.head main # branch.upstream origin/main # branch.ab +1 -0`; const result = parseStatusPorcelainV2(output); expect(result.branch).toBe('main'); expect(result.upstream).toBe('origin/main'); expect(result.ahead).toBe(1); expect(result.behind).toBe(0); }); it('should parse untracked files', () => { const output = `# branch.head main ? new-file.ts ? another-file.js`; const result = parseStatusPorcelainV2(output); expect(result.untracked.length).toBe(2); expect(result.untracked[0].path).toBe('new-file.ts'); expect(result.untracked[0].status).toBe('untracked'); }); it('should parse modified files', () => { const output = `# branch.head main 1 .M N... 100644 100644 100644 abc123 def456 src/index.ts`; const result = parseStatusPorcelainV2(output); expect(result.unstaged.length).toBe(1); expect(result.unstaged[0].status).toBe('modified'); }); }); describe('parseLogOutput', () => { it('should parse log with body', () => { const output = `abc123|abc1|John|john@example.com|2024-01-01T00:00:00Z|feat: Add feature This is the body END_COMMIT def456|def4|Jane|jane@example.com|2024-01-02T00:00:00Z|fix: Fix bug Another body END_COMMIT`; const result = parseLogOutput(output, true); expect(result.length).toBe(2); expect(result[0].hash).toBe('abc123'); expect(result[0].author).toBe('John'); expect(result[0].message).toBe('feat: Add feature'); expect(result[0].body).toBe('This is the body'); }); it('should parse oneline format', () => { const output = `abc123|abc1|John|john@example.com|2024-01-01T00:00:00Z|feat: Add feature def456|def4|Jane|jane@example.com|2024-01-02T00:00:00Z|fix: Fix bug`; const result = parseLogOutput(output, false); expect(result.length).toBe(2); expect(result[0].hash).toBe('abc123'); expect(result[1].hash).toBe('def456'); }); }); describe('parseDiffStat', () => { it('should parse diff stat output', () => { const output = ` src/index.ts | 10 ++++------ src/utils.ts | 5 ++--- 2 files changed, 6 insertions(+), 9 deletions(-)`; const result = parseDiffStat(output); expect(result.files.length).toBe(2); expect(result.totalAdditions).toBe(6); expect(result.totalDeletions).toBe(9); }); }); }); //# sourceMappingURL=git.test.js.map

Latest Blog Posts

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/MUSE-CODE-SPACE/vibe-coding-mcp'

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