162 lines
5.1 KiB
TypeScript
162 lines
5.1 KiB
TypeScript
/**
|
|
* Tests for Sanitize Utility
|
|
* FE-TEST-004: Test sanitize utility functions
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import {
|
|
sanitizeHTML,
|
|
sanitizeChatMessage,
|
|
sanitizeTextInput,
|
|
sanitizeURL,
|
|
sanitizeEmail,
|
|
validatePassword,
|
|
} from './sanitize';
|
|
|
|
// Mock DOMPurify
|
|
vi.mock('dompurify', () => ({
|
|
default: {
|
|
isSupported: true,
|
|
sanitize: vi.fn((html: string) =>
|
|
html.replace(/<script[^>]*>.*?<\/script>/gi, ''),
|
|
),
|
|
},
|
|
}));
|
|
|
|
describe('sanitize utilities', () => {
|
|
describe('sanitizeHTML', () => {
|
|
it('should remove script tags', () => {
|
|
const input = '<p>Hello</p><script>alert("xss")</script>';
|
|
const result = sanitizeHTML(input);
|
|
expect(result).not.toContain('<script>');
|
|
});
|
|
|
|
it('should escape HTML tags (sanitizeHTML escapes all HTML)', () => {
|
|
const input = '<p>Hello <strong>world</strong></p>';
|
|
const result = sanitizeHTML(input);
|
|
// sanitizeHTML escapes all HTML, so tags become entities
|
|
expect(result).toContain('<');
|
|
expect(result).toContain('Hello');
|
|
});
|
|
|
|
it('should remove dangerous patterns', () => {
|
|
const input = '<p onclick="alert(1)">Hello</p>';
|
|
const result = sanitizeHTML(input);
|
|
expect(result).not.toContain('onclick');
|
|
});
|
|
});
|
|
|
|
describe('sanitizeChatMessage', () => {
|
|
it('should sanitize chat messages', () => {
|
|
const input = '<p>Hello <script>alert("xss")</script></p>';
|
|
const result = sanitizeChatMessage(input);
|
|
expect(result).not.toContain('<script>');
|
|
});
|
|
|
|
it('should preserve basic formatting', () => {
|
|
const input = '<p>Hello <strong>world</strong></p>';
|
|
const result = sanitizeChatMessage(input);
|
|
expect(result).toContain('<p>');
|
|
expect(result).toContain('<strong>');
|
|
});
|
|
});
|
|
|
|
describe('sanitizeTextInput', () => {
|
|
it('should escape HTML', () => {
|
|
const input = '<script>alert("xss")</script>';
|
|
const result = sanitizeTextInput(input);
|
|
expect(result).not.toContain('<script>');
|
|
expect(result).toContain('<');
|
|
});
|
|
|
|
it('should trim input', () => {
|
|
expect(sanitizeTextInput(' hello ')).toBe('hello');
|
|
});
|
|
});
|
|
|
|
describe('sanitizeURL', () => {
|
|
it('should allow http URLs', () => {
|
|
const result = sanitizeURL('http://example.com');
|
|
// URL.toString() adds trailing slash
|
|
expect(result).toBe('http://example.com/');
|
|
});
|
|
|
|
it('should allow https URLs', () => {
|
|
const result = sanitizeURL('https://example.com');
|
|
// URL.toString() adds trailing slash
|
|
expect(result).toBe('https://example.com/');
|
|
});
|
|
|
|
it('should reject javascript URLs', () => {
|
|
const result = sanitizeURL('javascript:alert(1)');
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should reject file URLs', () => {
|
|
const result = sanitizeURL('file:///etc/passwd');
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null for invalid URLs', () => {
|
|
const result = sanitizeURL('not-a-url');
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('sanitizeEmail', () => {
|
|
it('should validate and normalize valid email', () => {
|
|
const result = sanitizeEmail('Test@Example.COM');
|
|
expect(result).toBe('test@example.com');
|
|
});
|
|
|
|
it('should return null for invalid email', () => {
|
|
expect(sanitizeEmail('not-an-email')).toBeNull();
|
|
expect(sanitizeEmail('test@')).toBeNull();
|
|
expect(sanitizeEmail('@example.com')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('validatePassword', () => {
|
|
it('should validate strong password', () => {
|
|
const result = validatePassword('StrongP@ssw0rd123');
|
|
expect(result.isValid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
|
|
it('should reject short password', () => {
|
|
const result = validatePassword('Short1!');
|
|
expect(result.isValid).toBe(false);
|
|
expect(result.errors.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should reject password without uppercase', () => {
|
|
const result = validatePassword('lowercase123!');
|
|
expect(result.isValid).toBe(false);
|
|
expect(result.errors.some((e) => e.includes('majuscule'))).toBe(true);
|
|
});
|
|
|
|
it('should reject password without lowercase', () => {
|
|
const result = validatePassword('UPPERCASE123!');
|
|
expect(result.isValid).toBe(false);
|
|
expect(result.errors.some((e) => e.includes('minuscule'))).toBe(true);
|
|
});
|
|
|
|
it('should reject password without number', () => {
|
|
const result = validatePassword('NoNumber!');
|
|
expect(result.isValid).toBe(false);
|
|
expect(result.errors.some((e) => e.includes('chiffre'))).toBe(true);
|
|
});
|
|
|
|
it('should reject password without special character', () => {
|
|
const result = validatePassword('NoSpecial123');
|
|
expect(result.isValid).toBe(false);
|
|
expect(result.errors.some((e) => e.includes('spécial'))).toBe(true);
|
|
});
|
|
|
|
it('should reject common weak patterns', () => {
|
|
const result = validatePassword('Password123!');
|
|
expect(result.isValid).toBe(false);
|
|
expect(result.errors.some((e) => e.includes('commun'))).toBe(true);
|
|
});
|
|
});
|
|
});
|