Skip to main content
Glama
company-validator.test.tsβ€’8.66 kB
/** * Tests for the company validator with attribute type validation */ import { describe, beforeEach, it, expect, vi } from 'vitest'; import { CompanyValidator } from '@/validators/company-validator.js'; import { detectFieldType, getAttributeTypeInfo, } from '@/api/attribute-types.js'; import { InvalidRequestError } from '@/errors/api-errors.js'; import { InvalidCompanyDataError } from '@/errors/company-errors.js'; // Mock the getAttributeTypeInfo function vi.mock('@/api/attribute-types.js', () => ({ getAttributeTypeInfo: vi.fn(), getFieldValidationRules: vi.fn(), detectFieldType: vi.fn(), })); describe('Company Validator', () => { // Reset mocks before each test beforeEach(() => { vi.restoreAllMocks(); vi.clearAllMocks(); vi.restoreAllMocks(); CompanyValidator.clearFieldTypeCache(); }); describe('validateAttributeTypes', () => { it('should validate and convert attributes based on their types', async () => { // Mock type info for name (string), employees (number), and active (boolean) (getAttributeTypeInfo as vi.Mock).mockImplementation( async (objectSlug, attributeName) => { switch (attributeName) { case 'name': return { fieldType: 'string', isArray: false, isRequired: true, isUnique: true, attioType: 'text', metadata: {}, }; case 'employees': return { fieldType: 'number', isArray: false, isRequired: false, isUnique: false, attioType: 'number', metadata: {}, }; case 'active': return { fieldType: 'boolean', isArray: false, isRequired: false, isUnique: false, attioType: 'checkbox', metadata: {}, }; default: return { fieldType: 'string', isArray: false, isRequired: false, isUnique: false, attioType: 'text', metadata: {}, }; } } ); // Test attributes with mixed types const attributes = { name: 'Test Company', employees: '250', // String that should be converted to number active: 'yes', // String that should be converted to boolean description: 'A test company', }; const result = await CompanyValidator.validateAttributeTypes(attributes); // Verify the result contains converted values expect(result).toEqual({ name: 'Test Company', employees: 250, // Should be a number now active: true, // Should be a boolean now description: 'A test company', }); // Verify API calls expect(getAttributeTypeInfo).toHaveBeenCalledTimes(4); }); it('should handle null values correctly', async () => { // Mock type info for a single attribute (getAttributeTypeInfo as vi.Mock).mockResolvedValue({ fieldType: 'string', isArray: false, isRequired: false, isUnique: false, attioType: 'text', metadata: {}, }); const attributes = { name: null, description: null, }; const result = await CompanyValidator.validateAttributeTypes(attributes); // Null values should remain null expect(result).toEqual({ name: null, description: null, }); // Type info should not be requested for null values expect(getAttributeTypeInfo).not.toHaveBeenCalled(); }); it('should throw an error for invalid attribute values', async () => { // Mock type info for employees as number (getAttributeTypeInfo as vi.Mock).mockResolvedValue({ fieldType: 'number', isArray: false, isRequired: false, isUnique: false, attioType: 'number', metadata: {}, }); const attributes = { employees: 'not-a-number', // Invalid for number type }; // Should throw an InvalidRequestError await expect( CompanyValidator.validateAttributeTypes(attributes) ).rejects.toThrow(InvalidRequestError); }); it('should proceed with original value if type info cannot be determined', async () => { // Mock getAttributeTypeInfo to throw an error (getAttributeTypeInfo as vi.Mock).mockRejectedValue( new Error('API error') ); const attributes = { custom_field: 'test value', }; const result = await CompanyValidator.validateAttributeTypes(attributes); // Should keep original value expect(result).toEqual({ custom_field: 'test value', }); }); }); describe('validateAttributeUpdate integration', () => { it('should validate and convert a single attribute value', async () => { // Mock the field type detection to return 'number' (getAttributeTypeInfo as vi.Mock).mockResolvedValue({ fieldType: 'number', isArray: false, isRequired: false, isUnique: false, attioType: 'number', metadata: {}, }); // Mock detectFieldType to avoid validation errors vi.spyOn(CompanyValidator as any, 'validateFieldType').mockResolvedValue( undefined ); vi.spyOn( CompanyValidator as any, 'performSpecialValidation' ).mockResolvedValue(undefined); // Test with a string value that should be converted to number const result = await CompanyValidator.validateAttributeUpdate( 'comp_123', 'revenue', '5000000' ); // Should be converted to a number expect(result).toBe(5000000); }); it('should throw an error for invalid attribute value', async () => { // Mock the field type detection to return 'boolean' (getAttributeTypeInfo as vi.Mock).mockResolvedValue({ fieldType: 'boolean', isArray: false, isRequired: false, isUnique: false, attioType: 'checkbox', metadata: {}, }); // Mock validateFieldType to avoid validation errors vi.spyOn(CompanyValidator as any, 'validateFieldType').mockResolvedValue( undefined ); vi.spyOn( CompanyValidator as any, 'performSpecialValidation' ).mockResolvedValue(undefined); // Test with an invalid boolean value await expect( CompanyValidator.validateAttributeUpdate( 'comp_123', 'is_active', 'invalid-boolean' ) ).rejects.toThrow(); }); }); describe('LinkedIn validation integration', () => { const stringAttributeMetadata = { fieldType: 'string', isArray: false, isRequired: false, isUnique: false, attioType: 'text', metadata: {}, }; it('validates linkedin_url in special validation flow', async () => { (getAttributeTypeInfo as vi.Mock).mockResolvedValue( stringAttributeMetadata ); (detectFieldType as vi.Mock).mockResolvedValue('string'); await expect( CompanyValidator.validateCreate({ name: 'Spoofed Corp', linkedin_url: 'https://linkedin.com.fake-site.io/company/foo', }) ).rejects.toThrow(InvalidCompanyDataError); }); it('extracts domain correctly when linkedin_url is valid', async () => { (getAttributeTypeInfo as vi.Mock).mockImplementation( async (_objectSlug, attributeName: string) => { if (attributeName === 'domains') { return { fieldType: 'array', isArray: true, isRequired: false, isUnique: false, attioType: 'text', metadata: {}, }; } return stringAttributeMetadata; } ); (detectFieldType as vi.Mock).mockImplementation( async (_resource, attributeName: string) => attributeName === 'domains' ? 'array' : 'string' ); const result = await CompanyValidator.validateCreate({ name: 'Valid Corp', website: 'https://valid.example.com/about', linkedin_url: 'https://www.linkedin.com/company/valid-corp', }); expect(result.domains).toEqual(['valid.example.com']); expect(result.linkedin_url).toBe( 'https://www.linkedin.com/company/valid-corp' ); }); }); });

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