use super::*;
#[test]
fn legacy_detect_js_contains_turnstile() {
assert!(DETECT_JS.contains("cf-turnstile"));
assert!(DETECT_JS.contains("challenges.cloudflare.com"));
}
#[test]
fn legacy_detect_js_contains_recaptcha() {
assert!(DETECT_JS.contains("g-recaptcha"));
assert!(DETECT_JS.contains("google.com/recaptcha"));
}
#[test]
fn legacy_detect_js_contains_hcaptcha() {
assert!(DETECT_JS.contains("h-captcha"));
assert!(DETECT_JS.contains("hcaptcha.com"));
}
#[test]
fn legacy_detect_js_contains_challenge_page() {
assert!(DETECT_JS.contains("Just a moment"));
assert!(DETECT_JS.contains("challenge-running"));
}
#[test]
fn legacy_detect_js_contains_image_captcha() {
assert!(DETECT_JS.contains("img[src*=\"captcha\"]"));
assert!(DETECT_JS.contains("'image'"));
}
#[test]
fn legacy_detect_js_contains_audio_captcha() {
assert!(DETECT_JS.contains("audio[src*=\"captcha\"]"));
assert!(DETECT_JS.contains("'audio'"));
}
#[test]
fn legacy_detect_js_recaptcha_prefers_g_recaptcha_class() {
assert!(DETECT_JS.contains("document.querySelector('.g-recaptcha')"));
}
#[test]
fn legacy_detect_js_recaptcha_fallback_excludes_other_providers() {
assert!(DETECT_JS.contains("!fallback.hasAttribute('data-hcaptcha-sitekey')"));
assert!(DETECT_JS.contains("!fallback.hasAttribute('data-turnstile-sitekey')"));
}
#[test]
fn legacy_detect_js_hcaptcha_fallback_excludes_other_providers() {
assert!(DETECT_JS.contains("!fallback.hasAttribute('data-turnstile-sitekey')"));
assert!(DETECT_JS.contains("!fallback.matches('.g-recaptcha')"));
}
#[test]
fn captcha_info_serializes() {
let info = CaptchaInfo {
kind: DetectedCaptcha::Turnstile,
site_key: Some("abc123".into()),
page_url: "https://example.com".into(),
container_selector: Some(".cf-turnstile".into()),
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("\"kind\":\"turnstile\""));
assert!(json.contains("\"site_key\":\"abc123\""));
}
#[test]
fn is_captcha_false_for_none() {
let info = CaptchaInfo {
kind: DetectedCaptcha::None,
site_key: None,
page_url: String::new(),
container_selector: None,
};
assert!(!is_captcha(&info));
}
#[test]
fn is_captcha_true_for_every_non_none_variant() {
for variant in [
DetectedCaptcha::Turnstile,
DetectedCaptcha::RecaptchaV2,
DetectedCaptcha::RecaptchaV3,
DetectedCaptcha::HCaptcha,
DetectedCaptcha::ImageCaptcha,
DetectedCaptcha::AudioCaptcha,
DetectedCaptcha::PowCaptcha,
DetectedCaptcha::SliderCaptcha,
DetectedCaptcha::CanvasCaptcha,
DetectedCaptcha::ShadowDomCaptcha,
DetectedCaptcha::MultiStepCaptcha,
] {
let info = CaptchaInfo {
kind: variant.clone(),
site_key: None,
page_url: String::new(),
container_selector: None,
};
assert!(is_captcha(&info), "variant {variant:?} should be a captcha");
}
}
#[test]
fn solver_kind_str_maps_all_variants() {
assert_eq!(
solver_kind_str(&DetectedCaptcha::Turnstile),
Some("turnstile")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::RecaptchaV2),
Some("recaptcha_v2")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::RecaptchaV3),
Some("recaptcha_v3")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::HCaptcha),
Some("hcaptcha")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::ImageCaptcha),
Some("image_captcha")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::AudioCaptcha),
Some("audio_captcha")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::PowCaptcha),
Some("pow_captcha")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::SliderCaptcha),
Some("slider_captcha")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::CanvasCaptcha),
Some("canvas_captcha")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::ShadowDomCaptcha),
Some("shadow_dom_captcha")
);
assert_eq!(
solver_kind_str(&DetectedCaptcha::MultiStepCaptcha),
Some("multi_step_captcha")
);
assert_eq!(solver_kind_str(&DetectedCaptcha::None), None);
}
#[test]
fn detected_captcha_serde_roundtrip_all_variants() {
for variant in [
DetectedCaptcha::Turnstile,
DetectedCaptcha::RecaptchaV2,
DetectedCaptcha::RecaptchaV3,
DetectedCaptcha::HCaptcha,
DetectedCaptcha::ImageCaptcha,
DetectedCaptcha::AudioCaptcha,
DetectedCaptcha::PowCaptcha,
DetectedCaptcha::SliderCaptcha,
DetectedCaptcha::CanvasCaptcha,
DetectedCaptcha::ShadowDomCaptcha,
DetectedCaptcha::MultiStepCaptcha,
DetectedCaptcha::None,
] {
let json = serde_json::to_string(&variant).unwrap();
let back: DetectedCaptcha = serde_json::from_str(&json).unwrap();
assert_eq!(variant, back, "roundtrip failed for {:?}", variant);
}
}
#[test]
fn captcha_info_serde_roundtrip() {
let info = CaptchaInfo {
kind: DetectedCaptcha::Turnstile,
site_key: Some("key".into()),
page_url: "https://example.com".into(),
container_selector: Some(".cf".into()),
};
let json = serde_json::to_string(&info).unwrap();
let back: CaptchaInfo = serde_json::from_str(&json).unwrap();
assert_eq!(info.kind, back.kind);
assert_eq!(info.site_key, back.site_key);
assert_eq!(info.page_url, back.page_url);
assert_eq!(info.container_selector, back.container_selector);
}
#[test]
fn captcha_info_serde_with_null_site_key() {
let info = CaptchaInfo {
kind: DetectedCaptcha::None,
site_key: None,
page_url: "https://example.com".into(),
container_selector: None,
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("null"));
let back: CaptchaInfo = serde_json::from_str(&json).unwrap();
assert!(back.site_key.is_none());
}
#[test]
fn challenge_page_detector_name_and_priority() {
let d = ChallengePageDetector;
assert_eq!(d.name(), "challenge_page");
assert_eq!(d.priority(), 5);
}
#[test]
fn turnstile_detector_name_and_priority() {
let d = TurnstileDetector;
assert_eq!(d.name(), "turnstile");
assert_eq!(d.priority(), 10);
}
#[test]
fn recaptcha_detector_name_and_priority() {
let d = RecaptchaDetector;
assert_eq!(d.name(), "recaptcha");
assert_eq!(d.priority(), 20);
}
#[test]
fn hcaptcha_detector_name_and_priority() {
let d = HCaptchaDetector;
assert_eq!(d.name(), "hcaptcha");
assert_eq!(d.priority(), 30);
}
#[test]
fn image_captcha_detector_name_and_priority() {
let d = ImageCaptchaDetector;
assert_eq!(d.name(), "image_captcha");
assert_eq!(d.priority(), 40);
}
#[test]
fn audio_captcha_detector_name_and_priority() {
let d = AudioCaptchaDetector;
assert_eq!(d.name(), "audio_captcha");
assert_eq!(d.priority(), 50);
}
#[test]
fn pow_captcha_detector_name_and_priority() {
let d = PowCaptchaDetector;
assert_eq!(d.name(), "pow_captcha");
assert_eq!(d.priority(), 60);
}
#[test]
fn canvas_captcha_detector_name_and_priority() {
let d = CanvasCaptchaDetector;
assert_eq!(d.name(), "canvas_captcha");
assert_eq!(d.priority(), 65);
}
#[test]
fn slider_captcha_detector_name_and_priority() {
let d = SliderCaptchaDetector;
assert_eq!(d.name(), "slider_captcha");
assert_eq!(d.priority(), 60);
}
#[test]
fn multi_step_captcha_detector_name_and_priority() {
let d = MultiStepCaptchaDetector;
assert_eq!(d.name(), "multi_step_captcha");
assert_eq!(d.priority(), 78);
}
#[test]
fn shadow_dom_detector_name_and_priority() {
let d = ShadowDomDetector;
assert_eq!(d.name(), "shadow_dom");
assert_eq!(d.priority(), 90);
}
#[test]
fn detector_registry_runs_in_priority_order() {
let registry = DetectorRegistry::new();
let priorities: Vec<i32> = registry.detectors.iter().map(|d| d.priority()).collect();
let mut sorted = priorities.clone();
sorted.sort_unstable();
assert_eq!(priorities, sorted);
}
#[test]
fn detector_registry_contains_all_detectors() {
let registry = DetectorRegistry::new();
let names: Vec<&str> = registry.detectors.iter().map(|d| d.name()).collect();
assert!(names.contains(&"challenge_page"));
assert!(names.contains(&"turnstile"));
assert!(names.contains(&"recaptcha"));
assert!(names.contains(&"hcaptcha"));
assert!(names.contains(&"image_captcha"));
assert!(names.contains(&"audio_captcha"));
assert!(names.contains(&"pow_captcha"));
assert!(names.contains(&"slider_captcha"));
assert!(names.contains(&"canvas_captcha"));
assert!(names.contains(&"multi_step_captcha"));
assert!(names.contains(&"shadow_dom"));
}
#[test]
fn parse_detection_result_returns_none_for_null() {
assert!(parse_detection_result(serde_json::Value::Null).is_none());
}
#[test]
fn parse_detection_result_returns_none_for_unknown_kind() {
let val = serde_json::json!({"kind": "unknown_xyz", "site_key": null, "container": null});
assert!(parse_detection_result(val).is_none());
}
#[test]
fn parse_detection_result_parses_all_kinds() {
let cases = [
("turnstile", DetectedCaptcha::Turnstile),
("recaptcha_v2", DetectedCaptcha::RecaptchaV2),
("recaptcha_v3", DetectedCaptcha::RecaptchaV3),
("hcaptcha", DetectedCaptcha::HCaptcha),
("image", DetectedCaptcha::ImageCaptcha),
("audio", DetectedCaptcha::AudioCaptcha),
("pow", DetectedCaptcha::PowCaptcha),
("slider", DetectedCaptcha::SliderCaptcha),
("canvas", DetectedCaptcha::CanvasCaptcha),
("shadow_dom", DetectedCaptcha::ShadowDomCaptcha),
("multi_step", DetectedCaptcha::MultiStepCaptcha),
];
for (kind_str, expected) in cases {
let val = serde_json::json!({
"kind": kind_str,
"site_key": "test_key",
"container": ".test"
});
let info = parse_detection_result(val).unwrap();
assert_eq!(info.kind, expected, "kind mismatch for {}", kind_str);
assert_eq!(info.site_key, Some("test_key".to_string()));
assert_eq!(info.container_selector, Some(".test".to_string()));
}
}