Files
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

194 lines
5.4 KiB
Rust

use serde_json::json;
use std::fs;
use std::path::PathBuf;
fn test_project_dir() -> PathBuf {
let dir = std::env::temp_dir().join("lean-ctx-lsp-test");
let src = dir.join("src");
fs::create_dir_all(&src).unwrap();
fs::write(
dir.join("Cargo.toml"),
r#"[package]
name = "lsp-test"
version = "0.1.0"
edition = "2021"
"#,
)
.unwrap();
fs::write(
src.join("main.rs"),
r#"fn greet(name: &str) -> String {
format!("Hello, {name}!")
}
fn main() {
let msg = greet("world");
println!("{msg}");
let msg2 = greet("lean-ctx");
println!("{msg2}");
}
"#,
)
.unwrap();
dir
}
fn has_rust_analyzer() -> bool {
std::process::Command::new("rust-analyzer")
.arg("--version")
.output()
.is_ok_and(|o| o.status.success())
}
fn call_refactor(args: &serde_json::Value, root: &str) -> String {
let abs_path = args
.get("path")
.and_then(|v| v.as_str())
.unwrap_or_default();
lean_ctx::tools::ctx_refactor::handle(args, root, abs_path)
}
fn wait_for_lsp_ready(dir: &std::path::Path, root: &str) {
let path = dir.join("src/main.rs").to_string_lossy().to_string();
for i in 0..15 {
let result = call_refactor(
&json!({"action": "definition", "path": &path, "line": 6, "column": 14}),
root,
);
if !result.contains("No results") && !result.contains("length: 0") {
eprintln!("LSP ready after {i} retries");
return;
}
std::thread::sleep(std::time::Duration::from_secs(2));
}
eprintln!("WARNING: LSP did not become fully ready within 30s");
}
#[test]
#[ignore = "requires rust-analyzer in PATH"]
fn test_lsp_definition() {
assert!(has_rust_analyzer(), "rust-analyzer not found in PATH");
let dir = test_project_dir();
let root = dir.to_string_lossy().to_string();
wait_for_lsp_ready(&dir, &root);
let path = dir.join("src/main.rs").to_string_lossy().to_string();
let result = call_refactor(
&json!({"action": "definition", "path": &path, "line": 6, "column": 14}),
&root,
);
eprintln!("definition result: {result}");
assert!(!result.starts_with("ERROR:"), "definition failed: {result}");
assert!(
result.contains("main.rs:1") || result.contains("location"),
"expected greet definition at line 1, got: {result}"
);
}
#[test]
#[ignore = "requires rust-analyzer in PATH"]
fn test_lsp_references() {
assert!(has_rust_analyzer(), "rust-analyzer not found in PATH");
let dir = test_project_dir();
let root = dir.to_string_lossy().to_string();
wait_for_lsp_ready(&dir, &root);
let path = dir.join("src/main.rs").to_string_lossy().to_string();
let result = call_refactor(
&json!({"action": "references", "path": &path, "line": 1, "column": 3}),
&root,
);
eprintln!("references result: {result}");
assert!(!result.starts_with("ERROR:"), "references failed: {result}");
}
#[test]
fn test_lsp_missing_path() {
// §4.5: the inner handle no longer guards `path` presence — that check now
// lives in the wrapper (require_resolved_path, unit-tested in tool_trait).
// Here `path` is absent, so call_refactor forwards an empty abs_path; the
// inner handle must still degrade gracefully to an ERROR (never panic) when
// open_file cannot resolve a language/file for it.
let result = call_refactor(
&json!({"action": "definition", "line": 1}),
"/tmp/nonexistent",
);
assert!(
result.starts_with("ERROR"),
"expected graceful error for empty path, got: {result}"
);
}
#[test]
#[ignore = "requires rust-analyzer in PATH"]
fn test_lsp_unknown_action() {
assert!(has_rust_analyzer(), "rust-analyzer not found in PATH");
let dir = test_project_dir();
let path = dir.join("src/main.rs").to_string_lossy().to_string();
let result = call_refactor(
&json!({"action": "foobar", "path": &path, "line": 1}),
dir.to_str().unwrap(),
);
assert!(
result.contains("Unknown action"),
"expected unknown action error, got: {result}"
);
}
#[test]
fn test_lsp_unsupported_extension() {
let dir = std::env::temp_dir().join("lean-ctx-lsp-test-ext");
fs::create_dir_all(&dir).unwrap();
let test_file = dir.join("test.xyz");
fs::write(&test_file, "content").unwrap();
let result = call_refactor(
&json!({
"action": "definition",
"path": test_file.to_string_lossy().to_string(),
"line": 1,
"column": 0
}),
dir.to_str().unwrap(),
);
assert!(
result.contains("ERROR") && result.contains("extension"),
"expected unsupported extension error, got: {result}"
);
}
#[test]
#[ignore = "requires rust-analyzer in PATH"]
fn test_lsp_timeout_not_triggered_on_valid_request() {
assert!(has_rust_analyzer(), "rust-analyzer not found in PATH");
let dir = test_project_dir();
let root = dir.to_string_lossy().to_string();
let path = dir.join("src/main.rs").to_string_lossy().to_string();
let result = call_refactor(
&json!({"action": "definition", "path": &path, "line": 1, "column": 3}),
&root,
);
eprintln!("timeout test result: {result}");
assert!(
!result.contains("timeout"),
"should not timeout on valid server, got: {result}"
);
}