use perl_regex::{RegexAnalyzer, RegexError, RegexValidator};
#[test]
fn scenario_safe_pattern_passes_validation() -> Result<(), Box<dyn std::error::Error>> {
let validator = RegexValidator::new();
let pattern = r"^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$";
let result = validator.validate(pattern, 0);
assert!(result.is_ok());
Ok(())
}
#[test]
fn scenario_excessive_unicode_properties_returns_offset_error()
-> Result<(), Box<dyn std::error::Error>> {
let validator = RegexValidator::new();
let pattern: String = (0..51).map(|_| r"\p{L}").collect::<Vec<_>>().join("");
let result = validator.validate(&pattern, 120);
match result {
Err(RegexError::Syntax { message, offset }) => {
assert!(message.contains("Too many Unicode properties"));
assert!(offset >= 120);
}
Ok(()) => return Err("expected Unicode property limit error".into()),
}
Ok(())
}
#[test]
fn scenario_nested_quantifier_fails_validation() -> Result<(), Box<dyn std::error::Error>> {
let validator = RegexValidator::new();
let pattern = "(a+)+";
let validation_result = validator.validate(pattern, 0);
let advisory_detected = validator.detect_nested_quantifiers(pattern);
assert!(validation_result.is_err());
assert!(advisory_detected);
Ok(())
}
#[test]
fn scenario_embedded_code_is_detected() -> Result<(), Box<dyn std::error::Error>> {
let validator = RegexValidator::new();
let pattern = r"^(\w+)(?{ die 'unsafe' })$";
let executes_code = validator.detects_code_execution(pattern);
assert!(executes_code);
Ok(())
}
#[test]
fn scenario_named_capture_indexes_follow_capture_order() -> Result<(), Box<dyn std::error::Error>> {
let pattern = r"(prefix)(?<id>\d+)(?:-)(?<suffix>\w+)";
let captures = RegexAnalyzer::extract_named_captures(pattern);
assert_eq!(captures.len(), 2);
assert_eq!(captures[0].name, "id");
assert_eq!(captures[0].index, 2);
assert_eq!(captures[1].name, "suffix");
assert_eq!(captures[1].index, 3);
Ok(())
}
#[test]
fn scenario_hover_text_summarizes_captures_and_modifiers() -> Result<(), Box<dyn std::error::Error>>
{
let pattern = r"(?<word>\w+)";
let modifiers = "gi";
let hover = RegexAnalyzer::hover_text_for_regex(pattern, modifiers);
assert!(hover.contains("Named captures"));
assert!(hover.contains("word"));
assert!(hover.contains("case-insensitive"));
assert!(hover.contains("global"));
Ok(())
}
#[test]
fn scenario_hover_text_lists_extended_perl_modifiers() -> Result<(), Box<dyn std::error::Error>> {
let hover = RegexAnalyzer::hover_text_for_regex(r"(?<word>\w+)", "anu");
assert!(hover.contains("ASCII-safe character classes"));
assert!(hover.contains("non-capturing by default"));
assert!(hover.contains("Unicode character semantics"));
Ok(())
}
#[test]
fn scenario_hover_text_deduplicates_and_reports_unknown_modifiers()
-> Result<(), Box<dyn std::error::Error>> {
let hover = RegexAnalyzer::hover_text_for_regex(r"\w+", "iizzz");
assert_eq!(hover.matches("case-insensitive matching").count(), 1);
assert!(hover.contains("Unknown modifiers: `z`"));
Ok(())
}
#[test]
fn scenario_hover_text_covers_substitution_specific_modifiers()
-> Result<(), Box<dyn std::error::Error>> {
let hover = RegexAnalyzer::hover_text_for_regex(r"foo", "re");
assert!(hover.contains("non-destructive substitution result"), "expected /r description");
assert!(hover.contains("evaluate replacement as code"), "expected /e description");
Ok(())
}
#[test]
fn scenario_hover_text_deduplicates_repeated_known_modifier()
-> Result<(), Box<dyn std::error::Error>> {
let hover = RegexAnalyzer::hover_text_for_regex(r"\s+", "xx");
assert_eq!(
hover.matches("extended mode: whitespace and comments allowed").count(),
1,
"duplicate /x should produce exactly one note"
);
assert!(
!hover.contains("Unknown modifiers"),
"repeated known modifier must not appear in unknown section"
);
Ok(())
}
#[test]
fn scenario_quoted_regex_literals_do_not_trigger_safety_findings()
-> Result<(), Box<dyn std::error::Error>> {
let validator = RegexValidator::new();
let pattern = r"\Q(?{ die 'literal' })(a+)+\p{L}(?<name>literal)\E";
let validation_result = validator.validate(pattern, 30);
let executes_code = validator.detects_code_execution(pattern);
let nested_quantifier = validator.detect_nested_quantifiers(pattern);
let captures = RegexAnalyzer::extract_named_captures(pattern);
assert!(validation_result.is_ok());
assert!(!executes_code);
assert!(!nested_quantifier);
assert!(captures.is_empty());
Ok(())
}
#[test]
fn scenario_named_capture_subpattern_preserves_quoted_literal_parentheses()
-> Result<(), Box<dyn std::error::Error>> {
let pattern = r"(?<literal>\Q)\E+)";
let captures = RegexAnalyzer::extract_named_captures(pattern);
assert_eq!(captures.len(), 1);
assert_eq!(captures[0].name, "literal");
assert_eq!(captures[0].pattern, r"\Q)\E+");
Ok(())
}