Skip to main content
Glama
error-handling-utils.ts6.46 kB
/** * Error Handling E2E Test Utilities * * Common utilities for error handling tests including: * - Safe content extraction with null checking * - Test data cleanup utilities * - Error validation helpers * - Batch operation utilities */ import type { McpToolResponse } from './assertions.js'; /** * Safely extracts record ID from MCP tool response with proper null checking * Handles both text-based responses (E2E mode) and data-based responses (backward compatibility) */ export function extractRecordId(response: McpToolResponse): string | undefined { if ( response.isError || !response.content || !Array.isArray(response.content) || response.content.length === 0 ) { return undefined; } const firstItem = response.content[0]; // Handle text-based responses (E2E mode format) if (firstItem?.text && typeof firstItem.text === 'string') { // Try to extract ID from formatted text like "Created company "Name" (ID: abc-123-def)" const idMatch = firstItem.text.match(/\(ID:\s*([a-f0-9-]+)\)/); if (idMatch && idMatch[1]) { return idMatch[1]; } // Try to parse JSON if the text is a JSON string try { const parsed = JSON.parse(firstItem.text); if (parsed?.id?.record_id) { return parsed.id.record_id; } } catch { // Not JSON, continue with other methods } } // Handle data-based responses (backward compatibility) const data = firstItem?.data as any; return data?.id?.record_id || undefined; } /** * Safely checks if a response contains valid content */ export function hasValidContent(response: McpToolResponse): boolean { return ( !response.isError && response.content !== undefined && response.content !== null && Array.isArray(response.content) && response.content.length > 0 ); } /** * Safely extracts content with null checking */ export function getResponseContent( response: McpToolResponse ): any[] | undefined { if (!hasValidContent(response)) { return undefined; } return response.content!; } /** * Batch cleanup utility for test records * Supports both string arrays (legacy) and object arrays with cleanup function */ export async function cleanupTestRecords( recordsOrCleanupFunction: | string[] | ((resourceType: string, recordId: string) => Promise<any>), records?: Array<{ resourceType: string; recordId: string }> ): Promise<void> { // Handle legacy case: cleanupTestRecords(['id1', 'id2']) if (Array.isArray(recordsOrCleanupFunction)) { // Legacy mode - just log that records would be cleaned up // In E2E tests, records are typically temporary test data that auto-cleanup const recordIds = recordsOrCleanupFunction; if (recordIds.length > 0) { console.log( `Test cleanup: ${recordIds.length} record(s) tracked for cleanup: ${recordIds.join(', ')}` ); } return; } // Handle new case: cleanupTestRecords(cleanupFunction, [{resourceType, recordId}]) if ( typeof recordsOrCleanupFunction === 'function' && records && Array.isArray(records) ) { const cleanupFunction = recordsOrCleanupFunction; const cleanupPromises = records.map( ({ resourceType, recordId }) => cleanupFunction(resourceType, recordId).catch(() => {}) // Ignore cleanup errors ); await Promise.allSettled(cleanupPromises); } } /** * Creates test record and returns ID safely */ export async function createTestRecord( createFunction: (resourceType: string, data: any) => Promise<unknown>, resourceType: string, recordData: any ): Promise<string | undefined> { try { const response = await createFunction(resourceType, recordData); const recordId = extractRecordId(response as McpToolResponse); return recordId ?? undefined; } catch (error) { console.warn(`Failed to create test ${resourceType} record:`, error); return undefined; } } /** * Validates error response and extracts error message safely */ export function validateErrorResponse( response: McpToolResponse ): string | undefined { if (!response.isError) { return undefined; } return response.error || 'Unknown error'; } /** * Creates a safe UUID for testing (valid format but non-existent) */ export function createTestUuid(prefix: string = '0'): string { return `${prefix.repeat(8)}-${prefix.repeat(4)}-${prefix.repeat(4)}-${prefix.repeat(4)}-${prefix.repeat(12)}`; } /** * Batch operation result analyzer */ export interface BatchOperationResult { total: number; successful: number; failed: number; successRate: number; } export function analyzeBatchResults( results: PromiseSettledResult<any>[] ): BatchOperationResult { const successful = results.filter((r) => r.status === 'fulfilled').length; const failed = results.filter((r) => r.status === 'rejected').length; const total = results.length; return { total, successful, failed, successRate: total > 0 ? successful / total : 0, }; } /** * Memory and performance test utilities */ export function generateLargeText( baseText: string, repeatCount: number ): string { return baseText.repeat(repeatCount); } /** * Concurrent operation helper */ export async function executeConcurrentOperations<T>( operations: (() => Promise<T>)[], maxConcurrency: number = 5 ): Promise<PromiseSettledResult<T>[]> { const batches: (() => Promise<T>)[][] = []; for (let i = 0; i < operations.length; i += maxConcurrency) { batches.push(operations.slice(i, i + maxConcurrency)); } const allResults: PromiseSettledResult<T>[] = []; for (const batch of batches) { const batchResults = await Promise.allSettled(batch.map((op) => op())); allResults.push(...batchResults); } return allResults; } /** * Retry mechanism for flaky operations */ export async function retryOperation<T>( operation: () => Promise<T>, maxAttempts: number = 3, delay: number = 100 ): Promise<{ result: T | null; attempts: number; success: boolean }> { let attempts = 0; let lastError: any; while (attempts < maxAttempts) { attempts++; try { const result = await operation(); return { result, attempts, success: true }; } catch (error) { lastError = error; if (attempts < maxAttempts) { await new Promise((resolve) => setTimeout(resolve, delay)); } } } return { result: null, attempts, success: 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/kesslerio/attio-mcp-server'

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