Skip to main content
Glama
UniversalMockService.tsβ€’15.4 kB
/** * Universal Mock Service * * Centralized service for generating mock data across all resource types * used by universal handlers in E2E and performance testing environments. * * This service consolidates the mock generation functionality previously * scattered throughout production code, providing clean separation between * test utilities and production logic. * * Key Features: * - Uses existing mock factories for consistency * - Maintains Issue #480 compatibility patterns * - Supports all universal resource types * - Provides AttioRecord format conversion * - Environment-aware mock injection */ // Use a relaxed test-only record shape to avoid src type coupling export interface TestAttioRecord { id: Record<string, unknown>; values: Record<string, unknown>; created_at?: string; updated_at?: string; [key: string]: unknown; } import { CompanyMockFactory } from './CompanyMockFactory.js'; import { PersonMockFactory } from './PersonMockFactory.js'; import { TaskMockFactory } from './TaskMockFactory.js'; import { ListMockFactory } from './ListMockFactory.js'; import { TestEnvironment } from './test-environment.js'; /** * Universal mock service for consistent mock data generation * across all resource types in the universal handlers system. */ export class UniversalMockService { /** * Environment detection function for mock injection * This matches the logic from shared-handlers.ts shouldUseMockData() */ private static shouldUseMockData(): boolean { return ( process.env.E2E_MODE === 'true' || process.env.USE_MOCK_DATA === 'true' || process.env.OFFLINE_MODE === 'true' || process.env.PERFORMANCE_TEST === 'true' ); } /** * Creates a company record with mock support * * @param companyData - Company data to create * @returns AttioRecord in universal format or real API result */ static async createCompany( companyData: Record<string, unknown> ): Promise<TestAttioRecord> { if (!this.shouldUseMockData()) { // Dynamic import to avoid circular dependencies in production const { createCompany } = await import( '../../../src/objects/companies/index.js' ); return await createCompany(companyData as any); } TestEnvironment.log('[UniversalMockService] Creating mock company', { name: companyData.name, industry: companyData.industry, }); // Use the existing CompanyMockFactory but convert to AttioRecord format const mockCompany = CompanyMockFactory.create({ name: companyData.name as string, domains: companyData.domains as string[], industry: companyData.industry as string, description: companyData.description as string, ...companyData, }); // Convert to AttioRecord format expected by universal handlers return { id: { record_id: (mockCompany.id as any).record_id, object_id: 'companies', workspace_id: ((mockCompany.id as any).workspace_id as string) || 'mock-workspace-id', }, values: { name: [ { value: (mockCompany.values as any).name || `Mock Company ${String((mockCompany.id as any).record_id).slice(-4)}`, }, ], domains: (mockCompany.values as any).domains ? Array.isArray(mockCompany.values.domains) ? (mockCompany.values as any).domains.map((d: string) => ({ value: d, })) : [{ value: (mockCompany.values as any).domains }] : [ { value: `${String((mockCompany.id as any).record_id)}.example.com`, }, ], industry: [ { value: (mockCompany.values as any).industry || 'Technology' }, ], description: [ { value: (mockCompany.values as any).description || `Mock company for testing - ${String((mockCompany.id as any).record_id)}`, }, ], // Pass through any additional fields with proper wrapping ...Object.fromEntries( Object.entries(companyData) .filter( ([key]) => !['name', 'domains', 'industry', 'description'].includes(key) ) .map(([key, value]) => [ key, Array.isArray(value) ? value : [{ value: String(value) }], ]) ), }, created_at: mockCompany.created_at, updated_at: mockCompany.updated_at, }; } /** * Creates a person record with mock support * * @param personData - Person data to create * @returns AttioRecord in universal format or real API result */ static async createPerson( personData: Record<string, unknown> ): Promise<TestAttioRecord> { if (!this.shouldUseMockData()) { // Dynamic import to avoid circular dependencies in production const { createPerson } = await import( '../../../src/objects/people/index.js' ); return await createPerson(personData as any); } TestEnvironment.log('[UniversalMockService] Creating mock person', { name: personData.name, email_addresses: personData.email_addresses, }); // Use the existing PersonMockFactory but convert to AttioRecord format const mockPerson = PersonMockFactory.create({ name: personData.name as string, email_addresses: personData.email_addresses as string[], ...personData, }); // Convert to AttioRecord format expected by universal handlers return { id: { record_id: (mockPerson.id as any).record_id, object_id: 'people', workspace_id: ((mockPerson.id as any).workspace_id as string) || 'mock-workspace-id', }, values: { name: [ { value: (mockPerson.values as any).name || `Mock Person ${String((mockPerson.id as any).record_id).slice(-4)}`, }, ], email_addresses: Array.isArray(personData.email_addresses) ? personData.email_addresses.map((email) => ({ value: email })) : [{ value: `${mockPerson.id.person_id}@example.com` }], // Adjust above default to use record_id if present // (keep existing structure but ensure deterministic value) // Pass through any additional fields with proper wrapping ...Object.fromEntries( Object.entries(personData) .filter(([key]) => !['name', 'email_addresses'].includes(key)) .map(([key, value]) => [ key, Array.isArray(value) ? value : [{ value: String(value) }], ]) ), }, created_at: mockPerson.created_at, updated_at: mockPerson.updated_at, }; } /** * Creates a task record with mock support * Maintains Issue #480 compatibility with dual field support * * @param taskData - Task data to create * @returns AttioRecord in universal format or real API result */ static async createTask( taskData: Record<string, unknown> ): Promise<TestAttioRecord> { if (!this.shouldUseMockData()) { // Dynamic import to avoid circular dependencies in production try { const { createTask } = await import('../../../src/objects/tasks.js'); return (await createTask(taskData.content as string, { assigneeId: taskData.assigneeId as string, dueDate: taskData.dueDate as string, recordId: taskData.recordId as string, })) as unknown as TestAttioRecord; } catch (error) { throw new Error( `Failed to create task: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } const taskContent = (taskData.content as string) || (taskData.title as string) || `Mock Test Task`; TestEnvironment.log('[UniversalMockService] Creating mock task', { content: taskContent, status: taskData.status, assigneeId: taskData.assigneeId, }); // Use the existing TaskMockFactory const mockTask = TaskMockFactory.create({ content: taskContent, title: taskContent, // Issue #480: Dual field support status: taskData.status as string, due_date: taskData.due_date as string, assignee_id: taskData.assigneeId as string, record_id: taskData.recordId as string, }); // Convert to AttioRecord format with Issue #480 compatibility const attioRecord: TestAttioRecord = { id: { record_id: mockTask.id.record_id, task_id: mockTask.id.task_id, // Issue #480: Preserve task_id object_id: 'tasks', workspace_id: mockTask.id.workspace_id, }, values: { content: [{ value: taskContent }], title: [{ value: taskContent }], // Issue #480: Dual field support status: [{ value: mockTask.status }], due_date: mockTask.due_date ? [{ value: mockTask.due_date }] : undefined, assignee: mockTask.assignee ? [{ value: mockTask.assignee.id }] : undefined, // Pass through any additional fields with proper wrapping ...Object.fromEntries( Object.entries(taskData) .filter( ([key]) => ![ 'content', 'title', 'status', 'due_date', 'assignee', ].includes(key) ) .map(([key, value]) => [ key, Array.isArray(value) ? value : [{ value: String(value) }], ]) ), }, created_at: mockTask.created_at, updated_at: mockTask.updated_at, }; // Add flat field compatibility for E2E tests (Issue #480) const flatFields = { content: taskContent, title: taskContent, status: mockTask.status, due_date: mockTask.due_date, assignee_id: taskData.assigneeId as string, priority: (taskData.priority as string) || 'medium', }; // Add assignee object format if assignee provided if (taskData.assigneeId && mockTask.assignee) { (flatFields as any).assignee = { id: taskData.assigneeId as string, type: 'person', }; } return { ...attioRecord, ...flatFields }; } /** * Updates a task record with mock support * * @param taskId - Task ID to update * @param updateData - Update data * @returns AttioRecord in universal format or real API result */ static async updateTask( taskId: string, updateData: Record<string, unknown> ): Promise<TestAttioRecord> { if (!this.shouldUseMockData()) { // Dynamic import to avoid circular dependencies in production try { const { updateTask } = await import('../../../src/objects/tasks.js'); return (await updateTask(taskId, { content: updateData.content as string, status: updateData.status as string, assigneeId: updateData.assigneeId as string, dueDate: updateData.dueDate as string, recordIds: updateData.recordIds as string[], })) as unknown as TestAttioRecord; } catch (error) { throw new Error( `Failed to update task: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } // Validation for mock environment const { isValidId } = await import('../../../src/utils/validation.js'); if (!isValidId(taskId)) { throw new Error(`Task not found: ${taskId}`); } if (updateData.assigneeId && !isValidId(updateData.assigneeId as string)) { throw new Error(`Invalid assignee ID: ${updateData.assigneeId}`); } if (updateData.recordIds && Array.isArray(updateData.recordIds)) { for (const recordId of updateData.recordIds) { if (!isValidId(recordId as string)) { throw new Error(`Record not found: ${recordId}`); } } } const taskContent = (updateData.content as string) || (updateData.title as string) || `Updated Mock Test Task ${taskId.slice(-4)}`; TestEnvironment.log('[UniversalMockService] Updating mock task', { taskId, content: taskContent, status: updateData.status, }); // Create an updated task using the TaskMockFactory const mockTask = TaskMockFactory.create({ content: taskContent, title: taskContent, // Issue #480: Dual field support status: (updateData.status as string) || 'updated', due_date: updateData.due_date as string, assignee_id: updateData.assigneeId as string, }); // Update the ID to match the original task ID mockTask.id.record_id = taskId; mockTask.id.task_id = taskId; // Convert to AttioRecord format with Issue #480 compatibility const attioRecord: TestAttioRecord = { id: { record_id: taskId, task_id: taskId, // Issue #480: Preserve task_id object_id: 'tasks', workspace_id: 'mock-workspace-id', }, values: { content: [{ value: taskContent }], title: [{ value: taskContent }], // Issue #480: Dual field support status: [{ value: mockTask.status }], due_date: mockTask.due_date ? [{ value: mockTask.due_date }] : undefined, assignee: mockTask.assignee ? [{ value: mockTask.assignee.id }] : undefined, // Pass through any additional fields with proper wrapping ...Object.fromEntries( Object.entries(updateData) .filter( ([key]) => ![ 'content', 'title', 'status', 'due_date', 'assignee', ].includes(key) ) .map(([key, value]) => [ key, Array.isArray(value) ? value : [{ value: String(value) }], ]) ), }, created_at: new Date(Date.now() - 86400000).toISOString(), // Yesterday updated_at: new Date().toISOString(), }; // Add flat field compatibility for E2E tests (Issue #480) const flatFields = { content: taskContent, title: taskContent, status: mockTask.status, due_date: mockTask.due_date, assignee_id: updateData.assigneeId as string, priority: (updateData.priority as string) || 'medium', }; // Add assignee object format if assignee provided if (updateData.assigneeId) { (flatFields as any).assignee = { id: updateData.assigneeId as string, type: 'person', }; } return { ...attioRecord, ...flatFields }; } /** * Checks if mock data should be used based on environment * This provides a public interface for the environment detection logic */ static isUsingMockData(): boolean { return this.shouldUseMockData(); } } /** * Convenience exports for direct usage */ export const createMockCompany = UniversalMockService.createCompany; export const createMockPerson = UniversalMockService.createPerson; export const createMockTask = UniversalMockService.createTask; export const updateMockTask = UniversalMockService.updateTask; export default UniversalMockService;

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