Files
jmap-webmail/lib/__tests__/validation.test.ts
T
Matthieu MALVACHEandClaude Sonnet 4.5 e8d6e0431f test(identity): add comprehensive unit tests for security utilities
- Setup Vitest testing framework with React Testing Library
- Add 27 test cases for email-sanitization.ts
  - XSS prevention (scripts, event handlers, iframes)
  - Safe HTML preservation (tables, formatting)
  - Signature-specific sanitization rules
  - HTML parsing safety checks

- Add 29 test cases for validation.ts
  - RFC 5322 email validation
  - Header injection prevention
  - Length limit enforcement
  - Email list validation

Coverage: >90% for both security-critical utilities
Zero new runtime dependencies (dev-only)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-08 16:19:45 +01:00

186 lines
6.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
isValidEmail,
validateEmailList,
getEmailValidationError,
} from '../validation';
describe('validation', () => {
describe('isValidEmail', () => {
it('should accept valid basic emails', () => {
expect(isValidEmail('user@example.com')).toBe(true);
expect(isValidEmail('john.doe@company.co.uk')).toBe(true);
expect(isValidEmail('test_user@subdomain.example.com')).toBe(true);
});
it('should accept emails with plus addressing', () => {
expect(isValidEmail('user+tag@example.com')).toBe(true);
expect(isValidEmail('user+shopping@example.com')).toBe(true);
});
it('should accept various valid formats', () => {
expect(isValidEmail('a@b.co')).toBe(true);
expect(isValidEmail('user123@test-domain.com')).toBe(true);
expect(isValidEmail('first.last+tag@example.co.uk')).toBe(true);
});
it('should reject emails without @ symbol', () => {
expect(isValidEmail('userexample.com')).toBe(false);
expect(isValidEmail('user')).toBe(false);
});
it('should reject emails without domain', () => {
expect(isValidEmail('user@')).toBe(false);
expect(isValidEmail('@example.com')).toBe(false);
});
it('should reject header injection attempts', () => {
expect(isValidEmail('test\r\nBcc:evil@example.com')).toBe(false);
expect(isValidEmail('test\rBcc:evil@example.com')).toBe(false);
expect(isValidEmail('test\nBcc:evil@example.com')).toBe(false);
});
it('should reject emails with dangerous characters', () => {
expect(isValidEmail('test<script>@example.com')).toBe(false);
expect(isValidEmail('test>evil@example.com')).toBe(false);
expect(isValidEmail('test@evil>.com')).toBe(false);
});
it('should reject overly long emails', () => {
const longLocal = 'a'.repeat(256);
expect(isValidEmail(`${longLocal}@example.com`)).toBe(false);
});
it('should reject emails with local part > 64 chars', () => {
const longLocal = 'a'.repeat(65);
expect(isValidEmail(`${longLocal}@example.com`)).toBe(false);
});
it('should reject emails with domain > 255 chars', () => {
const longDomain = 'a'.repeat(256) + '.com';
expect(isValidEmail(`user@${longDomain}`)).toBe(false);
});
it('should reject domains starting or ending with dot', () => {
expect(isValidEmail('user@.example.com')).toBe(false);
expect(isValidEmail('user@example.com.')).toBe(false);
});
it('should reject domains with consecutive dots', () => {
expect(isValidEmail('user@example..com')).toBe(false);
expect(isValidEmail('user@sub..domain.com')).toBe(false);
});
it('should reject empty or null input', () => {
expect(isValidEmail('')).toBe(false);
expect(isValidEmail(' ')).toBe(false);
});
it('should handle edge cases', () => {
expect(isValidEmail('user@localhost')).toBe(true); // Valid per RFC
expect(isValidEmail('user@192.168.1.1')).toBe(true); // IP address domain
});
});
describe('validateEmailList', () => {
it('should validate single valid email', () => {
const result = validateEmailList('user@example.com');
expect(result.valid).toBe(true);
expect(result.invalidEmails).toEqual([]);
});
it('should validate multiple valid emails', () => {
const result = validateEmailList('user1@example.com, user2@test.com, user3@domain.co.uk');
expect(result.valid).toBe(true);
expect(result.invalidEmails).toEqual([]);
});
it('should handle whitespace around emails', () => {
const result = validateEmailList(' user1@example.com , user2@test.com ');
expect(result.valid).toBe(true);
expect(result.invalidEmails).toEqual([]);
});
it('should reject list with one invalid email', () => {
const result = validateEmailList('user1@example.com, invalid-email, user3@domain.com');
expect(result.valid).toBe(false);
expect(result.invalidEmails).toEqual(['invalid-email']);
});
it('should identify all invalid emails', () => {
const result = validateEmailList('user1@example.com, bad1, user2@test.com, bad2@');
expect(result.valid).toBe(false);
expect(result.invalidEmails).toContain('bad1');
expect(result.invalidEmails).toContain('bad2@');
expect(result.invalidEmails).toHaveLength(2);
});
it('should handle empty string', () => {
const result = validateEmailList('');
expect(result.valid).toBe(true);
expect(result.invalidEmails).toEqual([]);
});
it('should handle whitespace-only string', () => {
const result = validateEmailList(' ');
expect(result.valid).toBe(true);
expect(result.invalidEmails).toEqual([]);
});
it('should filter out empty entries from commas', () => {
const result = validateEmailList('user1@example.com,,user2@test.com,');
expect(result.valid).toBe(true);
expect(result.invalidEmails).toEqual([]);
});
});
describe('getEmailValidationError', () => {
it('should return null for valid email', () => {
expect(getEmailValidationError('user@example.com')).toBeNull();
expect(getEmailValidationError('user+tag@example.com')).toBeNull();
});
it('should return error for empty email', () => {
const error = getEmailValidationError('');
expect(error).not.toBeNull();
expect(error).toContain('required');
});
it('should return error for whitespace-only email', () => {
const error = getEmailValidationError(' ');
expect(error).not.toBeNull();
expect(error).toContain('required');
});
it('should return error for overly long email', () => {
const longEmail = 'a'.repeat(256) + '@example.com';
const error = getEmailValidationError(longEmail);
expect(error).not.toBeNull();
expect(error).toContain('too long');
expect(error).toContain('254');
});
it('should return error for dangerous characters', () => {
const error = getEmailValidationError('test\r\nBcc:evil@example.com');
expect(error).not.toBeNull();
expect(error).toContain('invalid characters');
});
it('should return error for invalid format', () => {
const error = getEmailValidationError('not-an-email');
expect(error).not.toBeNull();
expect(error).toContain('valid email');
});
it('should provide user-friendly messages', () => {
const error1 = getEmailValidationError('test@');
const error2 = getEmailValidationError('@example.com');
const error3 = getEmailValidationError('no-at-sign');
expect(error1).toContain('valid');
expect(error2).toContain('valid');
expect(error3).toContain('valid');
});
});
});