Skip to main content
Glama

Git MCP Server

git-branch.tool.test.ts11.2 kB
/** * @fileoverview Unit tests for git-branch tool * @module tests/mcp-server/tools/definitions/unit/git-branch.tool.test */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { container } from 'tsyringe'; import { gitBranchTool } from '@/mcp-server/tools/definitions/git-branch.tool.js'; import { GitProviderFactory as GitProviderFactoryToken, StorageService as StorageServiceToken, } from '@/container/tokens.js'; import { createTestContext, createTestSdkContext, createMockGitProvider, createMockStorageService, assertJsonContent, assertJsonField, parseJsonContent, assertLlmFriendlyFormat, } from '../helpers/index.js'; import type { GitBranchResult } from '@/services/git/types.js'; import { GitProviderFactory } from '@/services/git/core/GitProviderFactory.js'; describe('git_branch tool', () => { const mockProvider = createMockGitProvider(); const mockStorage = createMockStorageService(); const mockFactory = { getProvider: vi.fn(async () => mockProvider), } as unknown as GitProviderFactory; beforeEach(() => { mockProvider.resetMocks(); mockStorage.clearAll(); container.clearInstances(); container.register(GitProviderFactoryToken, { useValue: mockFactory }); container.register(StorageServiceToken, { useValue: mockStorage }); const tenantId = 'test-tenant'; const context = createTestContext({ tenantId }); mockStorage.set(`session:workingDir:${tenantId}`, '/test/repo', context); }); describe('Input Schema', () => { it('validates list operation with defaults', () => { const input = { path: '.' }; const result = gitBranchTool.inputSchema.safeParse(input); expect(result.success).toBe(true); if (result.success) { expect(result.data.operation).toBe('list'); expect(result.data.force).toBe(false); expect(result.data.all).toBe(false); expect(result.data.remote).toBe(false); } }); it('accepts create operation with branch name', () => { const input = { path: '.', operation: 'create', name: 'feature-branch' }; const result = gitBranchTool.inputSchema.safeParse(input); expect(result.success).toBe(true); if (result.success) { expect(result.data.operation).toBe('create'); expect(result.data.name).toBe('feature-branch'); } }); it('accepts delete operation', () => { const input = { path: '.', operation: 'delete', name: 'old-branch' }; const result = gitBranchTool.inputSchema.safeParse(input); expect(result.success).toBe(true); }); it('accepts rename operation with newName', () => { const input = { path: '.', operation: 'rename', name: 'old-name', newName: 'new-name', }; const result = gitBranchTool.inputSchema.safeParse(input); expect(result.success).toBe(true); if (result.success) { expect(result.data.newName).toBe('new-name'); } }); it('accepts show-current operation', () => { const input = { path: '.', operation: 'show-current' }; const result = gitBranchTool.inputSchema.safeParse(input); expect(result.success).toBe(true); }); }); describe('Tool Logic - List Operation', () => { it('lists branches successfully', async () => { const mockResult: GitBranchResult = { mode: 'list', branches: [ { name: 'main', current: true, commitHash: 'abc123', upstream: 'origin/main', ahead: 0, behind: 0, }, { name: 'feature', current: false, commitHash: 'def456', }, ], }; mockProvider.branch.mockResolvedValue(mockResult); const parsedInput = gitBranchTool.inputSchema.parse({ path: '.', operation: 'list', }); const appContext = createTestContext({ tenantId: 'test-tenant' }); const sdkContext = createTestSdkContext(); const result = await gitBranchTool.logic( parsedInput, appContext, sdkContext, ); expect(mockProvider.branch).toHaveBeenCalledTimes(1); expect(result.success).toBe(true); expect(result.operation).toBe('list'); expect(result.branches).toHaveLength(2); expect(result.currentBranch).toBe('main'); }); it('passes all flag to provider', async () => { const mockResult: GitBranchResult = { mode: 'list', branches: [], }; mockProvider.branch.mockResolvedValue(mockResult); const parsedInput = gitBranchTool.inputSchema.parse({ path: '.', operation: 'list', all: true, }); const appContext = createTestContext({ tenantId: 'test-tenant' }); const sdkContext = createTestSdkContext(); await gitBranchTool.logic(parsedInput, appContext, sdkContext); const [branchOptions] = mockProvider.branch.mock.calls[0]!; expect(branchOptions.remote).toBe(true); }); }); describe('Tool Logic - Create Operation', () => { it('creates branch successfully', async () => { const mockResult: GitBranchResult = { mode: 'create', created: 'feature-branch', }; mockProvider.branch.mockResolvedValue(mockResult); const parsedInput = gitBranchTool.inputSchema.parse({ path: '.', operation: 'create', name: 'feature-branch', }); const appContext = createTestContext({ tenantId: 'test-tenant' }); const sdkContext = createTestSdkContext(); const result = await gitBranchTool.logic( parsedInput, appContext, sdkContext, ); expect(result.success).toBe(true); expect(result.operation).toBe('create'); expect(result.message).toContain('feature-branch'); expect(result.message).toContain('created'); }); }); describe('Tool Logic - Delete Operation', () => { it('deletes branch successfully', async () => { const mockResult: GitBranchResult = { mode: 'delete', deleted: 'old-branch', }; mockProvider.branch.mockResolvedValue(mockResult); const parsedInput = gitBranchTool.inputSchema.parse({ path: '.', operation: 'delete', name: 'old-branch', }); const appContext = createTestContext({ tenantId: 'test-tenant' }); const sdkContext = createTestSdkContext(); const result = await gitBranchTool.logic( parsedInput, appContext, sdkContext, ); expect(result.success).toBe(true); expect(result.operation).toBe('delete'); expect(result.message).toContain('old-branch'); expect(result.message).toContain('deleted'); }); }); describe('Tool Logic - Rename Operation', () => { it('renames branch successfully', async () => { const mockResult: GitBranchResult = { mode: 'rename', renamed: { from: 'old-name', to: 'new-name' }, }; mockProvider.branch.mockResolvedValue(mockResult); const parsedInput = gitBranchTool.inputSchema.parse({ path: '.', operation: 'rename', name: 'old-name', newName: 'new-name', }); const appContext = createTestContext({ tenantId: 'test-tenant' }); const sdkContext = createTestSdkContext(); const result = await gitBranchTool.logic( parsedInput, appContext, sdkContext, ); expect(result.success).toBe(true); expect(result.operation).toBe('rename'); expect(result.message).toContain('old-name'); expect(result.message).toContain('new-name'); }); }); describe('Response Formatter', () => { it('formats branch list with current branch', () => { const result = { success: true, operation: 'list' as const, branches: [ { name: 'main', current: true, commitHash: 'abc123', upstream: 'origin/main', ahead: 2, behind: 1, }, { name: 'develop', current: false, commitHash: 'def456', }, ], currentBranch: 'main', message: undefined, }; const content = gitBranchTool.responseFormatter!(result); assertJsonContent(content, { success: true, operation: 'list', currentBranch: 'main', }); assertJsonField(content, 'currentBranch', 'main'); assertJsonField(content, 'operation', 'list'); const parsed = parseJsonContent(content) as { branches: Array<{ name: string; current: boolean }>; }; expect(parsed.branches).toHaveLength(2); expect(parsed.branches[0]!.name).toBe('main'); expect(parsed.branches[0]!.current).toBe(true); expect(parsed.branches[1]!.name).toBe('develop'); assertLlmFriendlyFormat(content); }); it('formats create operation result', () => { const result = { success: true, operation: 'create' as const, branches: undefined, currentBranch: undefined, message: "Branch 'feature-x' created successfully.", }; const content = gitBranchTool.responseFormatter!(result); assertJsonContent(content, { success: true, operation: 'create', }); assertJsonField(content, 'operation', 'create'); assertJsonField( content, 'message', "Branch 'feature-x' created successfully.", ); }); it('formats delete operation result', () => { const result = { success: true, operation: 'delete' as const, branches: undefined, currentBranch: undefined, message: "Branch 'old-branch' deleted successfully.", }; const content = gitBranchTool.responseFormatter!(result); assertJsonContent(content, { success: true, operation: 'delete', }); assertJsonField(content, 'operation', 'delete'); assertJsonField( content, 'message', "Branch 'old-branch' deleted successfully.", ); }); }); describe('Tool Metadata', () => { it('has correct tool name', () => { expect(gitBranchTool.name).toBe('git_branch'); }); it('is marked as write operation', () => { expect(gitBranchTool.annotations?.readOnlyHint).toBe(false); }); it('has descriptive title and description', () => { expect(gitBranchTool.title).toBe('Git Branch'); expect(gitBranchTool.description).toBeTruthy(); expect(gitBranchTool.description.toLowerCase()).toContain('branch'); }); it('has valid schemas', () => { expect(gitBranchTool.inputSchema).toBeDefined(); expect(gitBranchTool.outputSchema).toBeDefined(); const inputShape = gitBranchTool.inputSchema.shape; expect(inputShape.operation).toBeDefined(); expect(inputShape.name).toBeDefined(); const outputShape = gitBranchTool.outputSchema.shape; expect(outputShape.success).toBeDefined(); expect(outputShape.operation).toBeDefined(); }); }); });

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/cyanheads/git-mcp-server'

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