use anyhow::{anyhow, Result}; use async_trait::async_trait; use goose::config::GooseMode; use goose::conversation::message::{Message, ToolRequest}; use goose::tool_inspection::{ InspectionAction, InspectionResult, ToolInspectionManager, ToolInspector, }; struct MockInspectorOk { name: &'static str, results: Vec, } struct MockInspectorErr { name: &'static str, } #[async_trait] impl ToolInspector for MockInspectorOk { fn name(&self) -> &'static str { self.name } fn as_any(&self) -> &dyn std::any::Any { self } async fn inspect( &self, _session_id: &str, _tool_requests: &[ToolRequest], _messages: &[Message], _goose_mode: GooseMode, ) -> Result> { Ok(self.results.clone()) } } #[async_trait] impl ToolInspector for MockInspectorErr { fn name(&self) -> &'static str { self.name } fn as_any(&self) -> &dyn std::any::Any { self } async fn inspect( &self, _session_id: &str, _tool_requests: &[ToolRequest], _messages: &[Message], _goose_mode: GooseMode, ) -> Result> { Err(anyhow!("simulated failure")) } } #[tokio::test] async fn test_inspect_tools_aggregates_and_handles_errors() { // Arrange: create a manager with one successful and one failing inspector let ok_results = vec![ InspectionResult { tool_request_id: "req_1".to_string(), action: InspectionAction::Allow, reason: "looks safe".to_string(), confidence: 0.95, inspector_name: "ok".to_string(), finding_id: None, }, InspectionResult { tool_request_id: "req_2".to_string(), action: InspectionAction::RequireApproval(Some("double check".to_string())), reason: "needs user confirmation".to_string(), confidence: 0.7, inspector_name: "ok".to_string(), finding_id: Some("FND-123".to_string()), }, ]; let mut manager = ToolInspectionManager::new(); manager.add_inspector(Box::new(MockInspectorOk { name: "ok", results: ok_results.clone(), })); manager.add_inspector(Box::new(MockInspectorErr { name: "err" })); // No specific input is required for this aggregation behavior let tool_requests: Vec = vec![]; let messages: Vec = vec![]; // Act let results = manager .inspect_tools( goose_test_support::TEST_SESSION_ID, &tool_requests, &messages, GooseMode::Approve, ) .await .expect("inspect_tools should not fail when one inspector errors"); // Assert: results from the successful inspector are returned; failing inspector is ignored assert_eq!( results.len(), 2, "Should aggregate results from successful inspectors only" ); // Also verify inspector_names() order/presence let names = manager.inspector_names(); assert_eq!( names, vec!["ok", "err"], "Inspector names should reflect registration order" ); // Verify that specific actions are preserved assert!(results .iter() .any(|r| matches!(r.action, InspectionAction::Allow))); assert!(results .iter() .any(|r| matches!(r.action, InspectionAction::RequireApproval(_)))); }