Skip to main content
Glama
error-handling-improvements.test.tsβ€’7.34 kB
/** * Test error handling improvements for Issue #523 Query API implementation * Validates that critical errors bubble up while benign errors are handled gracefully */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { UniversalSearchService } from '../../src/services/UniversalSearchService.js'; import { UniversalResourceType, SearchType, } from '../../src/handlers/tool-configs/universal/types.js'; import { AuthenticationError, NetworkError, ServerError, ResourceNotFoundError, } from '../../src/errors/api-errors.js'; // Mock the Attio client using global override mechanism const mockPost = vi.fn(); const mockClient = { post: mockPost, }; // Mock performance tracking vi.mock('../../src/middleware/performance-enhanced.js', () => ({ enhancedPerformanceTracker: { startOperation: () => 'test-perf-id', markTiming: () => {}, markApiStart: () => Date.now(), markApiEnd: () => {}, endOperation: () => {}, }, })); // Mock validation service vi.mock('../../src/services/ValidationService.js', () => ({ ValidationService: { validatePaginationParameters: () => {}, validateFiltersSchema: () => {}, }, })); describe('Error Handling Improvements - Issue #523', () => { beforeEach(() => { vi.clearAllMocks(); // Set up test-specific client override (globalThis as any).setTestApiClient?.(mockClient); }); afterEach(() => { vi.restoreAllMocks(); // Clear test-specific client override (globalThis as any).clearTestApiClient?.(); }); describe('Critical Errors Should Bubble Up', () => { it('should re-throw authentication errors instead of returning empty array', async () => { // Mock 401 authentication error mockPost.mockRejectedValue({ response: { status: 401, data: { message: 'Invalid API key' }, }, message: 'Request failed with status code 401', }); const params = { resource_type: UniversalResourceType.COMPANIES, search_type: SearchType.RELATIONSHIP, relationship_target_type: UniversalResourceType.PEOPLE, relationship_target_id: 'person_123', }; // Should throw AuthenticationError, not return empty array await expect( UniversalSearchService.searchRecords(params) ).rejects.toThrow(AuthenticationError); }); it('should re-throw network errors instead of returning empty array', async () => { // Mock network connection error mockPost.mockRejectedValue({ code: 'ECONNREFUSED', message: 'connect ECONNREFUSED 127.0.0.1:443', }); const params = { resource_type: UniversalResourceType.COMPANIES, search_type: SearchType.CONTENT, query: 'test', content_fields: ['name'], }; // Should throw NetworkError, not return empty array await expect( UniversalSearchService.searchRecords(params) ).rejects.toThrow(NetworkError); }); it('should re-throw server errors instead of returning empty array', async () => { // Mock 500 server error mockPost.mockRejectedValue({ response: { status: 500, data: { message: 'Internal server error' }, }, message: 'Request failed with status code 500', }); const params = { resource_type: UniversalResourceType.COMPANIES, search_type: SearchType.TIMEFRAME, timeframe_attribute: 'created_at', start_date: '2024-01-01', date_operator: 'greater_than' as const, }; // Should throw ServerError, not return empty array await expect( UniversalSearchService.searchRecords(params) ).rejects.toThrow(ServerError); }); }); describe('Benign Errors Should Be Handled Gracefully', () => { it('should return empty array for 404 errors (no results found)', async () => { // Mock 404 not found error mockPost.mockRejectedValue({ response: { status: 404, data: { message: 'No records found' }, }, message: 'Request failed with status code 404', }); const params = { resource_type: UniversalResourceType.COMPANIES, search_type: SearchType.RELATIONSHIP, relationship_target_type: UniversalResourceType.PEOPLE, relationship_target_id: 'person_123', }; // Should return empty array gracefully, not throw const result = await UniversalSearchService.searchRecords(params); expect(result).toEqual([]); }); it('should handle content search 404 gracefully', async () => { // Mock 404 for content search mockPost.mockRejectedValue({ response: { status: 404, data: { message: 'No matching content found' }, }, message: 'Request failed with status code 404', }); const params = { resource_type: UniversalResourceType.PEOPLE, search_type: SearchType.CONTENT, query: 'nonexistent', content_fields: ['name', 'email_addresses'], }; // Should return empty array gracefully const result = await UniversalSearchService.searchRecords(params); expect(result).toEqual([]); }); it('should handle timeframe search 404 gracefully', async () => { // Mock 404 for timeframe search mockPost.mockRejectedValue({ response: { status: 404, data: { message: 'No records in timeframe' }, }, message: 'Request failed with status code 404', }); const params = { resource_type: UniversalResourceType.TASKS, search_type: SearchType.TIMEFRAME, timeframe_attribute: 'created_at', start_date: '2024-01-01', end_date: '2024-01-02', date_operator: 'between' as const, }; // Should return empty array gracefully const result = await UniversalSearchService.searchRecords(params); expect(result).toEqual([]); }); }); describe('Error Type Detection', () => { it('should correctly identify different error types', async () => { const testCases = [ { mockError: { response: { status: 401, data: { message: 'Unauthorized' } }, }, expectedErrorType: AuthenticationError, description: '401 authentication error', }, { mockError: { code: 'ENOTFOUND', message: 'DNS lookup failed' }, expectedErrorType: NetworkError, description: 'DNS network error', }, { mockError: { response: { status: 502, data: { message: 'Bad Gateway' } }, }, expectedErrorType: ServerError, description: '502 server error', }, ]; for (const testCase of testCases) { mockPost.mockRejectedValueOnce(testCase.mockError); const params = { resource_type: UniversalResourceType.COMPANIES, search_type: SearchType.RELATIONSHIP, relationship_target_type: UniversalResourceType.PEOPLE, relationship_target_id: 'person_123', }; await expect( UniversalSearchService.searchRecords(params) ).rejects.toThrow(testCase.expectedErrorType); } }); }); });

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