Skip to main content
Glama
ErrorService-create-universal-error.test.tsβ€’10.5 kB
/** * Split: ErrorService.createUniversalError tests */ import { describe, it, expect, beforeEach, vi } from 'vitest'; const { createScopedLoggerMock, mockFieldContextLogger } = vi.hoisted(() => { const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }; return { createScopedLoggerMock: vi.fn(() => logger), mockFieldContextLogger: logger, }; }); vi.mock('../../src/utils/logger.js', () => ({ createScopedLogger: createScopedLoggerMock, OperationType: { DATA_PROCESSING: 'data_processing' }, })); import { ErrorService } from '../../src/services/ErrorService.js'; import { UniversalValidationError, ErrorType, } from '../../src/handlers/tool-configs/universal/schemas.js'; import { EnhancedApiError } from '../../src/errors/enhanced-api-errors.js'; vi.mock('../../src/handlers/tool-configs/universal/field-mapper.js', () => ({ validateResourceType: vi.fn(), getFieldSuggestions: vi.fn(), })); import { validateResourceType } from '../../src/handlers/tool-configs/universal/field-mapper.js'; import { buildAttributeMetadataIndex } from '../../src/services/utils/attribute-metadata.js'; describe('ErrorService.createUniversalError', () => { beforeEach(() => { vi.clearAllMocks(); createScopedLoggerMock.mockClear(); mockFieldContextLogger.debug.mockClear(); mockFieldContextLogger.info.mockClear(); mockFieldContextLogger.warn.mockClear(); mockFieldContextLogger.error.mockClear(); vi.mocked(validateResourceType).mockReturnValue({ valid: true, suggestion: undefined, } as any); }); it('passes through UniversalValidationError unchanged', () => { const original = new UniversalValidationError( 'Test error', ErrorType.USER_ERROR ); const result = ErrorService.createUniversalError( 'create', 'companies', original ); expect(result).toBe(original); }); it('passes through EnhancedApiError unchanged', () => { const original = new EnhancedApiError( 'Test error', 400, '/api/test', 'GET' ); const result = ErrorService.createUniversalError( 'update', 'people', original ); expect(result).toBe(original); }); it('preserves enhanced context field type metadata', () => { const original = new EnhancedApiError( 'Invalid status value', 400, '/api/test', 'POST', { field: 'status', fieldType: 'select', validValues: ['new', 'active'], } ); const result = ErrorService.createUniversalError( 'update', 'people', original ) as EnhancedApiError; expect(result).toBe(original); expect(result.context?.fieldType).toBe('select'); expect(result.getContextualMessage()).toContain("type 'select'"); }); it('extracts message from Error objects', () => { const original = new Error('Test error message'); const result = ErrorService.createUniversalError( 'search', 'tasks', original ) as UniversalValidationError; expect(result).toBeInstanceOf(UniversalValidationError); expect(result.message).toContain('Test error message'); expect(result.message).toContain( 'Universal search failed for resource type tasks' ); }); it('extracts message from objects with message property', () => { const original: any = { message: 'Object error message', status: 400 }; const result = ErrorService.createUniversalError( 'delete', 'deals', original ) as UniversalValidationError; expect(result).toBeInstanceOf(UniversalValidationError); expect(result.message).toContain('Object error message'); }); it('handles string errors', () => { const result = ErrorService.createUniversalError( 'create', 'companies', 'String error message' ) as UniversalValidationError; expect(result).toBeInstanceOf(UniversalValidationError); expect(result.message).toContain('String error message'); }); it('handles unknown error types', () => { const result = ErrorService.createUniversalError( 'update', 'people', 12345 as any ) as UniversalValidationError; expect(result).toBeInstanceOf(UniversalValidationError); expect(result.message).toContain('Unknown error'); }); it('classifies USER_ERROR for not found messages', () => { const result = ErrorService.createUniversalError( 'get', 'records', new Error('Record not found') ) as UniversalValidationError; expect(result.errorType).toBe(ErrorType.USER_ERROR); }); it('defaults to SYSTEM_ERROR for unclassified errors', () => { const result = ErrorService.createUniversalError( 'create', 'tasks', new Error('Some random error') ) as UniversalValidationError; expect(result.errorType).toBe(ErrorType.SYSTEM_ERROR); }); it('includes operation suggestion in error details', () => { vi.mocked(validateResourceType).mockReturnValue({ valid: true, suggestion: undefined, } as any); const result = ErrorService.createUniversalError( 'create', 'companies', new Error('Rate limit exceeded') ) as UniversalValidationError; expect(result.suggestion).toBeDefined(); expect(result.suggestion).toContain('wait a moment'); }); it('includes original error as cause', () => { const original = new Error('Original error'); const result = ErrorService.createUniversalError( 'update', 'people', original ) as UniversalValidationError; expect(result.cause).toBe(original); }); describe('fromAxios', () => { it('preserves field type metadata within validation errors', () => { const mapped = ErrorService.fromAxios({ response: { status: 400, data: { message: 'Invalid status value', validation_errors: [ { field: 'status', message: 'Invalid option provided', field_type: 'select', }, ], }, }, } as any); expect(mapped.details?.validation_errors?.[0]?.fieldType).toBe('select'); expect(mapped.message).toContain('type: select'); }); it('preserves field metadata when creating enhanced errors', () => { const fieldMetadata = { slug: 'priority', type: 'select', options: ['low', 'medium', 'high'], }; const original = new EnhancedApiError( 'Invalid priority value', 400, '/api/test', 'POST', { field: 'priority', fieldType: 'select', fieldMetadata, } ); const result = ErrorService.createUniversalError( 'update', 'tasks', original ) as EnhancedApiError; expect(result.context?.fieldMetadata).toBe(fieldMetadata); expect(result.context?.fieldType).toBe('select'); }); }); describe('field error utilities', () => { it('hydrates field context from attribute discovery metadata', () => { const attributes = [ { api_slug: 'custom_status', field_type: 'select', title: 'Custom Status', config: { select: { options: [ { id: 'new', title: 'New', value: 'new' }, { id: 'active', title: 'Active', value: 'active' }, ], }, }, }, ]; const attributeMetadataIndex = buildAttributeMetadataIndex(attributes); const error = ErrorService.createFieldError({ field: 'Custom Status', message: 'Invalid status value', resourceType: 'tasks', operation: 'update', attributeMetadataIndex, }) as EnhancedApiError; expect(error.context?.fieldType).toBe('select'); expect(error.context?.fieldMetadata).toMatchObject({ api_slug: 'custom_status', field_type: 'select', }); expect(error.getContextualMessage()).toContain( "Field 'Custom Status' expects values of type 'select'" ); }); it('creates enhanced field error with metadata-derived field type', () => { const attributeMetadataIndex = { status: { api_slug: 'status', type: 'select' }, } as Record<string, Record<string, unknown>>; const error = ErrorService.createFieldError({ field: 'status', message: 'Invalid status value', resourceType: 'tasks', operation: 'update', attributeMetadataIndex, }) as EnhancedApiError; expect(error).toBeInstanceOf(EnhancedApiError); expect(error.context?.fieldType).toBe('select'); expect(error.context?.fieldMetadata).toBe(attributeMetadataIndex.status); }); it('logs resolved field type for downstream debugging', () => { const attributeMetadataIndex = { status: { api_slug: 'status', field_type: 'select' }, } as Record<string, Record<string, unknown>>; ErrorService.createFieldError({ field: 'status', message: 'Invalid status value', resourceType: 'tasks', operation: 'update', attributeMetadataIndex, }); expect(mockFieldContextLogger.debug).toHaveBeenCalledWith( 'Resolved field type for error context', expect.objectContaining({ field: 'status', fieldType: 'select', resourceType: 'tasks', operation: 'update', }) ); }); it('creates validation error with metadata context', () => { const attributeMetadataIndex = { priority: { api_slug: 'priority', attribute_type: 'number' }, } as Record<string, Record<string, unknown>>; const error = ErrorService.createValidationError({ message: 'Priority must be numeric', resourceType: 'tasks', operation: 'update', field: 'priority', attributeMetadataIndex, documentationHint: 'Use numeric values between 1-5', retryable: false, }) as EnhancedApiError; expect(error).toBeInstanceOf(EnhancedApiError); expect(error.context?.fieldType).toBe('number'); expect(error.context?.fieldMetadata).toBe( attributeMetadataIndex.priority ); expect(error.context?.retryable).toBe(false); expect(error.context?.documentationHint).toContain('numeric'); }); }); });

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/kesslerio/attio-mcp-server'

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