Skip to main content
Glama
refactored-functions.test.ts16.2 kB
/** * Tests for refactored relationship functions * Testing the simplified business logic after Phase 2 refactoring */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { relationshipToolConfigs } from '@handlers/tool-configs/relationships/index.js'; // Mock dependencies vi.mock('@utils/relationship-helpers.js', () => ({ extractRecordIds: vi.fn(), extractSingleRecordId: vi.fn(), analyzeRelationshipState: vi.fn(), executeWithRetry: vi.fn(), })); vi.mock('@src/objects/companies/index.js', () => ({ getCompanyDetails: vi.fn(), updateCompany: vi.fn(), })); vi.mock('@src/objects/people/index.js', () => ({ getPersonDetails: vi.fn(), updatePerson: vi.fn(), })); import { extractRecordIds, extractSingleRecordId, executeWithRetry, } from '@utils/relationship-helpers.js'; import { getCompanyDetails, updateCompany, } from '@src/objects/companies/index.js'; import { getPersonDetails, updatePerson } from '@src/objects/people/index.js'; describe('Refactored Relationship Functions', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('linkPersonToCompany', () => { it('should successfully link person to company when not linked', async () => { const personId = 'person-123'; const companyId = 'company-456'; // Mock company data - no existing team vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { team: [] }, }); // Mock person data - no existing company vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { company: null }, }); // Mock extractRecordIds returning empty array vi.mocked(extractRecordIds).mockReturnValue([]); // Mock executeWithRetry to simulate successful updates vi.mocked(executeWithRetry).mockResolvedValue(undefined); const result = await relationshipToolConfigs.linkPersonToCompany.handler( personId, companyId ); expect(result.success).toBe(true); expect(result.message).toBe( 'Successfully linked person to company bidirectionally' ); expect(result.companyId).toBe(companyId); expect(result.personId).toBe(personId); expect(result.teamSize).toBe(1); // Verify both updates were attempted expect(executeWithRetry).toHaveBeenCalledWith([ expect.any(Function), expect.any(Function), ]); }); it('should return success if already fully linked', async () => { const personId = 'person-123'; const companyId = 'company-456'; // Mock company data with person already in team vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { team: [{ record_id: personId }] }, }); // Mock person data with company already set vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { company: companyId }, }); // Mock extractRecordIds returning person ID vi.mocked(extractRecordIds).mockReturnValue([personId]); const result = await relationshipToolConfigs.linkPersonToCompany.handler( personId, companyId ); expect(result.success).toBe(true); expect(result.message).toBe( 'Person is already bidirectionally linked to this company' ); expect(executeWithRetry).not.toHaveBeenCalled(); }); it('should handle conflict when person already has different company', async () => { const personId = 'person-123'; const companyId = 'company-456'; const otherCompanyId = 'company-789'; // Mock company data - no existing team vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { team: [] }, }); // Mock person data - already has different company vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { company: otherCompanyId }, }); vi.mocked(extractRecordIds).mockReturnValue([]); const result = await relationshipToolConfigs.linkPersonToCompany.handler( personId, companyId ); expect(result.success).toBe(false); expect(result.message).toContain( `Person is already linked to company ${otherCompanyId}` ); expect(result.error).toBe( 'Person already has a different company assigned' ); expect(executeWithRetry).not.toHaveBeenCalled(); }); it('should handle errors gracefully', async () => { const personId = 'person-123'; const companyId = 'company-456'; vi.mocked(getCompanyDetails).mockRejectedValue( new Error('Company not found') ); await expect( relationshipToolConfigs.linkPersonToCompany.handler(personId, companyId) ).rejects.toThrow('Failed to link person to company: Company not found'); }); }); describe('unlinkPersonFromCompany', () => { it('should successfully unlink person from company when fully linked', async () => { const personId = 'person-123'; const companyId = 'company-456'; // Mock company data with person in team vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { team: [{ record_id: personId }, { record_id: 'other-person' }], }, }); // Mock person data with company set vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { company: companyId }, }); vi.mocked(extractRecordIds).mockReturnValue([personId, 'other-person']); vi.mocked(executeWithRetry).mockResolvedValue(undefined); const result = await relationshipToolConfigs.unlinkPersonFromCompany.handler( personId, companyId ); expect(result.success).toBe(true); expect(result.message).toBe( 'Successfully unlinked person from company bidirectionally' ); expect(result.teamSize).toBe(1); // One person remaining expect(executeWithRetry).toHaveBeenCalled(); }); it('should return success if already not linked', async () => { const personId = 'person-123'; const companyId = 'company-456'; // Mock company data without person in team vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { team: [] }, }); // Mock person data without company set vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { company: null }, }); vi.mocked(extractRecordIds).mockReturnValue([]); const result = await relationshipToolConfigs.unlinkPersonFromCompany.handler( personId, companyId ); expect(result.success).toBe(true); expect(result.message).toBe('Person is not linked to this company'); expect(executeWithRetry).not.toHaveBeenCalled(); }); it('should handle errors gracefully', async () => { const personId = 'person-123'; const companyId = 'company-456'; vi.mocked(getCompanyDetails).mockRejectedValue( new Error('Company not found') ); await expect( relationshipToolConfigs.unlinkPersonFromCompany.handler( personId, companyId ) ).rejects.toThrow( 'Failed to unlink person from company: Company not found' ); }); }); describe('getPersonCompanies', () => { it('should return company when person has one company', async () => { const personId = 'person-123'; const companyId = 'company-456'; // Mock person data with company vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { company: companyId, companies: [], // Empty legacy field }, }); // Mock company data for validation vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { name: [{ value: 'Test Company' }], team: [{ record_id: personId }], }, }); vi.mocked(extractRecordIds).mockReturnValue([personId]); const result = await relationshipToolConfigs.getPersonCompanies.handler(personId); expect(result).toHaveLength(1); expect(result[0]).toEqual({ id: companyId, name: 'Test Company', }); }); it('should return empty array when person has no companies', async () => { const personId = 'person-123'; // Mock person data without company vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { company: null, companies: [], }, }); const result = await relationshipToolConfigs.getPersonCompanies.handler(personId); expect(result).toHaveLength(0); }); it('should detect inconsistent relationships', async () => { const personId = 'person-123'; const companyId = 'company-456'; // Mock person data with company vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { company: companyId, companies: [], }, }); // Mock company data where person is NOT in team (inconsistency) vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { name: [{ value: 'Test Company' }], team: [], // Person not in team }, }); vi.mocked(extractRecordIds).mockReturnValue([]); // Empty team const result = await relationshipToolConfigs.getPersonCompanies.handler(personId); expect(result).toHaveLength(1); expect(result[0].name).toContain('⚠️ (inconsistent - not in team)'); }); it('should handle errors gracefully', async () => { const personId = 'person-123'; vi.mocked(getPersonDetails).mockRejectedValue( new Error('Person not found') ); await expect( relationshipToolConfigs.getPersonCompanies.handler(personId) ).rejects.toThrow("Failed to get person's companies: Person not found"); }); }); describe('getCompanyTeam', () => { it('should return team members when company has team', async () => { const companyId = 'company-456'; const personId1 = 'person-123'; const personId2 = 'person-789'; // Mock company data with team vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { team: [{ record_id: personId1 }, { record_id: personId2 }], }, }); // Mock extractSingleRecordId calls vi.mocked(extractSingleRecordId) .mockReturnValueOnce(personId1) .mockReturnValueOnce(personId2); // Mock person details vi.mocked(getPersonDetails) .mockResolvedValueOnce({ id: { record_id: personId1 }, values: { name: [{ value: 'John Doe' }], company: companyId, }, }) .mockResolvedValueOnce({ id: { record_id: personId2 }, values: { full_name: [{ value: 'Jane Smith' }], company: companyId, }, }); const result = await relationshipToolConfigs.getCompanyTeam.handler(companyId); expect(result).toHaveLength(2); expect(result[0]).toEqual({ id: personId1, name: 'John Doe', }); expect(result[1]).toEqual({ id: personId2, name: 'Jane Smith', }); }); it('should return empty array when company has no team', async () => { const companyId = 'company-456'; // Mock company data without team vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { team: [] }, }); const result = await relationshipToolConfigs.getCompanyTeam.handler(companyId); expect(result).toHaveLength(0); }); it('should detect inconsistent relationships in team members', async () => { const companyId = 'company-456'; const personId = 'person-123'; const otherCompanyId = 'company-789'; // Mock company data with team vi.mocked(getCompanyDetails).mockResolvedValue({ id: { record_id: companyId }, values: { team: [{ record_id: personId }], }, }); vi.mocked(extractSingleRecordId).mockReturnValue(personId); // Mock person with different company (inconsistency) vi.mocked(getPersonDetails).mockResolvedValue({ id: { record_id: personId }, values: { name: [{ value: 'John Doe' }], company: otherCompanyId, // Different company }, }); const result = await relationshipToolConfigs.getCompanyTeam.handler(companyId); expect(result).toHaveLength(1); expect(result[0].name).toContain('⚠️ (inconsistent - company field:'); expect(result[0].name).toContain(otherCompanyId); }); it('should handle errors gracefully', async () => { const companyId = 'company-456'; vi.mocked(getCompanyDetails).mockRejectedValue( new Error('Company not found') ); await expect( relationshipToolConfigs.getCompanyTeam.handler(companyId) ).rejects.toThrow('Failed to get company team: Company not found'); }); }); describe('formatResult functions', () => { it('should format successful link result', () => { const result = { success: true, message: 'Successfully linked person to company', companyId: 'company-123', personId: 'person-456', teamSize: 3, }; const formatted = relationshipToolConfigs.linkPersonToCompany.formatResult(result); expect(formatted).toBe('Successfully linked person to company'); }); it('should format failed link result', () => { const result = { success: false, message: 'Failed to link', error: 'Person already has a different company', companyId: 'company-123', personId: 'person-456', }; const formatted = relationshipToolConfigs.linkPersonToCompany.formatResult(result); expect(formatted).toBe( 'Failed to link person to company: Person already has a different company' ); }); it('should format person companies result', () => { const companies = [ { id: 'company-1', name: 'Company A' }, { id: 'company-2', name: 'Company B' }, ]; const formatted = relationshipToolConfigs.getPersonCompanies.formatResult(companies); expect(formatted).toContain('Person is associated with 2 companies:'); expect(formatted).toContain('- Company A (ID: company-1)'); expect(formatted).toContain('- Company B (ID: company-2)'); }); it('should format empty companies result', () => { const companies: never[] = []; const formatted = relationshipToolConfigs.getPersonCompanies.formatResult(companies); expect(formatted).toBe( 'This person is not associated with any companies.' ); }); it('should format company team result', () => { const team = [ { id: 'person-1', name: 'John Doe' }, { id: 'person-2', name: 'Jane Smith' }, ]; const formatted = relationshipToolConfigs.getCompanyTeam.formatResult(team); expect(formatted).toContain('Company has 2 team members:'); expect(formatted).toContain('- John Doe (ID: person-1)'); expect(formatted).toContain('- Jane Smith (ID: person-2)'); }); it('should format empty team result', () => { const team: never[] = []; const formatted = relationshipToolConfigs.getCompanyTeam.formatResult(team); expect(formatted).toBe('This company has no team members.'); }); }); });

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