Files
claw/tests/browser_tool_test.rs
木炎 bdf8e12246 feat: align browser callback runtime and export flows
Consolidate the browser task runtime around the callback path, add safer artifact opening for Zhihu exports, and cover the new service/browser flows with focused tests and supporting docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 21:44:53 +08:00

184 lines
5.5 KiB
Rust

mod common;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use common::MockTransport;
use sgclaw::pipe::{
Action, AgentMessage, BrowserMessage, BrowserPipeTool, ExecutionSurfaceKind, Timing,
};
use sgclaw::security::MacPolicy;
fn test_policy() -> MacPolicy {
MacPolicy::from_json_str(
r#"{
"version": "1.0",
"domains": { "allowed": ["oa.example.com", "erp.example.com"] },
"pipe_actions": {
"allowed": ["click", "type", "navigate", "getText"],
"blocked": ["eval", "executeJsInPage"]
}
}"#,
)
.unwrap()
}
#[test]
fn browser_tool_signs_and_sends_command_then_waits_for_response() {
let transport = Arc::new(MockTransport::new(vec![BrowserMessage::Response {
seq: 1,
success: true,
data: serde_json::json!({"text": "ok"}),
aom_snapshot: vec![],
timing: Timing {
queue_ms: 1,
exec_ms: 20,
},
}]));
let tool = BrowserPipeTool::new(
transport.clone(),
test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
let result = tool
.invoke(
Action::Click,
serde_json::json!({ "selector": "#submit" }),
"oa.example.com",
)
.unwrap();
let sent = transport.sent_messages();
assert_eq!(result.seq, 1);
assert_eq!(result.data, serde_json::json!({"text": "ok"}));
assert_eq!(sent.len(), 1);
assert!(matches!(
&sent[0],
AgentMessage::Command {
seq,
action,
params,
security
} if *seq == 1
&& action == &Action::Click
&& params == &serde_json::json!({"selector": "#submit"})
&& security.expected_domain == "oa.example.com"
&& !security.hmac.is_empty()
));
}
#[test]
fn browser_tool_rejects_action_when_mac_policy_blocks_it() {
let transport = Arc::new(MockTransport::new(vec![]));
let tool = BrowserPipeTool::new(transport, test_policy(), vec![1, 2, 3, 4]);
let err = tool
.invoke(
Action::GetHtml,
serde_json::json!({ "selector": "body" }),
"oa.example.com",
)
.unwrap_err();
assert!(err.to_string().contains("action is not allowed"));
}
#[test]
fn browser_tool_exposes_privileged_surface_metadata_backed_by_mac_policy() {
let transport = Arc::new(MockTransport::new(vec![]));
let tool = BrowserPipeTool::new(transport, test_policy(), vec![1, 2, 3, 4]);
let metadata = tool.surface_metadata();
assert_eq!(metadata.kind, ExecutionSurfaceKind::PrivilegedBrowserPipe);
assert!(metadata.privileged);
assert!(!metadata.defines_runtime_identity);
assert_eq!(metadata.guard, "mac_policy");
assert_eq!(
metadata.allowed_domains,
vec!["oa.example.com", "erp.example.com"]
);
assert_eq!(
metadata.allowed_actions,
vec!["click", "type", "navigate", "getText"]
);
}
#[test]
fn browser_tool_accepts_approved_local_dashboard_navigate_request() {
let transport = Arc::new(MockTransport::new(vec![BrowserMessage::Response {
seq: 1,
success: true,
data: serde_json::json!({"navigated": true}),
aom_snapshot: vec![],
timing: Timing {
queue_ms: 1,
exec_ms: 20,
},
}]));
let tool = BrowserPipeTool::new(transport.clone(), test_policy(), vec![1, 2, 3, 4])
.with_response_timeout(Duration::from_secs(1));
let result = tool
.invoke(
Action::Navigate,
serde_json::json!({
"url": "file:///C:/tmp/zhihu-hotlist-screen.html",
"sgclaw_local_dashboard_open": {
"source": "compat.workflow_executor",
"kind": "zhihu_hotlist_screen",
"output_path": "C:/tmp/zhihu-hotlist-screen.html",
"presentation_url": "file:///C:/tmp/zhihu-hotlist-screen.html"
}
}),
"__sgclaw_local_dashboard__",
)
.unwrap();
let sent = transport.sent_messages();
assert!(result.success);
assert!(matches!(
&sent[0],
AgentMessage::Command {
action,
params,
security,
..
} if action == &Action::Navigate
&& security.expected_domain == "__sgclaw_local_dashboard__"
&& params["url"] == serde_json::json!("file:///C:/tmp/zhihu-hotlist-screen.html")
&& params["sgclaw_local_dashboard_open"]["kind"] == serde_json::json!("zhihu_hotlist_screen")
));
}
#[test]
fn default_rules_allow_zhihu_navigation() {
let rules_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("resources")
.join("rules.json");
let policy = MacPolicy::load_from_path(rules_path).unwrap();
policy.validate(&Action::Navigate, "www.zhihu.com").unwrap();
}
#[test]
fn mac_policy_rejects_non_html_local_dashboard_presentation() {
let rules_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("resources")
.join("rules.json");
let policy = MacPolicy::load_from_path(rules_path).unwrap();
let err = policy
.validate_local_dashboard_presentation(
&Action::Navigate,
"__sgclaw_local_dashboard__",
"file:///C:/tmp/zhihu-hotlist-screen.txt",
"C:/tmp/zhihu-hotlist-screen.txt",
)
.unwrap_err();
assert!(err.to_string().contains("local dashboard"));
}