import { describe, expect, it } from 'vitest'; import { classifyTwilioError, classifyDeliveryFailure } from '../../src/lib/twilioErrors.ts'; describe('classifyTwilioError', () => { it('classifies known code 20429 as retryable rate_limit', () => { const c = classifyTwilioError({ code: 20429, status: 429, message: 'Too many requests' }); expect(c.kind).toBe('retryable'); expect(c.category).toBe('rate_limit'); expect(c.code).toBe(20429); }); it('classifies known code 21211 as terminal invalid_number', () => { const c = classifyTwilioError({ code: 21211, status: 400, message: 'Invalid To' }); expect(c.kind).toBe('terminal'); expect(c.category).toBe('invalid_number'); }); it('classifies known code 20003 as terminal authentication', () => { const c = classifyTwilioError({ code: 20003, status: 401 }); expect(c.kind).toBe('terminal'); expect(c.category).toBe('authentication'); }); it('falls back to HTTP 500 → retryable server', () => { const c = classifyTwilioError({ status: 502, message: 'Bad gateway' }); expect(c.kind).toBe('retryable'); expect(c.category).toBe('server'); }); it('falls back to HTTP 401 → terminal authentication', () => { const c = classifyTwilioError({ status: 401 }); expect(c.kind).toBe('terminal'); expect(c.category).toBe('authentication'); }); it('falls back to HTTP 4xx → terminal unknown', () => { const c = classifyTwilioError({ status: 418 }); expect(c.kind).toBe('terminal'); expect(c.category).toBe('unknown'); }); it('classifies ETIMEDOUT as retryable network', () => { const c = classifyTwilioError({ errno: 'ETIMEDOUT', message: 'connect ETIMEDOUT' }); expect(c.kind).toBe('retryable'); expect(c.category).toBe('network'); }); it('never throws on odd input shapes', () => { expect(() => classifyTwilioError(null)).not.toThrow(); expect(() => classifyTwilioError('string error')).not.toThrow(); expect(() => classifyTwilioError(undefined)).not.toThrow(); expect(() => classifyTwilioError(new Error('boom'))).not.toThrow(); }); it('produces a user-safe message with no stack trace', () => { const c = classifyTwilioError({ code: 21211, message: 'x'.repeat(500) }); expect(c.userMessage).not.toMatch(/xxxxx/); expect(c.userMessage.length).toBeLessThan(200); }); }); describe('classifyDeliveryFailure', () => { it('returns undefined when there is no error code', () => { expect(classifyDeliveryFailure(undefined)).toBeUndefined(); expect(classifyDeliveryFailure('')).toBeUndefined(); expect(classifyDeliveryFailure(null)).toBeUndefined(); }); it('classifies 30003 → terminal unreachable', () => { const c = classifyDeliveryFailure('30003'); expect(c?.kind).toBe('terminal'); expect(c?.category).toBe('unreachable'); expect(c?.code).toBe(30003); }); it('classifies 30007 → terminal carrier_violation (spam)', () => { const c = classifyDeliveryFailure(30007); expect(c?.kind).toBe('terminal'); expect(c?.category).toBe('carrier_violation'); }); it('classifies unknown numeric codes as terminal unknown', () => { const c = classifyDeliveryFailure(99999); expect(c?.kind).toBe('terminal'); expect(c?.category).toBe('unknown'); expect(c?.code).toBe(99999); }); it('returns undefined on non-numeric strings', () => { expect(classifyDeliveryFailure('nope')).toBeUndefined(); }); });