// Test that ToolContext properly wires up the placeholder tools use agent_core::{ToolContext, execute_tool, get_tool_definitions_with_external}; use permissions::{Mode, PermissionManager}; use plugins::{ExternalToolDefinition, ExternalToolTransport, ExternalToolSchema}; use tools_todo::{TodoList, TodoStatus}; use tools_bash::ShellManager; use serde_json::json; use std::collections::HashMap; use std::path::PathBuf; #[tokio::test] async fn test_todo_write_with_context() { let todo_list = TodoList::new(); let ctx = ToolContext::new().with_todo_list(todo_list.clone()); let perms = PermissionManager::new(Mode::Code); // Allow all tools let arguments = json!({ "todos": [ { "content": "First task", "status": "pending", "active_form": "Working on first task" }, { "content": "Second task", "status": "in_progress", "active_form": "Working on second task" } ] }); let result = execute_tool("todo_write", &arguments, &perms, &ctx).await; assert!(result.is_ok(), "TodoWrite should succeed: {:?}", result); // Verify the todos were written let todos = todo_list.read(); assert_eq!(todos.len(), 2); assert_eq!(todos[0].content, "First task"); assert_eq!(todos[1].status, TodoStatus::InProgress); } #[tokio::test] async fn test_todo_write_without_context() { let ctx = ToolContext::new(); // No todo_list let perms = PermissionManager::new(Mode::Code); let arguments = json!({ "todos": [] }); let result = execute_tool("todo_write", &arguments, &perms, &ctx).await; assert!(result.is_err(), "TodoWrite should fail without TodoList"); assert!(result.unwrap_err().to_string().contains("not available")); } #[tokio::test] async fn test_bash_output_with_context() { let manager = ShellManager::new(); let ctx = ToolContext::new().with_shell_manager(manager.clone()); let perms = PermissionManager::new(Mode::Code); // Start a shell and run a command let shell_id = manager.start_shell().await.unwrap(); let _ = manager.execute(&shell_id, "echo test", None).await.unwrap(); let arguments = json!({ "shell_id": shell_id }); let result = execute_tool("bash_output", &arguments, &perms, &ctx).await; assert!(result.is_ok(), "BashOutput should succeed: {:?}", result); } #[tokio::test] async fn test_bash_output_without_context() { let ctx = ToolContext::new(); // No shell_manager let perms = PermissionManager::new(Mode::Code); let arguments = json!({ "shell_id": "fake-id" }); let result = execute_tool("bash_output", &arguments, &perms, &ctx).await; assert!(result.is_err(), "BashOutput should fail without ShellManager"); assert!(result.unwrap_err().to_string().contains("not available")); } #[tokio::test] async fn test_kill_shell_with_context() { let manager = ShellManager::new(); let ctx = ToolContext::new().with_shell_manager(manager.clone()); let perms = PermissionManager::new(Mode::Code); // Start a shell let shell_id = manager.start_shell().await.unwrap(); let arguments = json!({ "shell_id": shell_id }); let result = execute_tool("kill_shell", &arguments, &perms, &ctx).await; assert!(result.is_ok(), "KillShell should succeed: {:?}", result); } #[tokio::test] async fn test_ask_user_without_context() { let ctx = ToolContext::new(); // No ask_sender let perms = PermissionManager::new(Mode::Code); let arguments = json!({ "questions": [] }); let result = execute_tool("ask_user", &arguments, &perms, &ctx).await; assert!(result.is_err(), "AskUser should fail without AskSender"); assert!(result.unwrap_err().to_string().contains("not available")); } // ============================================================================ // External Tools Tests // ============================================================================ fn create_test_external_tool(name: &str, description: &str) -> ExternalToolDefinition { ExternalToolDefinition { name: name.to_string(), description: description.to_string(), transport: ExternalToolTransport::Stdio, command: Some("echo".to_string()), args: vec![], url: None, timeout_ms: 5000, input_schema: ExternalToolSchema { schema_type: "object".to_string(), properties: { let mut props = HashMap::new(); props.insert( "input".to_string(), json!({"type": "string", "description": "Test input"}), ); props }, required: vec!["input".to_string()], }, source_path: PathBuf::from("/test/plugin"), plugin_name: "test-plugin".to_string(), } } #[tokio::test] async fn test_with_external_tools() { let mut ext_tools = HashMap::new(); ext_tools.insert( "my_custom_tool".to_string(), create_test_external_tool("my_custom_tool", "A custom tool for testing"), ); let ctx = ToolContext::new().with_external_tools(ext_tools); assert!(ctx.has_external_tool("my_custom_tool")); assert!(!ctx.has_external_tool("nonexistent")); let tool = ctx.get_external_tool("my_custom_tool").unwrap(); assert_eq!(tool.name, "my_custom_tool"); assert_eq!(tool.description, "A custom tool for testing"); } #[tokio::test] async fn test_get_tool_definitions_with_external() { let mut ext_tools = HashMap::new(); ext_tools.insert( "external_analyzer".to_string(), create_test_external_tool("external_analyzer", "Analyze stuff externally"), ); ext_tools.insert( "external_formatter".to_string(), create_test_external_tool("external_formatter", "Format things externally"), ); let ctx = ToolContext::new().with_external_tools(ext_tools); let all_tools = get_tool_definitions_with_external(&ctx); // Should have built-in tools plus our 2 external tools let external_tool_names: Vec<_> = all_tools .iter() .filter(|t| t.function.name == "external_analyzer" || t.function.name == "external_formatter") .collect(); assert_eq!(external_tool_names.len(), 2); // Verify external tool schema is correct let analyzer = all_tools.iter().find(|t| t.function.name == "external_analyzer").unwrap(); assert!(analyzer.function.description.contains("Analyze stuff")); } #[tokio::test] async fn test_unknown_tool_without_external() { let ctx = ToolContext::new(); let perms = PermissionManager::new(Mode::Code); let arguments = json!({"input": "test"}); let result = execute_tool("completely_unknown_tool", &arguments, &perms, &ctx).await; assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("Unknown tool")); } #[tokio::test] async fn test_external_tool_permission_denied() { let mut ext_tools = HashMap::new(); ext_tools.insert( "dangerous_tool".to_string(), create_test_external_tool("dangerous_tool", "A dangerous tool"), ); let ctx = ToolContext::new().with_external_tools(ext_tools); let perms = PermissionManager::new(Mode::Plan); // Plan mode denies bash-like tools let arguments = json!({"input": "test"}); let result = execute_tool("dangerous_tool", &arguments, &perms, &ctx).await; // External tools are treated like Bash, so should require permission in Plan mode assert!(result.is_err()); let err = result.unwrap_err().to_string(); assert!(err.contains("Permission required") || err.contains("Permission denied")); }