use cdp_server::*;
use serde_json::{json, Value};
use std::sync::{Arc, Mutex};
struct NoopEventSender;
impl EventSender for NoopEventSender {
fn send_event(&self, _method: &str, _params: Value) {}
}
struct NoopTargetProvider;
impl TargetProvider for NoopTargetProvider {
fn list_targets(&self) -> Vec<TargetInfo> {
vec![TargetInfo {
id: "t1".into(),
target_type: "page".into(),
title: "Test".into(),
url: "about:blank".into(),
web_socket_debugger_url: "ws://127.0.0.1:9222/devtools/page/t1".into(),
}]
}
fn create_target(&self, url: &str) -> Result<TargetInfo, String> {
Ok(TargetInfo {
id: "t-new".into(),
target_type: "page".into(),
title: url.into(),
url: url.into(),
web_socket_debugger_url: "ws://127.0.0.1:9222/devtools/page/t-new".into(),
})
}
fn close_target(&self, id: &str) -> Result<(), String> {
if id == "notfound" {
Err("target not found".into())
} else {
Ok(())
}
}
fn activate_target(&self, _target_id: &str) -> Result<(), String> {
Ok(())
}
}
#[test]
fn test_cdp_server_new_default_config() {
let server = CdpServer::new(ServerConfig::default());
assert_eq!(server.port(), 9222);
}
#[test]
fn test_cdp_server_new_custom_port() {
let config = ServerConfig::builder().port(12345).build();
let server = CdpServer::new(config);
assert_eq!(server.port(), 12345);
}
#[test]
fn test_cdp_server_ws_url_default() {
let server = CdpServer::new(ServerConfig::default());
let url = server.ws_url_for_target("abc123");
assert_eq!(url, "ws://127.0.0.1:9222/devtools/page/abc123");
}
#[test]
fn test_cdp_server_ws_url_custom_host_port() {
let config = ServerConfig::builder().host("0.0.0.0").port(8080).build();
let server = CdpServer::new(config);
let url = server.ws_url_for_target("xyz");
assert_eq!(url, "ws://0.0.0.0:8080/devtools/page/xyz");
}
#[test]
fn test_cdp_server_ws_url_empty_target() {
let server = CdpServer::new(ServerConfig::default());
let url = server.ws_url_for_target("");
assert_eq!(url, "ws://127.0.0.1:9222/devtools/page/");
}
#[test]
fn test_cdp_server_ws_url_special_chars() {
let server = CdpServer::new(ServerConfig::default());
let url = server.ws_url_for_target("target-with-dashes_and_underscores");
assert!(url.contains("target-with-dashes_and_underscores"));
}
#[test]
fn test_cdp_server_registry_empty() {
let server = CdpServer::new(ServerConfig::default());
let registry = server.registry();
assert!(!registry.has_domain("Page"));
assert!(!registry.has_domain("Runtime"));
}
#[test]
fn test_cdp_server_broadcaster_clone() {
let server = CdpServer::new(ServerConfig::default());
let b1 = server.broadcaster();
let b2 = server.broadcaster();
b1.send_event("Page.loadEventFired", json!({}));
b2.send_event("Runtime.consoleAPICalled", json!({"type": "log"}));
}
#[test]
fn test_cdp_server_set_target_provider() {
let mut server = CdpServer::new(ServerConfig::default());
server.set_target_provider(Arc::new(NoopTargetProvider));
}
#[test]
fn test_cdp_server_no_target_provider_by_default() {
let server = CdpServer::new(ServerConfig::default());
assert_eq!(server.port(), 9222);
}
#[test]
fn test_server_config_default_values() {
let config = ServerConfig::default();
assert_eq!(config.host, "127.0.0.1");
assert_eq!(config.port, 9222);
assert_eq!(config.http_timeout_seconds, 30);
assert_eq!(config.max_sessions, 100);
assert_eq!(config.browser_name, "Bao/0.1.0");
assert_eq!(config.protocol_version, "1.3");
assert!(config.user_agent.is_none());
assert!(config.v8_version.is_none());
assert!(config.webkit_version.is_none());
}
#[test]
fn test_server_config_default_trait() {
let d1 = ServerConfig::default();
let d2: ServerConfig = Default::default();
assert_eq!(d1.host, d2.host);
assert_eq!(d1.port, d2.port);
assert_eq!(d1.max_sessions, d2.max_sessions);
}
#[test]
fn test_builder_host() {
let config = ServerConfig::builder().host("0.0.0.0").build();
assert_eq!(config.host, "0.0.0.0");
}
#[test]
fn test_builder_port() {
let config = ServerConfig::builder().port(65535).build();
assert_eq!(config.port, 65535);
}
#[test]
fn test_builder_port_zero() {
let config = ServerConfig::builder().port(0).build();
assert_eq!(config.port, 0);
}
#[test]
fn test_builder_http_timeout() {
let config = ServerConfig::builder().http_timeout_seconds(120).build();
assert_eq!(config.http_timeout_seconds, 120);
}
#[test]
fn test_builder_max_sessions() {
let config = ServerConfig::builder().max_sessions(1).build();
assert_eq!(config.max_sessions, 1);
}
#[test]
fn test_builder_max_sessions_large() {
let config = ServerConfig::builder().max_sessions(10000).build();
assert_eq!(config.max_sessions, 10000);
}
#[test]
fn test_builder_browser_name() {
let config = ServerConfig::builder().browser_name("Chrome/120").build();
assert_eq!(config.browser_name, "Chrome/120");
}
#[test]
fn test_builder_user_agent() {
let config = ServerConfig::builder()
.user_agent("Mozilla/5.0 TestBot")
.build();
assert_eq!(config.user_agent.as_deref(), Some("Mozilla/5.0 TestBot"));
}
#[test]
fn test_builder_v8_version() {
let config = ServerConfig::builder().v8_version("12.0.0").build();
assert_eq!(config.v8_version.as_deref(), Some("12.0.0"));
}
#[test]
fn test_builder_webkit_version() {
let config = ServerConfig::builder().webkit_version("537.36").build();
assert_eq!(config.webkit_version.as_deref(), Some("537.36"));
}
#[test]
fn test_builder_all_fields() {
let config = ServerConfig::builder()
.host("192.168.1.1")
.port(9999)
.http_timeout_seconds(60)
.max_sessions(50)
.browser_name("TestBrowser/1.0")
.user_agent("TestUA")
.v8_version("v8-10")
.webkit_version("wk-537")
.build();
assert_eq!(config.host, "192.168.1.1");
assert_eq!(config.port, 9999);
assert_eq!(config.http_timeout_seconds, 60);
assert_eq!(config.max_sessions, 50);
assert_eq!(config.browser_name, "TestBrowser/1.0");
assert_eq!(config.user_agent.as_deref(), Some("TestUA"));
assert_eq!(config.v8_version.as_deref(), Some("v8-10"));
assert_eq!(config.webkit_version.as_deref(), Some("wk-537"));
}
#[test]
fn test_builder_chaining_overwrites() {
let config = ServerConfig::builder().port(1111).port(2222).build();
assert_eq!(config.port, 2222);
}
#[test]
fn test_builder_empty_string_host() {
let config = ServerConfig::builder().host("").build();
assert_eq!(config.host, "");
}
#[derive(Debug)]
struct TrackedHandler {
domain: &'static str,
created: Arc<Mutex<Vec<String>>>,
destroyed: Arc<Mutex<Vec<String>>>,
}
impl DomainHandler for TrackedHandler {
fn domain_name(&self) -> &'static str {
self.domain
}
fn handle_command(
&self,
_cmd: &str,
_params: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
Ok(json!({}))
}
fn on_session_created(&self, session_id: &str) {
self.created.lock().unwrap().push(session_id.to_string());
}
fn on_session_destroyed(&self, session_id: &str) {
self.destroyed.lock().unwrap().push(session_id.to_string());
}
}
#[test]
fn test_registry_notify_session_created() {
let registry = DomainRegistry::<TrackedHandler>::new();
let created: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let destroyed: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
registry
.register(TrackedHandler {
domain: "TestDomain",
created: created.clone(),
destroyed: destroyed.clone(),
})
.unwrap();
registry.notify_session_created("TestDomain", "sess-1");
let c = created.lock().unwrap();
assert_eq!(c.len(), 1);
assert_eq!(c[0], "sess-1");
}
#[test]
fn test_registry_notify_session_created_unknown_domain() {
let registry = DomainRegistry::<TrackedHandler>::new();
let created: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let destroyed: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
registry
.register(TrackedHandler {
domain: "Foo",
created: created.clone(),
destroyed: destroyed.clone(),
})
.unwrap();
registry.notify_session_created("Bar", "sess-1");
assert!(created.lock().unwrap().is_empty());
}
#[test]
fn test_registry_notify_session_destroyed_multiple_domains() {
let registry = DomainRegistry::<TrackedHandler>::new();
let created_a: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let destroyed_a: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let created_b: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let destroyed_b: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
registry
.register(TrackedHandler {
domain: "Alpha",
created: created_a.clone(),
destroyed: destroyed_a.clone(),
})
.unwrap();
registry
.register(TrackedHandler {
domain: "Beta",
created: created_b.clone(),
destroyed: destroyed_b.clone(),
})
.unwrap();
registry.notify_session_destroyed(&["Alpha".into(), "Beta".into()], "sess-99");
assert_eq!(destroyed_a.lock().unwrap().len(), 1);
assert_eq!(destroyed_b.lock().unwrap().len(), 1);
assert_eq!(destroyed_a.lock().unwrap()[0], "sess-99");
assert_eq!(destroyed_b.lock().unwrap()[0], "sess-99");
}
#[test]
fn test_registry_notify_session_destroyed_empty() {
let registry = DomainRegistry::<TrackedHandler>::new();
let destroyed: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
registry
.register(TrackedHandler {
domain: "Gamma",
created: Arc::new(Mutex::new(Vec::new())),
destroyed: destroyed.clone(),
})
.unwrap();
registry.notify_session_destroyed(&[], "sess-x");
assert!(destroyed.lock().unwrap().is_empty());
}
#[test]
fn test_registry_notify_session_destroyed_unknown_domain() {
let registry = DomainRegistry::<TrackedHandler>::new();
let destroyed: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
registry
.register(TrackedHandler {
domain: "Delta",
created: Arc::new(Mutex::new(Vec::new())),
destroyed: destroyed.clone(),
})
.unwrap();
registry.notify_session_destroyed(&["NonExistent".into()], "sess-z");
assert!(destroyed.lock().unwrap().is_empty());
}
#[test]
fn test_registry_register_duplicate_returns_err() {
struct H;
impl DomainHandler for H {
fn domain_name(&self) -> &'static str {
"Dupe"
}
fn handle_command(
&self,
_: &str,
_: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
Ok(json!({}))
}
}
let registry = DomainRegistry::<H>::new();
assert!(registry.register(H).is_ok());
let result = registry.register(H);
assert!(result.is_err());
assert!(result.unwrap_err().contains("already registered"));
}
#[test]
fn test_registry_has_domain_after_register() {
struct H;
impl DomainHandler for H {
fn domain_name(&self) -> &'static str {
"Check"
}
fn handle_command(
&self,
_: &str,
_: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
Ok(json!({}))
}
}
let registry = DomainRegistry::<H>::new();
assert!(!registry.has_domain("Check"));
registry.register(H).unwrap();
assert!(registry.has_domain("Check"));
}
#[test]
fn test_registry_dispatch_unknown_domain() {
let registry = DomainRegistry::<cdp_server::EmptyHandler>::new();
let result = registry.dispatch_command("Unknown.method", json!({}), &NoopEventSender);
assert!(result.is_none());
}
#[test]
fn test_registry_dispatch_returns_handler_result() {
struct H;
impl DomainHandler for H {
fn domain_name(&self) -> &'static str {
"Echo"
}
fn handle_command(
&self,
cmd: &str,
_: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
Ok(json!({"echo": cmd}))
}
}
let registry = DomainRegistry::<H>::new();
registry.register(H).unwrap();
let result = registry.dispatch_command("Echo.test", json!({}), &NoopEventSender);
assert!(result.is_some());
let inner = result.unwrap();
assert!(inner.is_ok());
assert_eq!(inner.unwrap()["echo"], "Echo.test");
}
#[test]
fn test_registry_dispatch_handler_error() {
struct H;
impl DomainHandler for H {
fn domain_name(&self) -> &'static str {
"Fail"
}
fn handle_command(
&self,
_: &str,
_: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
Err(CdpError {
code: -32000,
message: "custom error".into(),
})
}
}
let registry = DomainRegistry::<H>::new();
registry.register(H).unwrap();
let result = registry.dispatch_command("Fail.cmd", json!({}), &NoopEventSender);
let err = result.unwrap().unwrap_err();
assert_eq!(err.code, -32000);
assert_eq!(err.message, "custom error");
}
#[test]
fn test_registry_dispatch_no_dot_in_method() {
struct H;
impl DomainHandler for H {
fn domain_name(&self) -> &'static str {
""
}
fn handle_command(
&self,
_: &str,
_: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
Ok(json!({}))
}
}
let registry = DomainRegistry::<H>::new();
registry.register(H).unwrap();
let result = registry.dispatch_command("NoMethod", json!({}), &NoopEventSender);
assert!(result.is_none());
}
#[test]
fn test_registry_default_trait() {
let registry = DomainRegistry::<cdp_server::EmptyHandler>::default();
assert!(!registry.has_domain("Any"));
}
#[test]
fn test_target_info_serialize() {
let info = TargetInfo {
id: "t1".into(),
target_type: "page".into(),
title: "Test Page".into(),
url: "https://example.com".into(),
web_socket_debugger_url: "ws://127.0.0.1:9222/devtools/page/t1".into(),
};
let json = serde_json::to_value(&info).unwrap();
assert_eq!(json["id"], "t1");
assert_eq!(json["type"], "page");
assert_eq!(json["title"], "Test Page");
assert_eq!(json["url"], "https://example.com");
assert_eq!(
json["web_socket_debugger_url"],
"ws://127.0.0.1:9222/devtools/page/t1"
);
}
#[test]
fn test_target_info_deserialize() {
let json = json!({
"id": "t2",
"type": "page",
"title": "Page 2",
"url": "about:blank",
"web_socket_debugger_url": "ws://localhost:9222/devtools/page/t2"
});
let info: TargetInfo = serde_json::from_value(json).unwrap();
assert_eq!(info.id, "t2");
assert_eq!(info.target_type, "page");
assert_eq!(info.title, "Page 2");
assert_eq!(info.url, "about:blank");
assert_eq!(
info.web_socket_debugger_url,
"ws://localhost:9222/devtools/page/t2"
);
}
#[test]
fn test_target_info_roundtrip() {
let info = TargetInfo {
id: "roundtrip".into(),
target_type: "worker".into(),
title: "SW".into(),
url: "sw.js".into(),
web_socket_debugger_url: "ws://x:1/devtools/page/roundtrip".into(),
};
let json = serde_json::to_string(&info).unwrap();
let back: TargetInfo = serde_json::from_str(&json).unwrap();
assert_eq!(back.id, info.id);
assert_eq!(back.target_type, info.target_type);
assert_eq!(back.title, info.title);
assert_eq!(back.url, info.url);
assert_eq!(back.web_socket_debugger_url, info.web_socket_debugger_url);
}
#[test]
fn test_target_info_debug() {
let info = TargetInfo {
id: "d1".into(),
target_type: "page".into(),
title: "Debug".into(),
url: "http://test".into(),
web_socket_debugger_url: "ws://t:1/d".into(),
};
let s = format!("{:?}", info);
assert!(s.contains("TargetInfo"));
assert!(s.contains("d1"));
}
#[test]
fn test_target_info_clone() {
let info = TargetInfo {
id: "c1".into(),
target_type: "page".into(),
title: "Clone".into(),
url: "http://c".into(),
web_socket_debugger_url: "ws://c:1/d".into(),
};
let cloned = info.clone();
assert_eq!(cloned.id, info.id);
assert_eq!(cloned.target_type, info.target_type);
}
#[test]
fn test_session_state_variants() {
let states = [
SessionState::Created,
SessionState::Active,
SessionState::Closing,
SessionState::Closed,
];
assert_eq!(states.len(), 4);
}
#[test]
fn test_session_state_equality() {
assert_eq!(SessionState::Created, SessionState::Created);
assert_ne!(SessionState::Created, SessionState::Active);
assert_ne!(SessionState::Active, SessionState::Closing);
assert_ne!(SessionState::Closing, SessionState::Closed);
}
#[test]
fn test_session_state_copy() {
let s = SessionState::Active;
let s2 = s;
assert_eq!(s, s2);
}
#[test]
fn test_session_state_debug() {
assert!(format!("{:?}", SessionState::Created).contains("Created"));
assert!(format!("{:?}", SessionState::Active).contains("Active"));
assert!(format!("{:?}", SessionState::Closing).contains("Closing"));
assert!(format!("{:?}", SessionState::Closed).contains("Closed"));
}
#[test]
fn test_session_state_clone() {
let s = SessionState::Closing;
let s2 = s.clone();
assert_eq!(s, s2);
}
#[test]
fn test_session_error_debug_closed() {
let e = SessionError::Closed;
let s = format!("{:?}", e);
assert!(s.contains("Closed"));
}
#[test]
fn test_session_error_debug_io() {
let e = SessionError::Io;
let s = format!("{:?}", e);
assert!(s.contains("Io"));
}
#[test]
fn test_cdp_error_serialize() {
let err = CdpError {
code: -32601,
message: "Method not found".into(),
};
let json = serde_json::to_value(&err).unwrap();
assert_eq!(json["code"], -32601);
assert_eq!(json["message"], "Method not found");
}
#[test]
fn test_cdp_error_clone() {
let err = CdpError {
code: -32000,
message: "test".into(),
};
let cloned = err.clone();
assert_eq!(cloned.code, err.code);
assert_eq!(cloned.message, err.message);
}
#[test]
fn test_cdp_error_debug() {
let err = CdpError {
code: -1,
message: "dbg".into(),
};
let s = format!("{:?}", err);
assert!(s.contains("CdpError"));
assert!(s.contains("-1"));
}
#[test]
fn test_cdp_message_parse_minimal() {
let msg: CdpMessage = serde_json::from_str(r#"{"method":"test"}"#).unwrap();
assert_eq!(msg.method, "test");
assert!(msg.id.is_none());
assert!(msg.params.is_none());
assert!(msg.session_id.is_none());
}
#[test]
fn test_cdp_message_parse_full() {
let msg: CdpMessage = serde_json::from_str(
r#"{"id":42,"method":"Page.navigate","params":{"url":"http://x"},"sessionId":"s1"}"#,
)
.unwrap();
assert_eq!(msg.id, Some(42));
assert_eq!(msg.method, "Page.navigate");
assert_eq!(msg.params.as_ref().unwrap()["url"], "http://x");
assert_eq!(msg.session_id.as_deref(), Some("s1"));
}
#[test]
fn test_cdp_message_parse_null_id() {
let msg: CdpMessage = serde_json::from_str(r#"{"id":null,"method":"test"}"#).unwrap();
assert!(msg.id.is_none());
}
#[test]
fn test_cdp_message_parse_negative_id() {
let msg: CdpMessage = serde_json::from_str(r#"{"id":-999,"method":"test"}"#).unwrap();
assert_eq!(msg.id, Some(-999));
}
#[test]
fn test_cdp_message_parse_large_id() {
let msg: CdpMessage = serde_json::from_str(r#"{"id":9999999999,"method":"test"}"#).unwrap();
assert_eq!(msg.id, Some(9999999999));
}
#[test]
fn test_cdp_message_clone() {
let msg = CdpMessage {
id: Some(1),
method: "test".into(),
params: Some(json!({"k": "v"})),
session_id: Some("s".into()),
};
let cloned = msg.clone();
assert_eq!(cloned.id, msg.id);
assert_eq!(cloned.method, msg.method);
}
#[test]
fn test_cdp_message_debug() {
let msg = CdpMessage {
id: Some(1),
method: "Debug.test".into(),
params: None,
session_id: None,
};
let s = format!("{:?}", msg);
assert!(s.contains("Debug.test"));
}
#[test]
fn test_cdp_message_invalid_json() {
let result = serde_json::from_str::<CdpMessage>("not json");
assert!(result.is_err());
}
#[test]
fn test_cdp_message_missing_method() {
let result = serde_json::from_str::<CdpMessage>(r#"{"id":1}"#);
assert!(result.is_err());
}
#[test]
fn test_cdp_response_ok() {
let resp = CdpResponse {
id: Some(1),
result: Some(json!({"value": 42})),
error: None,
};
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["id"], 1);
assert_eq!(json["result"]["value"], 42);
assert!(json.get("error").is_none());
}
#[test]
fn test_cdp_response_error() {
let resp = CdpResponse {
id: Some(2),
result: None,
error: Some(CdpError {
code: -32601,
message: "not found".into(),
}),
};
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["id"], 2);
assert!(json.get("result").is_none());
assert_eq!(json["error"]["code"], -32601);
}
#[test]
fn test_cdp_response_null_id() {
let resp = CdpResponse {
id: None,
result: Some(json!({})),
error: None,
};
let json = serde_json::to_value(&resp).unwrap();
assert!(json["id"].is_null());
}
#[test]
fn test_cdp_event_with_params() {
let ev = CdpEvent {
method: "Page.loadEventFired".into(),
params: Some(json!({"timestamp": 1234})),
};
let json = serde_json::to_value(&ev).unwrap();
assert_eq!(json["method"], "Page.loadEventFired");
assert_eq!(json["params"]["timestamp"], 1234);
}
#[test]
fn test_cdp_event_no_params() {
let ev = CdpEvent {
method: "Runtime.consoleAPICalled".into(),
params: None,
};
let json = serde_json::to_value(&ev).unwrap();
assert!(json.get("params").is_none());
}
#[test]
fn test_cdp_event_clone() {
let ev = CdpEvent {
method: "test".into(),
params: Some(json!({"x": 1})),
};
let cloned = ev.clone();
assert_eq!(cloned.method, ev.method);
}
#[test]
fn test_cdp_event_debug() {
let ev = CdpEvent {
method: "test".into(),
params: None,
};
assert!(format!("{:?}", ev).contains("CdpEvent"));
}
#[test]
fn test_parse_message_valid() {
let msg: CdpMessage = serde_json::from_str(r#"{"id":1,"method":"Page.enable"}"#).unwrap();
assert_eq!(msg.id, Some(1));
assert_eq!(msg.method, "Page.enable");
}
#[test]
fn test_parse_message_invalid() {
assert!(serde_json::from_str::<CdpMessage>("").is_err());
assert!(serde_json::from_str::<CdpMessage>("{").is_err());
assert!(serde_json::from_str::<CdpMessage>("null").is_err());
}
#[test]
fn test_serialize_response_ok() {
let resp = CdpResponse {
id: Some(1),
result: Some(json!({"data": "ok"})),
error: None,
};
let s = serde_json::to_string(&resp).unwrap();
assert!(s.contains("\"id\":1"));
assert!(s.contains("\"data\":\"ok\""));
}
#[test]
fn test_serialize_response_error() {
let resp = CdpResponse {
id: Some(2),
result: None,
error: Some(CdpError {
code: -32600,
message: "bad".into(),
}),
};
let s = serde_json::to_string(&resp).unwrap();
assert!(s.contains("-32600"));
assert!(s.contains("bad"));
}
#[test]
fn test_serialize_event_with_params() {
let ev = CdpEvent {
method: "Test.event".into(),
params: Some(json!({"key": "val"})),
};
let s = serde_json::to_string(&ev).unwrap();
assert!(s.contains("Test.event"));
assert!(s.contains("key"));
}
#[test]
fn test_serialize_event_no_params() {
let ev = CdpEvent {
method: "Test.evt".into(),
params: None,
};
let s = serde_json::to_string(&ev).unwrap();
assert!(s.contains("Test.evt"));
}
#[test]
fn test_parse_close_request_valid() {
let result = parse_close_request("GET /json/close/target-123 HTTP/1.1\r\nHost: localhost\r\n");
assert_eq!(result.unwrap(), "target-123");
}
#[test]
fn test_parse_close_request_no_prefix() {
assert!(parse_close_request("GET /json/list HTTP/1.1").is_none());
}
#[test]
fn test_parse_close_request_empty_id() {
let result = parse_close_request("GET /json/close/ HTTP/1.1");
assert!(result.is_some() || result.is_none()); }
#[test]
fn test_parse_activate_request_valid() {
let result = parse_activate_request("GET /json/activate/t1 HTTP/1.1\r\n");
assert_eq!(result.unwrap(), "t1");
}
#[test]
fn test_parse_activate_request_no_prefix() {
assert!(parse_activate_request("GET /other").is_none());
}
#[test]
fn test_parse_new_request_with_url() {
let result = parse_new_request("GET /json/new?https://example.com HTTP/1.1\r\n");
assert_eq!(result.unwrap(), "https://example.com");
}
#[test]
fn test_parse_new_request_default_url() {
let result = parse_new_request("GET /json/new HTTP/1.1\r\n");
assert_eq!(result.unwrap(), "about:blank");
}
#[test]
fn test_parse_new_request_encoded() {
let result = parse_new_request("GET /json/new?https%3A%2F%2Fexample.com HTTP/1.1\r\n");
assert_eq!(result.unwrap(), "https://example.com");
}
#[test]
fn test_parse_new_request_no_prefix() {
assert!(parse_new_request("GET /other").is_none());
}
#[test]
fn test_parse_new_request_plus_to_space() {
let result = parse_new_request("GET /json/new?hello+world HTTP/1.1\r\n");
assert_eq!(result.unwrap(), "hello world");
}
#[test]
fn test_parse_new_request_multi_percent() {
let result = parse_new_request("GET /json/new?%2Fpath%3Fq%3D1 HTTP/1.1\r\n");
assert_eq!(result.unwrap(), "/path?q=1");
}
#[test]
fn test_is_websocket_upgrade_uppercase() {
assert!(is_websocket_upgrade(
"GET / HTTP/1.1\r\nUpgrade: websocket\r\n"
));
}
#[test]
fn test_is_websocket_upgrade_lowercase() {
assert!(is_websocket_upgrade(
"GET / HTTP/1.1\r\nupgrade: websocket\r\n"
));
}
#[test]
fn test_is_websocket_upgrade_no_upgrade() {
assert!(!is_websocket_upgrade(
"GET / HTTP/1.1\r\nHost: localhost\r\n"
));
}
#[test]
fn test_is_websocket_upgrade_empty() {
assert!(!is_websocket_upgrade(""));
}
#[test]
fn test_broadcaster_new_empty() {
let sessions = Arc::new(Mutex::new(std::collections::HashMap::new()));
let broadcaster = EventBroadcaster::new(sessions);
broadcaster.send_event("Test.event", json!({"data": 1}));
}
#[test]
fn test_broadcaster_sender_boxed() {
let sessions = Arc::new(Mutex::new(std::collections::HashMap::new()));
let broadcaster = EventBroadcaster::new(sessions);
let sender = broadcaster.sender();
sender.send_event("Page.loadEventFired", json!({}));
}
#[test]
fn test_broadcaster_clone() {
let sessions = Arc::new(Mutex::new(std::collections::HashMap::new()));
let b1 = EventBroadcaster::new(sessions);
let b2 = b1.clone();
b1.send_event("Test.a", json!({}));
b2.send_event("Test.b", json!({}));
}
#[test]
fn test_custom_handler_invoke() {
struct MyHandler;
impl DomainHandler for MyHandler {
fn domain_name(&self) -> &'static str {
"Custom"
}
fn handle_command(
&self,
cmd: &str,
params: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
match cmd {
"Custom.echo" => Ok(params),
"Custom.fail" => Err(CdpError {
code: -32000,
message: "fail".into(),
}),
_ => Err(CdpError {
code: -32601,
message: format!("'{}' wasn't found", cmd),
}),
}
}
}
let h = MyHandler;
let es = NoopEventSender;
let result = h.handle_command("Custom.echo", json!({"key": "val"}), &es);
assert_eq!(result.unwrap()["key"], "val");
let err = h.handle_command("Custom.fail", json!({}), &es).unwrap_err();
assert_eq!(err.code, -32000);
let unknown = h
.handle_command("Custom.unknown", json!({}), &es)
.unwrap_err();
assert_eq!(unknown.code, -32601);
}
#[test]
fn test_custom_handler_lifecycle_callbacks() {
struct LifeHandler {
created: Arc<Mutex<bool>>,
destroyed: Arc<Mutex<bool>>,
}
impl DomainHandler for LifeHandler {
fn domain_name(&self) -> &'static str {
"Life"
}
fn handle_command(
&self,
_: &str,
_: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
Ok(json!({}))
}
fn on_session_created(&self, _: &str) {
*self.created.lock().unwrap() = true;
}
fn on_session_destroyed(&self, _: &str) {
*self.destroyed.lock().unwrap() = true;
}
}
let created = Arc::new(Mutex::new(false));
let destroyed = Arc::new(Mutex::new(false));
let h = LifeHandler {
created: created.clone(),
destroyed: destroyed.clone(),
};
h.on_session_created("s1");
assert!(*created.lock().unwrap());
h.on_session_destroyed("s1");
assert!(*destroyed.lock().unwrap());
}
#[test]
fn test_target_provider_list() {
let provider = NoopTargetProvider;
let targets = provider.list_targets();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].id, "t1");
assert_eq!(targets[0].target_type, "page");
}
#[test]
fn test_target_provider_create() {
let provider = NoopTargetProvider;
let info = provider.create_target("https://test.com").unwrap();
assert_eq!(info.id, "t-new");
assert_eq!(info.url, "https://test.com");
}
#[test]
fn test_target_provider_close_ok() {
let provider = NoopTargetProvider;
assert!(provider.close_target("t1").is_ok());
}
#[test]
fn test_target_provider_close_notfound() {
let provider = NoopTargetProvider;
assert!(provider.close_target("notfound").is_err());
}
#[test]
fn test_target_provider_activate() {
let provider = NoopTargetProvider;
assert!(provider.activate_target("t1").is_ok());
}
#[test]
fn test_server_with_provider_ws_url() {
let mut server = CdpServer::new(ServerConfig::builder().host("10.0.0.1").port(5555).build());
server.set_target_provider(Arc::new(NoopTargetProvider));
let url = server.ws_url_for_target("abc");
assert_eq!(url, "ws://10.0.0.1:5555/devtools/page/abc");
}
#[test]
fn test_server_registry_with_handler() {
struct H;
impl DomainHandler for H {
fn domain_name(&self) -> &'static str {
"Custom"
}
fn handle_command(
&self,
_: &str,
_: Value,
_: &dyn EventSender,
) -> Result<Value, CdpError> {
Ok(json!({"ok": true}))
}
}
let reg = Arc::new(DomainRegistry::<H>::new());
reg.register(H).unwrap();
let server = CdpServer::with_registry(ServerConfig::default(), reg);
assert!(server.registry().has_domain("Custom"));
let result = server
.registry()
.dispatch_command("Custom.test", json!({}), &NoopEventSender);
assert_eq!(result.unwrap().unwrap()["ok"], true);
}
#[test]
fn test_error_constants_values() {
assert_eq!(
cdp_server::CdpError {
code: -32600,
message: "".into()
}
.code,
-32600
);
assert_eq!(
cdp_server::CdpError {
code: -32601,
message: "".into()
}
.code,
-32601
);
}