captchaforge 0.2.39

Captcha detection and solving for Firefox and BiDi-driven browsers. Detection, vendor solver scaffolding, trusted cross-origin click delivery into nested OOPIFs, and stealth personas are implemented and tested; broad live-vendor solve rates are not yet benchmarked.
Documentation
//! Unit tests for [`super`] (solver backend registry + selection).

use super::*;

#[test]
fn vision_model_patterns_are_lowercase() {
    // Ollama tag matching is substring-and-lowercase; the patterns
    // list must follow.
    for p in VISION_MODEL_PATTERNS {
        assert_eq!(*p, p.to_lowercase(), "pattern {p} not lowercase");
    }
}

#[test]
fn pick_best_vision_model_returns_none_for_text_only() {
    let names = ["mistral:7b", "qwen2.5:14b", "llama3.1:8b"];
    assert_eq!(pick_best_vision_model(&names), None);
}

#[test]
fn pick_best_vision_model_finds_llava() {
    let names = ["llama3.1:8b", "llava:13b"];
    assert_eq!(pick_best_vision_model(&names), Some("llava:13b"));
}

#[test]
fn pick_best_vision_model_priority_order_respected() {
    // qwen3-vl is listed before llava in VISION_MODEL_PATTERNS;
    // when both are pulled, qwen3-vl wins.
    let names = ["llava:13b", "qwen3-vl:8b-thinking"];
    assert_eq!(pick_best_vision_model(&names), Some("qwen3-vl:8b-thinking"));
}

#[test]
fn pick_best_vision_model_handles_empty_list() {
    assert_eq!(pick_best_vision_model(&[]), None);
}

#[test]
fn capabilities_summary_no_backends() {
    let caps = Capabilities::default();
    assert_eq!(caps.summary(), "no backends detected");
    assert!(!caps.any());
}

#[test]
fn capabilities_summary_lists_each_backend() {
    let caps = Capabilities {
        vlm: Some(VlmBackend {
            endpoint: "http://localhost:11434".to_string(),
            model: "llava:13b".to_string(),
            source: "ollama".to_string(),
        }),
        stt: Some(SttBackend {
            kind: SttKind::WhisperCli,
            endpoint: "/usr/local/bin/whisper".to_string(),
            model: Some("tiny".to_string()),
        }),
        ocr: Some(OcrBackend {
            binary: "/usr/bin/tesseract".to_string(),
        }),
    };
    let s = caps.summary();
    assert!(s.contains("vlm:llava:13b"));
    assert!(s.contains("stt:WhisperCli"));
    assert!(s.contains("ocr:tesseract"));
    assert!(caps.any());
}

#[test]
fn default_vlm_pull_target_is_a_known_pattern() {
    assert!(
        VISION_MODEL_PATTERNS
            .iter()
            .any(|p| DEFAULT_VLM_AUTO_PULL.contains(p)),
        "DEFAULT_VLM_AUTO_PULL must match a VISION_MODEL_PATTERNS entry; \
             otherwise the post-pull probe won't find the new model",
    );
}

#[test]
fn which_finds_existing_binary_unix() {
    if cfg!(unix) {
        assert!(which("sh").is_some(), "sh should be on PATH");
    }
}

#[test]
fn which_returns_none_for_missing_binary() {
    assert!(which("definitely-not-a-real-binary-xyzzy12345").is_none());
}

#[tokio::test]
async fn probe_returns_some_capabilities_field_default() {
    // Pure-data sanity: probe always returns a Capabilities struct
    // even when nothing is installed.
    let caps = probe().await;
    // We can't assert exact contents (depends on machine), but the
    // type is stable.
    let _ = caps.summary();
}

#[test]
fn stt_kind_serializes_snake_case() {
    let json = serde_json::to_string(&SttKind::WhisperCli).unwrap();
    assert_eq!(json, r#""whisper_cli""#);
    // serde's snake_case mangles `OpenAIApi` to `open_a_i_api`
    // because it treats every uppercase letter as a word boundary.
    // We accept the serde default rather than hand-rolling a custom
    // serializer (the JSON form is internal/telemetry-only).
    let json = serde_json::to_string(&SttKind::OpenAIApi).unwrap();
    assert_eq!(json, r#""open_a_i_api""#);
    let json = serde_json::to_string(&SttKind::LocalServer).unwrap();
    assert_eq!(json, r#""local_server""#);
}