dci-tool 0.1.0

Direct Corpus Interaction: a sandboxed, ripgrep-backed corpus-search toolset and agent for cyber-focused LLM agents, built on rig.
Documentation
//! Tests exercising the corpus tools through the rig `Tool::call` interface —
//! the same path the agent uses at runtime.
#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::indexing_slicing,
    clippy::panic
)]

use std::path::PathBuf;

use dci_tool::sandbox::CorpusRoot;
use dci_tool::tools::{FindArgs, ReadArgs, SearchArgs};
use dci_tool::{FindTool, ReadTool, SearchTool};
use rig_core::tool::Tool;

fn corpus() -> CorpusRoot {
    let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
    CorpusRoot::new(dir).expect("fixture corpus")
}

#[tokio::test]
async fn search_tool_returns_hits() {
    let tool = SearchTool::new(corpus());
    let out = tool
        .call(SearchArgs {
            pattern: "Failed password".to_string(),
            path_glob: Some("**/*.log".to_string()),
            case_insensitive: None,
            context_lines: None,
            max_results: None,
        })
        .await
        .expect("search call");

    assert!(out.hits.len() >= 3);
    assert!(out.hits.iter().all(|h| h.path.ends_with("auth.log")));
}

#[tokio::test]
async fn find_tool_locates_files() {
    let tool = FindTool::new(corpus());
    let out = tool
        .call(FindArgs {
            glob: "**/*.md".to_string(),
            max_results: None,
        })
        .await
        .expect("find call");

    assert!(out.paths.iter().any(|p| p.ends_with("notes.md")));
}

#[tokio::test]
async fn read_tool_rejects_escape() {
    let tool = ReadTool::new(corpus());
    let err = tool
        .call(ReadArgs {
            path: "../../Cargo.toml".to_string(),
            start_line: None,
            line_count: None,
        })
        .await
        .unwrap_err();

    assert!(matches!(
        err,
        dci_tool::DciError::PathEscape { .. } | dci_tool::DciError::NotFound { .. }
    ));
}