use super::*;
use std::time::Duration;
#[test]
fn test_header_toon_format_no_brackets() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var("LEAN_CTX_META", "1");
let content = "use std::io;\nfn main() {}\n";
let header = build_header("F1", "main.rs", "rs", content, 2, false);
assert!(!header.contains('['));
assert!(!header.contains(']'));
assert!(header.contains("F1=main.rs 2L"));
crate::test_env::remove_var("LEAN_CTX_META");
}
#[test]
fn test_header_toon_deps_indented() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var("LEAN_CTX_META", "1");
let content = "use crate::core::cache;\nuse crate::tools;\npub fn main() {}\n";
let header = build_header("F1", "main.rs", "rs", content, 3, true);
if header.contains("deps") {
assert!(
header.contains("\n deps "),
"deps should use indented TOON format"
);
assert!(
!header.contains("deps:["),
"deps should not use bracket format"
);
}
crate::test_env::remove_var("LEAN_CTX_META");
}
#[test]
fn test_header_toon_saves_tokens() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var("LEAN_CTX_META", "1");
let content = "use crate::foo;\nuse crate::bar;\npub fn baz() {}\npub fn qux() {}\n";
let old_header = "F1=main.rs [4L +] deps:[foo,bar] exports:[baz,qux]".to_string();
let new_header = build_header("F1", "main.rs", "rs", content, 4, true);
let old_tokens = count_tokens(&old_header);
let new_tokens = count_tokens(&new_header);
assert!(
new_tokens <= old_tokens,
"TOON header ({new_tokens} tok) should be <= old format ({old_tokens} tok)"
);
crate::test_env::remove_var("LEAN_CTX_META");
}
#[test]
fn test_tdd_symbols_are_compact() {
let symbols = [
"⊕", "⊖", "∆", "→", "⇒", "✓", "✗", "⚠", "λ", "§", "∂", "τ", "ε",
];
for sym in &symbols {
let tok = count_tokens(sym);
assert!(tok <= 2, "Symbol {sym} should be 1-2 tokens, got {tok}");
}
}
#[test]
fn test_task_mode_filters_content() {
let content = (0..200)
.map(|i| {
if i % 20 == 0 {
format!("fn validate_token(token: &str) -> bool {{ /* line {i} */ }}")
} else {
format!("fn unrelated_helper_{i}(x: i32) -> i32 {{ x + {i} }}")
}
})
.collect::<Vec<_>>()
.join("\n");
let full_tokens = count_tokens(&content);
let task = Some("fix bug in validate_token");
let (result, result_tokens) = process_mode(
&content,
"task",
"F1",
"test.rs",
"rs",
full_tokens,
CrpMode::Off,
"test.rs",
task,
);
assert!(
result_tokens < full_tokens,
"task mode ({result_tokens} tok) should be less than full ({full_tokens} tok)"
);
assert!(
result.contains("task-filtered"),
"output should contain task-filtered marker"
);
}
#[test]
fn test_task_mode_without_task_returns_full() {
let content = "fn main() {}\nfn helper() {}\n";
let tokens = count_tokens(content);
let (result, _sent) = process_mode(
content,
"task",
"F1",
"test.rs",
"rs",
tokens,
CrpMode::Off,
"test.rs",
None,
);
assert!(
result.contains("no task set"),
"should indicate no task: {result}"
);
}
#[test]
fn test_reference_mode_one_line() {
let content = "fn main() {}\nfn helper() {}\nfn other() {}\n";
let tokens = count_tokens(content);
let (result, _sent) = process_mode(
content,
"reference",
"F1",
"test.rs",
"rs",
tokens,
CrpMode::Off,
"test.rs",
None,
);
let lines: Vec<&str> = result.lines().collect();
assert!(
lines.len() <= 3,
"reference mode should be very compact, got {} lines",
lines.len()
);
assert!(result.contains("lines"), "should contain line count");
assert!(result.contains("tok"), "should contain token count");
}
#[test]
fn cached_lines_mode_invalidates_on_mtime_change() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("file.txt");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "one\nsecond\n").unwrap();
let mut cache = SessionCache::new();
let r1 = handle_with_task_resolved(&mut cache, &p, "lines:1-1", CrpMode::Off, None);
let l1: Vec<&str> = r1.content.lines().collect();
let got1 = l1.get(1).copied().unwrap_or_default().trim();
let got1 = got1.split_once('|').map_or(got1, |(_, s)| s.trim());
assert_eq!(got1, "one");
std::thread::sleep(Duration::from_secs(1));
std::fs::write(&path, "two\nsecond\n").unwrap();
let r2 = handle_with_task_resolved(&mut cache, &p, "lines:1-1", CrpMode::Off, None);
let l2: Vec<&str> = r2.content.lines().collect();
let got2 = l2.get(1).copied().unwrap_or_default().trim();
let got2 = got2.split_once('|').map_or(got2, |(_, s)| s.trim());
assert_eq!(got2, "two");
}
#[test]
fn try_stub_hit_readonly_none_for_uncached_and_stale() {
let _lock = crate::core::data_dir::test_env_lock();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("hot.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() {}\n").unwrap();
let mut cache = SessionCache::new();
assert!(try_stub_hit_readonly(&cache, &p).is_none());
let _ = handle_with_task_resolved(&mut cache, &p, "full", CrpMode::Off, None);
std::thread::sleep(Duration::from_secs(1));
std::fs::write(&path, "fn main() { changed(); }\n").unwrap();
assert!(
try_stub_hit_readonly(&cache, &p).is_none(),
"stale file must never be served from the read-locked stub path"
);
}
#[test]
#[cfg_attr(tarpaulin, ignore)]
fn benchmark_task_conditioned_compression() {
let content = generate_benchmark_code(200);
let full_tokens = count_tokens(&content);
let task = Some("fix authentication in validate_token");
let (_full_output, full_tok) = process_mode(
&content,
"full",
"F1",
"server.rs",
"rs",
full_tokens,
CrpMode::Off,
"server.rs",
task,
);
let (_task_output, task_tok) = process_mode(
&content,
"task",
"F1",
"server.rs",
"rs",
full_tokens,
CrpMode::Off,
"server.rs",
task,
);
let (_sig_output, sig_tok) = process_mode(
&content,
"signatures",
"F1",
"server.rs",
"rs",
full_tokens,
CrpMode::Off,
"server.rs",
task,
);
let (_ref_output, ref_tok) = process_mode(
&content,
"reference",
"F1",
"server.rs",
"rs",
full_tokens,
CrpMode::Off,
"server.rs",
task,
);
eprintln!("\n=== Task-Conditioned Compression Benchmark ===");
eprintln!("Source: 200-line Rust file, task='fix authentication in validate_token'");
eprintln!(" full: {full_tok:>6} tokens (baseline)");
eprintln!(
" task: {task_tok:>6} tokens ({:.0}% savings)",
(1.0 - task_tok as f64 / full_tok as f64) * 100.0
);
eprintln!(
" signatures: {sig_tok:>6} tokens ({:.0}% savings)",
(1.0 - sig_tok as f64 / full_tok as f64) * 100.0
);
eprintln!(
" reference: {ref_tok:>6} tokens ({:.0}% savings)",
(1.0 - ref_tok as f64 / full_tok as f64) * 100.0
);
eprintln!("================================================\n");
assert!(task_tok < full_tok, "task mode should save tokens");
assert!(sig_tok < full_tok, "signatures should save tokens");
assert!(ref_tok < sig_tok, "reference should be most compact");
}
fn generate_benchmark_code(lines: usize) -> String {
let mut code = Vec::with_capacity(lines);
code.push("use std::collections::HashMap;".to_string());
code.push("use crate::core::auth;".to_string());
code.push(String::new());
code.push("pub struct Server {".to_string());
code.push(" config: Config,".to_string());
code.push(" cache: HashMap<String, String>,".to_string());
code.push("}".to_string());
code.push(String::new());
code.push("impl Server {".to_string());
code.push(
" pub fn validate_token(&self, token: &str) -> Result<Claims, AuthError> {".to_string(),
);
code.push(" let decoded = auth::decode_jwt(token)?;".to_string());
code.push(" if decoded.exp < chrono::Utc::now().timestamp() {".to_string());
code.push(" return Err(AuthError::Expired);".to_string());
code.push(" }".to_string());
code.push(" Ok(decoded.claims)".to_string());
code.push(" }".to_string());
code.push(String::new());
let remaining = lines.saturating_sub(code.len());
for i in 0..remaining {
if i % 30 == 0 {
code.push(format!(
" pub fn handler_{i}(&self, req: Request) -> Response {{"
));
} else if i % 30 == 29 {
code.push(" }".to_string());
} else {
code.push(format!(" let val_{i} = self.cache.get(\"key_{i}\").unwrap_or(&\"default\".to_string());"));
}
}
code.push("}".to_string());
code.join("\n")
}
#[test]
fn map_mode_inlines_task_relevant_body() {
let content = "pub fn alpha() {\n let a = 1;\n}\n\npub fn validate_token(t: &str) -> bool {\n let ok = check(t);\n ok\n}\n";
let tokens = count_tokens(content);
let (with_task, _) = process_mode(
content,
"map",
"F1",
"test.rs",
"rs",
tokens,
CrpMode::Off,
"test.rs",
Some("fix bug in validate_token"),
);
assert!(
with_task.contains("▸ body") && with_task.contains("validate_token"),
"map with task should inline the matching body: {with_task}"
);
let (no_task, _) = process_mode(
content,
"map",
"F1",
"test.rs",
"rs",
tokens,
CrpMode::Off,
"test.rs",
None,
);
assert!(
!no_task.contains("▸ body"),
"map without a task must not inline a body: {no_task}"
);
}
#[test]
fn lines_comma_multiselect_gets_hint() {
let content = (1..=30)
.map(|i| format!("line {i}"))
.collect::<Vec<_>>()
.join("\n");
let tokens = count_tokens(&content);
let (out, _) = process_mode(
&content,
"lines:5,10-12",
"F1",
"t.txt",
"txt",
tokens,
CrpMode::Off,
"t.txt",
None,
);
assert!(out.contains("line 5") && out.contains("line 10"), "{out}");
assert!(
out.contains("multi-select"),
"comma form must carry a hint: {out}"
);
}
#[test]
fn map_on_unsupported_language_carries_marker() {
let content = "some plain text\nwith no code\nstructure at all\n";
let tokens = count_tokens(content);
let (out, _) = process_mode(
content,
"map",
"F1",
"notes.txt",
"txt",
tokens,
CrpMode::Off,
"notes.txt",
None,
);
assert!(
out.contains("no extractable structure"),
"empty map must carry a marker: {out}"
);
}
#[test]
fn signatures_on_unsupported_language_carries_marker() {
let content = "some plain text\nwith no code\nstructure at all\n";
let tokens = count_tokens(content);
let (out, _) = process_mode(
content,
"signatures",
"F1",
"notes.txt",
"txt",
tokens,
CrpMode::Off,
"notes.txt",
None,
);
assert!(
out.contains("no extractable structure"),
"empty signatures must carry a marker: {out}"
);
}
#[test]
fn read_file_lossy_strips_utf8_bom() {
let p = std::env::temp_dir().join("lean_ctx_bom_test.txt");
std::fs::write(&p, b"\xEF\xBB\xBFhello\n").unwrap();
let s = read_file_lossy(p.to_str().unwrap()).unwrap();
let _ = std::fs::remove_file(&p);
assert!(
!s.starts_with('\u{feff}'),
"BOM must be stripped from read content"
);
assert!(
s.starts_with("hello"),
"content after BOM must survive: {s}"
);
}
#[test]
fn compressed_cache_key_distinguishes_task() {
let no_task = compressed_cache_key("map", CrpMode::Off, None, None, &[]);
let tdd_no_task = compressed_cache_key("map", CrpMode::Tdd, None, None, &[]);
let with_task = compressed_cache_key("map", CrpMode::Off, Some("fix login"), None, &[]);
let other_task = compressed_cache_key("map", CrpMode::Off, Some("refactor db"), None, &[]);
assert_eq!(no_task, "map:v2");
assert_eq!(tdd_no_task, "map:v2:tdd");
assert_ne!(with_task, no_task);
assert_ne!(with_task, other_task);
}
#[test]
fn compressed_cache_key_distinguishes_aggressiveness() {
let base = compressed_cache_key("map", CrpMode::Off, None, None, &[]);
assert_eq!(base, "map:v2");
let a = compressed_cache_key("map", CrpMode::Off, None, Some(0.7), &[]);
assert_eq!(
a,
compressed_cache_key("map", CrpMode::Off, None, Some(0.7), &[])
);
assert_ne!(a, base);
assert_ne!(
a,
compressed_cache_key("map", CrpMode::Off, None, Some(0.2), &[])
);
assert_eq!(
a,
compressed_cache_key("map", CrpMode::Off, None, Some(0.701), &[])
);
}
#[test]
fn compressed_cache_key_distinguishes_protect() {
let base = compressed_cache_key("entropy", CrpMode::Off, None, None, &[]);
assert_eq!(base, "entropy");
let p = compressed_cache_key("entropy", CrpMode::Off, None, None, &["TODO".to_string()]);
assert_ne!(p, base);
assert_eq!(
p,
compressed_cache_key("entropy", CrpMode::Off, None, None, &["TODO".to_string()])
);
let multi_a = compressed_cache_key(
"entropy",
CrpMode::Off,
None,
None,
&["a".to_string(), "b".to_string()],
);
let multi_b = compressed_cache_key(
"entropy",
CrpMode::Off,
None,
None,
&["b".to_string(), "a".to_string(), "a".to_string()],
);
assert_eq!(multi_a, multi_b);
assert_ne!(multi_a, p);
}
#[test]
fn aggressiveness_is_deterministic_and_monotonic() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var("LEAN_CTX_SHOW_SAVINGS", "0");
let mut content = String::new();
for i in 0..60 {
content.push_str(&format!(
"line {i}: the quick brown fox jumps over the lazy dog\n"
));
}
let render_at = |a: f64| -> String {
let (out, _) = process_mode_tuned(
&content,
"density:",
"F1",
"f.txt",
"txt",
count_tokens(&content),
CrpMode::Off,
"/tmp/f.txt",
None,
ReadTuning {
aggressiveness: Some(a),
protect: &[],
},
);
out
};
assert_eq!(render_at(0.7), render_at(0.7));
let low = count_tokens(&render_at(0.2));
let high = count_tokens(&render_at(0.9));
assert!(
high <= low,
"aggressiveness 0.9 ({high} tok) must not exceed 0.2 ({low} tok)"
);
crate::test_env::remove_var("LEAN_CTX_SHOW_SAVINGS");
}
#[test]
fn aggressive_json_uses_lossless_crush_core() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var("LEAN_CTX_SHOW_SAVINGS", "0");
let items: Vec<String> = (0..40)
.map(|i| {
format!(r#"{{"status":"active","region":"eu-central-1","tier":"standard","id":{i}}}"#)
})
.collect();
let content = format!("[{}]", items.join(","));
let original = count_tokens(&content);
let (out, sent) = process_mode_tuned(
&content,
"aggressive",
"F1",
"data.json",
"json",
original,
CrpMode::Off,
"/tmp/data.json",
None,
ReadTuning {
aggressiveness: None,
protect: &[],
},
);
assert!(
out.contains("_lc_crush"),
"aggressive json must compact via the crush core: {out}"
);
assert!(
sent < original,
"crush must reduce tokens ({sent} >= {original})"
);
crate::test_env::remove_var("LEAN_CTX_SHOW_SAVINGS");
}
#[test]
fn map_mode_includes_signature_line_ranges() {
let content = "pub struct Config {}\n\npub fn build() -> Config { Config {} }\n";
let (result, _) = process_mode(
content,
"map",
"F1",
"lib.rs",
"rs",
count_tokens(content),
CrpMode::Off,
"/tmp/lib.rs",
None,
);
assert!(
result.contains("API:"),
"map output should include API: {result}"
);
assert!(
result.contains("struct pub Config @L1"),
"struct signature should include line suffix: {result}"
);
assert!(
result.contains("fn pub build() → Config @L3"),
"function signature should include line suffix: {result}"
);
}
#[test]
fn map_mode_omits_exports_already_in_api() {
let content = "pub struct Config {}\n\npub fn build() -> Config { Config {} }\n";
let (result, _) = process_mode(
content,
"map",
"F1",
"lib.rs",
"rs",
count_tokens(content),
CrpMode::Off,
"/tmp/lib.rs",
None,
);
assert!(
result.contains("struct pub Config") && result.contains("fn pub build"),
"API section must still list exported symbols: {result}"
);
assert!(
!result.contains("exports:"),
"map must not repeat exports already shown in API: {result}"
);
}
#[test]
fn tdd_map_output_carries_symbol_legend() {
let content = "pub struct Config {}\n\npub fn build() -> Config { Config {} }\n";
let (result, _) = process_mode(
content,
"map",
"F1",
"lib.rs",
"rs",
count_tokens(content),
CrpMode::Tdd,
"/tmp/lib.rs",
None,
);
assert!(
result.contains("[λ=fn §=class +=pub]"),
"TDD map output must carry the symbol legend: {result}"
);
let (sigs, _) = process_mode(
content,
"signatures",
"F1",
"lib.rs",
"rs",
count_tokens(content),
CrpMode::Tdd,
"/tmp/lib.rs",
None,
);
assert!(
sigs.contains("[λ=fn §=class +=pub]"),
"TDD signatures output must carry the symbol legend: {sigs}"
);
}
#[test]
fn instruction_file_detection() {
assert!(is_instruction_file(
"/home/user/.pi/agent/skills/committing-changes/SKILL.md"
));
assert!(is_instruction_file("/workspace/.cursor/rules/lean-ctx.mdc"));
assert!(is_instruction_file("/project/AGENTS.md"));
assert!(is_instruction_file("/project/.cursorrules"));
assert!(is_instruction_file("/home/user/.claude/rules/my-rule.md"));
assert!(is_instruction_file("/skills/some-skill/README.md"));
assert!(!is_instruction_file("/project/src/main.rs"));
assert!(!is_instruction_file("/project/config.json"));
assert!(!is_instruction_file("/project/data/report.csv"));
}
#[test]
fn resolve_auto_mode_returns_full_for_instruction_files() {
let mode = resolve_auto_mode(
None,
"/home/user/.pi/agent/skills/committing-changes/SKILL.md",
5000,
Some("read"),
);
assert_eq!(mode, "full", "SKILL.md must always be read in full");
let mode = resolve_auto_mode(None, "/workspace/AGENTS.md", 3000, Some("read"));
assert_eq!(mode, "full", "AGENTS.md must always be read in full");
let mode = resolve_auto_mode(None, "/workspace/.cursorrules", 2000, None);
assert_eq!(mode, "full", ".cursorrules must always be read in full");
}
#[test]
fn anchored_mode_emits_line_hash_anchors() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("anc.rs");
let p = path.to_string_lossy().to_string();
let content = "fn main() {\n let x = 1;\n}\n";
std::fs::write(&path, content).unwrap();
let mut cache = SessionCache::new();
let r = handle_with_task_resolved(&mut cache, &p, "anchored", CrpMode::Off, None);
assert_eq!(r.resolved_mode, "anchored");
assert!(
r.content.contains("[anchored:"),
"anchored output must carry the self-describing legend: {}",
r.content
);
for (i, line) in content.lines().enumerate() {
let n = i + 1;
let expected = format!("{n}:{}|{line}", crate::core::anchor::line_hash(line));
assert!(
r.content.contains(&expected),
"missing anchor for line {n}: expected `{expected}` in:\n{}",
r.content
);
}
}
#[test]
fn anchored_mode_is_not_capped_to_raw_on_small_files() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tiny.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "a\n").unwrap();
let mut cache = SessionCache::new();
let r = handle_with_task_resolved(&mut cache, &p, "anchored", CrpMode::Off, None);
assert!(
r.content.contains("|a"),
"anchored output must keep anchors even on a tiny file: {}",
r.content
);
assert!(r.content.contains("[anchored:"), "legend must survive");
}
#[test]
fn raw_mode_returns_exact_file_content() {
let _lock = crate::core::data_dir::test_env_lock();
let content = "fn main() {\n println!(\"hello\");\n}\n";
let (output, _sent) = render::process_mode(
content,
"raw",
"F1",
"main.rs",
"rs",
100,
CrpMode::Off,
"/tmp/main.rs",
None,
);
assert_eq!(
output, content,
"raw mode must return exact file content with zero overhead"
);
assert!(
!output.contains("main.rs"),
"raw mode must not contain filename header"
);
assert!(!output.contains("deps"), "raw mode must not contain deps");
}
#[test]
fn verbatim_modes_preserve_decorative_comment_lines() {
let _lock = crate::core::data_dir::test_env_lock();
let sep_em = "// ————————————————————————————————————————";
let sep_dash = "// ------------------------------------------";
let content = format!(
"import {{ describe, it, expect }} from \"vitest\";\n\
\n\
{sep_em}\n\
// Section: arithmetic\n\
{sep_em}\n\
describe(\"add\", () => {{\n it(\"adds\", () => expect(1 + 1).toBe(2));\n}});\n\
\n\
{sep_dash}\n\
// Section: strings\n\
{sep_dash}\n"
);
for mode in ["full", "raw"] {
let (output, _) = render::process_mode(
&content,
mode,
"F1",
"math.test.ts",
"ts",
count_tokens(&content),
CrpMode::Off,
"/tmp/math.test.ts",
None,
);
assert!(
output.contains(sep_em) && output.contains(sep_dash),
"{mode} mode must keep every separator comment verbatim:\n{output}"
);
for line in content.lines().filter(|l| !l.is_empty()) {
assert!(
output.contains(line),
"{mode} mode dropped a source line: {line:?}"
);
}
}
let window = render::extract_line_range(&content, "1-5");
assert!(
window.contains(sep_em),
"lines: window dropped the separator comment:\n{window}"
);
assert!(
window.contains(" 3| ") && window.contains(" 5| "),
"lines: window must number the separator lines (3 and 5):\n{window}"
);
}
#[test]
fn process_mode_output_is_byte_stable_across_calls() {
let _iso = crate::core::data_dir::isolated_data_dir();
crate::test_env::remove_var("LEAN_CTX_SAVINGS_FOOTER");
crate::test_env::remove_var("LEAN_CTX_SHOW_SAVINGS");
crate::test_env::remove_var("LEAN_CTX_QUIET");
let content: String = (0..120)
.map(|i| format!("pub fn handler_{i}(x: u32) -> u32 {{ x * {i} }}"))
.collect::<Vec<_>>()
.join("\n");
let tokens = count_tokens(&content);
for mode in [
"map",
"signatures",
"reference",
"aggressive",
"entropy",
"raw",
"lines:5-20",
"anchored",
] {
let run = || {
render::process_mode(
&content,
mode,
"F1",
"stable.rs",
"rs",
tokens,
CrpMode::Off,
"/tmp/stable.rs",
None,
)
.0
};
let first = run();
let second = run();
assert_eq!(
first, second,
"mode '{mode}' produced non-deterministic output"
);
}
}
#[test]
fn recovery_footer_is_compressed_only_and_togglable() {
let _iso = crate::core::data_dir::isolated_data_dir();
let content: String = (0..120)
.map(|i| format!("pub fn handler_{i}(x: u32) -> u32 {{ x * {i} }}"))
.collect::<Vec<_>>()
.join("\n");
let tokens = count_tokens(&content);
let run = |mode: &str| {
render::process_mode(
&content,
mode,
"F1",
"rec.rs",
"rs",
tokens,
CrpMode::Off,
"/tmp/rec.rs",
None,
)
.0
};
crate::test_env::set_var("LEAN_CTX_RECOVERY_HINTS", "minimal");
let sigs = run("signatures");
assert!(
sigs.contains("read \"/tmp/rec.rs\" directly (no MCP)"),
"compressed view must surface the MCP-free recovery path: {sigs}"
);
assert_eq!(sigs, run("signatures"), "footer must be byte-stable");
assert!(
!run("raw").contains("(no MCP)"),
"raw view needs no recovery footer"
);
crate::test_env::set_var("LEAN_CTX_RECOVERY_HINTS", "off");
assert!(
!run("signatures").contains("(no MCP)"),
"recovery_hints=off must drop the footer"
);
crate::test_env::remove_var("LEAN_CTX_RECOVERY_HINTS");
}
#[test]
fn raw_mode_no_savings_footer() {
let _lock = crate::core::data_dir::test_env_lock();
let content = "x = 1\n";
let (output, _) = render::process_mode(
content,
"raw",
"F1",
"tiny.py",
"py",
50,
CrpMode::Off,
"/tmp/tiny.py",
None,
);
assert!(
!output.contains('\u{2500}'),
"raw mode must not contain savings footer box-drawing chars"
);
assert_eq!(output, content);
}
#[test]
fn cap_to_raw_falls_back_when_framing_inflates() {
let raw = "pub fn a() {}\n";
let framed = format!("F1=x.rs 1L\n deps foo,bar\n{raw}");
let raw_tokens = count_tokens(raw);
let framed_tokens = count_tokens(&framed);
assert!(
framed_tokens > raw_tokens,
"fixture must inflate to exercise the guard"
);
assert_eq!(
cap_to_raw(framed, framed_tokens, raw, raw_tokens),
raw,
"framing larger than raw must fall back to bare content"
);
}
#[test]
fn cap_to_raw_keeps_framing_when_not_larger() {
let raw = "a long original body that compresses well";
let framed = "sig summary".to_string();
let framed_tokens = count_tokens(&framed);
assert_eq!(
cap_to_raw(framed.clone(), framed_tokens, raw, 100),
framed,
"output at or below raw must be returned untouched"
);
}
#[test]
fn cap_to_raw_keeps_framing_for_empty_file() {
let framed = "F1=empty.rs 0L".to_string();
let framed_tokens = count_tokens(&framed);
assert_eq!(
cap_to_raw(framed.clone(), framed_tokens, "", 0),
framed,
"empty files keep their framing signal"
);
}
#[test]
fn auto_read_never_inflates_small_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("small.rs");
let p = path.to_string_lossy().to_string();
let content =
"use std::io;\n\npub fn greet(name: &str) -> String {\n format!(\"hi {name}\")\n}\n";
std::fs::write(&path, content).unwrap();
let mut cache = SessionCache::new();
let out = handle_with_task_resolved(&mut cache, &p, "auto", CrpMode::Off, None);
assert!(
out.output_tokens <= count_tokens(content),
"auto cold read inflated a small file: {} output tok > {} raw tok\n{}",
out.output_tokens,
count_tokens(content),
out.content
);
}
#[test]
fn full_read_never_inflates_tiny_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tiny.rs");
let p = path.to_string_lossy().to_string();
let content = "pub fn a() {}\n";
std::fs::write(&path, content).unwrap();
let mut cache = SessionCache::new();
let out = handle_with_task_resolved(&mut cache, &p, "full", CrpMode::Off, None);
assert!(
out.output_tokens <= count_tokens(content),
"full cold read inflated a tiny file: {} > {}\n{}",
out.output_tokens,
count_tokens(content),
out.content
);
}
#[test]
fn auto_read_still_compresses_large_file() {
let _iso = crate::core::data_dir::isolated_data_dir();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("big.rs");
let p = path.to_string_lossy().to_string();
let mut content = String::new();
for i in 0..400 {
content.push_str(&format!(
"pub fn function_number_{i}(x: i32, y: i32) -> i32 {{\n let z = x + y + {i};\n z * 2\n}}\n\n"
));
}
std::fs::write(&path, &content).unwrap();
let mut cache = SessionCache::new();
let out = handle_with_task_resolved(&mut cache, &p, "auto", CrpMode::Off, None);
assert!(
out.output_tokens < count_tokens(&content),
"auto read of a large file must still compress: {} >= {} (mode={})",
out.output_tokens,
count_tokens(&content),
out.resolved_mode
);
}
#[test]
fn explicit_compressed_mode_capped_on_tiny_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("lib.rs");
let p = path.to_string_lossy().to_string();
let content = "pub fn alpha() {}\npub fn beta() {}\n";
std::fs::write(&path, content).unwrap();
let mut cache = SessionCache::new();
let out = handle_with_task_resolved(&mut cache, &p, "signatures", CrpMode::Off, None);
assert!(
out.output_tokens <= count_tokens(content),
"explicit signatures of a tiny file must not inflate past raw: {} > {}\n{}",
out.output_tokens,
count_tokens(content),
out.content
);
}
#[test]
fn explicit_signatures_still_compresses_large_file() {
let _iso = crate::core::data_dir::isolated_data_dir();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("big.rs");
let p = path.to_string_lossy().to_string();
let mut content = String::new();
for i in 0..400 {
content.push_str(&format!(
"pub fn function_number_{i}(x: i32, y: i32) -> i32 {{\n let z = x + y + {i};\n z * 2\n}}\n\n"
));
}
std::fs::write(&path, &content).unwrap();
let mut cache = SessionCache::new();
let out = handle_with_task_resolved(&mut cache, &p, "signatures", CrpMode::Off, None);
assert!(
out.output_tokens < count_tokens(&content),
"explicit signatures of a large file must compress: {} >= {}",
out.output_tokens,
count_tokens(&content)
);
}
#[test]
fn cache_hit_stub_is_byte_stable_across_rereads() {
let _iso = crate::core::data_dir::isolated_data_dir();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("stable.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "pub fn alpha() {}\npub fn beta() {}\n").unwrap();
let mut cache = SessionCache::new();
let _ = handle_with_task_resolved(&mut cache, &p, "full", CrpMode::Off, None);
let r2 = handle_with_task_resolved(&mut cache, &p, "full", CrpMode::Off, None);
let r3 = handle_with_task_resolved(&mut cache, &p, "full", CrpMode::Off, None);
let r4 = handle_with_task_resolved(&mut cache, &p, "full", CrpMode::Off, None);
assert_eq!(
r2.content, r3.content,
"re-read drifted between reads 2 and 3"
);
assert_eq!(
r3.content, r4.content,
"re-read drifted between reads 3 and 4"
);
assert!(
!r2.content.contains("(read"),
"read-count note must not appear in the cache-hit body: {}",
r2.content
);
}
fn primed_full_cache(p: &str) -> SessionCache {
let mut cache = SessionCache::new();
let _ = handle_with_task_resolved(&mut cache, p, "full", CrpMode::Off, None);
debug_assert!(
cache.is_full_delivered(p),
"fixture must deliver full content"
);
cache
}
#[test]
fn auto_reread_of_fully_delivered_file_serves_unchanged_stub() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("warm.rs");
let p = path.to_string_lossy().to_string();
let body = (0..48)
.map(|i| format!("fn function_number_{i}() {{ let value_{i} = {i} * 2; }}"))
.collect::<Vec<_>>()
.join("\n");
std::fs::write(&path, format!("{body}\n")).unwrap();
let mut cold = SessionCache::new();
let full = handle_with_task_resolved(&mut cold, &p, "full", CrpMode::Off, None);
assert!(
!full.content.contains("[unchanged"),
"cold full read must deliver the body, not a stub"
);
let mut cache = primed_full_cache(&p);
let reread = handle_with_task_resolved(&mut cache, &p, "auto", CrpMode::Off, None);
assert!(
reread.content.contains("[unchanged"),
"auto re-read of an unchanged fully-delivered file must serve the stub, got: {}",
reread.content
);
assert!(
reread.output_tokens.saturating_mul(4) < full.output_tokens,
"stub ({} tok) must be far cheaper than a full re-delivery ({} tok)",
reread.output_tokens,
full.output_tokens
);
}
#[test]
fn conversation_scoped_stub_served_for_same_conversation() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("warm.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() { let x = 1; }\n").unwrap();
let cache = primed_full_cache(&p);
let delivered = cache.get(&p).unwrap().delivered_conversation.clone();
let out = try_stub_hit_readonly_scoped(&cache, &p, delivered.as_deref());
assert!(
out.is_some_and(|o| o.content.contains("[unchanged")),
"same-conversation re-read must serve the stub"
);
}
#[test]
fn conversation_scoped_stub_withheld_for_other_conversation() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("warm.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() { let x = 1; }\n").unwrap();
let cache = primed_full_cache(&p);
let foreign = "conversation-that-never-read-this-file";
assert_ne!(
cache.get(&p).unwrap().delivered_conversation.as_deref(),
Some(foreign),
"test fixture id collided with the foreign id"
);
let out = try_stub_hit_readonly_scoped(&cache, &p, Some(foreign));
assert!(
out.is_none(),
"a foreign conversation must get a full re-read, never a misleading [unchanged] stub"
);
}
#[test]
fn conversation_scoped_stub_served_when_no_context() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("warm.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() { let x = 1; }\n").unwrap();
let cache = primed_full_cache(&p);
let out = try_stub_hit_readonly_scoped(&cache, &p, None);
assert!(
out.is_some_and(|o| o.content.contains("[unchanged")),
"absent conversation context must keep legacy stub behavior"
);
}
fn seed_cold_record(p: &str, conv: &str) {
crate::core::read_stub_index::clear_for_test();
let primed = primed_full_cache(p);
let entry = primed.get(p).unwrap();
let rec = crate::core::read_stub_index::StubRecord::new(
crate::core::pathutil::normalize_tool_path(p),
entry.hash.clone(),
entry.stored_mtime,
entry.line_count,
primed.get_file_ref_readonly(p).unwrap_or_default(),
Some(conv.to_string()),
);
crate::core::read_stub_index::clear_for_test();
crate::core::read_stub_index::record(rec);
}
#[test]
#[serial_test::serial(stub_index)]
fn cold_fallback_serves_stub_for_same_conversation_after_restart() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("warm.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() { let x = 1; }\n").unwrap();
seed_cold_record(&p, "conv-a");
let cold = SessionCache::new();
let out = try_stub_hit_readonly_scoped(&cold, &p, Some("conv-a"));
crate::core::read_stub_index::clear_for_test();
assert!(
out.is_some_and(|o| o.content.contains("[unchanged")),
"same-conversation re-read after restart must serve the persisted stub"
);
}
#[test]
#[serial_test::serial(stub_index)]
fn cold_fallback_withheld_for_other_conversation() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("warm.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() { let x = 1; }\n").unwrap();
seed_cold_record(&p, "conv-a");
let cold = SessionCache::new();
let out = try_stub_hit_readonly_scoped(&cold, &p, Some("conv-b"));
crate::core::read_stub_index::clear_for_test();
assert!(
out.is_none(),
"a different conversation must get a cold full read, never a persisted stub"
);
}
#[test]
#[serial_test::serial(stub_index)]
fn cold_fallback_withheld_without_conversation_context() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("warm.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() { let x = 1; }\n").unwrap();
seed_cold_record(&p, "conv-a");
let cold = SessionCache::new();
let out = try_stub_hit_readonly_scoped(&cold, &p, None);
crate::core::read_stub_index::clear_for_test();
assert!(
out.is_none(),
"absent conversation context must NOT serve a cold persisted stub"
);
}
#[test]
#[serial_test::serial(stub_index)]
fn cold_fallback_withheld_when_file_changed() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("warm.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() { let x = 1; }\n").unwrap();
seed_cold_record(&p, "conv-a");
std::fs::write(&path, "fn main() { let x = 2; let y = 3; }\n").unwrap();
let cold = SessionCache::new();
let out = try_stub_hit_readonly_scoped(&cold, &p, Some("conv-a"));
crate::core::read_stub_index::clear_for_test();
assert!(
out.is_none(),
"a file changed on disk must get a cold full read, never a stale stub"
);
}
#[test]
fn delta_explicit_changed_file_diverts_full_reread_to_diff() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("changed.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() {}\n").unwrap();
let mut cache = primed_full_cache(&p);
std::thread::sleep(Duration::from_secs(1));
std::fs::write(&path, "fn main() { changed(); }\n").unwrap();
let decision = resolve_explicit_delta_mode(
&cache, &p, "full", true, false, true,
);
assert_eq!(
decision.mode, "diff",
"changed full re-read must divert to diff"
);
let note = decision
.note
.expect("a diff diversion must carry an advisory note");
assert!(
note.contains("[delta-explicit]"),
"note tag missing: {note}"
);
assert!(
note.contains("fresh=true"),
"note must mention the bypass: {note}"
);
let out = handle_with_task_resolved(&mut cache, &p, "diff", CrpMode::Off, None);
assert_eq!(out.resolved_mode, "diff");
assert!(
out.content.contains("[diff]"),
"engine must emit a diff: {}",
out.content
);
assert!(
out.content.contains("changed()"),
"diff must reflect the new on-disk content: {}",
out.content
);
}
#[test]
fn delta_explicit_changed_lines_request_diverts_to_diff() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("lines.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn a() {}\nfn b() {}\n").unwrap();
let cache = primed_full_cache(&p);
std::thread::sleep(Duration::from_secs(1));
std::fs::write(&path, "fn a() { x(); }\nfn b() {}\n").unwrap();
let decision = resolve_explicit_delta_mode(&cache, &p, "lines:1-1", true, false, true);
assert_eq!(
decision.mode, "diff",
"a changed-file lines: re-read must divert to diff, not re-extract a window"
);
assert!(decision.note.is_some());
}
#[test]
fn delta_explicit_diff_base_is_full_cached_content_not_compressed() {
let _iso = crate::core::data_dir::isolated_data_dir();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("big.rs");
let p = path.to_string_lossy().to_string();
let mut content = String::new();
for i in 0..60 {
content.push_str(&format!(
"pub fn original_fn_{i}(x: i32) -> i32 {{ x + {i} }}\n"
));
}
std::fs::write(&path, &content).unwrap();
let mut cache = SessionCache::new();
let _ = handle_with_task_resolved(&mut cache, &p, "full", CrpMode::Off, None);
let _ = handle_with_task_resolved(&mut cache, &p, "map", CrpMode::Off, None);
std::thread::sleep(Duration::from_secs(1));
let changed = content.replace(
"pub fn original_fn_7(x: i32) -> i32 { x + 7 }",
"pub fn original_fn_7(x: i32) -> i32 { x + 70707 }",
);
std::fs::write(&path, &changed).unwrap();
let out = handle_with_task_resolved(&mut cache, &p, "diff", CrpMode::Off, None);
assert!(
out.content.contains("[diff]"),
"expected a diff: {}",
out.content
);
assert!(
out.content.contains("70707"),
"diff must be computed against full cached source, got: {}",
out.content
);
assert!(
out.content.contains("+1/-1") || out.content.contains("-1/+1"),
"single-line change should diff as +1/-1: {}",
out.content
);
}
#[test]
fn delta_explicit_unchanged_lines_collapse_to_full_stub() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("same.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn a() {}\nfn b() {}\n").unwrap();
let cache = primed_full_cache(&p);
let decision = resolve_explicit_delta_mode(&cache, &p, "lines:1-1", true, false, true);
assert_eq!(
decision.mode, "full",
"unchanged lines: of a full file must collapse to the stub"
);
assert!(
decision.note.is_none(),
"a silent stub collapse must not carry a note"
);
}
#[test]
fn delta_explicit_unchanged_full_reread_is_untouched() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("same.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn a() {}\n").unwrap();
let cache = primed_full_cache(&p);
let decision = resolve_explicit_delta_mode(&cache, &p, "full", true, false, true);
assert_eq!(decision.mode, "full");
assert!(decision.note.is_none());
}
#[test]
fn delta_explicit_off_preserves_current_behavior() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("changed.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() {}\n").unwrap();
let cache = primed_full_cache(&p);
std::thread::sleep(Duration::from_secs(1));
std::fs::write(&path, "fn main() { changed(); }\n").unwrap();
let decision =
resolve_explicit_delta_mode(&cache, &p, "full", true, false, false);
assert_eq!(
decision.mode, "full",
"feature OFF must preserve the requested mode"
);
assert!(decision.note.is_none());
let lines = resolve_explicit_delta_mode(&cache, &p, "lines:1-1", true, false, false);
assert_eq!(
lines.mode, "lines:1-1",
"feature OFF must not touch lines: either"
);
assert!(lines.note.is_none());
}
#[test]
fn delta_explicit_fresh_bypasses() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("changed.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() {}\n").unwrap();
let cache = primed_full_cache(&p);
std::thread::sleep(Duration::from_secs(1));
std::fs::write(&path, "fn main() { changed(); }\n").unwrap();
let decision = resolve_explicit_delta_mode(&cache, &p, "full", true, true, true);
assert_eq!(
decision.mode, "full",
"fresh=true must bypass the diff diversion"
);
assert!(decision.note.is_none());
}
#[test]
fn delta_explicit_first_read_unaffected() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("new.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() {}\n").unwrap();
let cache = SessionCache::new();
let decision = resolve_explicit_delta_mode(&cache, &p, "full", true, false, true);
assert_eq!(
decision.mode, "full",
"an uncached first read must be served normally"
);
assert!(decision.note.is_none());
let lines = resolve_explicit_delta_mode(&cache, &p, "lines:1-1", true, false, true);
assert_eq!(lines.mode, "lines:1-1");
assert!(lines.note.is_none());
}
#[test]
fn delta_explicit_only_fires_for_explicit_mode() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("changed.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() {}\n").unwrap();
let cache = primed_full_cache(&p);
std::thread::sleep(Duration::from_secs(1));
std::fs::write(&path, "fn main() { changed(); }\n").unwrap();
let decision =
resolve_explicit_delta_mode(&cache, &p, "full", false, false, true);
assert_eq!(
decision.mode, "full",
"auto-resolved modes must not be diverted to diff"
);
assert!(decision.note.is_none());
}
#[test]
fn delta_explicit_decision_is_byte_stable() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("changed.rs");
let p = path.to_string_lossy().to_string();
std::fs::write(&path, "fn main() {}\n").unwrap();
let cache = primed_full_cache(&p);
std::thread::sleep(Duration::from_secs(1));
std::fs::write(&path, "fn main() { changed(); }\n").unwrap();
let d1 = resolve_explicit_delta_mode(&cache, &p, "full", true, false, true);
let d2 = resolve_explicit_delta_mode(&cache, &p, "full", true, false, true);
assert_eq!(
d1, d2,
"delta-explicit decision drifted between identical calls"
);
}
#[test]
fn compress_protect_glob_forces_full_verbatim_read() {
let _iso = crate::core::data_dir::isolated_data_dir();
crate::test_env::set_var("LEAN_CTX_SHOW_SAVINGS", "0");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("protected.rs");
let p = path.to_string_lossy().to_string();
let mut content = String::new();
for i in 0..60 {
content.push_str(&format!(
"// distinctive-comment-{i}\npub fn handler_{i}(x: u32) -> u32 {{ x + {i} }}\n"
));
}
std::fs::write(&path, &content).unwrap();
crate::core::config::Config::update_global(|c| c.proxy.compress_protect = None).unwrap();
let mut cold = SessionCache::new();
let stripped = handle_with_task_resolved(&mut cold, &p, "aggressive", CrpMode::Off, None);
assert!(
!stripped.content.contains("// distinctive-comment-0"),
"control: aggressive must strip comments when the path is not protected"
);
crate::core::config::Config::update_global(|c| {
c.proxy.compress_protect = Some(vec!["*.rs".into()]);
})
.unwrap();
let mut warm = SessionCache::new();
let protected = handle_with_task_resolved(&mut warm, &p, "aggressive", CrpMode::Off, None);
assert!(
protected.content.contains("// distinctive-comment-0")
&& protected.content.contains("// distinctive-comment-59"),
"a protected path must be returned verbatim with every comment intact"
);
crate::core::config::Config::update_global(|c| c.proxy.compress_protect = None).unwrap();
crate::test_env::remove_var("LEAN_CTX_SHOW_SAVINGS");
}