Files
yvgude--lean-ctx/rust/tests/efficiency_ux_scenarios.rs
T
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

867 lines
28 KiB
Rust

//! Scenario tests for the Efficiency + UX Hardening Plan (10 fixes)
//! and the shell compression error-guard hardening.
//!
//! Each test simulates a realistic user interaction pattern.
#![allow(clippy::needless_raw_string_hashes)]
use lean_ctx::core::cache::SessionCache;
use lean_ctx::core::protocol::CrpMode;
use std::io::Write;
// =============================================================================
// Fix 1 + 2: ctx_read Fast-Path + mtime guard
// =============================================================================
mod read_cache_hits {
use super::*;
#[test]
fn scenario_repeated_reads_hit_cache() {
let mut cache = SessionCache::new();
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.rs");
std::fs::write(&file, "fn main() { println!(\"hello\"); }\n").unwrap();
let path = file.to_str().unwrap();
// First read: stores in cache
let out1 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
assert!(!out1.content.is_empty());
assert_ne!(out1.resolved_mode, "error");
// Second read: should be a cache hit (mtime unchanged → fast path)
let out2 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
assert!(
out2.content.contains("unchanged") || out2.content.contains("cached"),
"Expected cache hit stub, got: {}",
&out2.content[..out2.content.len().min(200)]
);
}
#[test]
fn scenario_modified_file_detects_change() {
let mut cache = SessionCache::new();
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("app.rs");
std::fs::write(&file, "fn v1() {}\n").unwrap();
let path = file.to_str().unwrap();
lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
// Ensure mtime granularity catches the change
std::thread::sleep(std::time::Duration::from_millis(1100));
std::fs::write(&file, "fn v2() { updated(); }\n").unwrap();
let out = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
// Should detect the change and show new content or delta
assert!(
out.content.contains("v2")
|| out.content.contains("delta")
|| out.content.contains("diff"),
"Expected change detection, got: {}",
&out.content[..out.content.len().min(300)]
);
}
}
// =============================================================================
// Fix 3: zstd bypass — compressed output cache checked before decompression
// =============================================================================
mod zstd_bypass {
use super::*;
#[test]
fn scenario_map_mode_uses_compressed_cache() {
let mut cache = SessionCache::new();
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("module.rs");
std::fs::write(
&file,
"use std::io;\npub fn read_all() -> io::Result<String> { Ok(String::new()) }\npub fn write_all(data: &str) -> io::Result<()> { Ok(()) }\n",
).unwrap();
let path = file.to_str().unwrap();
// First read with full mode to populate cache
lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
// First map read — processes and caches compressed output
let out1 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"map",
CrpMode::Off,
None,
);
assert!(!out1.content.is_empty());
// Second map read — should hit compressed cache (no zstd decompression needed)
let out2 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"map",
CrpMode::Off,
None,
);
assert!(!out2.content.is_empty());
assert_eq!(out1.content, out2.content);
}
}
// =============================================================================
// Fix 4: ctx_shell exit code in response
// =============================================================================
mod exit_code {
#[test]
fn scenario_exit_code_format() {
// Test the exit code formatting logic that the registered tool uses
let code: i32 = 1;
let exit_suffix = if code != 0 {
format!("\n[exit:{code}]")
} else {
String::new()
};
assert_eq!(exit_suffix, "\n[exit:1]");
let code: i32 = 0;
let exit_suffix = if code != 0 {
format!("\n[exit:{code}]")
} else {
String::new()
};
assert!(exit_suffix.is_empty());
let code: i32 = 127;
let exit_suffix = if code != 0 {
format!("\n[exit:{code}]")
} else {
String::new()
};
assert_eq!(exit_suffix, "\n[exit:127]");
}
}
// =============================================================================
// Fix 5: Error as MCP ErrorData (not success body)
// =============================================================================
mod error_data {
use super::*;
#[test]
fn scenario_nonexistent_file_returns_error_mode() {
let mut cache = SessionCache::new();
let out = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
"/nonexistent/path/does_not_exist.rs",
"full",
CrpMode::Off,
None,
);
assert_eq!(out.resolved_mode, "error");
assert!(out.content.contains("ERROR"));
}
#[test]
fn scenario_search_nonexistent_dir_returns_error() {
let result = lean_ctx::tools::ctx_search::handle(
"pattern",
"/nonexistent_dir_xyz",
None,
50,
CrpMode::Off,
true,
false,
false,
)
.text;
assert!(
result.starts_with("ERROR:"),
"Expected ERROR prefix, got: {result}"
);
}
#[test]
fn scenario_tree_nonexistent_dir_returns_error() {
let (result, _) = lean_ctx::tools::ctx_tree::handle("/nonexistent_xyz", 3, false, true);
assert!(
result.starts_with("ERROR:"),
"Expected ERROR prefix, got: {result}"
);
}
#[test]
fn scenario_tree_file_input_returns_error() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("file.txt");
std::fs::write(&file, "data").unwrap();
let (result, _) = lean_ctx::tools::ctx_tree::handle(file.to_str().unwrap(), 3, false, true);
assert!(
result.starts_with("ERROR:"),
"Expected ERROR prefix, got: {result}"
);
}
}
// =============================================================================
// Fix 6: diff mode without cache returns guidance instead of full file
// =============================================================================
mod diff_guard {
use super::*;
#[test]
fn scenario_diff_without_cache_returns_guidance() {
let mut cache = SessionCache::new();
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("new_file.rs");
std::fs::write(&file, "fn brand_new() {}\n").unwrap();
let path = file.to_str().unwrap();
let out = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"diff",
CrpMode::Off,
None,
);
assert!(
out.content.contains("no cached version for diff")
|| out.content.contains("use mode=full first"),
"Expected guidance message, got: {}",
&out.content[..out.content.len().min(300)]
);
// Must NOT contain the file content
assert!(
!out.content.contains("brand_new"),
"Full file content leaked through diff guard!"
);
}
#[test]
fn scenario_diff_after_full_read_works() {
let mut cache = SessionCache::new();
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("tracked.rs");
std::fs::write(&file, "fn version_one() {}\n").unwrap();
let path = file.to_str().unwrap();
// Read full first
lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
// Modify file
std::thread::sleep(std::time::Duration::from_millis(1100));
std::fs::write(&file, "fn version_two() { changed(); }\n").unwrap();
// Now diff should work
let out = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"diff",
CrpMode::Off,
None,
);
assert!(
out.content.contains("diff") || out.content.contains("version_two"),
"Expected diff content, got: {}",
&out.content[..out.content.len().min(300)]
);
}
}
// =============================================================================
// Fix 7: ctx_search early abort
// =============================================================================
mod search_early_abort {
use super::*;
#[test]
fn scenario_search_respects_max_results() {
let dir = tempfile::tempdir().unwrap();
for i in 0..20 {
let file = dir.path().join(format!("file_{i:02}.rs"));
std::fs::write(&file, format!("fn search_target_{i}() {{}}\n")).unwrap();
}
let result = lean_ctx::tools::ctx_search::handle(
"search_target",
dir.path().to_str().unwrap(),
None,
5,
CrpMode::Off,
false,
false,
false,
)
.text;
let match_lines: Vec<&str> = result
.lines()
.filter(|l| l.contains("search_target"))
.collect();
assert!(
match_lines.len() <= 5,
"Expected at most 5 matches, got {}",
match_lines.len()
);
}
#[test]
fn scenario_search_finds_all_when_under_limit() {
let dir = tempfile::tempdir().unwrap();
for i in 0..3 {
let file = dir.path().join(format!("src_{i}.rs"));
std::fs::write(&file, format!("fn unique_pattern_{i}() {{}}\n")).unwrap();
}
let result = lean_ctx::tools::ctx_search::handle(
"unique_pattern",
dir.path().to_str().unwrap(),
None,
50,
CrpMode::Off,
false,
false,
false,
)
.text;
assert!(result.contains("3 matches"));
}
}
// =============================================================================
// Fix 8: Ledger debounce
// =============================================================================
mod ledger_debounce {
use lean_ctx::core::context_ledger::ContextLedger;
#[test]
fn scenario_debounce_skips_rapid_saves() {
let mut ledger = ContextLedger::new();
ledger.record("test.rs", "full", 100, 20);
// First debounced save — should execute (no prior flush)
ledger.save_debounced();
// Immediate second save — should be skipped (< 3s)
ledger.record("other.rs", "map", 50, 10);
ledger.save_debounced(); // No-op due to debounce — verifies no panic
}
}
// =============================================================================
// Fix 9: Cold-read hints only from 2nd read onwards
// =============================================================================
mod cold_read_hints {
use super::*;
#[test]
fn scenario_first_read_has_no_similarity_hints() {
let mut cache = SessionCache::new();
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("fresh.rs");
std::fs::write(
&file,
"use std::collections::HashMap;\npub fn compute() -> HashMap<String, i32> { HashMap::new() }\n",
).unwrap();
let path = file.to_str().unwrap();
let out = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
assert!(
!out.content.contains("[similar:") && !out.content.contains("[related:"),
"First read should not have similarity hints, got: {}",
&out.content[..out.content.len().min(500)]
);
}
}
// =============================================================================
// Fix 10: ctx_tree UX — file input / empty directory
// =============================================================================
mod tree_ux {
#[test]
fn scenario_file_input_suggests_parent() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("test.txt");
std::fs::write(&file, "content").unwrap();
let (result, _) = lean_ctx::tools::ctx_tree::handle(file.to_str().unwrap(), 3, false, true);
assert!(result.contains("is a file, not a directory"));
assert!(result.contains("Use path="));
}
#[test]
fn scenario_empty_directory_explicit_message() {
let dir = tempfile::tempdir().unwrap();
let (result, _) =
lean_ctx::tools::ctx_tree::handle(dir.path().to_str().unwrap(), 3, false, true);
assert!(
result.contains("empty directory"),
"Expected 'empty directory' message, got: {result}"
);
}
}
// =============================================================================
// Shell Compression Error Guard — CRITICAL
// =============================================================================
mod compression_error_guard {
use lean_ctx::shell::compress::compress_if_beneficial_pub;
#[test]
fn scenario_cargo_check_error_fully_preserved() {
let error_output = r#" Compiling myapp v0.1.0 (/home/user/myapp)
error[E0308]: mismatched types
--> src/main.rs:15:20
|
15 | let x: i32 = "hello";
| --- ^^^^^^^ expected `i32`, found `&str`
| |
| expected due to this
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.
error: could not compile `myapp` (bin "myapp") due to 1 previous error
"#;
let result = compress_if_beneficial_pub("cargo check", error_output);
assert!(
result.contains("src/main.rs:15:20"),
"Error location was compressed away! Got: {result}"
);
assert!(result.contains("E0308"));
assert!(result.contains("mismatched types"));
assert!(result.contains("expected `i32`, found `&str`"));
}
#[test]
fn scenario_cargo_clippy_warnings_fully_preserved() {
let warning_output = r#" Checking mylib v0.1.0
warning: unused variable: `x`
--> src/lib.rs:42:9
|
42 | let x = compute_heavy();
| ^ help: if this is intentional, prefix it with an underscore: `_x`
|
= note: `#[warn(unused_variables)]` on by default
warning: `mylib` (lib) generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.34s
"#;
let result = compress_if_beneficial_pub("cargo clippy -- -D warnings", warning_output);
assert!(
result.contains("src/lib.rs:42:9"),
"Warning location was compressed away! Got: {result}"
);
assert!(result.contains("unused variable: `x`"));
assert!(result.contains("prefix it with an underscore"));
}
#[test]
fn scenario_cargo_build_multiple_errors_preserved() {
let error_output = r#" Compiling lean-ctx v3.6.8
error[E0502]: cannot borrow `*cache` as mutable because it is also borrowed as immutable
--> src/tools/ctx_read.rs:320:9
|
318 | if let Some(existing) = cache.get(path) {
| -------------- immutable borrow occurs here
320 | cache.record_cache_hit(path);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
error[E0063]: missing field `last_flush` in initializer of `ContextLedger`
--> src/core/context_ledger.rs:101:9
|
101 | Self {
| ^^^^ missing `last_flush`
error: could not compile `lean-ctx` (lib) due to 2 previous errors
"#;
let result = compress_if_beneficial_pub("cargo check 2>&1", error_output);
assert!(
result.contains("src/tools/ctx_read.rs:320:9"),
"First error location lost! Got: {result}"
);
assert!(
result.contains("src/core/context_ledger.rs:101:9"),
"Second error location lost! Got: {result}"
);
assert!(result.contains("E0502"));
assert!(result.contains("E0063"));
}
#[test]
fn scenario_typescript_tsc_errors_preserved() {
let tsc_output = r#"src/components/Button.tsx(23,5): error TS2322: Type 'string' is not assignable to type 'number'.
src/utils/api.ts(45,10): error TS2304: Cannot find name 'fetchData'.
Found 2 errors in 2 files.
"#;
let result = compress_if_beneficial_pub("npx tsc --noEmit", tsc_output);
assert!(
result.contains("Button.tsx(23,5)"),
"TSC error location lost! Got: {result}"
);
assert!(result.contains("api.ts(45,10)"));
assert!(result.contains("TS2322"));
}
#[test]
fn scenario_go_build_errors_preserved() {
let go_output = r#"# myapp/internal/server
./server.go:15:2: undefined: handleRequest
./server.go:23:15: cannot use str (variable of type string) as int value in argument to process
"#;
let result = compress_if_beneficial_pub("go build ./...", go_output);
assert!(
result.contains("server.go:15:2"),
"Go error location lost! Got: {result}"
);
assert!(result.contains("undefined: handleRequest"));
}
#[test]
fn scenario_eslint_errors_preserved() {
let eslint_output = r#"/home/user/project/src/App.tsx
15:7 error 'unused' is defined but never used no-unused-vars
23:1 error Expected indentation of 2 spaces indent
/home/user/project/src/utils.ts
4:10 error 'x' is not defined no-undef
✖ 3 problems (3 errors, 0 warnings)
"#;
let result = compress_if_beneficial_pub("eslint src/", eslint_output);
assert!(
result.contains("15:7 error"),
"ESLint error compressed away! Got: {result}"
);
assert!(result.contains("no-unused-vars"));
}
#[test]
fn scenario_cargo_test_failures_preserved() {
let test_output = r#"running 3 tests
test tests::test_add ... ok
test tests::test_subtract ... FAILED
test tests::test_multiply ... ok
failures:
---- tests::test_subtract stdout ----
thread 'tests::test_subtract' panicked at src/math.rs:25:5:
assertion `left == right` failed
left: 5
right: 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::test_subtract
test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
"#;
let result = compress_if_beneficial_pub("cargo test", test_output);
assert!(
result.contains("src/math.rs:25:5"),
"Test failure location lost! Got: {result}"
);
assert!(result.contains("panicked at"));
assert!(result.contains("left: 5"));
}
#[test]
fn scenario_mypy_errors_preserved() {
let mypy_output = r#"src/main.py:10: error: Incompatible return value type (got "str", expected "int") [return-value]
src/utils.py:25: error: Name "undefined_var" is not defined [name-defined]
Found 2 errors in 2 files (checked 5 source files)
"#;
let result = compress_if_beneficial_pub("mypy src/", mypy_output);
assert!(
result.contains("src/main.py:10"),
"mypy error location lost! Got: {result}"
);
assert!(result.contains("Incompatible return value type"));
}
#[test]
fn scenario_ruff_errors_preserved() {
let ruff_output = r#"src/app.py:5:1: F401 [*] `os` imported but unused
src/utils.py:12:5: E741 Ambiguous variable name: `l`
Found 2 errors.
[*] 1 fixable with the `--fix` option.
"#;
let result = compress_if_beneficial_pub("ruff check src/", ruff_output);
assert!(
result.contains("src/app.py:5:1"),
"Ruff error location lost! Got: {result}"
);
assert!(result.contains("F401"));
}
#[test]
fn scenario_dotnet_build_errors_preserved() {
let dotnet_output = r#"Build started...
Build FAILED.
Program.cs(15,13): error CS1002: ; expected [/home/user/MyApp/MyApp.csproj]
Program.cs(23,5): error CS0103: The name 'undeclared' does not exist in the current context [/home/user/MyApp/MyApp.csproj]
2 Error(s)
"#;
let result = compress_if_beneficial_pub("dotnet build", dotnet_output);
assert!(
result.contains("Program.cs(15,13)"),
"dotnet error location lost! Got: {result}"
);
assert!(result.contains("CS1002"));
}
#[test]
fn scenario_successful_build_can_still_compress() {
// SUCCESS output without any error indicators should remain compressible
let success_output = " Compiling serde v1.0.193\n Compiling tokio v1.35.0\n Compiling myapp v0.1.0\n Finished `dev` profile [unoptimized + debuginfo] target(s) in 45.23s\n";
let result = compress_if_beneficial_pub("cargo build", success_output);
// Should not crash, and successful builds are NOT error-guarded
assert!(!result.is_empty());
}
#[test]
fn scenario_make_errors_preserved() {
let make_output = r#"gcc -Wall -o main main.c utils.c
main.c:23:5: error: implicit declaration of function 'undefined_func'
main.c:30:15: error: expected ';' before '}' token
make: *** [Makefile:12: main] Error 1
"#;
let result = compress_if_beneficial_pub("make build", make_output);
assert!(
result.contains("main.c:23:5"),
"Make/gcc error location lost! Got: {result}"
);
assert!(result.contains("implicit declaration"));
}
#[test]
fn scenario_gradle_errors_preserved() {
let gradle_output = r#"> Task :compileJava FAILED
/home/user/src/main/java/App.java:15: error: cannot find symbol
UndefinedClass obj = new UndefinedClass();
^
symbol: class UndefinedClass
location: class App
BUILD FAILED in 3s
"#;
let result = compress_if_beneficial_pub("./gradlew build", gradle_output);
assert!(
result.contains("App.java:15"),
"Gradle error location lost! Got: {result}"
);
assert!(result.contains("cannot find symbol"));
}
}
// =============================================================================
// Integration: Full agent workflow
// =============================================================================
mod integration_workflow {
use super::*;
#[test]
fn scenario_edit_save_reread_cycle() {
let mut cache = SessionCache::new();
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("workflow.rs");
std::fs::write(&file, "fn original() {}\n").unwrap();
let path = file.to_str().unwrap();
// 1. Agent reads file for the first time
let out1 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
assert!(out1.content.contains("original"));
assert_ne!(out1.resolved_mode, "error");
// 2. Agent reads same file again (cache hit, no disk I/O)
let out2 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
assert!(out2.content.contains("unchanged") || out2.content.contains("cached"));
// 3. File is edited externally
std::thread::sleep(std::time::Duration::from_millis(1100));
std::fs::write(&file, "fn edited_version() { new_logic(); }\n").unwrap();
// 4. Agent reads again — detects change
let out3 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
assert!(
out3.content.contains("edited_version")
|| out3.content.contains("delta")
|| out3.content.contains("diff"),
"Should detect file change, got: {}",
&out3.content[..out3.content.len().min(300)]
);
}
#[test]
fn scenario_search_in_project_with_many_files() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("src");
std::fs::create_dir_all(&src).unwrap();
for i in 0..10 {
let mut f = std::fs::File::create(src.join(format!("mod_{i:02}.rs"))).unwrap();
writeln!(f, "pub fn handler_{i}() {{}}").unwrap();
writeln!(f, "pub fn helper_{i}() {{}}").unwrap();
}
let result = lean_ctx::tools::ctx_search::handle(
"handler_",
dir.path().to_str().unwrap(),
Some("*.rs"),
50,
CrpMode::Off,
false,
false,
false,
)
.text;
assert!(result.contains("handler_"));
assert!(
result.contains("10 matches"),
"Expected 10 matches, got: {}",
&result[..result.len().min(200)]
);
}
#[test]
fn scenario_tree_of_project_structure() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("src");
let tests = dir.path().join("tests");
std::fs::create_dir_all(&src).unwrap();
std::fs::create_dir_all(&tests).unwrap();
std::fs::write(src.join("main.rs"), "fn main() {}").unwrap();
std::fs::write(src.join("lib.rs"), "pub mod utils;").unwrap();
std::fs::write(tests.join("integration.rs"), "#[test] fn it_works() {}").unwrap();
let (result, _) =
lean_ctx::tools::ctx_tree::handle(dir.path().to_str().unwrap(), 3, false, true);
assert!(result.contains("src"));
assert!(result.contains("tests"));
assert!(!result.starts_with("ERROR:"));
}
#[test]
fn scenario_multimode_read_workflow() {
let mut cache = SessionCache::new();
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("multi.rs");
std::fs::write(
&file,
"use std::io;\n\npub struct Config {\n pub name: String,\n pub port: u16,\n}\n\nimpl Config {\n pub fn new() -> Self {\n Self { name: \"app\".into(), port: 8080 }\n }\n}\n",
).unwrap();
let path = file.to_str().unwrap();
// 1. Read with full mode
let full = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"full",
CrpMode::Off,
None,
);
assert!(full.content.contains("Config"));
// 2. Read with signatures mode (should use compressed cache on 2nd call)
let sig1 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"signatures",
CrpMode::Off,
None,
);
assert!(!sig1.content.is_empty());
let sig2 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"signatures",
CrpMode::Off,
None,
);
// Should be identical (compressed cache hit)
assert_eq!(sig1.content, sig2.content);
// 3. Read with map mode
let map1 = lean_ctx::tools::ctx_read::handle_with_task_resolved(
&mut cache,
path,
"map",
CrpMode::Off,
None,
);
assert!(!map1.content.is_empty());
}
}