Skip to main content
Glama
blade47

ShadowGit MCP Server

by blade47
session-client.test.ts9.85 kB
import { describe, it, expect, beforeEach, jest, afterEach } from '@jest/globals'; import { SessionClient } from '../../src/core/session-client'; // Mock the global fetch global.fetch = jest.fn() as jest.MockedFunction<typeof fetch>; describe('SessionClient', () => { let client: SessionClient; let mockFetch: jest.MockedFunction<typeof fetch>; beforeEach(() => { jest.clearAllMocks(); client = new SessionClient(); mockFetch = global.fetch as jest.MockedFunction<typeof fetch>; }); afterEach(() => { jest.clearAllMocks(); }); describe('isHealthy', () => { it('should return true when API responds successfully', async () => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, } as Response); const result = await client.isHealthy(); expect(result).toBe(true); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/health'), expect.objectContaining({ signal: expect.any(Object), }) ); }); it('should return false when API returns non-200 status', async () => { mockFetch.mockResolvedValueOnce({ ok: false, status: 404, } as Response); const result = await client.isHealthy(); expect(result).toBe(false); }); it('should return false when fetch throws an error', async () => { mockFetch.mockRejectedValueOnce(new Error('Network error')); const result = await client.isHealthy(); expect(result).toBe(false); }); it('should return false when request times out', async () => { mockFetch.mockImplementationOnce(() => new Promise((_, reject) => setTimeout(() => reject(new Error('AbortError')), 100) ) ); const result = await client.isHealthy(); expect(result).toBe(false); }); }); describe('startSession', () => { it('should successfully start a session and return sessionId', async () => { const mockSessionId = 'test-session-123'; mockFetch.mockResolvedValueOnce({ ok: true, status: 200, json: (jest.fn() as any).mockResolvedValue({ success: true, sessionId: mockSessionId, }), } as unknown as Response); const result = await client.startSession({ repoPath: '/test/repo', aiTool: 'Claude', description: 'Testing session', }); expect(result).toBe(mockSessionId); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/session/start'), expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ repoPath: '/test/repo', aiTool: 'Claude', description: 'Testing session', }), signal: expect.any(AbortSignal), }) ); }); it('should return null when API returns failure', async () => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, json: (jest.fn() as any).mockResolvedValue({ success: false, error: 'Repository not found', }), } as unknown as Response); const result = await client.startSession({ repoPath: '/test/repo', aiTool: 'Claude', description: 'Testing session', }); expect(result).toBeNull(); }); it('should return null when API returns non-200 status', async () => { mockFetch.mockResolvedValueOnce({ ok: false, status: 404, json: (jest.fn() as any).mockResolvedValue({ error: 'Not found', }), } as unknown as Response); const result = await client.startSession({ repoPath: '/test/repo', aiTool: 'Claude', description: 'Testing session', }); expect(result).toBeNull(); }); it('should return null when fetch throws an error', async () => { mockFetch.mockRejectedValueOnce(new Error('Network error')); const result = await client.startSession({ repoPath: '/test/repo', aiTool: 'Claude', description: 'Testing session', }); expect(result).toBeNull(); }); it('should return null when response is not valid JSON', async () => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, json: (jest.fn() as any).mockRejectedValue(new Error('Invalid JSON')), } as unknown as Response); const result = await client.startSession({ repoPath: '/test/repo', aiTool: 'Claude', description: 'Testing session', }); expect(result).toBeNull(); }); }); describe('endSession', () => { it('should successfully end a session', async () => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, json: (jest.fn() as any).mockResolvedValue({ success: true, }), } as unknown as Response); const result = await client.endSession('test-session-123', 'abc1234'); expect(result).toBe(true); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/session/end'), expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: 'test-session-123', commitHash: 'abc1234', }), signal: expect.any(AbortSignal), }) ); }); it('should successfully end a session without commit hash', async () => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, json: (jest.fn() as any).mockResolvedValue({ success: true, }), } as unknown as Response); const result = await client.endSession('test-session-123'); expect(result).toBe(true); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/session/end'), expect.objectContaining({ body: JSON.stringify({ sessionId: 'test-session-123', }), }) ); }); it('should return false when API returns failure', async () => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, json: (jest.fn() as any).mockResolvedValue({ success: false, error: 'Session not found', }), } as unknown as Response); const result = await client.endSession('invalid-session'); expect(result).toBe(false); }); it('should return false when API returns non-200 status', async () => { mockFetch.mockResolvedValueOnce({ ok: false, status: 404, } as unknown as Response); const result = await client.endSession('test-session-123'); expect(result).toBe(false); }); it('should return false when fetch throws an error', async () => { mockFetch.mockRejectedValueOnce(new Error('Network error')); const result = await client.endSession('test-session-123'); expect(result).toBe(false); }); }); describe('Environment Variables', () => { it('should use custom SESSION_API_URL from environment', () => { const originalEnv = process.env.SHADOWGIT_SESSION_API; process.env.SHADOWGIT_SESSION_API = 'http://custom-api:5000/api'; // Create new client to pick up env var const customClient = new SessionClient(); // Reset environment if (originalEnv) { process.env.SHADOWGIT_SESSION_API = originalEnv; } else { delete process.env.SHADOWGIT_SESSION_API; } // We can't directly test the URL without exposing it, but we can verify // the client was created without errors expect(customClient).toBeDefined(); }); }); describe('Timeout Handling', () => { it('should timeout health check after 3 seconds', async () => { let timeoutCalled = false; mockFetch.mockImplementationOnce((_, options) => { const signal = (options as any).signal; // Simulate timeout return new Promise((_, reject) => { signal.addEventListener('abort', () => { timeoutCalled = true; reject(new Error('AbortError')); }); // Wait longer than timeout setTimeout(() => {}, 5000); }); }); const result = await client.isHealthy(); expect(result).toBe(false); // The timeout should have been triggered expect(mockFetch).toHaveBeenCalled(); }); it('should timeout startSession after 3 seconds', async () => { mockFetch.mockImplementationOnce(() => new Promise((_, reject) => setTimeout(() => reject(new Error('AbortError')), 5000) ) ); const result = await client.startSession({ repoPath: '/test/repo', aiTool: 'Claude', description: 'Test', }); expect(result).toBeNull(); }); it('should timeout endSession after 3 seconds', async () => { mockFetch.mockImplementationOnce(() => new Promise((_, reject) => setTimeout(() => reject(new Error('AbortError')), 5000) ) ); const result = await client.endSession('test-session-123'); expect(result).toBe(false); }); }); });

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/blade47/shadowgit-mcp'

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