Skip to main content
Glama
UniversalMetadataService.test.tsβ€’13.5 kB
/** * Test suite for UniversalMetadataService * * Tests universal metadata and attribute operations extracted from shared-handlers.ts * as part of Issue #489 Phase 3. */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { UniversalMetadataService } from '../../src/services/UniversalMetadataService.js'; import { UniversalResourceType } from '../../src/handlers/tool-configs/universal/types.js'; // Mock the Attio client using global override mechanism const mockGet = vi.fn(); const mockClient = { get: mockGet, }; // Mock resource-specific attribute functions vi.mock('../../src/objects/companies/index.js', () => ({ getCompanyAttributes: vi.fn(), discoverCompanyAttributes: vi.fn(), })); vi.mock('../../src/objects/lists.js', () => ({ getListAttributes: vi.fn(), })); import { getCompanyAttributes, discoverCompanyAttributes, } from '../../src/objects/companies/index.js'; import { getListAttributes } from '../../src/objects/lists.js'; describe('UniversalMetadataService', () => { beforeEach(() => { vi.clearAllMocks(); // Set up test-specific client override (globalThis as any).setTestApiClient?.(mockClient); UniversalMetadataService.reset(); }); describe('discoverAttributesForResourceType', () => { it('should discover attributes for non-task resource types', async () => { const mockAttributes = [ { id: 'attr1', title: 'Test Attribute', api_slug: 'test_attribute', type: 'text', }, { id: 'attr2', title: 'Another Attribute', api_slug: 'another_attribute', type: 'number', }, ]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); const result = await UniversalMetadataService.discoverAttributesForResourceType( UniversalResourceType.COMPANIES ); expect(mockGet).toHaveBeenCalledWith('/objects/companies/attributes'); expect(result).toEqual({ attributes: mockAttributes, mappings: { 'Test Attribute': 'test_attribute', 'Another Attribute': 'another_attribute', }, count: 2, resource_type: UniversalResourceType.COMPANIES, }); }); it('should handle task resource type with special discovery', async () => { const result = await UniversalMetadataService.discoverAttributesForResourceType( UniversalResourceType.TASKS ); expect(result).toHaveProperty('attributes'); expect(result).toHaveProperty('mappings'); expect(result).toHaveProperty('count'); expect(result).toHaveProperty( 'resource_type', UniversalResourceType.TASKS ); expect(result).toHaveProperty('api_endpoint', '/tasks'); expect(result).toHaveProperty('supports_objects_api', false); // Check that task attributes are properly defined expect(Array.isArray(result.attributes)).toBe(true); expect((result.attributes as any[]).length).toBeGreaterThan(0); }); it('should handle API errors gracefully', async () => { mockGet.mockRejectedValue(new Error('API Error')); await expect( UniversalMetadataService.discoverAttributesForResourceType( UniversalResourceType.PEOPLE ) ).rejects.toThrow('Failed to discover people attributes: API Error'); }); }); describe('discoverTaskAttributes', () => { it('should return task-specific attributes', async () => { const result = await UniversalMetadataService.discoverTaskAttributes(); expect(result).toHaveProperty('attributes'); expect(result).toHaveProperty('mappings'); expect(result).toHaveProperty('count'); expect(result).toHaveProperty( 'resource_type', UniversalResourceType.TASKS ); // Verify specific task attributes are present const attributes = result.attributes as any[]; const attributeSlugs = attributes.map((attr) => attr.api_slug); expect(attributeSlugs).toContain('content'); expect(attributeSlugs).toContain('status'); expect(attributeSlugs).toContain('assignee'); expect(attributeSlugs).toContain('due_date'); expect(attributeSlugs).toContain('linked_records'); }); it('should create proper title to api_slug mappings', async () => { const result = await UniversalMetadataService.discoverTaskAttributes(); const mappings = result.mappings as Record<string, string>; expect(mappings).toHaveProperty('Content', 'content'); expect(mappings).toHaveProperty('Status', 'status'); expect(mappings).toHaveProperty('Assignee', 'assignee'); expect(mappings).toHaveProperty('Due Date', 'due_date'); }); }); describe('getAttributesForRecord', () => { it('should get attributes for a specific record', async () => { const mockValues = { name: 'Test Company', email: 'test@example.com', status: 'active', }; mockGet.mockResolvedValue({ data: { data: { values: mockValues } }, }); const result = await UniversalMetadataService.getAttributesForRecord( UniversalResourceType.COMPANIES, 'comp_123' ); expect(mockGet).toHaveBeenCalledWith( '/objects/companies/records/comp_123' ); expect(result).toEqual(mockValues); }); it('should handle missing record data', async () => { mockGet.mockResolvedValue({ data: { data: {} }, }); const result = await UniversalMetadataService.getAttributesForRecord( UniversalResourceType.PEOPLE, 'person_456' ); expect(result).toEqual({}); }); it('should handle API errors', async () => { mockGet.mockRejectedValue(new Error('Record not found')); await expect( UniversalMetadataService.getAttributesForRecord( UniversalResourceType.LISTS, 'list_789' ) ).rejects.toThrow('Failed to get record attributes: Record not found'); }); }); describe('filterAttributesByCategory', () => { it('should return all attributes when no categories specified', () => { const attributes = { test: 'data' }; const result = UniversalMetadataService.filterAttributesByCategory(attributes); expect(result).toEqual(attributes); }); it('should filter array of attributes by category', () => { const attributes = [ { name: 'attr1', category: 'contact' }, { name: 'attr2', category: 'business' }, { name: 'attr3', category: 'contact' }, ]; const result = UniversalMetadataService.filterAttributesByCategory( attributes, ['contact'] ); expect(result).toEqual([ { name: 'attr1', category: 'contact' }, { name: 'attr3', category: 'contact' }, ]); }); it('should filter object with attributes array', () => { const attributes = { attributes: [ { name: 'attr1', type: 'contact' }, { name: 'attr2', type: 'business' }, ], count: 2, }; const result = UniversalMetadataService.filterAttributesByCategory( attributes, ['contact'] ); expect(result).toEqual({ attributes: [{ name: 'attr1', type: 'contact' }], count: 1, }); }); it('should handle multiple category field names', () => { const attributes = [ { name: 'attr1', category: 'contact' }, { name: 'attr2', type: 'business' }, { name: 'attr3', attribute_type: 'social' }, { name: 'attr4', group: 'contact' }, ]; const result = UniversalMetadataService.filterAttributesByCategory( attributes, ['contact', 'business'] ); expect(result).toEqual([ { name: 'attr1', category: 'contact' }, { name: 'attr2', type: 'business' }, { name: 'attr4', group: 'contact' }, ]); }); }); describe('getAttributes', () => { it('should get company attributes with record ID', async () => { const mockResult = { company: 'Test Company', attributes: ['name', 'website'], }; vi.mocked(getCompanyAttributes).mockResolvedValue(mockResult); const result = await UniversalMetadataService.getAttributes({ resource_type: UniversalResourceType.COMPANIES, record_id: 'comp_123', }); expect(getCompanyAttributes).toHaveBeenCalledWith('comp_123'); expect(result).toEqual(mockResult); }); it('should discover company attributes without record ID', async () => { const mockResult = { standard: [], custom: [], all: [] }; vi.mocked(discoverCompanyAttributes).mockResolvedValue(mockResult); const result = await UniversalMetadataService.getAttributes({ resource_type: UniversalResourceType.COMPANIES, }); expect(discoverCompanyAttributes).toHaveBeenCalled(); expect(result).toEqual(mockResult); }); it('should get list attributes', async () => { const mockResult = { attributes: [], count: 0 }; vi.mocked(getListAttributes).mockResolvedValue(mockResult); const result = await UniversalMetadataService.getAttributes({ resource_type: UniversalResourceType.LISTS, }); expect(getListAttributes).toHaveBeenCalled(); expect(result).toEqual(mockResult); }); it('should apply category filtering', async () => { const mockResult = { standard: ['attr1'], custom: ['attr2'], all: [ { name: 'attr1', type: 'contact', isCustom: false }, { name: 'attr2', type: 'other', isCustom: true }, ], }; vi.mocked(discoverCompanyAttributes).mockResolvedValue(mockResult); const result = await UniversalMetadataService.getAttributes({ resource_type: UniversalResourceType.COMPANIES, categories: ['contact'], }); expect(result).toEqual({ attributes: [{ name: 'attr1', type: 'contact', isCustom: false }], count: 1, }); }); it('should throw error for unsupported resource type', async () => { await expect( UniversalMetadataService.getAttributes({ resource_type: 'unsupported' as any, }) ).rejects.toThrow( 'Unsupported resource type for get attributes: unsupported' ); }); }); describe('discoverAttributes', () => { it('should discover company attributes', async () => { const mockResult = { standard: [], custom: [], all: [] }; vi.mocked(discoverCompanyAttributes).mockResolvedValue(mockResult); const result = await UniversalMetadataService.discoverAttributes( UniversalResourceType.COMPANIES ); expect(discoverCompanyAttributes).toHaveBeenCalled(); expect(result).toEqual(mockResult); }); it('should discover list attributes', async () => { const mockResult = { attributes: [], count: 0 }; vi.mocked(getListAttributes).mockResolvedValue(mockResult); const result = await UniversalMetadataService.discoverAttributes( UniversalResourceType.LISTS ); expect(getListAttributes).toHaveBeenCalled(); expect(result).toEqual(mockResult); }); it('should discover task attributes', async () => { const result = await UniversalMetadataService.discoverAttributes( UniversalResourceType.TASKS ); expect(result).toHaveProperty('attributes'); expect(result).toHaveProperty( 'resource_type', UniversalResourceType.TASKS ); }); it('should throw error for unsupported resource type', async () => { await expect( UniversalMetadataService.discoverAttributes('unsupported' as any) ).rejects.toThrow( 'Unsupported resource type for discover attributes: unsupported' ); }); }); describe('Integration with other resource types', () => { it('should handle people resource type for getAttributes', async () => { mockGet.mockResolvedValue({ data: { data: { values: { name: 'John Doe' } } }, }); const result = await UniversalMetadataService.getAttributes({ resource_type: UniversalResourceType.PEOPLE, record_id: 'person_123', }); expect(result).toEqual({ name: 'John Doe' }); }); it('should handle records resource type for discovery', async () => { const mockAttributes = [{ id: 'test', api_slug: 'test', title: 'Test' }]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); const result = await UniversalMetadataService.discoverAttributes( UniversalResourceType.RECORDS, { objectSlug: 'companies' } ); expect(mockGet).toHaveBeenCalledWith('/objects/companies/attributes'); expect(result).toHaveProperty('attributes', mockAttributes); }); it('should handle deals resource type', async () => { const mockAttributes = [ { id: 'deal_attr', api_slug: 'deal_attr', title: 'Deal Attribute' }, ]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); const result = await UniversalMetadataService.discoverAttributes( UniversalResourceType.DEALS ); expect(mockGet).toHaveBeenCalledWith('/objects/deals/attributes'); expect(result).toHaveProperty('attributes', mockAttributes); }); }); });

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