Skip to main content
Glama
mock-create.service.tsβ€’11.4 kB
/** * MockCreateService - Pure mock implementation * * Pure mock implementation with no API calls or environment checks. * Generates synthetic data for testing environments. */ import type { CreateService } from './types.js'; import type { AttioRecord } from '../../types/attio.js'; import type { E2EMeta, UnknownRecord } from '../../types/service-types.js'; import { extractRecordId } from '../../utils/validation/uuid-validation.js'; import { isValidId } from '../../utils/validation.js'; import { generateMockId } from './extractor.js'; /** * Pure mock implementation of CreateService */ export class MockCreateService implements CreateService { async createCompany(input: Record<string, unknown>): Promise<AttioRecord> { // Generate valid UUID format for mock IDs (exactly 36 chars) const mockId = generateMockId('12345678-1234-4000-8000'); return { id: { record_id: mockId, object_id: 'companies', workspace_id: 'mock-workspace-id', }, values: { name: (input.name as string) || `Mock Company ${mockId.slice(-4)}`, domains: Array.isArray(input.domains) ? (input.domains as string[]).map((d) => ({ value: d })) : [{ value: `${mockId}.example.com` }], industry: (input.industry as string) || 'Technology', description: (input.description as string) || `Mock company for testing - ${mockId}`, }, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; } async createPerson(input: Record<string, unknown>): Promise<AttioRecord> { // Generate valid UUID format for mock IDs (exactly 36 chars) const mockId = generateMockId('12345678-1234-4000-9000'); return { id: { record_id: mockId, object_id: 'people', workspace_id: 'mock-workspace-id', }, values: { name: (input.name as string) || `Mock Person ${mockId.slice(-4)}`, email_addresses: Array.isArray(input.email_addresses) ? (input.email_addresses as string[]).map((email) => ({ value: email, })) : [{ value: `${mockId}@example.com` }], }, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; } async createTask(input: Record<string, unknown>): Promise<AttioRecord> { // Use deterministic ID if record_id is provided (for test compatibility) const mockId = input.record_id ? (input.record_id as string) : generateMockId('12345678-1234-4000-a000'); const taskContent = (input.content as string) || (input.title as string) || 'Mock Test Task'; // Issue #480 compatible mock task try { const { logTaskDebug, sanitizePayload } = await import( '../../utils/task-debug.js' ); logTaskDebug( 'mock.createTask', 'Incoming taskData', sanitizePayload(input as Record<string, unknown>) ); } catch { // Ignore debug import failures in mock mode } const attioRecord: AttioRecord = { id: { record_id: mockId, task_id: mockId, // Issue #480: Preserve task_id object_id: 'tasks', workspace_id: 'mock-workspace-id', }, values: { content: taskContent, title: taskContent, // Issue #480: Dual field support status: [{ value: (input.status as string) || 'pending' }], due_date: input.due_date ? [{ value: input.due_date as string }] : undefined, assignee: input.assigneeId ? [{ value: input.assigneeId as string }] : undefined, }, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; // Add flat field compatibility for E2E tests (Issue #480) const flatFields: Record<string, unknown> = { content: taskContent, title: taskContent, status: (input.status as string) || 'pending', due_date: input.due_date as string, assignee_id: input.assigneeId as string, priority: (input.priority as string) || 'medium', }; // Add assignee object format if assignee provided if (input.assigneeId) { (flatFields as Record<string, unknown>).assignee = { id: input.assigneeId as string, type: 'person', }; } // Provide 'assignees' array for E2E expectations if (input.assigneeId) { (flatFields as Record<string, unknown>).assignees = [ { referenced_actor_type: 'workspace-member', referenced_actor_id: String(input.assigneeId), }, ]; } const result = { ...attioRecord, ...flatFields } as AttioRecord & Record<string, unknown>; // Emit top-level assignees for E2E expectation if (input.assigneeId) { (result as Record<string, unknown>).assignees = [ { referenced_actor_type: 'workspace-member', referenced_actor_id: String(input.assigneeId), }, ]; } try { const { logTaskDebug, inspectTaskRecordShape } = await import( '../../utils/task-debug.js' ); logTaskDebug( 'mock.createTask', 'Returning mock task', inspectTaskRecordShape(result) ); } catch { // Ignore debug import failures in mock mode } return result as AttioRecord; } async updateTask( taskId: string, updateData: Record<string, unknown> ): Promise<AttioRecord> { // Validation for mock environment if (!isValidId(taskId) || taskId === 'invalid') { 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)}`; // Issue #480 compatible updated mock task const attioRecord: AttioRecord = { id: { record_id: taskId, task_id: taskId, // Issue #480: Preserve task_id object_id: 'tasks', workspace_id: 'mock-workspace-id', }, values: { content: taskContent, title: taskContent, // Issue #480: Dual field support status: [{ value: (updateData.status as string) || 'updated' }], due_date: updateData.due_date ? [{ value: updateData.due_date as string }] : undefined, assignee: updateData.assigneeId ? [{ value: updateData.assigneeId as string }] : undefined, }, 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: Record<string, unknown> = { content: taskContent, title: taskContent, status: (updateData.status as string) || 'updated', due_date: updateData.due_date as string, assignee_id: updateData.assigneeId as string, priority: (updateData.priority as string) || 'medium', }; // Add assignee object format if assignee provided if (updateData.assigneeId) { (flatFields as Record<string, unknown>).assignee = { id: updateData.assigneeId as string, type: 'person', }; } // Provide 'assignees' array for E2E expectations on update if (updateData.assigneeId) { (flatFields as Record<string, unknown>).assignees = [ { referenced_actor_type: 'workspace-member', referenced_actor_id: String(updateData.assigneeId), }, ]; } const result = { ...attioRecord, ...flatFields } as AttioRecord & Record<string, unknown>; // Emit top-level assignees for E2E expectation if (updateData.assigneeId) { (result as Record<string, unknown>).assignees = [ { referenced_actor_type: 'workspace-member', referenced_actor_id: String(updateData.assigneeId), }, ]; } try { const { logTaskDebug, inspectTaskRecordShape } = await import( '../../utils/task-debug.js' ); logTaskDebug( 'mock.updateTask', 'Returning updated mock task', inspectTaskRecordShape(result) ); } catch { // Ignore debug import failures in mock mode } return result as AttioRecord; } async createNote(noteData: { resource_type: string; record_id: string; title: string; content: string; format?: string; }): Promise<Record<string, unknown>> { // Validate required parameters if ( !noteData.resource_type || !noteData.record_id || !noteData.title || !noteData.content ) { throw new Error('missing required parameter'); } // Extract UUID from record_id (handles URIs and raw UUIDs) const extractedRecordId = extractRecordId(noteData.record_id); if (!extractedRecordId) { throw new Error('record not found'); } // Check for invalid IDs following test patterns const invalidPatterns = [ '00000000-0000-0000-0000-000000000000', /invalid/i, /^invalid-.*-id-\d+$/, ]; const isInvalidId = invalidPatterns.some((pattern) => pattern instanceof RegExp ? pattern.test(extractedRecordId) : pattern === extractedRecordId ); if (isInvalidId) { throw new Error('record not found'); } // Generate mock note response following Attio API format const timestamp = Date.now(); const baseNote = { id: { workspace_id: 'ws_mock', note_id: `note_${timestamp}`, record_id: extractedRecordId, }, parent_object: noteData.resource_type, parent_record_id: extractedRecordId, title: noteData.title, content: noteData.content, content_markdown: noteData.format === 'markdown' || noteData.format === 'html' ? noteData.content : null, content_plaintext: noteData.format === 'plaintext' ? noteData.content : null, format: noteData.format || 'plaintext', tags: [], created_at: new Date().toISOString(), }; // Apply E2E markers for test data cleanup const markedNote = this.applyE2EMarkers(baseNote); return markedNote; } async listNotes(/* _params?: { resource_type?: string; record_id?: string; } */): Promise<Record<string, unknown>[]> { // Return empty array for mock mode (tests focus on creation) return []; } // Private helper methods /** * Apply consistent E2E test markers to mock data */ private applyE2EMarkers(data: UnknownRecord, meta?: E2EMeta): UnknownRecord { const baseTags = new Set([ ...(Array.isArray(data.tags) ? data.tags : []), 'e2e-test', 'e2e-suite:notes', ]); if (meta?.runId) { baseTags.add(`e2e-run:${meta.runId}`); } return { ...data, tags: Array.from(baseTags), metadata: Object.assign(data.metadata || {}, { e2e: true, }), }; } }

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