captchaforge 0.2.36

[DO NOT USE — UNDER ACTIVE DEVELOPMENT, NOT PRODUCTION-READY] Captcha solver scaffolding for chromiumoxide-driven browsers. The architecture is in place (vendor solvers, retry-loop iframe walking, VLM provider abstraction, real-WAF bench harness) but the live-vendor success rate is still 0% — Cloudflare Turnstile / hCaptcha / reCAPTCHA detect us at a TLS / CDP fingerprint layer that no flag-based stealth has cleared. Watch the repo; do not depend on this for any real workload.
Documentation
//! Smoke tests for fuzz target logic (without requiring cargo-fuzz).

use captchaforge::solver::{CaptchaType, PatternStore, SolveConfig, SolveMethod};

#[test]
fn fuzz_detect_arbitrary_html() {
    let htmls = [
        b"<html><body></body></html>" as &[u8],
        b"{\"kind\": \"turnstile\", \"site_key\": \"abc\"}",
        b"{\"kind\": \"recaptcha_v2\", \"site_key\": null}",
        b"garbage data here",
        b"",
        &[0u8, 159, 146, 150], // invalid UTF-8
    ];
    for data in &htmls {
        if let Ok(html) = std::str::from_utf8(data) {
            let _ = captchaforge::detect::DETECT_JS.len();
            if let Ok(val) = serde_json::from_str::<serde_json::Value>(html) {
                let kind_str = val["kind"].as_str().unwrap_or("none");
                let _ = matches!(
                    kind_str,
                    "turnstile" | "recaptcha_v2" | "recaptcha_v3" | "hcaptcha" | "image" | "audio"
                );
            }
        }
    }
}

#[test]
fn fuzz_solve_config_arbitrary_bytes() {
    let inputs: Vec<Vec<u8>> = vec![
        vec![0; 64],
        vec![255; 64],
        (0..64).collect(),
        vec![
            1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 0,
            0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0,
        ],
    ];
    for data in inputs {
        if data.len() >= 64 {
            let cfg = SolveConfig {
                checkbox_poll_interval_ms: u64::from_le_bytes(data[0..8].try_into().unwrap()),
                checkbox_max_attempts: u32::from_le_bytes(data[8..12].try_into().unwrap()),
                token_poll_interval_ms: u64::from_le_bytes(data[12..20].try_into().unwrap()),
                token_max_attempts: u32::from_le_bytes(data[20..24].try_into().unwrap()),
                audio_button_delay_ms: u64::from_le_bytes(data[24..32].try_into().unwrap()),
                audio_submit_delay_ms: u64::from_le_bytes(data[32..40].try_into().unwrap()),
                vlm_http_timeout_ms: u64::from_le_bytes(data[40..48].try_into().unwrap()),
                client_http_timeout_ms: u64::from_le_bytes(data[48..56].try_into().unwrap()),
            };
            let _ = cfg.checkbox_poll_interval_ms;
            let _ = cfg.checkbox_max_attempts;
        }
    }
}

#[test]
fn fuzz_selector_arbitrary_strings() {
    let selectors = ["#id", ".class", "[data-a='b']", "", "\"", "\\", "\n", "\0"];
    for selector in &selectors {
        let escaped = selector.replace('"', "\\\"");
        let _js = format!(r#"document.querySelector("{}")"#, escaped);
    }
}

#[test]
fn fuzz_pattern_key_arbitrary_strings() {
    let keys = ["", "a", "example.com", "foo/bar", "\n", "unicode_\u{1F600}"];
    for s in &keys {
        let store = PatternStore::default();
        let captcha_type = CaptchaType::Custom(s.to_string());
        store.record(
            "example.com",
            &captcha_type,
            true,
            100,
            SolveMethod::BehavioralBypass,
        );
        let _ = store.best_method("example.com", &captcha_type);
        let _ = store.all_patterns();
    }
}

#[test]
fn fuzz_pattern_key_with_many_types() {
    let store = PatternStore::default();
    for i in 0..100 {
        let ct = CaptchaType::Custom(format!("type_{}", i));
        store.record(
            "example.com",
            &ct,
            i % 2 == 0,
            i as u64 * 10,
            SolveMethod::BehavioralBypass,
        );
    }
    assert_eq!(store.all_patterns().len(), 100);
}