crabtalk-runtime 0.0.18

Crabtalk agent runtime — tool dispatch, MCP, skills, and memory
Documentation
//! Tests for Env dispatch logic — no daemon, no network, no disk.

use crabtalk_runtime::{Env, NoHost, SkillHandler, mcp::McpHandler};
use std::path::PathBuf;
use wcore::AgentConfig;

async fn test_hook() -> Env<NoHost> {
    let skills = SkillHandler::default();
    let mcp = McpHandler::load(&[]).await;
    let cwd = PathBuf::from("/test");
    Env::new(skills, mcp, cwd, None, NoHost)
}

#[tokio::test]
async fn tool_whitelist_rejects_unlisted() {
    let mut hook = test_hook().await;
    let mut config = AgentConfig::new("restricted");
    config.tools = vec!["bash".to_owned()];
    hook.register_scope("restricted".to_owned(), &config);

    let result: String = hook
        .dispatch_tool("recall", "{}", "restricted", "", None)
        .await;
    assert!(result.contains("tool not available"));
}

#[tokio::test]
async fn empty_whitelist_allows_all() {
    let mut hook = test_hook().await;
    let config = AgentConfig::new("open");
    hook.register_scope("open".to_owned(), &config);

    let result: String = hook
        .dispatch_tool("recall", r#"{"query":"test"}"#, "open", "", None)
        .await;
    assert!(result.contains("memory not available"));
}

#[tokio::test]
async fn delegate_member_scope_rejects_unlisted_agent() {
    let mut hook = test_hook().await;
    let mut config = AgentConfig::new("caller");
    config.members = vec!["agent-a".to_owned()];
    hook.register_scope("caller".to_owned(), &config);

    let args = r#"{"tasks":[{"agent":"agent-b","message":"hello"}]}"#;
    let result: String = hook
        .dispatch_tool("delegate", args, "caller", "", None)
        .await;
    assert!(result.contains("not in your members list"));
}

#[tokio::test]
async fn delegate_member_scope_allows_listed_agent() {
    let mut hook = test_hook().await;
    let mut config = AgentConfig::new("caller");
    config.members = vec!["agent-a".to_owned()];
    hook.register_scope("caller".to_owned(), &config);

    let args = r#"{"tasks":[{"agent":"agent-a","message":"hello"}]}"#;
    let result: String = hook
        .dispatch_tool("delegate", args, "caller", "", None)
        .await;
    assert!(!result.contains("not in your members list"));
}

#[tokio::test]
async fn delegate_empty_tasks() {
    let hook = test_hook().await;
    let result: String = hook
        .dispatch_tool("delegate", r#"{"tasks":[]}"#, "agent", "", None)
        .await;
    assert!(result.contains("no tasks provided"));
}

#[tokio::test]
async fn no_bridge_ask_user_default() {
    let hook = test_hook().await;
    let result: String = hook
        .dispatch_tool("ask_user", "{}", "agent", "", None)
        .await;
    assert!(result.contains("not available in this runtime mode"));
}

#[tokio::test]
async fn no_bridge_delegate_default() {
    let hook = test_hook().await;
    let args = r#"{"tasks":[{"agent":"x","message":"hi"}]}"#;
    let result: String = hook
        .dispatch_tool("delegate", args, "agent", "", None)
        .await;
    assert!(result.contains("not available in this runtime mode"));
}

#[tokio::test]
async fn unknown_tool_rejected() {
    let hook = test_hook().await;
    let result: String = hook
        .dispatch_tool("nonexistent", "{}", "agent", "", None)
        .await;
    assert!(result.contains("tool not available"));
}

#[tokio::test]
async fn bash_rejected_for_gateway_sender() {
    let hook = test_hook().await;
    let result: String = hook
        .dispatch_tool(
            "bash",
            r#"{"command":"echo hi"}"#,
            "agent",
            "gateway:telegram",
            None,
        )
        .await;
    assert!(result.contains("only available in the command line interface"));
}