Skip to main content
Glama
bidirectional-relationships.test.ts15.1 kB
/** * Unit tests for bidirectional relationship functionality - Issue #747 */ import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; import { relationshipToolConfigs, LinkPersonToCompanyToolConfig, UnlinkPersonFromCompanyToolConfig, GetPersonCompaniesToolConfig, GetCompanyTeamToolConfig, } from '../../../src/handlers/tool-configs/relationships/index.js'; // Mock the dependencies 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 { getCompanyDetails, updateCompany, } from '../../../src/objects/companies/index.js'; import { getPersonDetails, updatePerson, } from '../../../src/objects/people/index.js'; describe('Bidirectional Relationship Tools - Issue #747', () => { const mockPersonId = 'person-123'; const mockCompanyId = 'company-456'; beforeEach(() => { vi.clearAllMocks(); }); describe('link-person-to-company (Bidirectional)', () => { const linkHandler = ( relationshipToolConfigs.linkPersonToCompany as LinkPersonToCompanyToolConfig ).handler; it('should link person to company bidirectionally when neither is linked', async () => { // Mock company with no team members (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [] }, }); // Mock person with no company (getPersonDetails as Mock).mockResolvedValue({ values: { company: null }, }); // Mock successful updates (updateCompany as Mock).mockResolvedValue({}); (updatePerson as Mock).mockResolvedValue({}); const result = await linkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(true); expect(result.message).toBe( 'Successfully linked person to company bidirectionally' ); expect(updateCompany).toHaveBeenCalledWith(mockCompanyId, { team: [mockPersonId], }); expect(updatePerson).toHaveBeenCalledWith(mockPersonId, { company: mockCompanyId, }); }); it('should detect and report when person is already linked bidirectionally', async () => { // Mock company with person in team (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [mockPersonId] }, }); // Mock person with company set (getPersonDetails as Mock).mockResolvedValue({ values: { company: mockCompanyId }, }); const result = await linkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(true); expect(result.message).toBe( 'Person is already bidirectionally linked to this company' ); expect(updateCompany).not.toHaveBeenCalled(); expect(updatePerson).not.toHaveBeenCalled(); }); it('should fix inconsistency when person is in team but company field not set', async () => { // Mock company with person in team (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [mockPersonId, 'other-person'] }, }); // Mock person without company set (getPersonDetails as Mock).mockResolvedValue({ values: { company: null }, }); (updatePerson as Mock).mockResolvedValue({}); const result = await linkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(true); expect(result.message).toBe( 'Fixed relationship inconsistency: updated person company field' ); expect(updatePerson).toHaveBeenCalledWith(mockPersonId, { company: mockCompanyId, }); expect(updateCompany).not.toHaveBeenCalled(); }); it('should fix inconsistency when person has company set but not in team', async () => { // Mock company without person in team (getCompanyDetails as Mock).mockResolvedValue({ values: { team: ['other-person'] }, }); // Mock person with company set (getPersonDetails as Mock).mockResolvedValue({ values: { company: mockCompanyId }, }); (updateCompany as Mock).mockResolvedValue({}); const result = await linkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(true); expect(result.message).toBe( 'Fixed relationship inconsistency: updated company team field' ); expect(updateCompany).toHaveBeenCalledWith(mockCompanyId, { team: ['other-person', mockPersonId], }); expect(updatePerson).not.toHaveBeenCalled(); }); it('should reject linking when person already has different company', async () => { const differentCompanyId = 'company-789'; // Mock company (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [] }, }); // Mock person with different company (getPersonDetails as Mock).mockResolvedValue({ values: { company: differentCompanyId }, }); const result = await linkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(false); expect(result.message).toContain('Person is already linked to company'); expect(result.error).toBe( 'Person already has a different company assigned' ); expect(updateCompany).not.toHaveBeenCalled(); expect(updatePerson).not.toHaveBeenCalled(); }); }); describe('unlink-person-from-company (Bidirectional)', () => { const unlinkHandler = ( relationshipToolConfigs.unlinkPersonFromCompany as UnlinkPersonFromCompanyToolConfig ).handler; it('should unlink person from company bidirectionally', async () => { // Mock company with person in team (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [mockPersonId, 'other-person'] }, }); // Mock person with company set (getPersonDetails as Mock).mockResolvedValue({ values: { company: mockCompanyId }, }); (updateCompany as Mock).mockResolvedValue({}); (updatePerson as Mock).mockResolvedValue({}); const result = await unlinkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(true); expect(result.message).toBe( 'Successfully unlinked person from company bidirectionally' ); expect(updateCompany).toHaveBeenCalledWith(mockCompanyId, { team: ['other-person'], }); expect(updatePerson).toHaveBeenCalledWith(mockPersonId, { company: undefined, }); }); it('should report success when person is not linked', async () => { // Mock company without person (getCompanyDetails as Mock).mockResolvedValue({ values: { team: ['other-person'] }, }); // Mock person without company (getPersonDetails as Mock).mockResolvedValue({ values: { company: null }, }); const result = await unlinkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(true); expect(result.message).toBe('Person is not linked to this company'); expect(updateCompany).not.toHaveBeenCalled(); expect(updatePerson).not.toHaveBeenCalled(); }); it('should fix inconsistency when person in team but company not set', async () => { // Mock company with person in team (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [mockPersonId, 'other-person'] }, }); // Mock person without company (getPersonDetails as Mock).mockResolvedValue({ values: { company: null }, }); (updateCompany as Mock).mockResolvedValue({}); const result = await unlinkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(true); expect(result.message).toBe( 'Fixed relationship inconsistency: removed person from company team' ); expect(updateCompany).toHaveBeenCalledWith(mockCompanyId, { team: ['other-person'], }); expect(updatePerson).not.toHaveBeenCalled(); }); it('should fix inconsistency when person has company set but not in team', async () => { // Mock company without person (getCompanyDetails as Mock).mockResolvedValue({ values: { team: ['other-person'] }, }); // Mock person with company set (getPersonDetails as Mock).mockResolvedValue({ values: { company: mockCompanyId }, }); (updatePerson as Mock).mockResolvedValue({}); const result = await unlinkHandler(mockPersonId, mockCompanyId); expect(result.success).toBe(true); expect(result.message).toBe( 'Fixed relationship inconsistency: cleared person company field' ); expect(updatePerson).toHaveBeenCalledWith(mockPersonId, { company: undefined, }); expect(updateCompany).not.toHaveBeenCalled(); }); }); describe('get-person-companies (With Consistency Validation)', () => { const getPersonCompaniesHandler = ( relationshipToolConfigs.getPersonCompanies as GetPersonCompaniesToolConfig ).handler; it('should return company with consistency validation when bidirectionally linked', async () => { // Mock person with company (getPersonDetails as Mock).mockResolvedValue({ values: { company: mockCompanyId }, }); // Mock company with person in team (getCompanyDetails as Mock).mockResolvedValue({ values: { name: [{ value: 'Test Company' }], team: [mockPersonId], }, }); const result = await getPersonCompaniesHandler(mockPersonId); expect(result).toHaveLength(1); expect(result[0].id).toBe(mockCompanyId); expect(result[0].name).toBe('Test Company'); expect(result[0].name).not.toContain('⚠️'); }); it('should show warning when person has company but not in team', async () => { // Mock person with company (getPersonDetails as Mock).mockResolvedValue({ values: { company: mockCompanyId }, }); // Mock company without person in team (getCompanyDetails as Mock).mockResolvedValue({ values: { name: [{ value: 'Test Company' }], team: ['other-person'], }, }); const result = await getPersonCompaniesHandler(mockPersonId); expect(result).toHaveLength(1); expect(result[0].id).toBe(mockCompanyId); expect(result[0].name).toContain('⚠️ (inconsistent - not in team)'); }); it('should return empty array when person has no company', async () => { // Mock person without company (getPersonDetails as Mock).mockResolvedValue({ values: { company: null }, }); const result = await getPersonCompaniesHandler(mockPersonId); expect(result).toHaveLength(0); }); }); describe('get-company-team (With Consistency Validation)', () => { const getCompanyTeamHandler = ( relationshipToolConfigs.getCompanyTeam as GetCompanyTeamToolConfig ).handler; it('should return team members with consistency validation when bidirectionally linked', async () => { // Mock company with team (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [mockPersonId] }, }); // Mock person with company set (getPersonDetails as Mock).mockResolvedValue({ values: { name: [{ value: 'John Doe' }], company: mockCompanyId, }, }); const result = await getCompanyTeamHandler(mockCompanyId); expect(result).toHaveLength(1); expect(result[0].id).toBe(mockPersonId); expect(result[0].name).toBe('John Doe'); expect(result[0].name).not.toContain('⚠️'); }); it('should show warning when person in team but company field not set', async () => { // Mock company with team (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [mockPersonId] }, }); // Mock person without company set (getPersonDetails as Mock).mockResolvedValue({ values: { name: [{ value: 'John Doe' }], company: null, }, }); const result = await getCompanyTeamHandler(mockCompanyId); expect(result).toHaveLength(1); expect(result[0].id).toBe(mockPersonId); expect(result[0].name).toContain( '⚠️ (inconsistent - company field: none)' ); }); it('should return empty array when company has no team', async () => { // Mock company without team (getCompanyDetails as Mock).mockResolvedValue({ values: { team: [] }, }); const result = await getCompanyTeamHandler(mockCompanyId); expect(result).toHaveLength(0); }); }); describe('Format Results', () => { it('should format successful link operation result', () => { const result = { success: true, message: 'Successfully linked person to company bidirectionally', companyId: mockCompanyId, personId: mockPersonId, teamSize: 2, }; const formatted = relationshipToolConfigs.linkPersonToCompany.formatResult(result); expect(formatted).toBe( 'Successfully linked person to company bidirectionally' ); }); it('should format failed link operation result', () => { const result = { success: false, message: 'Person is already linked to another company', companyId: mockCompanyId, personId: mockPersonId, error: 'Person already has a different company assigned', }; const formatted = relationshipToolConfigs.linkPersonToCompany.formatResult(result); expect(formatted).toBe( 'Failed to link person to company: Person already has a different company assigned' ); }); it('should format person companies result with consistency warnings', () => { const companies = [ { id: 'company-1', name: 'Company One' }, { id: 'company-2', name: 'Company Two ⚠️ (inconsistent - not in team)', }, ]; const formatted = relationshipToolConfigs.getPersonCompanies.formatResult(companies); expect(formatted).toContain('Person is associated with 2 companies:'); expect(formatted).toContain('- Company One (ID: company-1)'); expect(formatted).toContain( '- Company Two ⚠️ (inconsistent - not in team) (ID: company-2)' ); }); it('should format company team result with consistency warnings', () => { const team = [ { id: 'person-1', name: 'John Doe' }, { id: 'person-2', name: 'Jane Smith ⚠️ (inconsistent - company field: none)', }, ]; 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 ⚠️ (inconsistent - company field: none) (ID: person-2)' ); }); }); });

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