146 lines
5.3 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|