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
//! Lock the mdbook documentation site structure so a refactor
//! can't drop the SUMMARY.md table-of-contents or remove the
//! book.toml that the GH Actions deploy workflow expects.

use std::fs;
use std::path::PathBuf;

fn book_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("book")
}

#[test]
fn book_toml_exists_and_declares_title() {
    let body = fs::read_to_string(book_dir().join("book.toml")).expect("book.toml exists");
    assert!(body.contains("title = \"captchaforge\""));
    assert!(body.contains("[output.html]"));
    assert!(body.contains("[output.html.search]"));
}

#[test]
fn summary_md_has_required_sections() {
    let body = fs::read_to_string(book_dir().join("src/SUMMARY.md")).unwrap();
    for required in [
        "# Getting Started",
        "# Architecture",
        "# Per-Vendor Cookbook",
        "# Integration",
        "# Deployment",
        "# Operations",
        "# Reference",
        "# Contributing",
        "# Project",
    ] {
        assert!(body.contains(required), "SUMMARY.md missing section {required:?}");
    }
}

#[test]
fn every_summary_link_resolves_to_a_file() {
    let summary = fs::read_to_string(book_dir().join("src/SUMMARY.md")).unwrap();
    let mut missing: Vec<String> = Vec::new();
    for line in summary.lines() {
        // Match `[Label](path.md)` Markdown link. The Label may
        // contain parens (e.g. "Kubernetes (Helm)") so we anchor
        // the opening `(` of the URL to the `]` that closes the
        // label.
        let Some(bracket_close) = line.find("](") else { continue };
        let url_start = bracket_close + 2; // skip `](`
        let Some(rel_close) = line[url_start..].find(')') else { continue };
        let path = &line[url_start..url_start + rel_close];
        if path.is_empty() || path.starts_with("http") {
            continue;
        }
        let p = book_dir().join("src").join(path);
        if !p.is_file() {
            missing.push(path.to_string());
        }
    }
    assert!(
        missing.is_empty(),
        "SUMMARY.md references missing files:\n  {}",
        missing.join("\n  ")
    );
}

#[test]
fn book_has_404_page() {
    assert!(book_dir().join("src/404.md").is_file());
}