use super::*;
use lean_ctx::core::context_field::{ContextItemId, ViewKind};
use lean_ctx::core::context_overlay::{
ContextOverlay, OverlayAuthor, OverlayOp, OverlayScope, OverlayStore,
};
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::RwLock;
fn isolated(test: &str) -> bool {
if std::env::var("LEAN_CTX_PINNED_READ_TEST").as_deref() == Ok(test) {
return false;
}
let directory = tempfile::tempdir().unwrap();
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
command
.args(["--exact", test, "--nocapture", "--test-threads=1"])
.current_dir(directory.path())
.env("LEAN_CTX_PINNED_READ_TEST", test)
.env("LEAN_CTX_ROLE", "coder")
.env("LEAN_CTX_PROFILE", "coder")
.env("LCTX_DELTA_EXPLICIT", "true")
.env("__LEAN_CTX_SKIP_EVENTS", "1")
.env("__LEAN_CTX_NO_DAEMON", "1");
for name in [
"LEAN_CTX_DATA_DIR",
"LEAN_CTX_CONFIG_DIR",
"LEAN_CTX_STATE_DIR",
"LEAN_CTX_CACHE_DIR",
] {
command.env(name, directory.path());
}
for name in [
"LEAN_CTX_PROJECT_ROOT",
"CLAUDE_PROJECT_DIR",
"WORKSPACE_FOLDER_PATHS",
"LEAN_CTX_RECEIPT_HOST_CONFIG",
] {
command.env_remove(name);
}
let child = {
let _environment = lean_ctx::core::data_dir::test_env_lock();
command
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.unwrap()
};
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stdout).contains("1 passed; 0 failed"),
"child must execute exactly the requested test"
);
true
}
fn context(root: &Path, path: &Path) -> ToolContext {
ToolContext {
project_root: root.to_string_lossy().into_owned(),
resolved_paths: HashMap::from([("path".into(), path.to_string_lossy().into_owned())]),
cache: Some(Arc::new(RwLock::new(
lean_ctx::core::cache::SessionCache::new(),
))),
session: Some(Arc::new(RwLock::new(
lean_ctx::core::session::SessionState::new(),
))),
..ToolContext::default()
}
}
fn set_view(root: &Path, path: &Path, view: ViewKind) {
let mut overlay = OverlayStore::new();
overlay.add(ContextOverlay::new(
ContextItemId::from_file(path.to_str().unwrap()),
OverlayOp::SetView(view),
OverlayScope::Project,
String::new(),
OverlayAuthor::User,
));
overlay.save_project(root).unwrap();
}
fn source() -> String {
use std::fmt::Write;
let mut source = String::new();
for i in 0..40 {
writeln!(
source,
"pub fn function_{i}() {{ let body_marker_{i} = {i}; }}"
)
.unwrap();
}
source
}
#[test]
fn concrete_modes_override_saved_view_preferences() {
if isolated(
"tools::registered::ctx_read::override_tests::concrete_modes_override_saved_view_preferences",
) {
return;
}
let _data = lean_ctx::core::data_dir::isolated_data_dir();
for mode in ["full", "map", "signatures", "aggressive"] {
let directory = tempfile::tempdir().unwrap();
let root = std::fs::canonicalize(directory.path()).unwrap();
let path = root.join("source.rs");
std::fs::write(&path, source()).unwrap();
set_view(
&root,
&path,
if mode == "full" {
ViewKind::Map
} else {
ViewKind::Full
},
);
let args = serde_json::json!({"path": path, "mode": mode});
let output = CtxReadTool
.handle(args.as_object().unwrap(), &context(&root, &path))
.unwrap();
assert_eq!(
output.mode.as_deref(),
Some(mode),
"explicit {mode}: {}",
output.text
);
assert!(!output.text.contains("[mode overridden:"));
}
}
#[test]
fn auto_mode_still_uses_saved_view_preferences() {
if isolated(
"tools::registered::ctx_read::override_tests::auto_mode_still_uses_saved_view_preferences",
) {
return;
}
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let directory = tempfile::tempdir().unwrap();
let root = std::fs::canonicalize(directory.path()).unwrap();
let path = root.join("source.rs");
std::fs::write(&path, source()).unwrap();
set_view(&root, &path, ViewKind::Signatures);
let args = serde_json::json!({"path": path, "mode": "auto"});
let output = CtxReadTool
.handle(args.as_object().unwrap(), &context(&root, &path))
.unwrap();
assert_eq!(output.mode.as_deref(), Some("signatures"));
}
#[test]
fn explicit_full_reread_is_not_replaced_by_automatic_delta() {
if isolated(
"tools::registered::ctx_read::override_tests::explicit_full_reread_is_not_replaced_by_automatic_delta",
) {
return;
}
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let directory = tempfile::tempdir().unwrap();
let root = std::fs::canonicalize(directory.path()).unwrap();
let path = root.join("source.rs");
let original = source();
std::fs::write(&path, &original).unwrap();
let ctx = context(&root, &path);
let args = serde_json::json!({"path": path, "mode": "full"});
CtxReadTool.handle(args.as_object().unwrap(), &ctx).unwrap();
std::fs::write(
&path,
original.replace("body_marker_20 = 20", "changed_marker_20 = 2000"),
)
.unwrap();
let output = CtxReadTool.handle(args.as_object().unwrap(), &ctx).unwrap();
assert_eq!(output.mode.as_deref(), Some("full"));
assert!(output.text.contains("changed_marker_20"));
assert!(output.text.contains("body_marker_0"));
assert!(output.text.contains("body_marker_39"));
}
fn functions(count: usize) -> String {
use std::fmt::Write;
let mut source = String::new();
for i in 0..count {
writeln!(
source,
"pub fn function_{i}() {{\n let body_marker_{i} = {i};\n}}"
)
.unwrap();
}
source
}
#[test]
fn unranged_reads_outline_large_files_and_ranges_stay_exact() {
if isolated(
"tools::registered::ctx_read::override_tests::unranged_reads_outline_large_files_and_ranges_stay_exact",
) {
return;
}
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let directory = tempfile::tempdir().unwrap();
let root = std::fs::canonicalize(directory.path()).unwrap();
let large = root.join("large.rs");
std::fs::write(&large, functions(120)).unwrap();
let read = |ctx: &ToolContext, extra: serde_json::Value| {
let mut args = serde_json::json!({"path": large});
args.as_object_mut()
.unwrap()
.extend(extra.as_object().unwrap().clone());
CtxReadTool
.handle(args.as_object().unwrap(), ctx)
.unwrap()
.text
};
let is_outline = |text: &str| {
text.contains("function_119") && text.contains("@L") && !text.contains("body_marker_")
};
let ctx = context(&root, &large);
let first = read(&ctx, serde_json::json!({}));
assert!(is_outline(&first), "no mode → outline: {first}");
let again = read(&context(&root, &large), serde_json::json!({}));
assert_eq!(first, again);
for extra in [
serde_json::json!({"mode": "auto"}),
serde_json::json!({"mode": "auto", "fresh": true}),
] {
let text = read(&context(&root, &large), extra.clone());
assert!(is_outline(&text), "{extra} → outline: {text}");
}
for extra in [
serde_json::json!({"mode": "lines:4-6"}),
serde_json::json!({"lines": "4-6"}),
serde_json::json!({"lines": "4:6"}),
serde_json::json!({"lines": [4, 6]}),
serde_json::json!({"lines": 4, "limit": 3}),
serde_json::json!({"lines:4-6": "lines:4-6"}),
serde_json::json!({"mode": "anchored", "lines": "4-6"}),
serde_json::json!({"mode": "full", "lines": "4-6"}),
serde_json::json!({"symbol": "function_1"}),
] {
let text = read(&ctx, extra.clone());
assert!(
text.contains("body_marker_1 ") && !text.contains("body_marker_2 "),
"{extra} → lines 4-6 only: {text}"
);
}
let after_window = read(&ctx, serde_json::json!({}));
assert!(
is_outline(&after_window),
"re-read → outline: {after_window}"
);
let full = read(&ctx, serde_json::json!({"mode": "full"}));
assert!(full.contains("body_marker_0 ") && full.contains("body_marker_119 "));
let small = root.join("small.rs");
std::fs::write(&small, functions(40)).unwrap();
let args = serde_json::json!({"path": small});
let text = CtxReadTool
.handle(args.as_object().unwrap(), &context(&root, &small))
.unwrap()
.text;
assert!(
text.contains("body_marker_0 ") && text.contains("body_marker_39 "),
"120 lines → whole: {text}"
);
}
#[test]
fn edits_work_after_an_outline_read() {
if isolated("tools::registered::ctx_read::override_tests::edits_work_after_an_outline_read") {
return;
}
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let directory = tempfile::tempdir().unwrap();
let root = std::fs::canonicalize(directory.path()).unwrap();
let path = root.join("large.rs");
std::fs::write(&path, functions(120)).unwrap();
let ctx = context(&root, &path);
let call =
|tool: &dyn McpTool, args: serde_json::Value| tool.handle(args.as_object().unwrap(), &ctx);
let patch = crate::tools::registered::ctx_patch::CtxPatchTool;
let outline = call(&CtxReadTool, serde_json::json!({"path": path})).unwrap();
assert!(!outline.text.contains("body_marker_"), "{}", outline.text);
call(
&patch,
serde_json::json!({"path": path, "op": "replace_unique",
"old_text": "let body_marker_100 = 100;", "new_text": "let body_marker_100 = 1000;"}),
)
.unwrap();
let window = call(
&CtxReadTool,
serde_json::json!({"path": path, "mode": "anchored:5-5"}),
)
.unwrap()
.text;
let hash = window
.lines()
.find_map(|line| line.trim_start().strip_prefix("5:"))
.and_then(|rest| rest.split('|').next())
.unwrap_or_else(|| panic!("anchor for line 5: {window}"))
.to_string();
call(
&patch,
serde_json::json!({"path": path, "op": "set_line", "line": 5, "hash": hash,
"new_text": " let body_marker_1 = 11;"}),
)
.unwrap();
let source = std::fs::read_to_string(&path).unwrap();
assert!(source.contains("let body_marker_100 = 1000;"), "{source}");
assert!(source.contains("let body_marker_1 = 11;"), "{source}");
}