mod common; use std::sync::Arc; use std::time::Duration; use common::MockTransport; use sgclaw::pipe::{Action, AgentMessage, BrowserMessage, BrowserPipeTool, Timing}; use sgclaw::security::MacPolicy; use sgclaw::skill::zhihu::{execute, load_flow, ZhihuWriteRequest}; fn test_policy() -> MacPolicy { MacPolicy::from_json_str( r#"{ "version": "1.0", "domains": { "allowed": ["www.zhihu.com", "zhuanlan.zhihu.com"] }, "pipe_actions": { "allowed": ["click", "type", "navigate", "getText", "getHtml", "waitForSelector", "scrollTo"], "blocked": [] } }"#, ) .unwrap() } fn response(seq: u64, data: serde_json::Value) -> BrowserMessage { BrowserMessage::Response { seq, success: true, data, aom_snapshot: vec![], timing: Timing { queue_ms: 1, exec_ms: 10, }, } } #[test] fn load_flow_preserves_validated_zhihu_literals() { let flow = load_flow().unwrap(); assert_eq!(flow.entry_url, "https://www.zhihu.com/creator"); assert_eq!(flow.editor_url, "https://zhuanlan.zhihu.com/write"); assert_eq!(flow.literals["write_entry_text"], "写文章"); assert_eq!(flow.literals["publish_confirm_text"], "确认发布"); assert_eq!( flow.literals["title_placeholder"], "请输入标题(最多 100 个字)" ); assert_eq!( flow.selectors["creator_write_entry"], "div.css-1q62b6s > div.css-byu4by" ); assert_eq!( flow.selectors["publish_confirm_button"], "div[role='dialog'] button.Button--primary.Button--blue" ); } #[test] fn zhihu_skill_stops_before_publish_when_publish_is_false() { let transport = Arc::new(MockTransport::new(vec![ response( 1, serde_json::json!({ "url": "https://www.zhihu.com/creator" }), ), response( 2, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response( 3, serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(4, serde_json::json!({ "typed": true })), response(5, serde_json::json!({ "typed": true })), ])); let browser_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 = execute( transport.as_ref(), &browser_tool, ZhihuWriteRequest { title: "自动发文能力测试".to_string(), body: "第一段\n\n第二段".to_string(), publish: false, }, ) .unwrap(); let sent = transport.sent_messages(); assert_eq!(result.summary, "知乎文章草稿已填充:自动发文能力测试"); assert_eq!(sent.len(), 10); assert!(matches!( &sent[5], AgentMessage::Command { seq, action, .. } if *seq == 3 && action == &Action::WaitForSelector )); assert!(matches!( &sent[9], AgentMessage::Command { seq, action, .. } if *seq == 5 && action == &Action::Type )); } #[test] fn zhihu_skill_publishes_only_after_confirming_dialog_title_and_final_url() { let transport = Arc::new(MockTransport::new(vec![ response( 1, serde_json::json!({ "url": "https://www.zhihu.com/creator" }), ), response( 2, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response( 3, serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(4, serde_json::json!({ "typed": true })), response(5, serde_json::json!({ "typed": true })), response(6, serde_json::json!({ "scrolled": true })), response( 7, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(8, serde_json::json!({ "ready": true })), response( 9, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/p/123456" }), ), response( 10, serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/p/123456" }), ), response( 11, serde_json::json!({ "text": "自动发文能力测试", "url": "https://zhuanlan.zhihu.com/p/123456" }), ), ])); let browser_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 = execute( transport.as_ref(), &browser_tool, ZhihuWriteRequest { title: "自动发文能力测试".to_string(), body: "第一段\n\n第二段".to_string(), publish: true, }, ) .unwrap(); let sent = transport.sent_messages(); assert_eq!( result.summary, "知乎文章已发布:自动发文能力测试 (https://zhuanlan.zhihu.com/p/123456)" ); assert_eq!( result.final_url.as_deref(), Some("https://zhuanlan.zhihu.com/p/123456") ); assert!(result.published); assert_eq!(sent.len(), 22); assert!(matches!( &sent[11], AgentMessage::Command { seq, action, .. } if *seq == 6 && action == &Action::ScrollTo )); assert!(matches!( &sent[21], AgentMessage::Command { seq, action, .. } if *seq == 11 && action == &Action::GetText )); } #[test] fn zhihu_skill_accepts_edit_url_as_published_article_url() { let transport = Arc::new(MockTransport::new(vec![ response( 1, serde_json::json!({ "url": "https://www.zhihu.com/creator" }), ), response( 2, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response( 3, serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(4, serde_json::json!({ "typed": true })), response(5, serde_json::json!({ "typed": true })), response(6, serde_json::json!({ "scrolled": true })), response( 7, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(8, serde_json::json!({ "ready": true })), response( 9, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/p/123456/edit" }), ), response( 10, serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/p/123456/edit" }), ), response( 11, serde_json::json!({ "text": "自动发文能力测试", "url": "https://zhuanlan.zhihu.com/p/123456/edit" }), ), ])); let browser_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 = execute( transport.as_ref(), &browser_tool, ZhihuWriteRequest { title: "自动发文能力测试".to_string(), body: "第一段\n\n第二段".to_string(), publish: true, }, ) .unwrap(); assert_eq!( result.final_url.as_deref(), Some("https://zhuanlan.zhihu.com/p/123456") ); assert_eq!( result.summary, "知乎文章已发布:自动发文能力测试 (https://zhuanlan.zhihu.com/p/123456)" ); } #[test] fn zhihu_skill_fails_when_publish_confirmation_never_returns_article_url() { let transport = Arc::new(MockTransport::new(vec![ response( 1, serde_json::json!({ "url": "https://www.zhihu.com/creator" }), ), response( 2, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response( 3, serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(4, serde_json::json!({ "typed": true })), response(5, serde_json::json!({ "typed": true })), response(6, serde_json::json!({ "scrolled": true })), response( 7, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(8, serde_json::json!({ "ready": true })), response(9, serde_json::json!({ "clicked": true })), response(10, serde_json::json!({ "ready": true })), response(11, serde_json::json!({ "text": "自动发文能力测试" })), ])); let browser_tool = BrowserPipeTool::new( transport.clone(), test_policy(), vec![1, 2, 3, 4, 5, 6, 7, 8], ) .with_response_timeout(Duration::from_secs(1)); let err = execute( transport.as_ref(), &browser_tool, ZhihuWriteRequest { title: "自动发文能力测试".to_string(), body: "第一段\n\n第二段".to_string(), publish: true, }, ) .unwrap_err(); assert!(err.to_string().contains("did not return article url")); } #[test] fn zhihu_skill_fails_when_published_title_does_not_match_request_title() { let transport = Arc::new(MockTransport::new(vec![ response( 1, serde_json::json!({ "url": "https://www.zhihu.com/creator" }), ), response( 2, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response( 3, serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(4, serde_json::json!({ "typed": true })), response(5, serde_json::json!({ "typed": true })), response(6, serde_json::json!({ "scrolled": true })), response( 7, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/write" }), ), response(8, serde_json::json!({ "ready": true })), response( 9, serde_json::json!({ "clicked": true, "url": "https://zhuanlan.zhihu.com/p/123456" }), ), response( 10, serde_json::json!({ "ready": true, "url": "https://zhuanlan.zhihu.com/p/123456" }), ), response( 11, serde_json::json!({ "text": "别的标题", "url": "https://zhuanlan.zhihu.com/p/123456" }), ), ])); let browser_tool = BrowserPipeTool::new( transport.clone(), test_policy(), vec![1, 2, 3, 4, 5, 6, 7, 8], ) .with_response_timeout(Duration::from_secs(1)); let err = execute( transport.as_ref(), &browser_tool, ZhihuWriteRequest { title: "自动发文能力测试".to_string(), body: "第一段\n\n第二段".to_string(), publish: true, }, ) .unwrap_err(); assert!(err .to_string() .contains("expected text `自动发文能力测试`, got `别的标题`")); }