Skip to main content
Glama
validation-utils.test.tsβ€’12 kB
/** * Unit tests for enhanced validation utilities */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { validateSelectField, validateMultiSelectField, validateReadOnlyFields, suggestFieldName, validateFieldExistence, validateRecordFields, getResourceAttributes, clearAttributeCache, } from '../../../src/utils/validation-utils.js'; // Mock the Attio client const mockGet = vi.fn(); const mockClient = { get: mockGet, post: vi.fn(), patch: vi.fn(), delete: vi.fn(), defaults: { headers: {} }, }; vi.mock('../../../src/api/attio-client.js', () => ({ getAttioClient: () => mockClient, createAttioClient: () => mockClient, })); describe('validation-utils', () => { beforeEach(() => { clearAttributeCache(); mockGet.mockClear(); }); afterEach(() => { clearAttributeCache(); }); describe('getResourceAttributes', () => { it('should fetch and cache resource attributes', async () => { const mockAttributes = [ { api_slug: 'name', type: 'text', title: 'Name' }, { api_slug: 'company_type', type: 'select', title: 'Type', options: [ { title: 'Startup', value: 'startup' }, { title: 'Enterprise', value: 'enterprise' }, ], }, ]; mockGet.mockResolvedValueOnce({ data: { data: mockAttributes }, }); const result = await getResourceAttributes('companies'); expect(mockGet).toHaveBeenCalledWith('/objects/companies/attributes'); expect(result).toEqual(mockAttributes); // Second call should use cache await getResourceAttributes('companies'); expect(mockGet).toHaveBeenCalledTimes(1); }); it('should handle API errors gracefully', async () => { mockGet.mockRejectedValueOnce(new Error('API Error')); const result = await getResourceAttributes('companies'); expect(result).toEqual([]); }); }); describe('validateSelectField', () => { beforeEach(() => { const mockAttributes = [ { api_slug: 'company_type', type: 'select', options: [ { title: 'Startup', value: 'startup' }, { title: 'Enterprise', value: 'enterprise' }, { title: 'Small Business', value: 'small_business' }, { title: 'Non Profit', value: 'non_profit' }, ], }, { api_slug: 'name', type: 'text' }, ]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); }); it('should validate valid select option', async () => { const result = await validateSelectField( 'companies', 'company_type', 'startup' ); expect(result.isValid).toBe(true); expect(result.error).toBeUndefined(); }); it('should reject invalid select option with helpful message', async () => { const result = await validateSelectField( 'companies', 'company_type', 'invalid_option' ); expect(result.isValid).toBe(false); expect(result.error).toContain( "Invalid value 'invalid_option' for field 'company_type'" ); expect(result.error).toContain( "Valid options are: ['Startup', 'Enterprise', 'Small Business', 'Non Profit']" ); expect(result.error).toContain('Please choose one of the valid values'); }); it('should pass validation for non-select fields', async () => { const result = await validateSelectField( 'companies', 'name', 'any_value' ); expect(result.isValid).toBe(true); }); it('should handle missing field gracefully', async () => { const result = await validateSelectField( 'companies', 'non_existent_field', 'value' ); expect(result.isValid).toBe(true); }); }); describe('validateMultiSelectField', () => { beforeEach(() => { const mockAttributes = [ { api_slug: 'tags', type: 'multi_select', options: [ { title: 'Important', value: 'important' }, { title: 'Urgent', value: 'urgent' }, { title: 'Follow Up', value: 'follow_up' }, ], }, ]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); }); it('should validate valid multi-select options', async () => { const result = await validateMultiSelectField('companies', 'tags', [ 'important', 'urgent', ]); expect(result.isValid).toBe(true); expect(result.error).toBeUndefined(); }); it('should reject invalid multi-select options', async () => { const result = await validateMultiSelectField('companies', 'tags', [ 'important', 'invalid_tag', ]); expect(result.isValid).toBe(false); expect(result.error).toContain( "Invalid values ['invalid_tag'] for multi-select field 'tags'" ); expect(result.error).toContain( "Valid options are: ['Important', 'Urgent', 'Follow Up']" ); }); }); describe('validateReadOnlyFields', () => { beforeEach(() => { const mockAttributes = [ { api_slug: 'created_at', type: 'datetime', read_only: true }, { api_slug: 'updated_at', type: 'datetime', read_only: true }, { api_slug: 'name', type: 'text', read_only: false }, ]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); }); it('should pass validation when no read-only fields are updated', async () => { const result = await validateReadOnlyFields('companies', { name: 'Test Company', }); expect(result.isValid).toBe(true); expect(result.error).toBeUndefined(); }); it('should reject updates to single read-only field', async () => { const result = await validateReadOnlyFields('companies', { name: 'Test Company', created_at: '2024-01-01T00:00:00Z', }); expect(result.isValid).toBe(false); expect(result.error).toContain( "Cannot update read-only field 'created_at'" ); expect(result.error).toContain('automatically managed by the system'); expect(result.error).toContain( 'Remove this field from your update request' ); }); it('should reject updates to multiple read-only fields', async () => { const result = await validateReadOnlyFields('companies', { name: 'Test Company', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }); expect(result.isValid).toBe(false); expect(result.error).toContain( "Cannot update read-only fields 'created_at', 'updated_at'" ); expect(result.error).toContain( 'Remove these fields from your update request' ); }); }); describe('suggestFieldName', () => { beforeEach(() => { const mockAttributes = [ { api_slug: 'name', type: 'text' }, { api_slug: 'company_name', type: 'text' }, { api_slug: 'description', type: 'text' }, { api_slug: 'notes', type: 'text' }, { api_slug: 'website', type: 'text' }, ]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); }); it('should suggest similar field names for typos', async () => { const suggestions = await suggestFieldName('companies', 'companyname'); expect(suggestions).toContain('company_name'); expect(suggestions.length).toBeLessThanOrEqual(3); }); it('should suggest field names based on similarity', async () => { const suggestions = await suggestFieldName('companies', 'descriptin'); expect(suggestions).toContain('description'); expect(suggestions.length).toBeLessThanOrEqual(3); }); it('should return empty array for very dissimilar names', async () => { const suggestions = await suggestFieldName('companies', 'xyz123456'); expect(suggestions).toEqual([]); }); it('should handle exact matches', async () => { const suggestions = await suggestFieldName('companies', 'name'); expect(suggestions).toContain('name'); }); }); describe('validateFieldExistence', () => { beforeEach(() => { const mockAttributes = [ { api_slug: 'name', type: 'text' }, { api_slug: 'description', type: 'text' }, { api_slug: 'notes', type: 'text' }, { api_slug: 'website', type: 'text' }, ]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); }); it('should validate existing fields', async () => { const result = await validateFieldExistence('companies', [ 'name', 'website', ]); expect(result.isValid).toBe(true); expect(result.error).toBeUndefined(); }); it('should reject unknown fields with suggestions', async () => { const result = await validateFieldExistence('companies', ['descriptin']); expect(result.isValid).toBe(false); expect(result.error).toContain( "Unknown field 'descriptin' for resource type 'companies'" ); expect(result.error).toContain('Did you mean: '); expect(result.error).toContain( 'Use get-attributes to see all available fields' ); }); it('should handle multiple field validation', async () => { const result = await validateFieldExistence('companies', [ 'name', 'invalid_field', ]); expect(result.isValid).toBe(false); expect(result.error).toContain("Unknown field 'invalid_field'"); }); }); describe('validateRecordFields', () => { beforeEach(() => { const mockAttributes = [ { api_slug: 'name', type: 'text', read_only: false }, { api_slug: 'created_at', type: 'datetime', read_only: true }, { api_slug: 'company_type', type: 'select', read_only: false, options: [ { title: 'Startup', value: 'startup' }, { title: 'Enterprise', value: 'enterprise' }, ], }, ]; mockGet.mockResolvedValue({ data: { data: mockAttributes }, }); }); it('should validate valid record fields for creation', async () => { const result = await validateRecordFields( 'companies', { name: 'Test Company', company_type: 'startup', }, false ); expect(result.isValid).toBe(true); }); it('should validate valid record fields for update', async () => { const result = await validateRecordFields( 'companies', { name: 'Updated Company', company_type: 'enterprise', }, true ); expect(result.isValid).toBe(true); }); it('should reject read-only fields in updates', async () => { const result = await validateRecordFields( 'companies', { name: 'Test Company', created_at: '2024-01-01T00:00:00Z', }, true ); expect(result.isValid).toBe(false); expect(result.error).toContain('Cannot update read-only field'); }); it('should reject invalid select options', async () => { const result = await validateRecordFields( 'companies', { name: 'Test Company', company_type: 'invalid_type', }, false ); expect(result.isValid).toBe(false); expect(result.error).toContain('Invalid value'); expect(result.error).toContain('company_type'); }); it('should reject unknown fields', async () => { const result = await validateRecordFields( 'companies', { invalid_field: 'value', }, false ); expect(result.isValid).toBe(false); expect(result.error).toContain('Unknown field'); }); }); });

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