captchaforge 0.2.13

Automatic CAPTCHA detection and multi-strategy solving for chromiumoxide-driven headless browsers (Cloudflare Turnstile, reCAPTCHA v2/v3, hCaptcha, image grids, audio, sliders).
Documentation
//! Chaos and resilience tests.
//!
//! These tests verify graceful degradation under failure conditions.
//! They are marked `#[ignore]` because they manipulate browser state
//! and may be flaky.

use captchaforge::detect::{CaptchaInfo, DetectedCaptcha, DetectorRegistry};
use captchaforge::solver::{CaptchaSolveResult, SolveConfig, SolveMethod};

mod common;

/// Verify that `CaptchaSolveResult::failure` handles extreme time values.
#[test]
fn failure_result_with_max_u64_time() {
    let result = CaptchaSolveResult::failure(SolveMethod::BehavioralBypass, u64::MAX);
    assert!(!result.success);
    assert_eq!(result.time_ms, u64::MAX);
}

/// Verify that `CaptchaSolveResult::unsolved` handles extreme time values.
#[test]
fn unsolved_result_with_max_u64_time() {
    let result = CaptchaSolveResult::unsolved(u64::MAX, None);
    assert!(!result.success);
    assert_eq!(result.time_ms, u64::MAX);
}

/// Verify that `SolveConfig` can be constructed with zero values.
#[test]
fn solve_config_zero_values() {
    let cfg = SolveConfig {
        checkbox_poll_interval_ms: 0,
        checkbox_max_attempts: 0,
        token_poll_interval_ms: 0,
        token_max_attempts: 0,
        audio_button_delay_ms: 0,
        audio_submit_delay_ms: 0,
        vlm_http_timeout_ms: 0,
        client_http_timeout_ms: 0,
    };
    // Should not panic.
    assert_eq!(cfg.checkbox_poll_interval_ms, 0);
}

/// Verify that empty strings in result fields don't panic.
#[test]
fn solve_result_empty_solution() {
    let result = CaptchaSolveResult {
        solution: String::new(),
        confidence: 0.0,
        method: SolveMethod::BehavioralBypass,
        time_ms: 0,
        success: true,
        screenshot: None,
        cookies: Vec::new(),
        verified_outcome: None,
    };
    assert!(result.success);
    assert!(result.solution.is_empty());
}

/// Verify that very long solution strings are handled.
#[test]
fn solve_result_very_long_solution() {
    let long = "x".repeat(1_000_000);
    let result = CaptchaSolveResult {
        solution: long.clone(),
        confidence: 1.0,
        method: SolveMethod::BehavioralBypass,
        time_ms: 1,
        success: true,
        screenshot: None,
        cookies: Vec::new(),
        verified_outcome: None,
    };
    assert_eq!(result.solution.len(), 1_000_000);
}

/// Verify serde rejects unknown enum variants.
#[test]
fn serde_rejects_unknown_captcha_kind() {
    let bad = r#"{"kind": "unknown_type", "site_key": null}"#;
    let result: Result<captchaforge::detect::CaptchaInfo, _> = serde_json::from_str(bad);
    assert!(result.is_err());
}

/// Verify that NaN confidence doesn't serialize to invalid JSON.
#[test]
fn serde_nan_confidence() {
    let result = CaptchaSolveResult {
        solution: "test".to_string(),
        confidence: f32::NAN,
        method: SolveMethod::BehavioralBypass,
        time_ms: 100,
        success: false,
        screenshot: None,
        cookies: Vec::new(),
        verified_outcome: None,
    };
    let json = serde_json::to_string(&result);
    assert!(json.is_ok());
}

/// Verify that infinite confidence doesn't serialize to invalid JSON.
#[test]
fn serde_infinite_confidence() {
    let result = CaptchaSolveResult {
        solution: "test".to_string(),
        confidence: f32::INFINITY,
        method: SolveMethod::BehavioralBypass,
        time_ms: 100,
        success: false,
        screenshot: None,
        cookies: Vec::new(),
        verified_outcome: None,
    };
    let json = serde_json::to_string(&result);
    assert!(json.is_ok());
}

/// Verify that DetectorRegistry::detect returns None on a page with no captcha.
#[tokio::test]
async fn detector_registry_detect_returns_none_for_clean_page() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let registry = DetectorRegistry::new();
    let result = registry.detect(&page).await.unwrap();
    assert!(result.is_none());
    browser.close().await.unwrap();
}

/// Verify that DetectedCaptcha serde handles very long container_selector strings (1KB).
#[test]
fn detected_captcha_serde_with_1kb_container_selector() {
    let long_selector = "a".repeat(1024);
    let info = CaptchaInfo {
        kind: DetectedCaptcha::CanvasCaptcha,
        site_key: None,
        page_url: "https://example.com".to_string(),
        container_selector: Some(long_selector.clone()),
    };
    let json = serde_json::to_string(&info).unwrap();
    let back: CaptchaInfo = serde_json::from_str(&json).unwrap();
    assert_eq!(back.container_selector, Some(long_selector));
}