Skip to main content
Glama
regression.test.tsβ€’22.9 kB
/** * Performance Regression Test Suite * * Automated performance tests with budgets to prevent regressions. * Integrated into CI/CD pipeline to catch performance degradations early. */ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'; import { config } from 'dotenv'; import { performance } from 'perf_hooks'; // Load environment variables config(); // Set performance test flag to use mock data when API key is not available if (!process.env.ATTIO_API_KEY || process.env.E2E_MODE !== 'true') { process.env.PERFORMANCE_TEST = 'true'; } // Mock the API client for tests (when not using real API) if (!process.env.ATTIO_API_KEY || process.env.E2E_MODE !== 'true') { vi.mock('../../src/api/attio-client', () => ({ getAttioClient: vi.fn(() => ({ post: vi.fn().mockResolvedValue({ data: { data: { id: { record_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' }, values: { name: [{ value: 'Mock Company' }], }, }, }, }), get: vi.fn().mockResolvedValue({ data: { data: [] } }), put: vi.fn().mockResolvedValue({ data: { data: {} } }), delete: vi.fn().mockResolvedValue({ data: { data: { success: true } } }), })), initializeAttioClient: vi.fn(), isAttioClientInitialized: vi.fn(() => true), })); // Mock UniversalSearchService to avoid import path issues vi.mock('../../src/services/UniversalSearchService.js', () => ({ UniversalSearchService: { searchRecords: vi.fn().mockResolvedValue([ { id: { record_id: 'search_001' }, values: { name: [{ value: 'Mock Search Result 1' }], domain: [{ value: 'search1.com' }], }, }, { id: { record_id: 'search_002' }, values: { name: [{ value: 'Mock Search Result 2' }], domain: [{ value: 'search2.com' }], }, }, ]), }, })); // Mock resource-specific search functions to prevent real API calls vi.mock('../../src/objects/companies/index.js', async (importOriginal) => { const actual = (await importOriginal()) as any; return { ...actual, advancedSearchCompanies: vi.fn().mockResolvedValue([ { id: { record_id: 'comp_001' }, values: { name: [{ value: 'Mock Company 1' }], domain: [{ value: 'mock1.com' }], }, }, { id: { record_id: 'comp_002' }, values: { name: [{ value: 'Mock Company 2' }], domain: [{ value: 'mock2.com' }], }, }, ]), getCompanyDetails: vi.fn().mockResolvedValue({ id: { record_id: 'comp_001' }, values: { name: [{ value: 'Mock Company Details' }], domain: [{ value: 'mock.com' }], }, }), updateCompany: vi.fn().mockResolvedValue({ id: { record_id: 'comp_001' }, values: { name: [{ value: 'Updated Mock Company' }], domain: [{ value: 'updated-mock.com' }], }, }), deleteCompany: vi.fn().mockResolvedValue({ success: true }), createCompany: vi.fn().mockResolvedValue({ id: { record_id: 'comp_new' }, values: { name: [{ value: 'New Mock Company' }], domain: [{ value: 'new-mock.com' }], }, }), }; }); vi.mock('../../src/objects/people/index.js', async (importOriginal) => { const actual = (await importOriginal()) as any; return { ...actual, advancedSearchPeople: vi.fn().mockResolvedValue([ { id: { record_id: 'person_001' }, values: { name: [{ full_name: 'John Doe' }], email: [{ value: 'john@mock1.com' }], }, }, ]), getPersonDetails: vi.fn().mockResolvedValue({ id: { record_id: 'person_001' }, values: { name: [{ full_name: 'Mock Person Details' }], email: [{ value: 'mock@person.com' }], }, }), updatePerson: vi.fn().mockResolvedValue({ id: { record_id: 'person_001' }, values: { name: [{ full_name: 'Updated Mock Person' }], email: [{ value: 'updated@person.com' }], }, }), deletePerson: vi.fn().mockResolvedValue({ success: true }), createPerson: vi.fn().mockResolvedValue({ id: { record_id: 'person_new' }, values: { name: [{ full_name: 'New Mock Person' }], email: [{ value: 'new@person.com' }], }, }), }; }); vi.mock('../../src/objects/lists.js', async (importOriginal) => { const actual = (await importOriginal()) as any; return { ...actual, searchLists: vi.fn().mockResolvedValue([ { id: { record_id: 'list_001' }, values: { name: [{ value: 'Mock List' }], }, }, ]), }; }); vi.mock('../../src/objects/records/index.js', async (importOriginal) => { const actual = (await importOriginal()) as any; return { ...actual, listObjectRecords: vi.fn().mockResolvedValue([ { id: { record_id: 'record_001' }, values: { name: [{ value: 'Mock Record' }], }, }, ]), }; }); vi.mock('../../src/objects/tasks.js', async (importOriginal) => { const actual = (await importOriginal()) as any; return { ...actual, listTasks: vi.fn().mockResolvedValue([ { id: { task_id: 'task_001' }, content: 'Mock Task', status: 'pending', }, ]), }; }); } import { coreOperationsToolConfigs, advancedOperationsToolConfigs, } from '../../src/handlers/tool-configs/universal/index.js'; import { UniversalResourceType } from '../../src/handlers/tool-configs/universal/types.js'; import { initializeAttioClient } from '../../src/api/attio-client.js'; import { enhancedPerformanceTracker } from '../../src/middleware/performance-enhanced.js'; // Environment-aware performance budgets (following universal test pattern) const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true'; const CI_MULTIPLIER = isCI ? 2.5 : 1; // 2.5x longer timeouts for CI environments console.log( `Performance regression testing with ${ isCI ? 'CI' : 'LOCAL' } budgets (multiplier: ${CI_MULTIPLIER}x)` ); // Performance test configuration - use environment variables or defaults with CI adjustments const PERFORMANCE_BUDGETS = { notFound: Math.round( parseInt(process.env.PERF_BUDGET_NOT_FOUND || '2000', 10) * CI_MULTIPLIER ), search: Math.round( parseInt(process.env.PERF_BUDGET_SEARCH || '3000', 10) * CI_MULTIPLIER ), create: Math.round( parseInt(process.env.PERF_BUDGET_CREATE || '3000', 10) * CI_MULTIPLIER ), update: Math.round( parseInt(process.env.PERF_BUDGET_UPDATE || '3000', 10) * CI_MULTIPLIER ), delete: Math.round( parseInt(process.env.PERF_BUDGET_DELETE || '2000', 10) * CI_MULTIPLIER ), getDetails: Math.round( parseInt(process.env.PERF_BUDGET_GET_DETAILS || '2000', 10) * CI_MULTIPLIER ), batchSmall: Math.round( parseInt(process.env.PERF_BUDGET_BATCH_SMALL || '5000', 10) * CI_MULTIPLIER ), batchLarge: Math.round( parseInt(process.env.PERF_BUDGET_BATCH_LARGE || '10000', 10) * CI_MULTIPLIER ), }; // Test timeout with buffer - environment-aware like universal test vi.setConfig({ testTimeout: Math.max(30000, Math.round(30000 * CI_MULTIPLIER)), // At least 30s, more in CI hookTimeout: Math.round(20000 * CI_MULTIPLIER), // Scaled hook timeout for cleanup }); // Skip tests if no API key available const SKIP_TESTS = !process.env.ATTIO_API_KEY || process.env.SKIP_PERFORMANCE_TESTS === 'true'; describe('Performance Regression Tests', () => { if (SKIP_TESTS) { it.skip('Skipping performance tests - no API key or explicitly skipped', () => {}); return; } let testRecordId: string | null = null; const timestamp = Date.now(); beforeAll(async () => { // Initialize API client const apiKey = process.env.ATTIO_API_KEY!; await initializeAttioClient(apiKey); // Clear performance tracker enhancedPerformanceTracker.clear(); // Create a test record for performance testing try { const createResult = await coreOperationsToolConfigs[ 'create-record' ].handler({ resource_type: UniversalResourceType.COMPANIES, record_data: { name: `Perf Test Company ${timestamp}`, website: `https://perftest-${timestamp}.com`, description: 'Performance regression test record', }, }); testRecordId = (createResult as any)?.id?.record_id || null; console.log('Created test record:', testRecordId); } catch (error: unknown) { console.error('Failed to create test record:', error); } }); afterAll(async () => { // Clean up test record if (testRecordId) { try { await coreOperationsToolConfigs['delete-record'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: testRecordId, }); console.log('Cleaned up test record:', testRecordId); } catch (error: unknown) { console.error('Failed to clean up test record:', error); } } // Generate performance report const report = enhancedPerformanceTracker.generateReport(); console.log('\n' + report); }); describe('404 Response Performance', () => { it('should return 404 for invalid ID format within budget', async () => { const invalidId = 'invalid-id-format'; const startTime = performance.now(); try { await coreOperationsToolConfigs['get-record-details'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: invalidId, }); // Should not reach here expect.fail('Expected error for invalid ID'); } catch (error: any) { const duration = performance.now() - startTime; // Verify it's a validation error (enhanced error message format) expect(error).toMatchObject({ body: expect.objectContaining({ message: expect.stringContaining( 'Invalid record identifier format' ), }), }); // Check performance budget expect(duration).toBeLessThan(PERFORMANCE_BUDGETS.notFound); // Log for visibility console.log( `404 (invalid format) response time: ${duration.toFixed(0)}ms` ); } }); it('should return 404 for non-existent valid ID within budget', async () => { // Valid MongoDB ObjectId format but doesn't exist const nonExistentId = '507f1f77bcf86cd799439011'; const startTime = performance.now(); try { await coreOperationsToolConfigs['get-record-details'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: nonExistentId, }); // Should not reach here expect.fail('Expected error for non-existent ID'); } catch (error: any) { const duration = performance.now() - startTime; // Check performance budget expect(duration).toBeLessThan(PERFORMANCE_BUDGETS.notFound); // Log for visibility console.log( `404 (non-existent) response time: ${duration.toFixed(0)}ms` ); } }); it.skip('should cache 404 responses for faster subsequent requests', async () => { const nonExistentId = '507f1f77bcf86cd799439012'; // First request - should hit API const firstStart = performance.now(); try { await coreOperationsToolConfigs['get-record-details'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: nonExistentId, }); } catch (error: unknown) { // Expected } const firstDuration = performance.now() - firstStart; // Second request - should hit cache const secondStart = performance.now(); try { await coreOperationsToolConfigs['get-record-details'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: nonExistentId, }); } catch (error: unknown) { // Expected } const secondDuration = performance.now() - secondStart; // Second request should be significantly faster or both should be very fast (< 5ms) // If both are already sub-5ms, the cache is working effectively const bothVeryFast = firstDuration < 5 && secondDuration < 5; const secondFaster = secondDuration < firstDuration * 0.8; // More lenient timing expect(bothVeryFast || secondFaster).toBe(true); console.log( `404 cache performance: First: ${firstDuration.toFixed( 0 )}ms, Second: ${secondDuration.toFixed(0)}ms` ); }); }); describe('Search Operation Performance', () => { it('should complete search within budget', async () => { const startTime = performance.now(); const results = await coreOperationsToolConfigs['search-records'].handler( { resource_type: UniversalResourceType.COMPANIES, query: 'test', limit: 10, } ); const duration = performance.now() - startTime; // Check performance budget expect(duration).toBeLessThan(PERFORMANCE_BUDGETS.search); // Verify results expect(Array.isArray(results)).toBe(true); console.log( `Search operation time: ${duration.toFixed(0)}ms (${ (results as any[]).length } results)` ); }); it('should handle pagination efficiently', async () => { const startTime = performance.now(); const results = await coreOperationsToolConfigs['search-records'].handler( { resource_type: UniversalResourceType.COMPANIES, limit: 20, offset: 0, } ); const duration = performance.now() - startTime; // Check performance budget expect(duration).toBeLessThan(PERFORMANCE_BUDGETS.search); console.log(`Paginated search time: ${duration.toFixed(0)}ms`); }); it('should validate parameters quickly', async () => { const startTime = performance.now(); try { await coreOperationsToolConfigs['search-records'].handler({ resource_type: UniversalResourceType.COMPANIES, limit: -5, // Invalid parameter }); } catch (error: any) { const duration = performance.now() - startTime; // Validation should be very fast (under 100ms) expect(duration).toBeLessThan(100); // Schema validation returns specific error message expect(error).toMatchObject({ body: expect.objectContaining({ message: expect.stringMatching( /must be at least 1|positive integer/i ), }), }); console.log(`Parameter validation time: ${duration.toFixed(0)}ms`); } }); }); describe('CRUD Operation Performance', () => { it('should get record details within budget', async () => { if (!testRecordId) { console.warn('Skipping test - no test record available'); return; } const startTime = performance.now(); const record = await coreOperationsToolConfigs[ 'get-record-details' ].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: testRecordId, }); const duration = performance.now() - startTime; // Check performance budget expect(duration).toBeLessThan(PERFORMANCE_BUDGETS.getDetails); expect(record).toBeDefined(); console.log(`Get details time: ${duration.toFixed(0)}ms`); }); it('should update record within budget', async () => { if (!testRecordId) { console.warn('Skipping test - no test record available'); return; } const startTime = performance.now(); const updated = await coreOperationsToolConfigs['update-record'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: testRecordId, record_data: { description: `Updated at ${new Date().toISOString()}`, }, }); const duration = performance.now() - startTime; // Check performance budget expect(duration).toBeLessThan(PERFORMANCE_BUDGETS.update); expect(updated).toBeDefined(); console.log(`Update operation time: ${duration.toFixed(0)}ms`); }); it('should create record within budget', async () => { const startTime = performance.now(); const created = await coreOperationsToolConfigs['create-record'].handler({ resource_type: UniversalResourceType.COMPANIES, record_data: { name: `Perf Test Create ${timestamp}`, website: `https://create-${timestamp}.com`, }, }); const duration = performance.now() - startTime; // Check performance budget expect(duration).toBeLessThan(PERFORMANCE_BUDGETS.create); // Log the response for debugging console.log('Create response:', created); // Only check for record ID if creation succeeded and has proper structure // When using mocks, the response might be empty or different if (created && Object.keys(created).length > 0) { expect(created).toBeDefined(); // Check for either new or legacy response structure const recordId = (created as any)?.id?.record_id || (created as any)?.record_id || (created as any)?.data?.id?.record_id || (created as any)?.data?.data?.id?.record_id; // Only assert on record ID if we're using real API if (process.env.ATTIO_API_KEY && process.env.E2E_MODE === 'true') { expect(recordId).toBeDefined(); } } else { // Skip test assertions when using mocks or API issues console.warn( 'Skipping create test assertions - mock or API response issue' ); } console.log(`Create operation time: ${duration.toFixed(0)}ms`); // Clean up (only if we have a real record ID) if (created && Object.keys(created).length > 0) { const recordId = (created as any)?.id?.record_id || (created as any)?.record_id || (created as any)?.data?.id?.record_id || (created as any)?.data?.data?.id?.record_id; if ( recordId && process.env.ATTIO_API_KEY && process.env.E2E_MODE === 'true' ) { try { await coreOperationsToolConfigs['delete-record'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: recordId, }); } catch (deleteError) { console.warn('Failed to clean up test record:', deleteError); } } } }); it('should delete record within budget', async () => { // Create a record to delete const toDelete = await coreOperationsToolConfigs['create-record'].handler( { resource_type: UniversalResourceType.COMPANIES, record_data: { name: `Perf Test Delete ${timestamp}`, website: `https://delete-${timestamp}.com`, }, } ); // Check for either new or legacy response structure const deleteId = (toDelete as any)?.id?.record_id || (toDelete as any)?.record_id || (toDelete as any)?.data?.id?.record_id; if (!deleteId) { console.warn('Skipping delete test - failed to create record'); return; } const startTime = performance.now(); const result = await coreOperationsToolConfigs['delete-record'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: deleteId, }); const duration = performance.now() - startTime; // Check performance budget expect(duration).toBeLessThan(PERFORMANCE_BUDGETS.delete); expect((result as any).success).toBe(true); console.log(`Delete operation time: ${duration.toFixed(0)}ms`); }); }); describe('Performance Statistics', () => { it('should track timing splits correctly', async () => { if (!testRecordId) { console.warn('Skipping test - no test record available'); return; } // Perform an operation await coreOperationsToolConfigs['get-record-details'].handler({ resource_type: UniversalResourceType.COMPANIES, record_id: testRecordId, }); // Get statistics const stats = enhancedPerformanceTracker.getStatistics('get-record-details'); expect(stats).toBeDefined(); expect(stats.count).toBeGreaterThan(0); expect((stats as any).timing.p95).toBeDefined(); expect((stats as any).apiTiming.average).toBeDefined(); expect((stats as any).overhead.average).toBeDefined(); console.log('Performance Statistics:', { operations: stats.count, avgTotal: (stats as any).timing.average.toFixed(0) + 'ms', p95Total: (stats as any).timing.p95.toFixed(0) + 'ms', avgAPI: (stats as any).apiTiming.average.toFixed(0) + 'ms', avgOverhead: (stats as any).overhead.average.toFixed(0) + 'ms', }); }); it('should have acceptable p95 and p99 latencies', async () => { const stats = enhancedPerformanceTracker.getStatistics(); if (stats && (stats as any).count > 0) { // P95 should be under 5 seconds expect((stats as any).timing.p95).toBeLessThan(5000); // P99 should be under 10 seconds expect((stats as any).timing.p99).toBeLessThan(10000); console.log( `Latency percentiles - P50: ${(stats as any).timing.p50.toFixed( 0 )}ms, P95: ${(stats as any).timing.p95.toFixed(0)}ms, P99: ${( stats as any ).timing.p99.toFixed(0)}ms` ); } }); }); describe('Performance Alerts', () => { it('should generate alerts for operations exceeding budget', async () => { // Intentionally trigger a slow operation (search with large limit) try { await coreOperationsToolConfigs['search-records'].handler({ resource_type: UniversalResourceType.COMPANIES, limit: 100, }); } catch (error: unknown) { // Might fail due to limit validation } // Check if any alerts were generated const report = enhancedPerformanceTracker.generateReport(); console.log( 'Performance alerts check:', report.includes('Budget Violations') ); }); }); });

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