Skip to main content
Glama
context-tool.test.ts14 kB
import { contextHandler } from '../../../mcp/services/handlers/unified/context-handler'; import { ToolHandlerContext } from '../../../mcp/types/sdk-custom'; import { ContextService } from '../../../services/domain/context.service'; import { MemoryService } from '../../../services/memory.service'; // Define discriminated union type for context handler results type ContextResult = | { success: true; message?: string; context: { id: string; iso_date: string; agent: string; summary: string; observation: string | null; repository: string; branch: string; created_at: string | null; updated_at: string | null; }; } | { success: true; message?: string; } | { success: false; message: string; }; describe('context tool handler', () => { let mockContext: ToolHandlerContext; let mockMemoryService: jest.Mocked<MemoryService>; let mockContextService: jest.Mocked<ContextService>; beforeEach(() => { // Reset mocks jest.clearAllMocks(); // Create mock context mockContext = { session: { clientProjectRoot: '/test/project', repository: 'test-repo', branch: 'main', }, logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), }, sendProgress: jest.fn(), } as unknown as ToolHandlerContext; mockContextService = { updateContext: jest.fn(), } as unknown as jest.Mocked<ContextService>; // Create mock memory service mockMemoryService = { context: mockContextService, services: { context: mockContextService, }, } as unknown as jest.Mocked<MemoryService>; }); describe('update operation', () => { it('should update context successfully', async () => { const mockUpdateResult = { success: true, message: 'Context updated successfully', context: { id: 'ctx-20241210-test-repo-main', iso_date: '2024-12-10', agent: 'cursor', summary: 'Implemented new feature', observation: null, repository: 'test-repo:main', branch: 'main', created_at: '2024-12-10T12:00:00.000Z', updated_at: '2024-12-10T12:00:00.000Z', }, }; mockContextService.updateContext.mockResolvedValueOnce(mockUpdateResult); const params = { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Implemented new feature', observation: 'Used new pattern for error handling', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(mockContextService.updateContext).toHaveBeenCalledWith(mockContext, '/test/project', { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Implemented new feature', observation: 'Used new pattern for error handling', }); expect(result).toEqual({ success: true, message: 'Context updated successfully', context: { id: 'ctx-20241210-test-repo-main', iso_date: '2024-12-10', agent: 'cursor', summary: 'Implemented new feature', observation: null, repository: 'test-repo:main', branch: 'main', created_at: '2024-12-10T12:00:00.000Z', updated_at: '2024-12-10T12:00:00.000Z', }, }); expect(mockContext.sendProgress).toHaveBeenCalledWith({ status: 'complete', message: 'Context updated successfully', percent: 100, isFinal: true, }); }); it('should handle update without observation', async () => { const mockUpdateResult = { success: true, context: { id: 'ctx-20241210-test-repo-main', iso_date: '2024-12-10', agent: 'cursor', summary: 'Simple update', observation: null, repository: 'test-repo:main', branch: 'main', created_at: null, updated_at: null, }, }; mockContextService.updateContext.mockResolvedValueOnce(mockUpdateResult); const params = { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Simple update', // observation omitted }; const result = (await contextHandler( params, mockContext, mockMemoryService, )) as ContextResult; if (result.success && 'context' in result) { expect(result.success).toBe(true); expect(result.context?.observation).toBeNull(); } else { fail('Expected result to be successful with context'); } }); it('should handle failed update', async () => { const mockUpdateResult = { success: false, message: 'Database connection failed', }; mockContextService.updateContext.mockResolvedValueOnce(mockUpdateResult); const params = { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Failed update', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'Database connection failed', }); }); it('should handle unexpected response format', async () => { // Mock returning null mockContextService.updateContext.mockResolvedValueOnce(null as any); const params = { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Test update', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'Failed to update context: unexpected response format', }); }); }); describe('error handling', () => { it('should handle missing session context', async () => { mockContext.session = {}; const params = { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Test update', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'No active session for context tool. Use memory-bank tool with operation "init" first.', }); }); it('should handle service errors gracefully', async () => { mockContextService.updateContext.mockRejectedValueOnce(new Error('Database error')); const params = { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Test update', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'Database error', }); expect(mockContext.sendProgress).toHaveBeenCalledWith({ status: 'error', message: 'Failed to execute context update: Database error', percent: 100, isFinal: true, }); }); it('should validate required parameters', async () => { const params = { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', // summary missing }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'summary parameter is required for update operation', }); }); it('should throw error for invalid operation', async () => { const params = { operation: 'invalid-op', repository: 'test-repo', summary: 'test', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: "operation must be 'update', received: invalid-op", }); }); it('should throw error for missing repository', async () => { const params = { operation: 'update', // repository missing summary: 'test', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'repository parameter is required', }); }); it('should throw error for invalid repository format', async () => { const params = { operation: 'update', repository: 'test@repo!', // Invalid characters summary: 'test', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'repository name contains invalid characters. Only alphanumeric, hyphens, and underscores are allowed: test@repo!', }); }); it('should trim whitespace from parameters', async () => { const mockUpdateResult = { success: true, context: { id: 'ctx-20241210-test-repo-main', iso_date: '2024-12-10', agent: 'cursor-agent', summary: 'Test summary', observation: 'Test observation', repository: 'test-repo:main', branch: 'main', created_at: null, updated_at: null, }, }; mockContextService.updateContext.mockResolvedValueOnce(mockUpdateResult); const params = { operation: ' update ', // Whitespace should be trimmed repository: ' test-repo ', branch: ' main ', agent: ' cursor ', summary: ' Test summary ', observation: ' Test observation ', }; await contextHandler(params, mockContext, mockMemoryService); // Verify trimmed values were used in the service call expect(mockContextService.updateContext).toHaveBeenCalledWith(mockContext, '/test/project', { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Test summary', observation: 'Test observation', }); }); it('should throw error for whitespace-only parameters', async () => { const params = { operation: ' ', // Whitespace-only repository: 'test-repo', summary: 'test', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'operation parameter cannot be empty or whitespace-only', }); }); it('should handle non-object params', async () => { const result = await contextHandler('invalid-params' as any, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'params must be an object', }); }); it('should handle non-string parameters', async () => { const params = { operation: 123, // Number instead of string repository: 'test-repo', summary: 'test', }; const result = await contextHandler(params, mockContext, mockMemoryService); expect(result).toEqual({ success: false, message: 'operation parameter must be a string, received number', }); }); it('should use default branch when not provided', async () => { const mockUpdateResult = { success: true, context: { id: 'ctx-20241210-test-repo-main', iso_date: '2024-12-10', agent: 'cursor-agent', summary: 'Test summary', observation: null, repository: 'test-repo:main', branch: 'main', created_at: null, updated_at: null, }, }; mockContextService.updateContext.mockResolvedValueOnce(mockUpdateResult); const params = { operation: 'update', repository: 'test-repo', // branch not provided - should default to 'main' summary: 'Test summary', }; await contextHandler(params, mockContext, mockMemoryService); // Verify default branch was used expect(mockContextService.updateContext).toHaveBeenCalledWith(mockContext, '/test/project', { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor-agent', summary: 'Test summary', observation: undefined, }); }); }); describe('progress reporting', () => { it('should report progress during update operation', async () => { const mockUpdateResult = { success: true, context: { id: 'ctx-20241210-test-repo-main', iso_date: '2024-12-10', agent: 'cursor', summary: 'Test update', observation: null, repository: 'test-repo:main', branch: 'main', created_at: null, updated_at: null, }, }; mockContextService.updateContext.mockResolvedValueOnce(mockUpdateResult); const params = { operation: 'update', repository: 'test-repo', branch: 'main', agent: 'cursor', summary: 'Test update', }; await contextHandler(params, mockContext, mockMemoryService); expect(mockContext.sendProgress).toHaveBeenCalledWith({ status: 'in_progress', message: 'Updating context...', percent: 50, }); expect(mockContext.sendProgress).toHaveBeenCalledWith({ status: 'complete', message: 'Context updated successfully', percent: 100, isFinal: true, }); }); }); });

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/Jakedismo/KuzuMem-MCP'

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