vtcode-core 0.136.0

Core library for VT Code - a Rust-based terminal coding agent
#![allow(missing_docs)]

use anyhow::Result;
use serde_json::json;
use tempfile::TempDir;
use vtcode_core::config::constants::tools;
use vtcode_core::tools::ToolRegistry;

#[tokio::test]
async fn preflight_normalises_functions_code_search_namespace() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let registry = ToolRegistry::new(temp_dir.path().to_path_buf()).await;

    let outcome = registry.preflight_validate_call(
        "functions.code_search",
        &json!({"query": "ReasoningStage", "path": ".", "max_results": 10}),
    )?;
    assert_eq!(outcome.normalized_tool_name, tools::CODE_SEARCH);
    assert!(outcome.readonly_classification);
    Ok(())
}

#[tokio::test]
async fn code_search_rejects_every_former_public_field() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let registry = ToolRegistry::new(temp_dir.path().to_path_buf()).await;

    for field in [
        "action",
        "workflow",
        "pattern",
        "kind",
        "config_path",
        "filter",
        "lang",
        "selector",
        "strictness",
        "view",
        "items",
        "type",
        "match",
        "pub_members",
        "follow",
        "debug_query",
        "globs",
        "skip_snapshot_tests",
        "context_lines",
        "severities",
        "no_ignore",
        "threads",
        "format",
        "report_style",
        "before_lines",
        "after_lines",
        "builtin_rules",
    ] {
        let mut payload = json!({"query": "ReasoningStage"});
        payload
            .as_object_mut()
            .expect("request object")
            .insert(field.to_string(), json!(true));
        let err = registry
            .preflight_validate_call(tools::CODE_SEARCH, &payload)
            .expect_err("former public field must fail schema validation");
        let text = err.to_string();
        assert!(text.contains("Invalid arguments for tool 'code_search'"));
        assert!(text.contains(field), "{field}: {text}");
    }
    Ok(())
}

#[tokio::test]
async fn code_search_validates_five_property_contract() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let registry = ToolRegistry::new(temp_dir.path().to_path_buf()).await;

    let valid = registry.preflight_validate_call(
        tools::CODE_SEARCH,
        &json!({
            "query": "ReasoningStage",
            "path": "src",
            "file_types": ["rust", ".rs"],
            "result_types": ["usage", "definition"],
            "max_results": 5
        }),
    )?;
    assert_eq!(valid.normalized_tool_name, tools::CODE_SEARCH);
    assert!(valid.readonly_classification);

    for invalid in [
        json!({}),
        json!({"query": ""}),
        json!({"query": "   "}),
        json!({"query": 42}),
        json!({"query": "Widget", "path": 42}),
        json!({"query": "Widget", "file_types": []}),
        json!({"query": "Widget", "file_types": "rust"}),
        json!({"query": "Widget", "file_types": ["unknown-language"]}),
        json!({"query": "Widget", "result_types": []}),
        json!({"query": "Widget", "result_types": ["reference"]}),
        json!({"query": "Widget", "result_types": "text"}),
        json!({"query": "Widget", "max_results": 0}),
        json!({"query": "Widget", "max_results": 101}),
        json!({"query": "Widget", "max_results": "20"}),
    ] {
        let error = registry
            .preflight_validate_call(tools::CODE_SEARCH, &invalid)
            .expect_err("invalid request must fail preflight");
        assert!(error.to_string().contains("Invalid arguments for tool 'code_search'"), "{invalid}: {error}");
    }
    Ok(())
}

#[tokio::test]
async fn code_search_registry_executor_returns_normalised_typed_response() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let registry = ToolRegistry::new(temp_dir.path().to_path_buf()).await;

    let response = registry
        .execute_tool(
            tools::CODE_SEARCH,
            json!({
                "query": "  Widget  ",
                "file_types": [".rs", "rust"],
                "result_types": ["path", "path"]
            }),
        )
        .await?;

    let mut top_level_fields = response
        .as_object()
        .expect("response object")
        .keys()
        .map(String::as_str)
        .collect::<Vec<_>>();
    top_level_fields.sort_unstable();
    assert_eq!(top_level_fields, ["filters", "hints", "query", "results", "returned", "truncated"]);
    assert_eq!(response["query"], "Widget");
    assert_eq!(response["filters"]["path"], ".");
    assert_eq!(response["filters"]["file_types"], json!(["rust"]));
    assert_eq!(response["filters"]["result_types"], json!(["path"]));
    assert_eq!(response["filters"]["max_results"], 20);
    assert_eq!(response["results"], json!([]));
    assert_eq!(response["returned"], 0);
    assert_eq!(response["truncated"], false);
    assert_eq!(response["hints"], json!([]));
    Ok(())
}