use super::*;
#[test]
fn parses_minimal_rule() {
let toml = r#"
[[provider]]
name = "aws_waf"
priority = 12
[provider.triggers]
selectors = [".aws-waf-token"]
"#;
let set = RuleSet::parse(toml).unwrap();
assert_eq!(set.providers.len(), 1);
assert_eq!(set.providers[0].name, "aws_waf");
assert_eq!(set.providers[0].priority, 12);
assert_eq!(set.providers[0].triggers.selectors, vec![".aws-waf-token"]);
}
#[test]
fn parses_title_and_cookie_triggers() {
let toml = r#"
[[provider]]
name = "ddos_screen"
priority = 50
[provider.triggers]
title_contains = ["DDoS protection", "Checking your browser"]
cookie_names = ["__cf_bm", "_dd_jwt"]
"#;
let set = RuleSet::parse(toml).unwrap();
assert_eq!(set.providers[0].triggers.title_contains.len(), 2);
assert_eq!(set.providers[0].triggers.cookie_names.len(), 2);
let det: RuleDetector = set.providers[0].clone().into();
let js = det.js();
assert!(
js.contains("document.title"),
"title trigger missing from JS: {js}"
);
assert!(
js.contains("document.cookie"),
"cookie trigger missing from JS: {js}"
);
assert!(js.contains("ddos protection"));
}
#[test]
fn parses_multiple_rules() {
let toml = r#"
[[provider]]
name = "aws_waf"
priority = 12
[provider.triggers]
selectors = [".aws-waf-token"]
window_globals = ["awsWafCaptcha"]
[[provider]]
name = "datadome"
priority = 13
[provider.triggers]
selectors = ["[data-datadome]"]
script_src_contains = ["js.datadome.co"]
"#;
let set = RuleSet::parse(toml).unwrap();
assert_eq!(set.providers.len(), 2);
assert_eq!(set.providers[1].name, "datadome");
}
#[test]
fn empty_rules_block_yields_no_providers() {
let set = RuleSet::parse("").unwrap();
assert!(set.providers.is_empty());
}
#[test]
fn missing_required_field_is_an_error() {
let toml = r#"
[[provider]]
priority = 10
[provider.triggers]
"#;
assert!(RuleSet::parse(toml).is_err());
}
#[test]
fn build_probe_js_escapes_selector_with_quotes() {
let rule = ProviderRule {
name: "evil".into(),
priority: 99,
solver_methods: default_solver_methods(),
solver_names: Vec::new(),
triggers: Triggers {
selectors: vec!["[data-x=\"a\"]".into()],
window_globals: vec![],
script_src_contains: vec![],
title_contains: vec![],
cookie_names: vec![],
},
};
let js = build_probe_js(&rule);
assert!(
js.contains("\\\""),
"expected an escaped quote in generated JS; got: {js}"
);
}
#[test]
fn build_probe_js_handles_all_three_trigger_types() {
let rule = ProviderRule {
name: "aws_waf".into(),
priority: 12,
solver_methods: default_solver_methods(),
solver_names: Vec::new(),
triggers: Triggers {
selectors: vec![".aws-waf-token".into()],
window_globals: vec!["awsWafCaptcha".into()],
script_src_contains: vec!["awswafcaptcha.com".into()],
title_contains: vec![],
cookie_names: vec![],
},
};
let js = build_probe_js(&rule);
assert!(js.contains("querySelector(\".aws-waf-token\")"));
assert!(js.contains("typeof window[\"awsWafCaptcha\"]"));
assert!(js.contains("document.querySelectorAll('script[src]')"));
assert!(js.contains("\"awswafcaptcha.com\""));
assert!(js.contains("\"custom:aws_waf\""));
}
#[test]
fn rule_detector_round_trips_name_and_priority() {
let rule = ProviderRule {
name: "datadome".into(),
priority: 13,
solver_methods: default_solver_methods(),
solver_names: Vec::new(),
triggers: Triggers::default(),
};
let det = RuleDetector::from(rule);
assert_eq!(<RuleDetector as Detector>::name(&det), "datadome");
assert_eq!(det.priority(), 13);
}
#[test]
fn built_in_rules_parse_and_cover_expected_vendors() {
let set = built_in_rules().expect("bundled community.toml parses");
let names: Vec<&str> = set.providers.iter().map(|p| p.name.as_str()).collect();
for expected in [
"datadome",
"akamai_bot_manager",
"perimeterx_human",
"arkose_funcaptcha",
"geetest_v3",
"geetest_v4",
"aws_waf_captcha",
"friendly_captcha",
"mtcaptcha",
"wp_math_captcha",
] {
assert!(
names.contains(&expected),
"bundled community.toml is missing rule: {expected}",
);
}
}
#[test]
fn built_in_rules_priorities_dont_collide_with_each_other() {
let set = built_in_rules().unwrap();
let mut prios: Vec<i32> = set.providers.iter().map(|p| p.priority).collect();
prios.sort_unstable();
let mut deduped = prios.clone();
deduped.dedup();
assert_eq!(prios, deduped, "priorities must be unique within the pack");
}
#[test]
fn built_in_rules_become_detectors_with_valid_js_probes() {
let set = built_in_rules().unwrap();
assert!(set.providers.len() >= 10);
for rule in set.providers {
let det = RuleDetector::from(rule);
let js = det.js();
let name = <RuleDetector as Detector>::name(&det);
assert!(js.starts_with("(function()"), "{name}: bad prefix");
assert!(js.ends_with("})()"), "{name}: bad suffix");
assert!(js.contains("custom:"), "{name}: missing custom-kind tag");
}
}
#[test]
fn into_detectors_keeps_insertion_order() {
let toml = r#"
[[provider]]
name = "a"
priority = 100
[provider.triggers]
selectors = [".a"]
[[provider]]
name = "b"
priority = 5
[provider.triggers]
selectors = [".b"]
"#;
let dets = RuleSet::parse(toml).unwrap().into_detectors();
assert_eq!(dets.len(), 2);
assert_eq!(dets[0].name(), "a");
assert_eq!(dets[1].name(), "b");
}