use reqwest::Client;
use rust_analyzer_cli::daemon::server::start_daemon_server;
use rust_analyzer_cli::lsp::types::*;
use std::net::TcpListener;
use std::path::PathBuf;
use tokio::time::{Duration, sleep};
const FIXTURE_FILE: &str = "tests/code_example.rs";
const FIXTURE_DEFINITION_LINE: u32 = 156;
const FIXTURE_DEFINITION_COL: u32 = 5;
fn get_free_port() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind free port");
let port = listener.local_addr().unwrap().port();
drop(listener);
port
}
#[tokio::test]
async fn test_daemon_full_workflow() {
let port = get_free_port();
let workspace = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let daemon_workspace = workspace.clone();
tokio::spawn(async move {
if let Err(e) = start_daemon_server(daemon_workspace, port).await {
eprintln!("Daemon server error: {}", e);
}
});
sleep(Duration::from_millis(500)).await;
let client = Client::new();
let base_url = format!("http://127.0.0.1:{}", port);
let status_res = client
.get(format!("{}/status", base_url))
.send()
.await
.expect("Failed to send /status request");
assert!(status_res.status().is_success());
let status: DaemonStatusResponse = status_res.json().await.unwrap();
let expected_root = workspace
.canonicalize()
.unwrap()
.to_string_lossy()
.to_string();
assert_eq!(
status.workspace_root.trim_start_matches(r"\\?\"),
expected_root.trim_start_matches(r"\\?\")
);
let mut symbol_found = false;
for _ in 0..20 {
let symbol_req = SymbolQueryRequest {
name: "ExampleStruct".to_string(),
kind: "any".to_string(),
exact: true,
};
let symbol_res = client
.post(format!("{}/api/symbol", base_url))
.json(&symbol_req)
.send()
.await
.expect("Failed to send /api/symbol request");
if symbol_res.status().is_success() {
let symbols: Vec<SymbolItem> = symbol_res.json().await.unwrap();
if symbols.iter().any(|s| s.name == "ExampleStruct") {
symbol_found = true;
break;
}
}
sleep(Duration::from_millis(500)).await;
}
assert!(
symbol_found,
"Expected at least one symbol for ExampleStruct"
);
let outline_req = OutlineQueryRequest {
file: PathBuf::from(FIXTURE_FILE),
};
let outline_res = client
.post(format!("{}/api/outline", base_url))
.json(&outline_req)
.send()
.await
.expect("Failed to send /api/outline request");
assert!(outline_res.status().is_success());
let outline: Vec<OutlineItem> = outline_res.json().await.unwrap();
assert!(
!outline.is_empty(),
"Expected outline items for code_example"
);
assert!(outline.iter().any(|item| item.name == "ExampleStruct"));
assert!(outline.iter().any(|item| item.name == "long_example"));
let def_req = DefinitionQueryRequest {
file: PathBuf::from(FIXTURE_FILE),
line: FIXTURE_DEFINITION_LINE,
col: FIXTURE_DEFINITION_COL,
};
let def_res = client
.post(format!("{}/api/definition", base_url))
.json(&def_req)
.send()
.await
.expect("Failed to send /api/definition request");
assert!(def_res.status().is_success());
let definitions: Vec<DefinitionItem> = def_res.json().await.unwrap();
assert!(
!definitions.is_empty(),
"Expected definition for long_example"
);
assert!(
definitions
.iter()
.any(|item| item.line < FIXTURE_DEFINITION_LINE)
);
let body_req = BodyQueryRequest {
file: PathBuf::from(FIXTURE_FILE),
line: FIXTURE_DEFINITION_LINE,
col: FIXTURE_DEFINITION_COL,
max_lines: 2,
};
let body_res = client
.post(format!("{}/api/body", base_url))
.json(&body_req)
.send()
.await
.expect("Failed to send /api/body request");
assert!(body_res.status().is_success());
let body_item: BodyItem = body_res.json().await.unwrap();
assert!(
!body_item.body.is_empty(),
"Expected non-empty body for long_example"
);
assert_eq!(body_item.body.lines().count(), 2);
assert!(body_item.total_lines > 100);
assert!(body_item.is_truncated);
let default_body_res = client
.post(format!("{}/api/body", base_url))
.json(&serde_json::json!({
"file": FIXTURE_FILE,
"line": FIXTURE_DEFINITION_LINE,
"col": FIXTURE_DEFINITION_COL,
}))
.send()
.await
.expect("Failed to send default /api/body request");
assert!(default_body_res.status().is_success());
let default_body: BodyItem = default_body_res.json().await.unwrap();
assert_eq!(default_body.body.lines().count(), 100);
assert!(default_body.is_truncated);
let hover_req = HoverQueryRequest {
file: PathBuf::from(FIXTURE_FILE),
line: FIXTURE_DEFINITION_LINE,
col: FIXTURE_DEFINITION_COL,
};
let hover_res = client
.post(format!("{}/api/hover", base_url))
.json(&hover_req)
.send()
.await
.expect("Failed to send /api/hover request");
assert!(hover_res.status().is_success());
let hover: Option<HoverItem> = hover_res.json().await.unwrap();
let hover = hover.expect("Expected hover documentation for long_example");
assert!(
hover
.contents
.contains("Runs a long example for Hover tests.")
);
assert!(hover.contents.contains("```rust"));
assert!(hover.range.is_some());
let empty_hover_res = client
.post(format!("{}/api/hover", base_url))
.json(&HoverQueryRequest {
file: PathBuf::from(FIXTURE_FILE),
line: 4,
col: 1,
})
.send()
.await
.expect("Failed to send empty /api/hover request");
assert!(empty_hover_res.status().is_success());
let empty_hover: Option<HoverItem> = empty_hover_res.json().await.unwrap();
assert!(empty_hover.is_none());
let cursor_depth_one = client
.post(format!("{}/api/cursor", base_url))
.json(&CursorQueryRequest {
file: PathBuf::from(FIXTURE_FILE),
line: 167,
col: 8,
mode: "outgoing".to_string(),
depth: 1,
})
.send()
.await
.expect("Failed to send depth-one cursor request");
assert!(cursor_depth_one.status().is_success());
let cursor_depth_one: Vec<CursorItem> = cursor_depth_one.json().await.unwrap();
assert!(
cursor_depth_one
.iter()
.any(|item| item.name == "depth_middle")
);
assert!(
!cursor_depth_one
.iter()
.any(|item| item.name == "depth_leaf")
);
let cursor_depth_two = client
.post(format!("{}/api/cursor", base_url))
.json(&CursorQueryRequest {
file: PathBuf::from(FIXTURE_FILE),
line: 167,
col: 8,
mode: "outgoing".to_string(),
depth: 2,
})
.send()
.await
.expect("Failed to send depth-two cursor request");
assert!(cursor_depth_two.status().is_success());
let cursor_depth_two: Vec<CursorItem> = cursor_depth_two.json().await.unwrap();
let mut cursor_keys = std::collections::HashSet::new();
for item in &cursor_depth_two {
assert!(cursor_keys.insert((item.file.clone(), item.line, item.col)));
}
assert!(
cursor_depth_two
.iter()
.any(|item| item.name == "depth_leaf")
);
assert!(cursor_depth_two.len() >= cursor_depth_one.len());
let check_req = CheckQueryRequest { target: None };
let check_res = client
.post(format!("{}/api/check", base_url))
.json(&check_req)
.send()
.await
.expect("Failed to send /api/check request");
assert!(check_res.status().is_success());
let check: CheckResponse = check_res.json().await.unwrap();
assert!(check.success);
}