#![cfg(feature = "headless-search")]
use a3s_code_core::config::{BrowserBackend, HeadlessConfig, SearchConfig};
use a3s_code_core::tools::{ToolContext, ToolExecutor};
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
use std::sync::Arc;
fn make_context(headless: Option<HeadlessConfig>) -> ToolContext {
let search_config = headless.map(|h| {
Arc::new(SearchConfig {
timeout: 30,
health: None,
engines: HashMap::new(),
headless: Some(h),
})
});
let mut context = ToolContext::new(PathBuf::from("/tmp")).with_session_id("test-session");
context.search_config = search_config;
context
}
#[tokio::test]
async fn test_web_search_tool_creation() {
let executor = ToolExecutor::new("/tmp".to_string());
let definitions = executor.definitions();
assert!(
definitions.iter().any(|t| t.name == "web_search"),
"web_search tool should be registered"
);
}
#[tokio::test]
async fn test_web_search_http_engine() {
let executor = ToolExecutor::new("/tmp".to_string());
let args = serde_json::json!({
"query": "test",
"engines": "duckduckgo"
});
let result = executor.execute("web_search", &args).await;
match result {
Ok(output) => {
println!("✅ DuckDuckGo search succeeded!");
println!("Output length: {}", output.output.len());
println!("Exit code: {}", output.exit_code);
}
Err(e) => {
println!("⚠️ DuckDuckGo search failed: {:?}", e);
}
}
}
#[tokio::test]
async fn test_web_search_with_baidu_headless_engine() {
let executor = ToolExecutor::new("/tmp".to_string());
let headless_config = HeadlessConfig {
backend: BrowserBackend::Chrome,
max_tabs: 2,
browser_path: None,
launch_args: Vec::new(),
proxy_url: None,
};
let context = make_context(Some(headless_config));
let args = serde_json::json!({
"query": "test query",
"engines": "baidu",
"timeout": 3
});
let result = executor
.execute_with_context("web_search", &args, &context)
.await;
match result {
Ok(output) => {
println!("✅ Baidu headless search succeeded!");
println!("Output length: {}", output.output.len());
println!("Exit code: {:?}", output.exit_code);
}
Err(e) => {
println!(
"⚠️ Baidu headless search failed (browser may not be available): {:?}",
e
);
}
}
}
#[tokio::test]
#[ignore] async fn test_baidu_headless_search_actual() {
let executor = ToolExecutor::new("/tmp".to_string());
let headless_config = HeadlessConfig {
backend: BrowserBackend::Chrome,
max_tabs: 2,
browser_path: None,
launch_args: Vec::new(),
proxy_url: None,
};
let context = make_context(Some(headless_config));
let args = serde_json::json!({
"query": "rust programming language",
"engines": "baidu"
});
let result = executor
.execute_with_context("web_search", &args, &context)
.await
.unwrap();
assert_eq!(
result.exit_code, 0,
"Search should succeed, got exit_code={}, output={}",
result.exit_code, result.output
);
assert!(
!result.output.trim().is_empty(),
"Search output should not be empty"
);
println!("Search output: {}", result.output);
}
#[tokio::test]
#[ignore] async fn test_google_headless_search_actual() {
let executor = ToolExecutor::new("/tmp".to_string());
let headless_config = HeadlessConfig {
backend: BrowserBackend::Chrome,
max_tabs: 2,
browser_path: None,
launch_args: Vec::new(),
proxy_url: None,
};
let context = make_context(Some(headless_config));
let args = serde_json::json!({
"query": "rust programming language",
"engines": "google"
});
let result = executor
.execute_with_context("web_search", &args, &context)
.await
.unwrap();
assert_eq!(
result.exit_code, 0,
"Search should succeed, got exit_code={}, output={}",
result.exit_code, result.output
);
assert!(
!result.output.trim().is_empty(),
"Search output should not be empty"
);
println!("Search output: {}", result.output);
}
#[tokio::test]
#[ignore = "requires the controlled local HTTPS fixture and Chrome"]
async fn test_google_headless_search_controlled_local_cdp() {
let browser_path = env::var("A3S_HEADLESS_TEST_BROWSER")
.expect("A3S_HEADLESS_TEST_BROWSER must identify the workflow-managed Chrome binary");
assert_eq!(
env::var("A3S_HEADLESS_TEST_FIXTURE")
.expect("A3S_HEADLESS_TEST_FIXTURE must select the controlled fixture"),
"controlled-local-https-v1",
"the ignored qualification must never run against the public Google endpoint"
);
let marker = "controlled-cdp-v1";
let headless_config = HeadlessConfig {
backend: BrowserBackend::Chrome,
max_tabs: 1,
browser_path: Some(browser_path),
launch_args: vec![
"--ignore-certificate-errors".to_string(),
"--disable-background-networking".to_string(),
"--disable-quic".to_string(),
"--no-proxy-server".to_string(),
"--host-resolver-rules=MAP www.google.com 127.0.0.1".to_string(),
format!("--a3s-headless-fixture={marker}"),
],
proxy_url: None,
};
let context = make_context(Some(headless_config));
let executor = ToolExecutor::new("/tmp".to_string());
let output = executor
.execute_with_context(
"web_search",
&serde_json::json!({
"query": "a3s controlled cdp fixture",
"engines": ["google"],
"limit": 1,
"timeout": 15,
"format": "json"
}),
&context,
)
.await
.expect("controlled headless search must dispatch");
assert_eq!(output.exit_code, 0, "{}", output.output);
assert!(
output.output.contains("A3S Controlled CDP Fixture"),
"the production Google parser must return the controlled result: {}",
output.output
);
assert!(
output
.output
.contains("https://docs.a3s.dev/controlled-cdp-fixture"),
"the controlled result must retain its independently asserted source URL: {}",
output.output
);
}