use perl_regex::{CaptureGroup, RegexAnalyzer, RegexValidator, validator::RegexFinding};
use perl_regex::prelude::*;
#[test]
fn prelude_reexports_validator() -> Result<(), Box<dyn std::error::Error>> {
let v: RegexValidator = RegexValidator::new();
v.validate("hello", 0)?;
Ok(())
}
#[test]
fn prelude_reexports_analyzer() -> Result<(), Box<dyn std::error::Error>> {
let caps = RegexAnalyzer::extract_named_captures(r"(?<x>\d+)");
assert_eq!(caps.len(), 1);
Ok(())
}
#[test]
fn prelude_reexports_regex_error() -> Result<(), Box<dyn std::error::Error>> {
let err = RegexError::syntax("test", 0);
assert!(err.to_string().contains("test"));
Ok(())
}
#[test]
fn prelude_reexports_capture_group() -> Result<(), Box<dyn std::error::Error>> {
let cg = CaptureGroup { name: "x".to_string(), index: 1, pattern: "\\d+".to_string() };
assert_eq!(cg.name, "x");
Ok(())
}
#[test]
fn regex_finding_debug_contains_offset_and_message() -> Result<(), Box<dyn std::error::Error>> {
let v = RegexValidator::new();
let finding = v.find_code_execution("(?{ run })", 0).ok_or("expected finding")?;
let dbg = format!("{finding:?}");
assert!(dbg.contains("offset"), "debug output missing 'offset': {dbg}");
assert!(dbg.contains("message"), "debug output missing 'message': {dbg}");
Ok(())
}
#[test]
fn regex_finding_clone_produces_equal_value() -> Result<(), Box<dyn std::error::Error>> {
let v = RegexValidator::new();
let original = v.find_code_execution("(?{ run })", 5).ok_or("expected finding")?;
let cloned = original.clone();
assert_eq!(original, cloned);
Ok(())
}
#[test]
fn regex_finding_partial_eq_same_values() -> Result<(), Box<dyn std::error::Error>> {
let f1 = RegexFinding { offset: 3, message: "bad" };
let f2 = RegexFinding { offset: 3, message: "bad" };
assert_eq!(f1, f2);
Ok(())
}
#[test]
fn regex_finding_partial_eq_different_offset() -> Result<(), Box<dyn std::error::Error>> {
let f1 = RegexFinding { offset: 3, message: "bad" };
let f2 = RegexFinding { offset: 4, message: "bad" };
assert_ne!(f1, f2);
Ok(())
}
#[test]
fn regex_finding_partial_eq_different_message() -> Result<(), Box<dyn std::error::Error>> {
let f1 = RegexFinding { offset: 3, message: "bad" };
let f2 = RegexFinding { offset: 3, message: "worse" };
assert_ne!(f1, f2);
Ok(())
}
#[test]
fn find_code_execution_returns_none_for_safe_pattern() -> Result<(), Box<dyn std::error::Error>> {
let v = RegexValidator::new();
let result = v.find_code_execution("hello world", 0);
assert!(result.is_none(), "expected None for safe pattern");
Ok(())
}
#[test]
fn find_code_execution_returns_none_for_empty_pattern() -> Result<(), Box<dyn std::error::Error>> {
let v = RegexValidator::new();
let result = v.find_code_execution("", 0);
assert!(result.is_none());
Ok(())
}
#[test]
fn find_code_execution_returns_none_for_non_capturing_group()
-> Result<(), Box<dyn std::error::Error>> {
let v = RegexValidator::new();
let result = v.find_code_execution("(?:safe)", 0);
assert!(result.is_none());
Ok(())
}
#[test]
fn find_nested_quantifier_returns_none_for_safe_pattern() -> Result<(), Box<dyn std::error::Error>>
{
let v = RegexValidator::new();
let result = v.find_nested_quantifier("(abc)+", 0);
assert!(result.is_none(), "expected None for pattern without inner quantifier");
Ok(())
}
#[test]
fn find_nested_quantifier_returns_none_for_empty_pattern() -> Result<(), Box<dyn std::error::Error>>
{
let v = RegexValidator::new();
let result = v.find_nested_quantifier("", 0);
assert!(result.is_none());
Ok(())
}
#[test]
fn find_nested_quantifier_returns_none_for_simple_literal() -> Result<(), Box<dyn std::error::Error>>
{
let v = RegexValidator::new();
let result = v.find_nested_quantifier("a+b*c?", 0);
assert!(result.is_none());
Ok(())
}
#[test]
fn extract_named_captures_empty_angle_bracket_name_ignored()
-> Result<(), Box<dyn std::error::Error>> {
let caps = RegexAnalyzer::extract_named_captures("(?<>\\d+)");
assert!(
caps.is_empty(),
"empty angle-bracket name must not produce a CaptureGroup, got: {caps:?}"
);
Ok(())
}
#[test]
fn extract_named_captures_empty_angle_name_does_not_affect_subsequent_capture_index()
-> Result<(), Box<dyn std::error::Error>> {
let caps = RegexAnalyzer::extract_named_captures(r"(?<>\w+)(?<real>\d+)");
assert_eq!(caps.len(), 1, "only the real named capture should appear");
assert_eq!(caps[0].name, "real");
Ok(())
}
#[test]
fn extract_named_captures_unclosed_angle_bracket_ignored() -> Result<(), Box<dyn std::error::Error>>
{
let caps = RegexAnalyzer::extract_named_captures("(?<unclosed\\d+)");
assert!(
caps.is_empty(),
"unclosed angle-bracket capture must not produce a CaptureGroup, got: {caps:?}"
);
Ok(())
}
#[test]
fn capture_group_partial_eq_same_values() -> Result<(), Box<dyn std::error::Error>> {
let a = CaptureGroup { name: "id".to_string(), index: 1, pattern: "\\d+".to_string() };
let b = CaptureGroup { name: "id".to_string(), index: 1, pattern: "\\d+".to_string() };
assert_eq!(a, b);
Ok(())
}
#[test]
fn capture_group_partial_eq_different_name() -> Result<(), Box<dyn std::error::Error>> {
let a = CaptureGroup { name: "foo".to_string(), index: 1, pattern: "\\d+".to_string() };
let b = CaptureGroup { name: "bar".to_string(), index: 1, pattern: "\\d+".to_string() };
assert_ne!(a, b);
Ok(())
}
#[test]
fn capture_group_partial_eq_different_index() -> Result<(), Box<dyn std::error::Error>> {
let a = CaptureGroup { name: "x".to_string(), index: 1, pattern: "\\d+".to_string() };
let b = CaptureGroup { name: "x".to_string(), index: 2, pattern: "\\d+".to_string() };
assert_ne!(a, b);
Ok(())
}
#[test]
fn capture_group_partial_eq_different_pattern() -> Result<(), Box<dyn std::error::Error>> {
let a = CaptureGroup { name: "x".to_string(), index: 1, pattern: "\\d+".to_string() };
let b = CaptureGroup { name: "x".to_string(), index: 1, pattern: "\\w+".to_string() };
assert_ne!(a, b);
Ok(())
}
#[test]
fn extract_named_captures_subpattern_with_nested_group() -> Result<(), Box<dyn std::error::Error>> {
let caps = RegexAnalyzer::extract_named_captures(r"(?<outer>(inner)\d+)");
assert_eq!(caps.len(), 1, "one named capture expected");
assert_eq!(caps[0].name, "outer");
assert!(
caps[0].pattern.contains("inner"),
"sub-pattern should include the nested group content, got: {:?}",
caps[0].pattern
);
Ok(())
}
#[test]
fn extract_named_captures_subpattern_with_char_class_containing_paren()
-> Result<(), Box<dyn std::error::Error>> {
let caps = RegexAnalyzer::extract_named_captures(r"(?<tok>[^)]+)rest");
assert_eq!(caps.len(), 1);
assert_eq!(caps[0].name, "tok");
assert_eq!(caps[0].pattern, "[^)]+");
Ok(())
}
#[test]
fn validate_code_execution_finding_has_message_field() -> Result<(), Box<dyn std::error::Error>> {
let v = RegexValidator::new();
let finding = v.find_code_execution("abc(?{ run })", 0).ok_or("expected finding")?;
assert!(
finding.message.contains("Embedded code execution"),
"unexpected message: {}",
finding.message
);
Ok(())
}
#[test]
fn validate_deferred_code_execution_finding_has_deferred_message()
-> Result<(), Box<dyn std::error::Error>> {
let v = RegexValidator::new();
let finding = v.find_code_execution("abc(??{ run })", 0).ok_or("expected deferred finding")?;
assert!(
finding.message.contains("Deferred embedded code execution"),
"unexpected message: {}",
finding.message
);
Ok(())
}
#[test]
fn validate_nested_quantifier_finding_has_message_field() -> Result<(), Box<dyn std::error::Error>>
{
let v = RegexValidator::new();
let finding = v.find_nested_quantifier("(a+)+", 0).ok_or("expected finding")?;
assert!(
finding.message.contains("Nested quantifiers"),
"unexpected message: {}",
finding.message
);
Ok(())
}