agent-ecosystem/test/main/utils/regexValidation.test.ts

146 lines
5.3 KiB
TypeScript

/**
* Tests for regex validation utilities (ReDoS protection).
*/
import { describe, expect, it } from 'vitest';
import { createSafeRegExp, validateRegexPattern } from '../../../src/main/utils/regexValidation';
describe('regexValidation', () => {
describe('validateRegexPattern', () => {
describe('basic validation', () => {
it('should reject empty pattern', () => {
const result = validateRegexPattern('');
expect(result.valid).toBe(false);
expect(result.error).toContain('non-empty string');
});
it('should accept valid simple patterns', () => {
expect(validateRegexPattern('hello')).toEqual({ valid: true });
expect(validateRegexPattern('error')).toEqual({ valid: true });
expect(validateRegexPattern('[a-z]+')).toEqual({ valid: true });
});
it('should accept valid patterns with special chars', () => {
expect(validateRegexPattern('foo\\.bar')).toEqual({ valid: true });
expect(validateRegexPattern('\\d+\\.\\d+')).toEqual({ valid: true });
expect(validateRegexPattern('^test$')).toEqual({ valid: true });
});
});
describe('length validation', () => {
it('should reject patterns over 100 chars', () => {
const longPattern = 'a'.repeat(101);
const result = validateRegexPattern(longPattern);
expect(result.valid).toBe(false);
expect(result.error).toContain('too long');
});
it('should accept patterns at 100 chars', () => {
const maxPattern = 'a'.repeat(100);
expect(validateRegexPattern(maxPattern).valid).toBe(true);
});
});
describe('ReDoS protection', () => {
it('should reject nested quantifiers (a+)+', () => {
const result = validateRegexPattern('(a+)+');
expect(result.valid).toBe(false);
expect(result.error).toContain('performance issues');
});
it('should reject nested quantifiers (a*)+', () => {
const result = validateRegexPattern('(a*)+');
expect(result.valid).toBe(false);
});
it('should reject nested quantifiers (a+)*', () => {
const result = validateRegexPattern('(a+)*');
expect(result.valid).toBe(false);
});
it('should reject overlapping alternation with quantifiers', () => {
const result = validateRegexPattern('(a|a)+');
expect(result.valid).toBe(false);
});
it('should reject backreferences with quantifiers', () => {
const result = validateRegexPattern('(.)\\1+');
expect(result.valid).toBe(false);
});
it('should accept safe quantifier patterns', () => {
expect(validateRegexPattern('a+')).toEqual({ valid: true });
expect(validateRegexPattern('a*b+')).toEqual({ valid: true });
expect(validateRegexPattern('[a-z]+')).toEqual({ valid: true });
expect(validateRegexPattern('\\d{1,3}')).toEqual({ valid: true });
});
});
describe('bracket balance', () => {
it('should reject unbalanced parentheses', () => {
const result = validateRegexPattern('(abc');
expect(result.valid).toBe(false);
expect(result.error).toContain('unbalanced');
});
it('should reject unbalanced brackets', () => {
const result = validateRegexPattern('[abc');
expect(result.valid).toBe(false);
expect(result.error).toContain('unbalanced');
});
it('should accept balanced patterns', () => {
expect(validateRegexPattern('(abc)')).toEqual({ valid: true });
expect(validateRegexPattern('[a-z]')).toEqual({ valid: true });
expect(validateRegexPattern('((a)(b))')).toEqual({ valid: true });
});
it('should handle escaped brackets', () => {
expect(validateRegexPattern('\\(abc\\)')).toEqual({ valid: true });
expect(validateRegexPattern('\\[test\\]')).toEqual({ valid: true });
});
});
describe('syntax validation', () => {
it('should reject invalid regex syntax', () => {
const result = validateRegexPattern('*invalid');
expect(result.valid).toBe(false);
expect(result.error).toContain('Invalid regex syntax');
});
it('should reject invalid quantifier syntax', () => {
// Note: 'a{abc}' is valid JS regex (matches 'a' followed by literal '{abc}')
// We test actual invalid syntax
const result = validateRegexPattern('a{2,1}'); // min > max is invalid
expect(result.valid).toBe(false);
expect(result.error).toContain('Invalid regex syntax');
});
});
});
describe('createSafeRegExp', () => {
it('should return RegExp for valid pattern', () => {
const regex = createSafeRegExp('test');
expect(regex).toBeInstanceOf(RegExp);
expect(regex?.test('test')).toBe(true);
});
it('should return null for invalid pattern', () => {
expect(createSafeRegExp('')).toBeNull();
expect(createSafeRegExp('(a+)+')).toBeNull();
expect(createSafeRegExp('*invalid')).toBeNull();
});
it('should use default case-insensitive flag', () => {
const regex = createSafeRegExp('test');
expect(regex?.flags).toContain('i');
expect(regex?.test('TEST')).toBe(true);
});
it('should use provided flags', () => {
const regex = createSafeRegExp('test', 'g');
expect(regex?.flags).toBe('g');
});
});
});