use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use serde_json::json; use sgclaw::browser::bridge_contract::{ BridgeBrowserActionError, BridgeBrowserActionReply, BridgeBrowserActionRequest, BridgeBrowserActionSuccess, }; use sgclaw::browser::bridge_transport::BridgeActionTransport; use sgclaw::browser::{BridgeBrowserBackend, BrowserBackend}; use sgclaw::pipe::{Action, PipeError, Timing}; use sgclaw::security::MacPolicy; fn test_policy() -> MacPolicy { MacPolicy::from_json_str( r#"{ "version": "1.0", "domains": { "allowed": ["www.baidu.com"] }, "pipe_actions": { "allowed": ["click", "type", "navigate", "getText", "eval"], "blocked": [] } }"#, ) .unwrap() } struct FakeBridgeTransport { requests: Mutex>, replies: Mutex>>, } impl FakeBridgeTransport { fn new(replies: Vec>) -> Self { Self { requests: Mutex::new(Vec::new()), replies: Mutex::new(replies.into()), } } fn recorded_requests(&self) -> Vec { self.requests.lock().unwrap().clone() } } impl BridgeActionTransport for FakeBridgeTransport { fn execute( &self, request: BridgeBrowserActionRequest, ) -> Result { self.requests.lock().unwrap().push(request); self.replies .lock() .unwrap() .pop_front() .unwrap_or(Err(PipeError::Timeout)) } } #[test] fn bridge_backend_maps_navigate_to_bridge_action_request() { let transport = Arc::new(FakeBridgeTransport::new(vec![Ok( BridgeBrowserActionReply::Success(BridgeBrowserActionSuccess { data: json!({ "navigated": true }), aom_snapshot: vec![], timing: Timing { queue_ms: 1, exec_ms: 11, }, }), )])); let backend = BridgeBrowserBackend::new(transport.clone(), test_policy()); let output = backend .invoke( Action::Navigate, json!({ "url": "https://www.baidu.com" }), "www.baidu.com", ) .unwrap(); assert_eq!( transport.recorded_requests(), vec![BridgeBrowserActionRequest::new( "navigate", json!({ "url": "https://www.baidu.com" }), "www.baidu.com", )] ); assert_eq!(output.seq, 1); assert!(output.success); } #[test] fn bridge_backend_normalizes_successful_bridge_reply() { let transport = Arc::new(FakeBridgeTransport::new(vec![Ok( BridgeBrowserActionReply::Success(BridgeBrowserActionSuccess { data: json!({ "text": "天气" }), aom_snapshot: vec![json!({ "role": "textbox", "name": "百度一下" })], timing: Timing { queue_ms: 4, exec_ms: 14, }, }), )])); let backend = BridgeBrowserBackend::new(transport, test_policy()); let output = backend .invoke( Action::GetText, json!({ "selector": "#content_left" }), "www.baidu.com", ) .unwrap(); assert_eq!(output.seq, 1); assert!(output.success); assert_eq!(output.data, json!({ "text": "天气" })); assert_eq!( output.aom_snapshot, vec![json!({ "role": "textbox", "name": "百度一下" })] ); assert_eq!( output.timing, Timing { queue_ms: 4, exec_ms: 14, } ); } #[test] fn bridge_backend_maps_bridge_failure_to_pipe_error() { let transport = Arc::new(FakeBridgeTransport::new(vec![Ok( BridgeBrowserActionReply::Error(BridgeBrowserActionError { message: "selector not found".to_string(), details: json!({ "selector": "#missing" }), }), )])); let backend = BridgeBrowserBackend::new(transport, test_policy()); let error = backend .invoke( Action::Click, json!({ "selector": "#missing" }), "www.baidu.com", ) .unwrap_err(); assert!(matches!(error, PipeError::Protocol(message) if message == "bridge action failed: selector not found")); }