Skip to main content
Glama
mock-injector.tsβ€’16.7 kB
/** * Mock Data Injection System * * Provides clean injection points for mock data into production code paths * when running in test environments. This replaces the scattered test * environment checks and hardcoded mock data previously embedded in * production handlers. * * Key Features: * - Clean separation between test and production code * - Type-safe mock data injection * - Compatibility with existing API patterns * - Support for all resource types (tasks, companies, people, lists) * - Issue #480 compatibility for E2E tests */ import type { AttioTask, AttioRecord, AttioList, } from '../../../src/types/attio.js'; import { TaskMockFactory, CompanyMockFactory, PersonMockFactory, ListMockFactory, } from './index.js'; import { TestEnvironment, shouldUseMockData } from './test-environment.js'; import { UUIDMockGenerator } from './uuid-mock-generator.js'; // In-memory storage for created mock records (persists during test session) const mockStorage = new Map<string, any>(); // Helper to generate persistent mock IDs in UUID format // Addresses PR #483: Use UUID-compliant IDs for validation compatibility function generateMockId(prefix: string = 'mock'): string { switch (prefix) { case 'company': return UUIDMockGenerator.generateCompanyUUID(); case 'person': return UUIDMockGenerator.generatePersonUUID(); case 'task': return UUIDMockGenerator.generateTaskUUID(); case 'list': return UUIDMockGenerator.generateListUUID(); default: return UUIDMockGenerator.generateDeterministicUUID(prefix); } } // Helper to store and retrieve mock objects function storeMock(id: string, object: any): void { mockStorage.set(id, object); } function retrieveMock(id: string): any | undefined { return mockStorage.get(id); } function getAllMocksByType(type: string): any[] { const results: any[] = []; for (const [id, obj] of Array.from(mockStorage.entries())) { if (id.startsWith(`${type}-`)) { results.push(obj); } } return results; } // Clear mock storage (useful for test cleanup) export function clearMockStorage(): void { mockStorage.clear(); } /** * Task-specific mock injection utilities * * Handles the specific requirements for Issue #480: * - Provides both content and title fields for E2E test compatibility * - Ensures task_id is preserved in the ID structure * - Maintains compatibility with existing task creation patterns */ export class TaskMockInjector { /** * Creates a task using mock data when in test environment, otherwise calls real API * * @param content - Task content * @param options - Task creation options * @param realCreateTask - Function that performs the real API call * @returns Either mock AttioTask or result from real API */ static async createTask( content: string, options: Record<string, unknown> = {}, realCreateTask?: ( content: string, options: Record<string, unknown> ) => Promise<AttioTask> ): Promise<AttioTask> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for task creation', { content, options, }); // Generate a persistent task ID const taskId = generateMockId('task'); // Issue #480: Create mock task with E2E test compatibility const mockTask = TaskMockFactory.create({ content, title: content, // Issue #480: Provide both fields for test compatibility assignee_id: options.assigneeId as string, due_date: options.dueDate as string, record_id: options.recordId as string, linked_records: options.recordIds ? (options.recordIds as string[]).map((id) => ({ id, object_id: 'mock-object', object_slug: 'companies', title: 'Mock Linked Record', })) : undefined, }); // Issue #480: Set consistent task ID if (mockTask.id && typeof mockTask.id === 'object') { (mockTask.id as any).record_id = taskId; (mockTask.id as any).task_id = taskId; } // Store the task for future retrieval/updates storeMock(taskId, mockTask); return mockTask; } if (!realCreateTask) { throw new Error( 'Real task creation function not provided for production environment' ); } return await realCreateTask(content, options); } /** * Updates a task using mock data when in test environment */ static async updateTask( taskId: string, updateData: Record<string, unknown>, realUpdateTask?: ( taskId: string, updateData: Record<string, unknown> ) => Promise<AttioTask> ): Promise<AttioTask> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for task update', { taskId, updateData, }); // Try to retrieve existing task const existingTask = retrieveMock(taskId); if (!existingTask) { // Task not found - create appropriate error const error = new Error(`Task not found: ${taskId}`); error.name = 'TaskNotFoundError'; throw error; } // Update the existing task with new data const updatedTask = { ...existingTask, ...updateData, content: updateData.content || existingTask.content, title: updateData.content || existingTask.title, // Keep both for compatibility status: updateData.status || existingTask.status, assignee_id: updateData.assigneeId || existingTask.assignee_id, due_date: updateData.dueDate || existingTask.due_date, updated_at: new Date().toISOString(), }; // Preserve task ID structure if (updatedTask.id && typeof updatedTask.id === 'object') { (updatedTask.id as any).record_id = taskId; (updatedTask.id as any).task_id = taskId; } // Store the updated task storeMock(taskId, updatedTask); return updatedTask; } if (!realUpdateTask) { throw new Error( 'Real task update function not provided for production environment' ); } return await realUpdateTask(taskId, updateData); } /** * Gets a task using mock data when in test environment */ static async getTask( taskId: string, realGetTask?: (taskId: string) => Promise<AttioTask> ): Promise<AttioTask> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for task retrieval', { taskId }); // Try to retrieve existing task const existingTask = retrieveMock(taskId); if (!existingTask) { // Task not found - create appropriate error const error = new Error(`Task not found: ${taskId}`); error.name = 'TaskNotFoundError'; throw error; } return existingTask; } if (!realGetTask) { throw new Error( 'Real task get function not provided for production environment' ); } return await realGetTask(taskId); } /** * Lists tasks using mock data when in test environment */ static async listTasks( realListTasks?: () => Promise<AttioTask[]> ): Promise<AttioTask[]> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for task listing'); // Return all stored tasks const storedTasks = getAllMocksByType('task'); // If no tasks stored, return empty array (not create new ones) return storedTasks; } if (!realListTasks) { throw new Error( 'Real task list function not provided for production environment' ); } return await realListTasks(); } /** * Deletes a task using mock success response when in test environment */ static async deleteTask( taskId: string, realDeleteTask?: ( taskId: string ) => Promise<{ success: boolean; record_id: string }> ): Promise<{ success: boolean; record_id: string }> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for task deletion', { taskId }); // Check if task exists const existingTask = retrieveMock(taskId); if (!existingTask) { // Task not found - create appropriate error const error = new Error(`Task not found: ${taskId}`); error.name = 'TaskNotFoundError'; throw error; } // Remove task from storage mockStorage.delete(taskId); return { success: true, record_id: taskId }; } if (!realDeleteTask) { throw new Error( 'Real task delete function not provided for production environment' ); } return await realDeleteTask(taskId); } } /** * Company-specific mock injection utilities */ export class CompanyMockInjector { static async createCompany( companyData: Record<string, unknown>, realCreateCompany?: ( companyData: Record<string, unknown> ) => Promise<AttioRecord> ): Promise<AttioRecord> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for company creation', companyData); return CompanyMockFactory.create(companyData); } if (!realCreateCompany) { throw new Error( 'Real company creation function not provided for production environment' ); } return await realCreateCompany(companyData); } static async getCompany( companyId: string, realGetCompany?: (companyId: string) => Promise<AttioRecord> ): Promise<AttioRecord> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for company retrieval', { companyId, }); const mockCompany = CompanyMockFactory.create({ name: `Mock Company ${companyId.slice(-4)}`, }); // Preserve the requested company ID if (mockCompany.id && typeof mockCompany.id === 'object') { (mockCompany.id as any).record_id = companyId; } return mockCompany; } if (!realGetCompany) { throw new Error( 'Real company get function not provided for production environment' ); } return await realGetCompany(companyId); } } /** * Person-specific mock injection utilities */ export class PersonMockInjector { static async createPerson( personData: Record<string, unknown>, realCreatePerson?: ( personData: Record<string, unknown> ) => Promise<AttioRecord> ): Promise<AttioRecord> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for person creation', personData); return PersonMockFactory.create(personData); } if (!realCreatePerson) { throw new Error( 'Real person creation function not provided for production environment' ); } return await realCreatePerson(personData); } static async getPerson( personId: string, realGetPerson?: (personId: string) => Promise<AttioRecord> ): Promise<AttioRecord> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for person retrieval', { personId }); const mockPerson = PersonMockFactory.create({ name: `Mock Person ${personId.slice(-4)}`, }); // Preserve the requested person ID if (mockPerson.id && typeof mockPerson.id === 'object') { (mockPerson.id as any).record_id = personId; } return mockPerson; } if (!realGetPerson) { throw new Error( 'Real person get function not provided for production environment' ); } return await realGetPerson(personId); } } /** * List-specific mock injection utilities */ export class ListMockInjector { static async createList( listData: Record<string, unknown>, realCreateList?: (listData: Record<string, unknown>) => Promise<AttioList> ): Promise<AttioList> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for list creation', listData); return ListMockFactory.create(listData); } if (!realCreateList) { throw new Error( 'Real list creation function not provided for production environment' ); } return await realCreateList(listData); } static async getList( listId: string, realGetList?: (listId: string) => Promise<AttioList> ): Promise<AttioList> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for list retrieval', { listId }); const mockList = ListMockFactory.create({ name: `Mock List ${listId.slice(-4)}`, }); // Preserve the requested list ID if (mockList.id && typeof mockList.id === 'object') { (mockList.id as any).list_id = listId; } return mockList; } if (!realGetList) { throw new Error( 'Real list get function not provided for production environment' ); } return await realGetList(listId); } static async listLists( options: { objectSlug?: string; limit?: number } = {}, realListLists?: () => Promise<AttioList[]> ): Promise<AttioList[]> { if (shouldUseMockData()) { TestEnvironment.log('Using mock data for list listing', options); const count = Math.min(options.limit || 5, 10); const mockLists = ListMockFactory.createMultiple(count, { object_slug: options.objectSlug, }); return mockLists; } if (!realListLists) { throw new Error( 'Real list listing function not provided for production environment' ); } return await realListLists(); } } /** * Universal mock injector for all resource types */ export class UniversalMockInjector { /** * Injects appropriate mock data based on resource type */ static async inject( resourceType: string, operation: string, data: Record<string, unknown> = {}, realOperation?: (...args: any[]) => Promise<any> ): Promise<any> { if (!shouldUseMockData()) { if (!realOperation) { throw new Error( `Real ${operation} function not provided for production environment` ); } return await realOperation(data); } TestEnvironment.log( `Using mock data for ${resourceType} ${operation}`, data ); switch (resourceType.toLowerCase()) { case 'tasks': return this.handleTaskOperation(operation, data); case 'companies': return this.handleCompanyOperation(operation, data); case 'people': return this.handlePersonOperation(operation, data); case 'lists': return this.handleListOperation(operation, data); default: throw new Error( `Unsupported resource type for mock injection: ${resourceType}` ); } } private static handleTaskOperation( operation: string, data: Record<string, unknown> ): any { switch (operation) { case 'create': return TaskMockFactory.create(data); case 'update': return TaskMockFactory.create({ ...data, updated_at: new Date().toISOString(), }); case 'list': return TaskMockFactory.createMultiple(5); default: return TaskMockFactory.create(data); } } private static handleCompanyOperation( operation: string, data: Record<string, unknown> ): any { switch (operation) { case 'create': return CompanyMockFactory.create(data); case 'update': return CompanyMockFactory.create({ ...data, updated_at: new Date().toISOString(), }); case 'list': return CompanyMockFactory.createMultiple(5); default: return CompanyMockFactory.create(data); } } private static handlePersonOperation( operation: string, data: Record<string, unknown> ): any { switch (operation) { case 'create': return PersonMockFactory.create(data); case 'update': return PersonMockFactory.create({ ...data, updated_at: new Date().toISOString(), }); case 'list': return PersonMockFactory.createMultiple(5); default: return PersonMockFactory.create(data); } } private static handleListOperation( operation: string, data: Record<string, unknown> ): any { switch (operation) { case 'create': return ListMockFactory.create(data); case 'update': return ListMockFactory.create({ ...data, updated_at: new Date().toISOString(), }); case 'list': return ListMockFactory.createMultiple(5); default: return ListMockFactory.create(data); } } } // Injector classes are already exported inline above

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