/** * Comprehensive Unit Tests for Sanitization Utilities * * These tests ensure XSS and injection attack prevention */ import { describe, it, expect } from 'vitest'; import { sanitizeHTML, sanitizeURL, sanitizePlainText, containsSuspiciousContent } from '@/lib/sanitize'; describe('sanitizeURL', () => { describe('Valid URLs', () => { it('should allow valid http URLs', () => { expect(sanitizeURL('http://example.com')).toBe('http://example.com'); }); it('should allow valid https URLs', () => { expect(sanitizeURL('https://example.com/path?query=value')).toBe('https://example.com/path?query=value'); }); it('should allow valid mailto URLs', () => { expect(sanitizeURL('mailto:user@example.com')).toBe('mailto:user@example.com'); }); it('should allow URLs with special characters in query strings', () => { const url = 'https://example.com/search?q=test%20query&sort=desc'; expect(sanitizeURL(url)).toBe(url); }); it('should allow URLs with fragments', () => { const url = 'https://example.com/page#section'; expect(sanitizeURL(url)).toBe(url); }); it('should allow URLs with authentication', () => { const url = 'https://user:pass@example.com/path'; expect(sanitizeURL(url)).toBe(url); }); it('should allow URLs with ports', () => { const url = 'https://example.com:8080/path'; expect(sanitizeURL(url)).toBe(url); }); }); describe('Dangerous Protocols - XSS Prevention', () => { it('should block javascript: protocol', () => { expect(sanitizeURL('javascript:alert("XSS")')).toBe('#'); }); it('should block javascript: protocol with uppercase', () => { expect(sanitizeURL('JAVASCRIPT:alert("XSS")')).toBe('#'); }); it('should block javascript: protocol with mixed case', () => { expect(sanitizeURL('JaVaScRiPt:alert("XSS")')).toBe('#'); }); it('should block data: protocol', () => { expect(sanitizeURL('data:text/html,')).toBe('#'); }); it('should block data: protocol with base64', () => { expect(sanitizeURL('data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=')).toBe('#'); }); it('should block vbscript: protocol', () => { expect(sanitizeURL('vbscript:msgbox("XSS")')).toBe('#'); }); it('should block file: protocol', () => { expect(sanitizeURL('file:///etc/passwd')).toBe('#'); }); it('should block ftp: protocol', () => { expect(sanitizeURL('ftp://example.com/file')).toBe('#'); }); }); describe('Edge Cases', () => { it('should handle invalid URLs', () => { expect(sanitizeURL('not a url')).toBe('#'); expect(sanitizeURL('')).toBe('#'); expect(sanitizeURL(' ')).toBe('#'); }); it('should handle null/undefined gracefully', () => { expect(sanitizeURL(null as any)).toBe('#'); expect(sanitizeURL(undefined as any)).toBe('#'); }); it('should handle malformed URLs', () => { expect(sanitizeURL('http://')).toBe('#'); expect(sanitizeURL('https://')).toBe('#'); expect(sanitizeURL('://')).toBe('#'); }); it('should handle URLs with only protocol', () => { expect(sanitizeURL('http:')).toBe('#'); expect(sanitizeURL('https:')).toBe('#'); }); it('should handle relative URLs', () => { expect(sanitizeURL('/path/to/page')).toBe('#'); expect(sanitizeURL('./relative')).toBe('#'); expect(sanitizeURL('../parent')).toBe('#'); }); it('should handle URLs with whitespace (URL constructor allows it)', () => { // Note: URL constructor successfully parses URLs with surrounding whitespace const result = sanitizeURL(' https://example.com '); // Either returns the URL as-is or we could trim it first expect(result).toBe(' https://example.com '); }); it('should handle empty or whitespace-only strings', () => { expect(sanitizeURL('')).toBe('#'); expect(sanitizeURL(' ')).toBe('#'); expect(sanitizeURL('\n\t')).toBe('#'); }); }); }); describe('sanitizePlainText', () => { describe('HTML Entity Escaping', () => { it('should escape script tags', () => { expect(sanitizePlainText('')) .toBe('<script>alert("XSS")</script>'); }); it('should escape ampersands', () => { expect(sanitizePlainText('Tom & Jerry')).toBe('Tom & Jerry'); }); it('should escape double quotes', () => { expect(sanitizePlainText('"Hello"')).toContain('"'); }); it('should escape single quotes', () => { expect(sanitizePlainText("'World'")).toContain('''); }); it('should escape less than symbols', () => { expect(sanitizePlainText('5 < 10')).toBe('5 < 10'); }); it('should escape greater than symbols', () => { expect(sanitizePlainText('10 > 5')).toBe('10 > 5'); }); it('should escape forward slashes', () => { expect(sanitizePlainText('path/to/file')).toBe('path/to/file'); }); it('should escape all special characters together', () => { const input = '