/**
* Unit tests for phone validation utilities
* @attio-mcp/core - Issue #951
*/
import { describe, it, expect } from 'vitest';
import {
validatePhoneNumber,
toE164,
hasCountryCode,
isPossiblePhoneNumber,
isValidPhoneNumber,
normalizePhoneForAttio,
PhoneValidationError,
} from '../../src/utils/phone-validation.js';
describe('phone-validation', () => {
describe('validatePhoneNumber', () => {
it('validates E.164 format correctly', () => {
const result = validatePhoneNumber('+12025550134');
expect(result.valid).toBe(true);
expect(result.e164).toBe('+12025550134');
expect(result.country).toBe('US');
});
it('validates US number with formatting', () => {
const result = validatePhoneNumber('+1 (202) 555-0134');
expect(result.valid).toBe(true);
expect(result.e164).toBe('+12025550134');
expect(result.country).toBe('US');
});
it('applies default country when no prefix', () => {
const result = validatePhoneNumber('2025550134', {
defaultCountry: 'US',
});
expect(result.valid).toBe(true);
expect(result.e164).toBe('+12025550134');
});
it('detects country from international format - UK', () => {
const result = validatePhoneNumber('+44 20 7946 0958');
expect(result.valid).toBe(true);
expect(result.country).toBe('GB');
expect(result.e164).toBe('+442079460958');
});
it('detects country from international format - Japan', () => {
const result = validatePhoneNumber('+81 3 1234 5678');
expect(result.valid).toBe(true);
expect(result.country).toBe('JP');
});
it('detects country from international format - Australia', () => {
const result = validatePhoneNumber('+61 2 9374 4000');
expect(result.valid).toBe(true);
expect(result.country).toBe('AU');
});
it('returns error for invalid format', () => {
const result = validatePhoneNumber('not-a-phone');
expect(result.valid).toBe(false);
expect(result.error).toBeDefined();
expect(result.error?.code).toBe('NOT_A_NUMBER');
});
it('returns error for too-short numbers', () => {
const result = validatePhoneNumber('+1202', { defaultCountry: 'US' });
expect(result.valid).toBe(false);
expect(result.error?.code).toBe('TOO_SHORT');
});
it('returns error for too-long numbers', () => {
const result = validatePhoneNumber('+12025550134567890123456');
expect(result.valid).toBe(false);
expect(result.error?.code).toBe('TOO_LONG');
});
it('returns error for empty input', () => {
const result = validatePhoneNumber('');
expect(result.valid).toBe(false);
expect(result.error?.code).toBe('INVALID_FORMAT');
expect(result.error?.message).toContain('empty');
});
it('returns error for whitespace-only input', () => {
const result = validatePhoneNumber(' ');
expect(result.valid).toBe(false);
expect(result.error?.code).toBe('INVALID_FORMAT');
});
it('returns error for non-string input', () => {
// @ts-expect-error Testing invalid input type
const result = validatePhoneNumber(12345);
expect(result.valid).toBe(false);
expect(result.error?.code).toBe('INVALID_TYPE');
});
});
describe('toE164', () => {
it('converts US format to E.164', () => {
expect(toE164('(202) 555-0134', 'US')).toBe('+12025550134');
});
it('converts international format to E.164', () => {
// Use 202 (DC) instead of 555 (fictional)
expect(toE164('+1 202 555 0134')).toBe('+12025550134');
});
it('returns null for invalid', () => {
expect(toE164('invalid')).toBeNull();
});
it('returns null for empty string', () => {
expect(toE164('')).toBeNull();
});
it('returns null for non-string input', () => {
expect(toE164(12345)).toBeNull();
expect(toE164(null)).toBeNull();
expect(toE164(undefined)).toBeNull();
});
});
describe('hasCountryCode', () => {
it('detects + prefix', () => {
expect(hasCountryCode('+1 555 123 4567')).toBe(true);
expect(hasCountryCode('+442079460958')).toBe(true);
});
it('returns false without + prefix', () => {
expect(hasCountryCode('555 123 4567')).toBe(false);
expect(hasCountryCode('2025550134')).toBe(false);
});
it('handles whitespace', () => {
expect(hasCountryCode(' +1 555')).toBe(true);
});
it('returns false for non-string', () => {
// @ts-expect-error Testing invalid input type
expect(hasCountryCode(12345)).toBe(false);
});
});
describe('isPossiblePhoneNumber', () => {
it('returns true for possible numbers', () => {
expect(isPossiblePhoneNumber('+12025550134')).toBe(true);
expect(isPossiblePhoneNumber('2025550134', 'US')).toBe(true);
});
it('returns false for clearly invalid', () => {
expect(isPossiblePhoneNumber('abc')).toBe(false);
expect(isPossiblePhoneNumber('')).toBe(false);
});
});
describe('isValidPhoneNumber', () => {
it('returns true for valid numbers', () => {
expect(isValidPhoneNumber('+12025550134')).toBe(true);
});
it('returns false for invalid numbers', () => {
expect(isValidPhoneNumber('invalid')).toBe(false);
});
});
describe('normalizePhoneForAttio', () => {
it('normalizes string phone to Attio format', () => {
// Use 202 (DC) instead of 555 (fictional)
const result = normalizePhoneForAttio('+1 202 555 0134');
expect(result).toEqual({
original_phone_number: '+12025550134',
});
});
it('normalizes object with phone_number key', () => {
const result = normalizePhoneForAttio({
phone_number: '+1 202 555 0134',
label: 'work',
});
expect(result).toEqual({
original_phone_number: '+12025550134',
label: 'work',
});
});
it('preserves metadata fields', () => {
const result = normalizePhoneForAttio({
phone_number: '+1 202 555 0134',
label: 'work',
type: 'mobile',
extension: '123',
is_primary: true,
});
expect(result.original_phone_number).toBe('+12025550134');
expect(result.label).toBe('work');
expect(result.type).toBe('mobile');
expect(result.extension).toBe('123');
expect(result.is_primary).toBe(true);
});
it('removes phone_number key after normalization', () => {
const result = normalizePhoneForAttio({
phone_number: '+1 202 555 0134',
});
expect(result).not.toHaveProperty('phone_number');
expect(result).toHaveProperty('original_phone_number');
});
it('applies default country for national format', () => {
const result = normalizePhoneForAttio('2025550134', {
defaultCountry: 'US',
});
expect(result.original_phone_number).toBe('+12025550134');
});
it('throws PhoneValidationError for invalid phone', () => {
expect(() => normalizePhoneForAttio('invalid')).toThrow(
PhoneValidationError
);
});
it('throws for empty phone', () => {
expect(() => normalizePhoneForAttio('')).toThrow(PhoneValidationError);
});
});
describe('PhoneValidationError', () => {
it('has correct properties', () => {
const error = new PhoneValidationError(
'INVALID_FORMAT',
'Test message',
'+123',
'US'
);
expect(error.name).toBe('PhoneValidationError');
expect(error.code).toBe('INVALID_FORMAT');
expect(error.message).toBe('Test message');
expect(error.input).toBe('+123');
expect(error.country).toBe('US');
});
it('is instanceof Error', () => {
const error = new PhoneValidationError('INVALID_FORMAT', 'Test', 'input');
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(PhoneValidationError);
});
});
});