Skip to main content
Glama
record-management.e2e.test.tsβ€’20.4 kB
/** * Record Management E2E Test Suite * * Consolidates universal record operations and advanced workflow tests: * - tasks-management-advanced.e2e.test.ts (Score: 31) * - tasks-management-validation.e2e.test.ts (Score: 24) * - notes-management/notes-validation.e2e.test.ts (Score: 25) * * This consolidated suite covers: * - Universal record CRUD operations across resource types * - Advanced task management workflows (filtering, pagination, relationships) * - Record validation and error handling scenarios * - Data consistency and integration validation * - Cross-resource relationship management * * Total coverage: Advanced record management workflows * Combined business value score: 80/100 * * Part of Issue #526 Sprint 4 - E2E Test Consolidation */ import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, } from 'vitest'; import { E2ETestBase } from '../setup.js'; import { E2EAssertions } from '../utils/assertions.js'; import { CompanyFactory, PersonFactory, TaskFactory, } from '../fixtures/index.js'; import type { TestDataObject, McpToolResponse, McpResponseData, } from '../types/index.js'; // Import enhanced tool callers import { callTasksTool, callUniversalTool, callNotesTool, validateTestEnvironment, getToolMigrationStats, } from '../utils/enhanced-tool-caller.js'; import { startTestSuite, endTestSuite } from '../utils/logger.js'; // Import notes validation setup import { testCompanies, testPeople, createdNotes, createSharedSetup, createTestCompany, createTestPerson, noteFixtures, } from './notes-management/shared-setup.js'; /** * Helper function to safely cast tool responses to McpToolResponse */ function asToolResponse(response: unknown): McpToolResponse { return response as McpToolResponse; } /** * Record Management E2E Test Suite - Universal Operations & Advanced Workflows */ describe.skipIf( !process.env.ATTIO_API_KEY || process.env.SKIP_E2E_TESTS === 'true' )('Record Management E2E Tests - Universal Operations', () => { // Universal record management test data const testCompaniesRecord: TestDataObject[] = []; const testPeopleRecord: TestDataObject[] = []; const createdTasks: TestDataObject[] = []; const createdRecords: TestDataObject[] = []; // Notes validation setup const notesSetup = createSharedSetup(); beforeAll(async () => { // Start comprehensive logging for this test suite startTestSuite('record-management'); // Validate test environment and tool migration setup const envValidation = await validateTestEnvironment(); if (!envValidation.valid) { console.warn('⚠️ Test environment warnings:', envValidation.warnings); } console.error('πŸ“Š Tool migration stats:', getToolMigrationStats()); await E2ETestBase.setup({ requiresRealApi: false, cleanupAfterTests: true, timeout: 120000, }); // Initialize notes validation setup await notesSetup.beforeAll(); // Ensure consistent backend for create + read flows in this suite // Force real API if key is present to avoid mock-created IDs that cannot be read (globalThis as any).__prevForceRealApi = process.env.FORCE_REAL_API; if (process.env.ATTIO_API_KEY) process.env.FORCE_REAL_API = 'true'; console.error( 'πŸš€ Starting Record Management E2E Tests - Universal Operations' ); }, 60000); afterAll(async () => { // Cleanup notes validation await notesSetup.afterAll(); // End comprehensive logging for this test suite endTestSuite(); console.error('βœ… Record Management E2E Tests completed'); // Restore environment flag const prev = (globalThis as any).__prevForceRealApi as string | undefined; if (prev === undefined) delete process.env.FORCE_REAL_API; else process.env.FORCE_REAL_API = prev; }, 60000); beforeEach(() => { vi.clearAllMocks(); notesSetup.beforeEach(); }); describe('Universal Record Operations', () => { // Ensure prerequisites for pagination and retrieval tests beforeAll(async () => { try { if (testCompanies.length === 0) { await createTestCompany(); } const company = testCompanies[0] as any; const companyId = company?.id?.record_id; if (!companyId) return; // Ensure at least 3 notes exist for the company for pagination tests const existingForCompany = createdNotes.filter( (n: any) => n?.parent_record_id === companyId || n?.parent?.record_id === companyId ); if (existingForCompany.length < 3) { for (let i = existingForCompany.length; i < 3; i++) { const response = asToolResponse( await callNotesTool('create-note', { resource_type: 'companies', record_id: companyId, title: `E2E Pagination Seed Note ${i + 1}`, content: 'Seed note content for pagination tests', format: 'markdown', }) ); if (!response.isError) { const note = E2EAssertions.expectMcpData(response); createdNotes.push(note); } } } } catch {} }, 45000); it('should create records across different resource types', async () => { // Create company record const companyData = CompanyFactory.create(); const companyResponse = asToolResponse( await callUniversalTool('create-record', { resource_type: 'companies', record_data: companyData as any, }) ); E2EAssertions.expectMcpSuccess(companyResponse); const company = E2EAssertions.expectMcpData(companyResponse)!; E2EAssertions.expectCompanyRecord(company); testCompaniesRecord.push(company); createdRecords.push(company); // Create person record const personData = PersonFactory.create(); const personResponse = asToolResponse( await callUniversalTool('create-record', { resource_type: 'people', record_data: personData as any, }) ); E2EAssertions.expectMcpSuccess(personResponse); const person = E2EAssertions.expectMcpData(personResponse)!; E2EAssertions.expectPersonRecord(person); testPeopleRecord.push(person); createdRecords.push(person); // Create task record const taskData = TaskFactory.create(); const taskResponse = asToolResponse( await callUniversalTool('create-record', { resource_type: 'tasks', record_data: { content: taskData.content, due_date: taskData.due_date, }, }) ); E2EAssertions.expectMcpSuccess(taskResponse); const task = E2EAssertions.expectMcpData(taskResponse)!; E2EAssertions.expectTaskRecord(task); createdTasks.push(task); createdRecords.push(task); console.error('βœ… Created records across multiple resource types'); }, 45000); it('should retrieve record details across resource types', async () => { if (createdRecords.length < 3) { console.error( '⏭️ Skipping retrieval test - insufficient created records' ); return; } // Test retrieving each type of record for (const record of createdRecords.slice(0, 3)) { let resourceType = ''; if ((record as any).id?.task_id) { resourceType = 'tasks'; } else if ((record as any).id?.record_id) { // Both companies and people use record_id, need to differentiate by fields // Check for person-specific fields first if ( (record as any).values?.email_addresses || (record as any).values?.phone_numbers || (record as any).values?.job_title || (record as any).values?.seniority ) { resourceType = 'people'; } else { // Default to companies for records with name but no person-specific fields resourceType = 'companies'; } } if (resourceType) { const recordId = (record as any).id?.record_id || (record as any).id?.task_id; const response = asToolResponse( await callUniversalTool('get-record-details', { resource_type: resourceType as any, record_id: recordId, }) ); E2EAssertions.expectMcpSuccess(response); const retrievedRecord = E2EAssertions.expectMcpData(response); expect(retrievedRecord).toBeDefined(); console.error(`βœ… Retrieved ${resourceType} record details`); } } }, 45000); it('should update records with universal patterns', async () => { if (testCompaniesRecord.length === 0) { console.error('⏭️ Skipping update test - no company records available'); return; } const company = testCompaniesRecord[0]; const companyId = (company as any).id?.record_id; if (!companyId) { console.error('⏭️ Skipping update test - invalid company ID'); return; } const response = asToolResponse( await callUniversalTool('update-record', { resource_type: 'companies', record_id: companyId, record_data: { description: 'Updated via universal record management', }, }) ); E2EAssertions.expectMcpSuccess(response); const updatedRecord = E2EAssertions.expectMcpData(response); expect(updatedRecord).toBeDefined(); console.error('βœ… Updated record with universal pattern'); }, 30000); // Search tests moved to: record-management-search.e2e.test.ts it('should handle bulk record operations', async () => { // Create multiple records of the same type const companyBatch = CompanyFactory.createMany(3); const createdCompanies: McpResponseData[] = []; for (const companyData of companyBatch) { const response = asToolResponse( await callUniversalTool('create-record', { resource_type: 'companies', record_data: companyData as any, }) ); if (!response.isError) { const company = E2EAssertions.expectMcpData(response); if (company) createdCompanies.push(company); } } expect(createdCompanies.length).toBeGreaterThan(0); console.error( `βœ… Created bulk records: ${createdCompanies.length} companies` ); // Test bulk retrieval for (const company of createdCompanies.slice(0, 2)) { const companyId = (company as any).id?.record_id; if (companyId) { const response = asToolResponse( await callUniversalTool('get-record-details', { resource_type: 'companies', record_id: companyId, }) ); expect(response).toBeDefined(); } } console.error('βœ… Bulk record operations completed'); }, 60000); }); describe('Advanced Task Management Workflows', () => { it('should filter tasks with pagination', async () => { // First ensure we have tasks to filter if (createdTasks.length === 0) { const taskData = TaskFactory.create(); const response = asToolResponse( await callTasksTool('create-record', { resource_type: 'tasks', record_data: { content: taskData.content, due_date: taskData.due_date, }, }) ); if (!response.isError) { const task = E2EAssertions.expectMcpData(response); createdTasks.push(task); } } // Test task filtering with pagination const response = asToolResponse( await callTasksTool('search-records', { resource_type: 'tasks', query: 'test', limit: 10, offset: 0, }) ); E2EAssertions.expectMcpSuccess(response); const tasks = E2EAssertions.expectMcpData(response); expect(tasks).toBeDefined(); console.error('βœ… Task filtering with pagination completed'); }, 30000); it('should manage task relationships with records', async () => { if (createdTasks.length === 0 || testCompaniesRecord.length === 0) { console.error('⏭️ Skipping relationship test - insufficient test data'); return; } const task = createdTasks[0]; const company = testCompaniesRecord[0]; const taskId = (task as any).id?.task_id; const companyId = (company as any).id?.record_id; if (!taskId || !companyId) { console.error('⏭️ Skipping relationship test - invalid IDs'); return; } // Link task to company const response = asToolResponse( await callTasksTool('update-record', { resource_type: 'tasks', record_id: taskId, record_data: { recordId: companyId, targetObject: 'companies', }, }) ); E2EAssertions.expectMcpSuccess(response); const updatedTask = E2EAssertions.expectMcpData(response); expect(updatedTask).toBeDefined(); console.error('βœ… Task-record relationship established'); }, 30000); it('should handle task lifecycle workflows', async () => { // Create a task with specific lifecycle const taskData = TaskFactory.create(); const createResponse = asToolResponse( await callTasksTool('create-record', { resource_type: 'tasks', record_data: { content: taskData.content, due_date: taskData.due_date, status: 'pending', }, }) ); E2EAssertions.expectMcpSuccess(createResponse); const task = E2EAssertions.expectMcpData(createResponse); const taskId = (task as any).id?.task_id; if (!taskId) { console.error('⏭️ Skipping lifecycle test - invalid task ID'); return; } // Progress through lifecycle: pending -> in_progress -> completed const statuses = ['in_progress', 'completed']; for (const status of statuses) { const updateResponse = asToolResponse( await callTasksTool('update-record', { resource_type: 'tasks', record_id: taskId, record_data: { status }, }) ); E2EAssertions.expectMcpSuccess(updateResponse); console.error(`βœ… Task status updated to: ${status}`); } console.error('βœ… Task lifecycle workflow completed'); }, 45000); it('should validate task data consistency', async () => { // Test creating task with various data configurations const taskConfigs = [ { content: 'Basic task', due_date: '2024-12-31' }, { content: 'Task with long content that tests field limits and validation', due_date: '2024-12-31', }, { content: 'Urgent task', due_date: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) .toISOString() .split('T')[0], }, ]; for (const config of taskConfigs) { const response = asToolResponse( await callTasksTool('create-record', { resource_type: 'tasks', record_data: config, }) ); // Should handle all configurations appropriately expect(response).toBeDefined(); if (!response.isError) { const task = E2EAssertions.expectMcpData(response); E2EAssertions.expectTaskRecord(task); } } console.error('βœ… Task data consistency validation completed'); }, 45000); }); describe('Notes Validation Workflows', () => { it('should validate note format and content', async () => { // Ensure we have test data if (testCompanies.length === 0) { await createTestCompany(); } if (testCompanies.length === 0) { console.error('⏭️ Skipping note validation test - no test companies'); return; } const testCompany = testCompanies[0]; const companyId = (testCompany as any).id?.record_id; if (!companyId) { console.error('⏭️ Skipping note validation test - invalid company ID'); return; } // Test various note formats and content const noteTests = [ { title: 'Basic Text Note', content: 'Simple text content for validation testing', format: 'markdown', }, { title: 'Markdown Note', content: '# Header\n\n**Bold text** and *italic text*\n\n- List item 1\n- List item 2', format: 'markdown', }, { title: 'Long Content Note', content: 'A'.repeat(1000), // Long content format: 'markdown', }, ]; for (const noteData of noteTests) { const response = asToolResponse( await callNotesTool('create-note', { resource_type: 'companies', record_id: companyId, title: noteData.title, content: noteData.content, format: noteData.format, }) ); // Should handle all note formats appropriately expect(response).toBeDefined(); if (!response.isError) { const note = E2EAssertions.expectMcpData(response); E2EAssertions.expectValidNoteStructure(note); createdNotes.push(note); } console.error(`βœ… Note validation test completed: ${noteData.title}`); } }, 60000); it('should validate cross-resource note operations', async () => { // Test notes across different resource types const resourceConfigs = [ { type: 'companies', data: testCompanies }, { type: 'people', data: testPeople }, ]; for (const config of resourceConfigs) { if (config.data.length === 0) continue; const record = config.data[0]; const recordId = (record as any).id?.record_id; if (!recordId) continue; const response = asToolResponse( await callNotesTool('create-note', { resource_type: config.type as any, record_id: recordId, title: `Cross-resource ${config.type} note`, content: `Validation test note for ${config.type} resource`, format: 'markdown', }) ); expect(response).toBeDefined(); if (!response.isError) { const note = E2EAssertions.expectMcpData(response); createdNotes.push(note); console.error(`βœ… Cross-resource note created for ${config.type}`); } } console.error('βœ… Cross-resource note validation completed'); }, 45000); it('should handle note retrieval and pagination validation', async () => { // Test note retrieval with various parameters if (testCompanies.length === 0) { console.error('⏭️ Skipping note retrieval test - no test companies'); return; } const testCompany = testCompanies[0]; const companyId = (testCompany as any).id?.record_id; if (!companyId) { console.error('⏭️ Skipping note retrieval test - invalid company ID'); return; } // Test different pagination parameters const paginationTests = [ { limit: 5, offset: 0 }, { limit: 10, offset: 0 }, { limit: 1, offset: 0 }, ]; for (const params of paginationTests) { const response = asToolResponse( await callNotesTool('list-notes', { resource_type: 'companies', record_id: companyId, limit: params.limit, offset: params.offset, }) ); E2EAssertions.expectMcpSuccess(response); const notes = E2EAssertions.expectMcpData(response); expect(notes).toBeDefined(); console.error( `βœ… Note pagination test: limit=${params.limit}, offset=${params.offset}` ); } console.error('βœ… Note retrieval and pagination validation completed'); }, 30000); }); });

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