Skip to main content
Glama
handler.factory.test.ts10.7 kB
/** * @fileoverview Tests for handler factory functions */ import { vi, MockedFunction } from 'vitest'; import { createBaseHandlerFactory, createDefaultHandlerDeps, wrapInApiResponse, createErrorResponse, isApiResponse, } from '../../../handlers/base/handler.factory.js'; import { BaseHandlerDeps } from '../../../handlers/base/handler.interface.js'; import { DeepSourceClientFactory } from '../../../client/factory.js'; import { Logger } from '../../../utils/logging/logger.js'; // Mock dependencies vi.mock('../../../client/factory.js'); vi.mock('../../../config/index.js', () => ({ getApiKey: vi.fn(() => 'test-api-key'), })); vi.mock('../../../utils/logging/logger.js', () => ({ createLogger: vi.fn(() => ({ info: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn(), })), })); // Set up test environment beforeEach(() => { process.env.DEEPSOURCE_API_KEY = 'test-api-key'; }); afterEach(() => { delete process.env.DEEPSOURCE_API_KEY; }); describe('handler.factory', () => { describe('createBaseHandlerFactory', () => { let mockDeps: BaseHandlerDeps; let mockLogger: { info: MockedFunction<(message: string, data?: unknown) => void>; error: MockedFunction<(message: string, error?: unknown) => void>; }; beforeEach(() => { mockLogger = { info: vi.fn(), error: vi.fn(), }; mockDeps = { clientFactory: new DeepSourceClientFactory('test-key'), logger: mockLogger as Logger, getApiKey: vi.fn(() => 'test-api-key'), }; }); it('should create a handler factory with logging', async () => { const handlerLogic = vi.fn(async (deps, params) => ({ data: `Hello ${params.name}`, })); const factory = createBaseHandlerFactory('test_handler', handlerLogic); const handler = factory(mockDeps); const result = await handler({ name: 'World' }); expect(handlerLogic).toHaveBeenCalledWith(mockDeps, { name: 'World' }); expect(result).toEqual({ data: 'Hello World' }); // Check logging expect(mockLogger.info).toHaveBeenCalledWith( 'Handler test_handler invoked', expect.objectContaining({ hasParams: true, params: { name: 'World' } }) ); expect(mockLogger.info).toHaveBeenCalledWith( 'Handler test_handler completed successfully', expect.objectContaining({ duration: expect.any(Number) }) ); }); it('should log handler errors and re-throw', async () => { const error = new Error('Handler failed'); const handlerLogic = vi.fn(async () => { throw error; }); const factory = createBaseHandlerFactory('error_handler', handlerLogic); const handler = factory(mockDeps); await expect(handler({})).rejects.toThrow('Handler failed'); expect(mockLogger.error).toHaveBeenCalledWith( 'Handler error_handler failed', expect.objectContaining({ duration: expect.any(Number), errorType: 'Error', errorMessage: 'Handler failed', errorStack: expect.stringContaining('Error: Handler failed'), }) ); }); it('should handle non-Error throws', async () => { const handlerLogic = vi.fn(async () => { throw 'String error'; }); const factory = createBaseHandlerFactory('string_error_handler', handlerLogic); const handler = factory(mockDeps); await expect(handler({})).rejects.toBe('String error'); expect(mockLogger.error).toHaveBeenCalledWith( 'Handler string_error_handler failed', expect.objectContaining({ errorType: 'string', errorMessage: 'String error', errorStack: undefined, }) ); }); it('should log result count for array data', async () => { const handlerLogic = vi.fn(async () => ({ data: ['item1', 'item2', 'item3'], })); const factory = createBaseHandlerFactory('array_handler', handlerLogic); const handler = factory(mockDeps); await handler({}); expect(mockLogger.info).toHaveBeenCalledWith( 'Handler array_handler completed successfully', expect.objectContaining({ duration: expect.any(Number), resultCount: 3, }) ); }); }); describe('createDefaultHandlerDeps', () => { it('should create default dependencies', () => { const deps = createDefaultHandlerDeps(); expect(deps.clientFactory).toBeInstanceOf(DeepSourceClientFactory); expect(deps.logger).toBeDefined(); expect(deps.getApiKey).toBeDefined(); expect(deps.getApiKey()).toBe('test-api-key'); }); it('should accept overrides', () => { const customLogger = { info: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn(), }; const deps = createDefaultHandlerDeps({ logger: customLogger as Logger, }); expect(deps.logger).toBe(customLogger); }); }); describe('wrapInApiResponse', () => { it('should wrap data in ApiResponse format', () => { const data = { message: 'Hello', value: 42 }; const response = wrapInApiResponse(data); expect(response).toEqual({ content: [ { type: 'text', text: JSON.stringify(data, null, 2), }, ], }); }); it('should handle arrays', () => { const data = ['item1', 'item2']; const response = wrapInApiResponse(data); expect(response.content[0].text).toBe('[\n "item1",\n "item2"\n]'); }); it('should handle null and undefined', () => { expect(wrapInApiResponse(null).content[0].text).toBe('null'); expect(wrapInApiResponse(undefined).content[0].text).toBe(undefined); }); }); describe('createErrorResponse', () => { it('should handle Error instances', () => { const error = new Error('Something went wrong'); const response = createErrorResponse(error); expect(response).toEqual({ isError: true, content: [ { type: 'text', text: JSON.stringify({ code: 'HANDLER_ERROR', message: 'Something went wrong', }), }, ], }); }); it('should handle Error with code property', () => { const error = new Error('API failed'); (error as Record<string, unknown>).code = 'API_ERROR'; const response = createErrorResponse(error); expect(response.content[0].text).toContain('"code":"API_ERROR"'); expect(response.content[0].text).toContain('"message":"API failed"'); }); it('should handle Error with details and suggestions', () => { const error = new Error('Validation failed'); (error as Record<string, unknown>).code = 'VALIDATION_ERROR'; (error as Record<string, unknown>).details = { field: 'email', reason: 'invalid format' }; (error as Record<string, unknown>).suggestions = ['Check email format', 'Use valid domain']; const response = createErrorResponse(error); const errorData = JSON.parse(response.content[0].text); expect(errorData).toEqual({ code: 'VALIDATION_ERROR', message: 'Validation failed', details: { field: 'email', reason: 'invalid format' }, suggestions: ['Check email format', 'Use valid domain'], }); }); it('should handle non-Error objects', () => { const response = createErrorResponse({ error: 'Custom error object' }); expect(response.content[0].text).toContain('"code":"UNKNOWN_ERROR"'); expect(response.content[0].text).toContain('[object Object]'); }); it('should handle string errors', () => { const response = createErrorResponse('Simple string error'); expect(response.content[0].text).toContain('"code":"UNKNOWN_ERROR"'); expect(response.content[0].text).toContain('"message":"Simple string error"'); }); it('should handle null/undefined errors', () => { const nullResponse = createErrorResponse(null); const undefinedResponse = createErrorResponse(undefined); expect(nullResponse.content[0].text).toContain('"code":"UNKNOWN_ERROR"'); expect(nullResponse.content[0].text).toContain('"message":"null"'); expect(undefinedResponse.content[0].text).toContain('"code":"UNKNOWN_ERROR"'); // undefined becomes "undefined" string const undefinedData = JSON.parse(undefinedResponse.content[0].text); expect(undefinedData.code).toBe('UNKNOWN_ERROR'); expect(undefinedData.message).toBe('undefined'); }); it('should use custom default message', () => { const response = createErrorResponse(null, 'Custom error message'); // null still produces "null" as the message when stringified expect(response.content[0].text).toContain('"message":"null"'); }); it('should handle Error with empty message', () => { const error = new Error(''); const response = createErrorResponse(error, 'Default message'); expect(response.content[0].text).toContain('"message":"Default message"'); }); }); describe('isApiResponse', () => { it('should return true for valid ApiResponse', () => { const validResponse = { content: [{ type: 'text', text: 'data' }], }; expect(isApiResponse(validResponse)).toBe(true); }); it('should return true for ApiResponse with isError', () => { const errorResponse = { content: [{ type: 'text', text: 'error' }], isError: true, }; expect(isApiResponse(errorResponse)).toBe(true); }); it('should return false for null', () => { expect(isApiResponse(null)).toBe(false); }); it('should return false for undefined', () => { expect(isApiResponse(undefined)).toBe(false); }); it('should return false for non-objects', () => { expect(isApiResponse('string')).toBe(false); expect(isApiResponse(123)).toBe(false); expect(isApiResponse(true)).toBe(false); }); it('should return false for objects without content', () => { expect(isApiResponse({})).toBe(false); expect(isApiResponse({ data: 'test' })).toBe(false); }); it('should return false for objects with non-array content', () => { expect(isApiResponse({ content: 'not an array' })).toBe(false); expect(isApiResponse({ content: { type: 'text' } })).toBe(false); }); it('should return true for empty content array', () => { expect(isApiResponse({ content: [] })).toBe(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/sapientpants/deepsource-mcp-server'

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