#[cfg(feature = "vision")]
use anyhow::Result;
#[tokio::test]
#[cfg(feature = "vision")]
async fn model_hub_downloads_yolov8n() -> Result<()> {
let hub = captchaforge::vision::ModelHub::new();
let path = hub.resolve(captchaforge::vision::ModelId::YoloV8n).await?;
assert!(path.exists(), "YOLOv8n model should exist after download");
assert!(path.metadata()?.len() > 1_000_000, "model should be > 1MB");
Ok(())
}
#[test]
#[cfg(feature = "vision")]
fn yolov8n_detects_on_synthetic_image() -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
let hub = captchaforge::vision::ModelHub::new();
let path = rt.block_on(async { hub.resolve(captchaforge::vision::ModelId::YoloV8n).await })?;
let mut detector = captchaforge::vision::YoloDetector::load(&path)?;
let mut img = image::RgbImage::new(640, 480);
for pixel in img.pixels_mut() {
*pixel = image::Rgb([128, 128, 128]);
}
for y in 100..300 {
for x in 100..300 {
img.put_pixel(x, y, image::Rgb([255, 0, 0]));
}
}
let dyn_img = image::DynamicImage::ImageRgb8(img);
let detections = detector.detect(&dyn_img)?;
println!("detections: {}", detections.len());
Ok(())
}
#[test]
#[cfg(feature = "vision")]
fn task_to_coco_mapping_coverage() {
use captchaforge::vision::yolo::task_to_coco_classes;
assert!(task_to_coco_classes("Select all images with traffic lights").is_some());
assert!(task_to_coco_classes("Select all buses").is_some());
assert!(task_to_coco_classes("Select all cars").is_some());
assert!(task_to_coco_classes("Select all bicycles").is_some());
assert!(task_to_coco_classes("Select all vehicles").is_some());
assert!(task_to_coco_classes("Select all fire hydrants").is_some());
assert!(task_to_coco_classes("Select all crosswalks").is_none());
assert!(task_to_coco_classes("Select all chimneys").is_none());
assert!(task_to_coco_classes("Select all stairs").is_none());
assert!(task_to_coco_classes("Select all palm trees").is_none());
}
#[test]
#[cfg(feature = "vision")]
fn crnn_recognizer_loads_when_model_available() {
let rt = tokio::runtime::Runtime::new().unwrap();
let hub = captchaforge::vision::ModelHub::new();
let path = rt.block_on(async { hub.resolve(captchaforge::vision::ModelId::CrnnText).await });
if let Ok(path) = path {
let charset = captchaforge::vision::crnn::default_charset();
let result = captchaforge::vision::crnn::CrnnRecognizer::load(&path, charset);
assert!(result.is_ok(), "CRNN should load when model is available");
} else {
println!("CRNN model not available — skipping load test (run scripts/train_crnn.py to generate)");
}
}
#[test]
#[cfg(not(feature = "vision"))]
fn vision_module_compiles_without_feature() {
use captchaforge::solver::CaptchaSolver;
let yolo = captchaforge::solver::YoloGridSolver::new();
assert!(!yolo.supports(&captchaforge::captcha_detect::DetectedCaptcha::RecaptchaV2));
let crnn = captchaforge::solver::CrnnTextSolver::new();
assert!(!crnn.supports(&captchaforge::captcha_detect::DetectedCaptcha::ImageCaptcha));
}