Skip to main content
Glama
enhanced-validation.test.tsβ€’12.4 kB
/** * Unit tests for enhanced validation utilities */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { validateReadOnlyFields, validateFieldExistence, validateSelectField, validateRecordFields, createEnhancedErrorResponse, EnhancedValidationResult, EnhancedErrorResponse, } from '../../src/utils/enhanced-validation.js'; // Mock the attribute-types module vi.mock('../../src/api/attribute-types.js', () => ({ getObjectAttributeMetadata: vi.fn(), getFieldValidationRules: vi.fn(), AttioAttributeMetadata: {}, })); import { getObjectAttributeMetadata, getFieldValidationRules, } from '../../src/api/attribute-types.js'; describe('Enhanced Validation Utils', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('validateReadOnlyFields', () => { it('should pass validation when no read-only fields are provided', () => { const data = { name: 'Test Company', website: 'https://test.com' }; const readOnlyFields: string[] = []; const result = validateReadOnlyFields(data, readOnlyFields); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); expect(result.readOnlyFields).toHaveLength(0); }); it('should pass validation when read-only fields are not in data', () => { const data = { name: 'Test Company', website: 'https://test.com' }; const readOnlyFields = ['id', 'created_at']; const result = validateReadOnlyFields(data, readOnlyFields); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); expect(result.readOnlyFields).toHaveLength(0); }); it('should fail validation when read-only fields are provided in data', () => { const data = { name: 'Test Company', id: 'some-id', created_at: '2023-01-01', }; const readOnlyFields = ['id', 'created_at']; const result = validateReadOnlyFields(data, readOnlyFields); expect(result.isValid).toBe(false); expect(result.errors).toHaveLength(2); expect(result.errors[0]).toBe( "Field 'id' is read-only and cannot be modified" ); expect(result.errors[1]).toBe( "Field 'created_at' is read-only and cannot be modified" ); expect(result.readOnlyFields).toEqual(['id', 'created_at']); }); it('should ignore fields with undefined values', () => { const data = { name: 'Test Company', id: undefined }; const readOnlyFields = ['id']; const result = validateReadOnlyFields(data, readOnlyFields); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); }); describe('validateFieldExistence', () => { it('should pass validation when all required fields are present', () => { const data = { name: 'Test Company', email: 'test@example.com' }; const requiredFields = ['name', 'email']; const result = validateFieldExistence(data, requiredFields); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); expect(result.missingFields).toHaveLength(0); }); it('should fail validation when required fields are missing', () => { const data = { name: 'Test Company' }; const requiredFields = ['name', 'email', 'phone']; const result = validateFieldExistence(data, requiredFields); expect(result.isValid).toBe(false); expect(result.errors).toHaveLength(2); expect(result.errors[0]).toBe( "Required field 'email' is missing or empty" ); expect(result.errors[1]).toBe( "Required field 'phone' is missing or empty" ); expect(result.missingFields).toEqual(['email', 'phone']); }); it('should fail validation when required fields are null or empty', () => { const data = { name: '', email: null, phone: undefined }; const requiredFields = ['name', 'email', 'phone']; const result = validateFieldExistence(data, requiredFields); expect(result.isValid).toBe(false); expect(result.errors).toHaveLength(3); expect(result.missingFields).toEqual(['name', 'email', 'phone']); }); }); describe('validateSelectField', () => { const validOptions = ['option1', 'option2', 'option3']; it('should pass validation for valid single select value', () => { const result = validateSelectField('option1', validOptions, 'testField'); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should pass validation for valid multiselect values', () => { const result = validateSelectField( ['option1', 'option3'], validOptions, 'testField' ); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should fail validation for invalid single select value', () => { const result = validateSelectField( 'invalid_option', validOptions, 'testField' ); expect(result.isValid).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors[0]).toBe( "Invalid option 'invalid_option' for testField. Valid options: option1, option2, option3" ); }); it('should fail validation for invalid multiselect values', () => { const result = validateSelectField( ['option1', 'invalid_option'], validOptions, 'testField' ); expect(result.isValid).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors[0]).toBe( "Invalid option 'invalid_option' for testField. Valid options: option1, option2, option3" ); }); it('should pass validation for null or undefined values', () => { expect(validateSelectField(null, validOptions).isValid).toBe(true); expect(validateSelectField(undefined, validOptions).isValid).toBe(true); }); it('should use default field label when none provided', () => { const result = validateSelectField('invalid_option', validOptions); expect(result.errors[0]).toContain('for field.'); }); }); describe('validateRecordFields', () => { const mockMetadata = new Map(); const mockValidationRules = { type: 'string', required: false, unique: false, allowMultiple: false, }; beforeEach(() => { mockMetadata.clear(); // Mock attribute metadata mockMetadata.set('name', { api_slug: 'name', type: 'text', is_required: true, is_writable: true, is_unique: false, }); mockMetadata.set('email', { api_slug: 'email', type: 'email', is_required: true, is_writable: true, is_unique: true, }); mockMetadata.set('created_at', { api_slug: 'created_at', type: 'timestamp', is_required: false, is_writable: false, is_unique: false, }); vi.mocked(getObjectAttributeMetadata).mockResolvedValue(mockMetadata); vi.mocked(getFieldValidationRules).mockResolvedValue(mockValidationRules); }); it('should pass validation for valid record data', async () => { const data = { name: 'Test Company', email: 'test@example.com' }; const result = await validateRecordFields('companies', data, false); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should fail validation for read-only fields', async () => { const data = { name: 'Test Company', created_at: '2023-01-01' }; const result = await validateRecordFields('companies', data, false); expect(result.isValid).toBe(false); expect(result.errors).toContain( "Field 'created_at' is read-only and cannot be modified" ); expect(result.readOnlyFields).toContain('created_at'); }); it('should fail validation for missing required fields in create operations', async () => { const data = { name: 'Test Company' }; // Missing required email field const result = await validateRecordFields('companies', data, false); expect(result.isValid).toBe(false); expect(result.missingFields).toContain('email'); }); it('should not check required fields for update operations', async () => { const data = { name: 'Updated Company' }; // Missing email, but it's an update const result = await validateRecordFields('companies', data, true); expect(result.isValid).toBe(true); expect(result.missingFields).toHaveLength(0); }); it('should warn about unrecognized fields', async () => { const data = { name: 'Test Company', unknown_field: 'value' }; const result = await validateRecordFields('companies', data, false); expect(result.warnings).toContain( "Field 'unknown_field' is not recognized for resource type 'companies'" ); }); it('should handle metadata fetch errors gracefully', async () => { vi.mocked(getObjectAttributeMetadata).mockRejectedValue( new Error('API error') ); const data = { name: 'Test Company' }; const result = await validateRecordFields('companies', data, false); expect(result.isValid).toBe(false); expect(result.errors).toContain('Failed to validate fields: API error'); }); it('should warn when no metadata is available', async () => { vi.mocked(getObjectAttributeMetadata).mockResolvedValue(new Map()); const data = { name: 'Test Company' }; const result = await validateRecordFields('companies', data, false); expect(result.warnings).toContain( "No attribute metadata found for resource type 'companies'. Validation may be incomplete." ); }); }); describe('createEnhancedErrorResponse', () => { it('should create basic error response', () => { const validation: EnhancedValidationResult = { isValid: false, errors: ['Error 1', 'Error 2'], warnings: ['Warning 1'], }; const response: EnhancedErrorResponse = createEnhancedErrorResponse( validation, 'test-operation' ); expect(response.error).toBe('Error 1; Error 2'); expect(response.warnings).toEqual(['Warning 1']); expect(response.operation).toBe('test-operation'); }); it('should include suggestions for missing fields', () => { const validation: EnhancedValidationResult = { isValid: false, errors: ['Missing fields'], warnings: [], missingFields: ['name', 'email'], }; const response = createEnhancedErrorResponse(validation, 'create-record'); expect(response.suggestions).toContain( 'Add required fields: name, email' ); expect(response.suggestions).toContain( 'Ensure all required fields have values before creating a record' ); }); it('should include suggestions for read-only fields', () => { const validation: EnhancedValidationResult = { isValid: false, errors: ['Read-only field error'], warnings: [], readOnlyFields: ['id', 'created_at'], }; const response = createEnhancedErrorResponse(validation, 'update-record'); expect(response.suggestions).toContain( 'Remove read-only fields: id, created_at' ); expect(response.suggestions).toContain( 'Use separate calls to update read-only fields if they support it, or remove them from the update' ); }); it('should include suggestions for invalid fields', () => { const validation: EnhancedValidationResult = { isValid: false, errors: ['Invalid field values'], warnings: [], invalidFields: ['email', 'phone'], }; const response = createEnhancedErrorResponse(validation, 'create-record'); expect(response.suggestions).toContain( 'Fix invalid field values for: email, phone' ); }); it('should not include suggestions when there are no issues to suggest fixes for', () => { const validation: EnhancedValidationResult = { isValid: false, errors: ['Generic error'], warnings: [], }; const response = createEnhancedErrorResponse( validation, 'test-operation' ); expect(response.suggestions).toBeUndefined(); }); }); });

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