use super::super::test_helpers::{make_scope, make_server_with_lawyer, make_temp_workspace};
use super::*;
use crate::server::types::LocateParams;
use crate::server::PathfinderServer;
use pathfinder_common::config::PathfinderConfig;
use pathfinder_common::sandbox::Sandbox;
use pathfinder_common::types::{DegradedReason, WorkspaceRoot};
use pathfinder_lsp::{DefinitionLocation, MockLawyer};
use pathfinder_search::MockScout;
use pathfinder_treesitter::mock::MockSurgeon;
use std::sync::Arc;
fn unpack_def(res: rmcp::model::CallToolResult) -> crate::server::types::GetDefinitionResponse {
serde_json::from_value(res.structured_content.expect("structured_content")).unwrap()
}
#[tokio::test]
async fn test_get_definition_routes_to_lawyer_success() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let lawyer = Arc::new(MockLawyer::default());
lawyer.set_goto_definition_result(Ok(Some(DefinitionLocation {
file: "src/auth.rs".into(),
line: 42,
column: 5,
preview: "pub fn login() -> bool {".into(),
})));
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer.clone());
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let call_res = result.expect("should succeed");
let val = unpack_def(call_res);
assert_eq!(val.file, "src/auth.rs");
assert_eq!(val.line, 42);
assert_eq!(val.preview, "pub fn login() -> bool {");
assert!(!val.degraded);
assert_eq!(lawyer.goto_definition_call_count(), 1);
}
#[tokio::test]
async fn test_get_definition_degrades_when_no_lsp() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let lawyer = Arc::new(pathfinder_lsp::NoOpLawyer);
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
let server = PathfinderServer::with_all_engines(
ws,
config,
sandbox,
Arc::new(MockScout::default()),
surgeon,
lawyer,
);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Err(err) = result else {
panic!("expected error but got Ok");
};
let code = err
.data
.as_ref()
.and_then(|d| d.get("error"))
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(code, "NO_LSP_AVAILABLE");
}
#[tokio::test]
async fn test_get_definition_rejects_empty_semantic_path() {
let surgeon = Arc::new(MockSurgeon::default());
let lawyer = Arc::new(MockLawyer::default());
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer);
let params = LocateParams {
semantic_path: Some(String::default()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_get_definition_rejects_sandbox_denied_path() {
let surgeon = Arc::new(MockSurgeon::new());
let lawyer = Arc::new(MockLawyer::default());
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer);
let params = LocateParams {
semantic_path: Some(".git/objects/abc::def".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Err(err) = result else {
panic!("expected error but got Ok");
};
let code = err
.data
.as_ref()
.and_then(|d| d.get("error"))
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(code, "ACCESS_DENIED");
}
#[tokio::test]
async fn test_get_definition_lsp_error_no_grep_match_returns_lsp_error() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let lawyer = Arc::new(MockLawyer::default());
lawyer.set_goto_definition_result(Err(LspError::Protocol("LSP protocol error".to_string())));
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Err(err) = result else {
panic!("expected error but got Ok");
};
let code = err
.data
.as_ref()
.and_then(|d| d.get("error"))
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(code, "LSP_ERROR");
}
#[tokio::test]
async fn test_get_definition_generic_lsp_error_falls_back_to_grep() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
std::fs::create_dir_all(ws_dir.path().join("src")).unwrap();
std::fs::write(
ws_dir.path().join("src/auth.rs"),
"fn login() -> bool { true }",
)
.unwrap();
let scout = Arc::new(MockScout::default());
scout.set_result(Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/auth.rs".to_string(),
line: 1,
column: 1,
content: "fn login() -> bool { true }".to_string(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
is_definition: None,
version_hash: "sha256:abc".to_string(),
known: Some(false),
}],
total_matches: 1,
truncated: false,
files_searched: 0,
files_in_scope: 0,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}));
let lawyer = Arc::new(MockLawyer::default());
lawyer.set_goto_definition_result(Err(LspError::Protocol("protocol violation".to_string())));
let server = PathfinderServer::with_all_engines(ws, config, sandbox, scout, surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Ok(res) = result else {
panic!("expected Ok with grep fallback, got Err");
};
let val = unpack_def(res);
assert!(val.degraded, "should be degraded");
assert_eq!(val.file, "src/auth.rs");
assert_eq!(
val.degraded_reason,
Some(DegradedReason::LspErrorGrepFallback),
"degraded_reason should be lsp_error_grep_fallback: {:?}",
val.degraded_reason
);
}
#[tokio::test]
async fn test_get_definition_connection_lost_falls_back_to_grep() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
std::fs::create_dir_all(ws_dir.path().join("src")).unwrap();
std::fs::write(
ws_dir.path().join("src/auth.rs"),
"fn login() -> bool { true }",
)
.unwrap();
let scout = Arc::new(MockScout::default());
scout.set_result(Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/auth.rs".to_string(),
line: 1,
column: 1,
content: "fn login() -> bool { true }".to_string(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
is_definition: None,
version_hash: "sha256:abc".to_string(),
known: Some(false),
}],
total_matches: 1,
truncated: false,
files_searched: 0,
files_in_scope: 0,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}));
let lawyer = Arc::new(MockLawyer::default());
lawyer.set_goto_definition_result(Err(LspError::ConnectionLost));
let server = PathfinderServer::with_all_engines(ws, config, sandbox, scout, surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Ok(res) = result else {
panic!("expected Ok with grep fallback, got Err");
};
let val = unpack_def(res);
assert!(val.degraded, "should be degraded");
assert_eq!(
val.degraded_reason,
Some(DegradedReason::LspErrorGrepFallback),
"degraded_reason: {:?}",
val.degraded_reason
);
}
#[tokio::test]
async fn test_get_definition_lsp_none_no_grep_fallback_returns_symbol_not_found() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
surgeon
.extract_symbols_results
.lock()
.unwrap()
.push(Ok(Vec::new()));
let lawyer = Arc::new(MockLawyer::default());
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Err(err) = result else {
panic!("expected error but got Ok");
};
let code = err
.data
.as_ref()
.and_then(|d| d.get("error"))
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(code, "SYMBOL_NOT_FOUND");
}
#[tokio::test]
async fn test_get_definition_grep_fallback_with_mock_scout() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let _lawyer = Arc::new(MockLawyer::default());
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
std::fs::create_dir_all(ws_dir.path().join("src")).unwrap();
std::fs::write(
ws_dir.path().join("src/other.rs"),
"fn login() -> bool { true }",
)
.unwrap();
let scout = Arc::new(MockScout::default());
scout.set_result(Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/other.rs".to_string(),
line: 1,
column: 1,
content: "fn login() -> bool { true }".to_string(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
is_definition: None,
version_hash: "sha256:abc".to_string(),
known: Some(false),
}],
total_matches: 1,
truncated: false,
files_searched: 0,
files_in_scope: 0,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}));
let server = PathfinderServer::with_all_engines(
ws,
config,
sandbox,
scout,
surgeon,
Arc::new(pathfinder_lsp::NoOpLawyer),
);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Ok(res) = result else {
panic!("expected Ok with grep fallback, got Err");
};
let val = unpack_def(res);
assert!(val.degraded);
assert_eq!(val.file, "src/other.rs");
assert!(val
.degraded_reason
.as_ref()
.unwrap()
.to_string()
.contains("grep_fallback"));
}
#[tokio::test]
async fn test_get_definition_closes_document_on_success() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let lawyer = Arc::new(MockLawyer::default());
lawyer.set_goto_definition_result(Ok(Some(DefinitionLocation {
file: "src/auth.rs".into(),
line: 42,
column: 5,
preview: "pub fn login() -> bool {".into(),
})));
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer.clone());
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let _ = server.get_definition_impl(params).await;
tokio::task::yield_now().await;
assert_eq!(
lawyer.did_open_call_count(),
lawyer.did_close_call_count(),
"DS-1: did_open and did_close must be symmetric on success"
);
}
#[tokio::test]
async fn test_get_definition_closes_document_on_lsp_error() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let lawyer = Arc::new(MockLawyer::default());
lawyer.set_goto_definition_result(Err(LspError::Protocol("LSP crashed".to_string())));
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer.clone());
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let _ = server.get_definition_impl(params).await;
tokio::task::yield_now().await;
assert_eq!(
lawyer.did_open_call_count(),
lawyer.did_close_call_count(),
"DS-1: did_close must be called even when LSP returns an error"
);
}
#[tokio::test]
async fn test_get_definition_returns_did_you_mean_suggestions_on_symbol_not_found() {
use pathfinder_treesitter::surgeon::{ExtractedSymbol, SymbolKind};
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let symbols = vec![
ExtractedSymbol {
name: "logIn".to_owned(),
semantic_path: "logIn".to_owned(),
kind: SymbolKind::Function,
byte_range: 0..5,
start_line: 0,
end_line: 0,
name_column: 0,
access_level: pathfinder_treesitter::surgeon::AccessLevel::Public,
children: vec![],
},
ExtractedSymbol {
name: "logon".to_owned(),
semantic_path: "logon".to_owned(),
kind: SymbolKind::Function,
byte_range: 10..15,
start_line: 1,
end_line: 1,
name_column: 0,
access_level: pathfinder_treesitter::surgeon::AccessLevel::Public,
children: vec![],
},
];
surgeon
.extract_symbols_results
.lock()
.unwrap()
.push(Ok(symbols));
let lawyer = Arc::new(MockLawyer::default());
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Err(err) = result else {
panic!("expected SYMBOL_NOT_FOUND error, got Ok");
};
let code = err
.data
.as_ref()
.and_then(|d| d.get("error"))
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(
code, "SYMBOL_NOT_FOUND",
"error code must be SYMBOL_NOT_FOUND"
);
let suggestions = err
.data
.as_ref()
.and_then(|d| d.get("details"))
.and_then(|d| d.get("did_you_mean"))
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
assert!(
!suggestions.is_empty(),
"did_you_mean must contain suggestions when similar symbols exist"
);
let has_login_variant = suggestions
.iter()
.any(|s| s.as_str().is_some_and(|s| s.contains("log")));
assert!(
has_login_variant,
"suggestions should include close matches like 'logIn' or 'logon', got: {suggestions:?}"
);
}
#[tokio::test]
async fn test_get_definition_grep_fallback_when_lsp_returns_none() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let lawyer = Arc::new(MockLawyer::default());
let scout = Arc::new(MockScout::default());
scout.set_result(Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/auth.rs".to_owned(),
line: 10,
column: 4,
content: "pub fn login() -> bool {".to_owned(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: Some("src/auth.rs::login".to_owned()),
is_definition: Some(true),
version_hash: "hash".to_owned(),
known: None,
}],
total_matches: 1,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}));
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
let server = PathfinderServer::with_all_engines(ws, config, sandbox, scout, surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let call_res = result.expect("should succeed via grep fallback");
let val = unpack_def(call_res);
assert_eq!(val.file, "src/auth.rs");
assert_eq!(val.line, 10);
assert!(val.degraded, "should be degraded when using grep fallback");
assert!(val.degraded_reason.is_some(), "degraded_reason must be set");
}
#[tokio::test]
async fn test_get_definition_grep_fallback_when_no_lsp() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let lawyer = Arc::new(pathfinder_lsp::NoOpLawyer);
let scout = Arc::new(MockScout::default());
scout.set_result(Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/auth.rs".to_owned(),
line: 10,
column: 4,
content: "pub fn login() -> bool {".to_owned(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: Some("src/auth.rs::login".to_owned()),
is_definition: Some(true),
version_hash: "hash".to_owned(),
known: None,
}],
total_matches: 1,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}));
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
let server = PathfinderServer::with_all_engines(ws, config, sandbox, scout, surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let call_res = result.expect("should succeed via grep fallback");
let val = unpack_def(call_res);
assert_eq!(val.file, "src/auth.rs");
assert_eq!(val.line, 10);
assert!(val.degraded, "should be degraded when using grep fallback");
}
#[tokio::test]
async fn test_get_definition_lsp_timeout_falls_back_to_grep() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
std::fs::create_dir_all(ws_dir.path().join("src")).unwrap();
std::fs::write(
ws_dir.path().join("src/auth.rs"),
"pub fn login() -> bool { true }",
)
.unwrap();
let scout = Arc::new(MockScout::default());
scout.set_result(Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/auth.rs".to_string(),
line: 1,
column: 1,
content: "pub fn login() -> bool { true }".to_string(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
is_definition: None,
version_hash: "sha256:abc".to_string(),
known: Some(false),
}],
total_matches: 1,
truncated: false,
files_searched: 0,
files_in_scope: 0,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}));
let lawyer = Arc::new(MockLawyer::default());
lawyer.set_goto_definition_result(Err(LspError::Timeout {
operation: "goto_definition".to_string(),
timeout_ms: 10000,
}));
let server = PathfinderServer::with_all_engines(ws, config, sandbox, scout, surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Ok(res) = result else {
panic!("expected Ok with grep fallback after timeout, got Err");
};
let val = unpack_def(res);
assert!(val.degraded, "should be degraded");
assert_eq!(val.file, "src/auth.rs");
assert_eq!(
val.degraded_reason,
Some(DegradedReason::LspTimeoutGrepFallback),
"degraded_reason should be LspTimeoutGrepFallback: {:?}",
val.degraded_reason
);
}
#[tokio::test]
async fn test_get_definition_multi_strategy_fallback() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
std::fs::create_dir_all(ws_dir.path().join("src")).unwrap();
std::fs::write(
ws_dir.path().join("src/other.rs"),
"pub fn login() -> bool { true }",
)
.unwrap();
let scout = Arc::new(MockScout::default());
scout.set_results(vec![
Ok(pathfinder_search::SearchResult {
matches: vec![],
total_matches: 0,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}),
Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/other.rs".to_string(),
line: 1,
column: 1,
content: "pub fn login() -> bool { true }".to_string(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
is_definition: None,
version_hash: "sha256:abc".to_string(),
known: Some(false),
}],
total_matches: 1,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}),
]);
let server = PathfinderServer::with_all_engines(
ws,
config,
sandbox,
scout,
surgeon,
Arc::new(pathfinder_lsp::NoOpLawyer),
);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Ok(res) = result else {
panic!("expected Ok with multi-strategy grep fallback, got Err");
};
let val = unpack_def(res);
assert!(val.degraded, "should be degraded");
assert_eq!(val.file, "src/other.rs");
assert_eq!(
val.degraded_reason,
Some(DegradedReason::NoLspGrepFallback),
"degraded_reason: {:?}",
val.degraded_reason
);
}
#[tokio::test]
async fn test_get_definition_warmup_retry_success() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let lawyer = Arc::new(MockLawyer::default());
lawyer.push_goto_definition_result(Ok(None));
lawyer.set_goto_definition_result(Ok(Some(DefinitionLocation {
file: "src/auth.rs".into(),
line: 42,
column: 5,
preview: "pub fn login() -> bool {".into(),
})));
let (server, _ws) = make_server_with_lawyer(surgeon, lawyer);
let params = LocateParams {
semantic_path: Some("src/auth.rs::login".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let call_res = result.expect("should succeed on retry");
let val = unpack_def(call_res);
assert_eq!(val.file, "src/auth.rs");
assert_eq!(val.line, 42);
assert!(!val.degraded, "should NOT be degraded on retry success");
assert_eq!(
val.resolution_strategy,
Some("lsp_retry".to_owned()),
"should indicate retry strategy"
);
assert_eq!(
val.lsp_readiness,
Some("warming_up".to_owned()),
"should indicate warming_up"
);
assert_eq!(
val.warm_start_in_progress,
Some(true),
"should indicate warm_start_in_progress"
);
}
#[tokio::test]
async fn test_get_definition_grep_fallback_with_two_segment_symbol() {
let surgeon = Arc::new(MockSurgeon::new());
let mut scope = make_scope();
scope.content = "pub fn my_method(&self) { ... }".to_string();
surgeon.read_symbol_scope_results.lock().unwrap().clear();
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(scope));
let scout = Arc::new(MockScout::default());
scout.set_result(Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/mystruct.rs".to_string(),
line: 10,
column: 4,
content: "pub fn my_method(&self) {}".to_string(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
is_definition: None,
version_hash: "sha256:def".to_string(),
known: Some(false),
}],
total_matches: 1,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}));
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
std::fs::write(
ws_dir.path().join("src/mystruct.rs"),
"impl MyStruct { pub fn my_method(&self) {} }",
)
.unwrap();
let server = PathfinderServer::with_all_engines(
ws,
config,
sandbox,
scout,
surgeon,
Arc::new(pathfinder_lsp::NoOpLawyer),
);
let params = LocateParams {
semantic_path: Some("src/mystruct.rs::MyStruct.my_method".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
match &result {
Ok(res) => {
let val = unpack_def(res.clone());
assert!(val.degraded, "should be degraded");
assert_eq!(val.file, "src/mystruct.rs");
assert_eq!(val.line, 10);
assert!(
val.degraded_reason.is_some(),
"degraded_reason should be set"
);
assert_eq!(
val.degraded_reason,
Some(DegradedReason::NoLspGrepFallback),
"degraded_reason: {:?}",
val.degraded_reason
);
}
Err(err) => {
let code = err
.data
.as_ref()
.and_then(|d| d.get("error"))
.and_then(|v| v.as_str())
.unwrap_or("");
panic!("expected Ok with grep fallback, got Err({code}): {err:?}");
}
}
}
#[tokio::test]
#[allow(clippy::too_many_lines)]
async fn test_get_definition_grep_impl_method_strategy() {
let surgeon = Arc::new(MockSurgeon::new());
surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(make_scope()));
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
std::fs::create_dir_all(ws_dir.path().join("src")).unwrap();
std::fs::write(
ws_dir.path().join("src/sandbox.rs"),
"impl Sandbox {\n pub fn check(&self) -> bool { true }\n}",
)
.unwrap();
let scout = Arc::new(MockScout::default());
scout.set_results(vec![
Ok(pathfinder_search::SearchResult {
matches: vec![],
total_matches: 0,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}),
Ok(pathfinder_search::SearchResult {
matches: vec![],
total_matches: 0,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}),
Ok(pathfinder_search::SearchResult {
matches: vec![],
total_matches: 0,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}),
Ok(pathfinder_search::SearchResult {
matches: vec![],
total_matches: 0,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}),
Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/sandbox.rs".to_string(),
line: 1,
column: 1,
content: "impl Sandbox {".to_string(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
is_definition: None,
version_hash: "sha256:abc".to_string(),
known: Some(false),
}],
total_matches: 1,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}),
Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/sandbox.rs".to_string(),
line: 2,
column: 4,
content: "pub fn check(&self) -> bool { true }".to_string(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
is_definition: None,
version_hash: "sha256:def".to_string(),
known: Some(false),
}],
total_matches: 1,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}),
]);
let server = PathfinderServer::with_all_engines(
ws,
config,
sandbox,
scout,
surgeon,
Arc::new(pathfinder_lsp::NoOpLawyer),
);
let params = LocateParams {
semantic_path: Some("src/sandbox.rs::Sandbox.check".to_owned()),
..Default::default()
};
let result = server.get_definition_impl(params).await;
let Ok(res) = result else {
panic!("expected Ok with grep_impl_method fallback, got Err");
};
let val = unpack_def(res);
assert!(val.degraded, "should be degraded");
assert_eq!(val.file, "src/sandbox.rs");
assert_eq!(val.line, 2);
assert_eq!(
val.degraded_reason,
Some(DegradedReason::GrepFallbackImplScoped),
"degraded_reason should be GrepFallbackImplScoped, got {:?}",
val.degraded_reason
);
assert_eq!(
val.resolution_strategy,
Some("grep_impl".to_owned()),
"resolution_strategy should be grep_impl"
);
}