Skip to main content
Glama
PersonMockFactory.tsβ€’13.1 kB
/** * Person Mock Factory * * Generates mock AttioRecord data for person resources. * * This factory creates realistic person mock data matching the Attio API * response format with proper email, phone, and company associations. */ import type { AttioValue } from '../../../src/types/attio.js'; import { TestEnvironment } from './test-environment.js'; import type { MockFactory } from './TaskMockFactory.js'; import { UUIDMockGenerator } from './uuid-mock-generator.js'; /** * Interface for mock person factory options */ export interface MockPersonOptions { name?: string | { first_name: string; last_name: string }; email_addresses?: string | string[]; phone_numbers?: string | string[]; job_title?: string; department?: string; seniority?: string; company?: string; // Company record ID created_at?: string; updated_at?: string; // Additional fields that might be present [key: string]: unknown; } /** * PersonMockFactory - Generates mock AttioRecord data for people * * Creates mock person data that matches the Attio API response format * with proper AttioValue wrappers and realistic personal/professional data. * * @example * ```typescript * // Basic person * const person = PersonMockFactory.create(); * * // Executive with custom data * const executive = PersonMockFactory.create({ * name: 'John Smith', * job_title: 'CEO', * seniority: 'Executive' * }); * * // Multiple people * const people = PersonMockFactory.createMultiple(5); * ``` */ // Local test-friendly record type accepting AttioValue[] wrappers export interface TestAttioRecord { id: { record_id: string; [key: string]: unknown }; values: Record<string, unknown>; created_at?: string; updated_at?: string; [key: string]: unknown; } export class PersonMockFactory implements MockFactory<TestAttioRecord> { /** * Generates a unique mock person ID in UUID format * * Uses deterministic UUID generation for consistent performance testing * while satisfying UUID validation requirements (addresses PR #483). */ static generateMockId(): string { // Use random UUID generation for unique IDs return UUIDMockGenerator.generatePersonUUID(); } /** * Builds email addresses array from overrides or default * @private */ private static buildEmailAddresses( overrides: MockPersonOptions, defaultEmail: string ): Array<AttioValue> | undefined { // Not specified in overrides - use default generated email if (!('email_addresses' in overrides)) { return [this.wrapValue(defaultEmail)]; } // Explicitly set to null - omit email addresses if (overrides.email_addresses === null) { return undefined; } // Array of emails if (Array.isArray(overrides.email_addresses)) { return overrides.email_addresses.map((email) => this.wrapValue(email)); } // Single string email if (typeof overrides.email_addresses === 'string') { return [this.wrapValue(overrides.email_addresses)]; } return undefined; } /** * Creates a mock person AttioRecord with realistic data * * @param overrides - Optional overrides for specific fields * @returns Mock AttioRecord for person matching API response format */ static create(overrides: MockPersonOptions = {}): TestAttioRecord { const personId = this.generateMockId(); const now = new Date().toISOString(); const personNumber = this.extractNumberFromId(personId); // Generate realistic person data const firstName = this.getRandomFirstName(); const lastName = this.getRandomLastName(); const fullName = overrides.name ? typeof overrides.name === 'string' ? overrides.name : `${overrides.name.first_name} ${overrides.name.last_name}` : `${firstName} ${lastName}`; const email = this.generateEmail(fullName, personNumber); const phone = this.generatePhone(); const basePerson: TestAttioRecord = { id: { record_id: personId, object_id: 'people', workspace_id: 'mock-workspace-id', }, values: { // Name handling - support both string and structured formats name: this.wrapValue(fullName), // Email addresses are now optional (Issue #895) // Use helper method for clarity ...(this.buildEmailAddresses(overrides, email) ? { email_addresses: this.buildEmailAddresses(overrides, email) } : {}), phone_numbers: Array.isArray(overrides.phone_numbers) ? overrides.phone_numbers.map((phone) => this.wrapValue(phone)) : overrides.phone_numbers ? [this.wrapValue(overrides.phone_numbers)] : [this.wrapValue(phone)], }, created_at: overrides.created_at || now, updated_at: overrides.updated_at || now, }; // Add optional professional fields if (overrides.job_title) { basePerson.values.job_title = this.wrapValue(overrides.job_title); } else { basePerson.values.job_title = this.wrapValue(this.getRandomJobTitle()); } if (overrides.department) { basePerson.values.department = this.wrapValue(overrides.department); } if (overrides.seniority) { basePerson.values.seniority = this.wrapValue(overrides.seniority); } else { basePerson.values.seniority = this.wrapValue(this.getRandomSeniority()); } // Company association if (overrides.company) { basePerson.values.company = this.wrapValue(overrides.company); } // Add any additional overrides Object.entries(overrides).forEach(([key, value]) => { if ( ![ 'name', 'email_addresses', 'phone_numbers', 'job_title', 'department', 'seniority', 'company', 'created_at', 'updated_at', ].includes(key) ) { basePerson.values[key] = this.wrapValue(value); } }); TestEnvironment.log(`Created mock person: ${personId}`, { name: fullName, email: (basePerson.values as any).email_addresses?.[0], jobTitle: (basePerson.values as any).job_title, }); return basePerson; } /** * Creates multiple mock people * * @param count - Number of people to create * @param overrides - Optional overrides applied to all people * @returns Array of mock AttioRecord objects for people */ static createMultiple( count: number, overrides: MockPersonOptions = {} ): TestAttioRecord[] { return Array.from({ length: count }, (_, index) => { const personNumber = index + 1; return this.create({ ...overrides, // Don't override name if explicitly provided ...(overrides.name ? {} : {}), // Generate unique emails if not provided ...(overrides.email_addresses ? {} : {}), }); }); } /** * Creates an executive person mock */ static createExecutive(overrides: MockPersonOptions = {}): TestAttioRecord { const executiveTitles = [ 'CEO', 'CTO', 'CFO', 'COO', 'VP of Sales', 'VP of Marketing', 'Chief Executive Officer', ]; return this.create({ ...overrides, job_title: overrides.job_title || executiveTitles[Math.floor(Math.random() * executiveTitles.length)], seniority: 'Executive', }); } /** * Creates a sales person mock */ static createSalesPerson(overrides: MockPersonOptions = {}): TestAttioRecord { const salesTitles = [ 'Account Executive', 'Sales Representative', 'Business Development Manager', 'Sales Manager', ]; return this.create({ ...overrides, job_title: overrides.job_title || salesTitles[Math.floor(Math.random() * salesTitles.length)], department: 'Sales', seniority: overrides.seniority || 'Mid-level', }); } /** * Creates an engineer mock */ static createEngineer(overrides: MockPersonOptions = {}): TestAttioRecord { const engineeringTitles = [ 'Software Engineer', 'Senior Software Engineer', 'Principal Engineer', 'Engineering Manager', 'DevOps Engineer', ]; return this.create({ ...overrides, job_title: overrides.job_title || engineeringTitles[Math.floor(Math.random() * engineeringTitles.length)], department: 'Engineering', seniority: overrides.seniority || 'Mid-level', }); } /** * Creates a marketing person mock */ static createMarketingPerson( overrides: MockPersonOptions = {} ): TestAttioRecord { const marketingTitles = [ 'Marketing Manager', 'Content Marketing Manager', 'Digital Marketing Specialist', 'Marketing Director', ]; return this.create({ ...overrides, job_title: overrides.job_title || marketingTitles[Math.floor(Math.random() * marketingTitles.length)], department: 'Marketing', seniority: overrides.seniority || 'Mid-level', }); } /** * Creates a person with company association */ static createWithCompany( companyId: string, overrides: MockPersonOptions = {} ): TestAttioRecord { return this.create({ ...overrides, company: companyId, }); } /** * Implementation of MockFactory interface */ create(overrides: MockPersonOptions = {}): TestAttioRecord { return PersonMockFactory.create(overrides); } createMultiple( count: number, overrides: MockPersonOptions = {} ): TestAttioRecord[] { return PersonMockFactory.createMultiple(count, overrides); } generateMockId(): string { return PersonMockFactory.generateMockId(); } /** * Private helper to wrap values in AttioValue format */ private static wrapValue<T>( value: T ): T extends string ? AttioValue<string>[] : T { if (typeof value === 'string') { return [{ value }] as T extends string ? AttioValue<string>[] : T; } return value as T extends string ? AttioValue<string>[] : T; } /** * Private helper to extract number from generated ID */ private static extractNumberFromId(id: string): string { const match = id.match(/(\d+)/); return match ? match[1].slice(-4) : Math.floor(Math.random() * 9999).toString(); } /** * Private helper to generate email from name */ private static generateEmail(name: string, suffix: string): string { const cleanName = name .toLowerCase() .replace(/[^a-z\s]/g, '') .replace(/\s+/g, '.'); return `${cleanName}.mock${suffix}@example.com`; } /** * Private helper to generate phone number */ private static generatePhone(): string { const areaCode = Math.floor(Math.random() * 900) + 100; const exchange = Math.floor(Math.random() * 900) + 100; const number = Math.floor(Math.random() * 9000) + 1000; return `+1-${areaCode}-${exchange}-${number}`; } /** * Private helpers for generating random data */ private static getRandomFirstName(): string { const names = [ 'Alex', 'Jordan', 'Taylor', 'Morgan', 'Casey', 'Riley', 'Avery', 'Quinn', 'John', 'Jane', 'Michael', 'Sarah', 'David', 'Lisa', 'Chris', 'Amy', 'Robert', 'Emily', 'James', 'Jessica', 'William', 'Ashley', 'Daniel', 'Michelle', ]; return names[Math.floor(Math.random() * names.length)]; } private static getRandomLastName(): string { const names = [ 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez', 'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson', 'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin', 'Lee', 'Perez', 'Thompson', ]; return names[Math.floor(Math.random() * names.length)]; } private static getRandomJobTitle(): string { const titles = [ 'Software Engineer', 'Product Manager', 'Sales Representative', 'Marketing Manager', 'Data Analyst', 'UX Designer', 'Customer Success Manager', 'Operations Manager', 'Business Analyst', 'Project Manager', 'Account Executive', 'Content Writer', ]; return titles[Math.floor(Math.random() * titles.length)]; } private static getRandomSeniority(): string { const levels = [ 'Junior', 'Mid-level', 'Senior', 'Lead', 'Principal', 'Executive', ]; const weights = [15, 35, 30, 12, 6, 2]; // Realistic distribution const random = Math.random() * 100; let cumulative = 0; for (let i = 0; i < levels.length; i++) { cumulative += weights[i]; if (random <= cumulative) { return levels[i]; } } return 'Mid-level'; // Fallback } } /** * Convenience export for direct usage */ export default PersonMockFactory;

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