#![allow(clippy::await_holding_lock)] use serde_json::json; fn setup_project() -> (tempfile::TempDir, lean_ctx::engine::ContextEngine) { let dir = tempfile::tempdir().expect("tempdir"); let project = dir.path().join("project"); std::fs::create_dir_all(&project).expect("create project"); std::fs::create_dir_all(project.join("src")).expect("create src"); std::fs::write( project.join("src/main.rs"), r#"fn main() { let config = load_config(); println!("Running with {:?}", config); } fn load_config() -> Config { Config { debug: false, port: 8080 } } #[derive(Debug)] struct Config { debug: bool, port: u16, } fn helper_a() -> String { "hello".to_string() } fn helper_b(x: i32) -> i32 { x * 2 } "#, ) .expect("write main.rs"); std::fs::write( project.join("src/lib.rs"), r#"pub mod utils; pub fn greet(name: &str) -> String { format!("Hello, {name}!") } pub fn add(a: i32, b: i32) -> i32 { a + b } "#, ) .expect("write lib.rs"); std::fs::create_dir_all(project.join("src/utils")).expect("create utils"); std::fs::write( project.join("src/utils/mod.rs"), r#"pub fn format_bytes(n: u64) -> String { if n < 1024 { format!("{n} B") } else if n < 1024 * 1024 { format!("{:.1} KB", n as f64 / 1024.0) } else { format!("{:.1} MB", n as f64 / (1024.0 * 1024.0)) } } "#, ) .expect("write utils/mod.rs"); std::fs::write( project.join("Cargo.toml"), r#"[package] name = "test-project" version = "0.1.0" edition = "2021" "#, ) .expect("write Cargo.toml"); std::fs::write( project.join("README.md"), "# Test Project\nA sample project.\n", ) .expect("write README.md"); let data_dir = dir.path().join("data"); std::fs::create_dir_all(&data_dir).expect("create data dir"); unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string()); } let engine = lean_ctx::engine::ContextEngine::with_project_root(&project); (dir, engine) } // ── Phase 1: Setup & Orientation ─────────────────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase1_session_task_and_overview() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let out = engine .call_tool_text( "ctx_session", Some(json!({"action": "task", "content": "Implement caching layer"})), ) .await .expect("session task"); assert!( out.contains("task") || out.contains("Task") || out.contains("set"), "task output: {out}" ); let overview = engine .call_tool_text("ctx_overview", None) .await .expect("overview"); assert!( overview.contains("src") || overview.contains("project"), "overview should contain project structure: {overview}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase1_tree_depth() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let tree = engine .call_tool_text("ctx_tree", Some(json!({"depth": 3}))) .await .expect("tree"); assert!(tree.contains("src"), "tree should show src: {tree}"); assert!( tree.contains("Cargo.toml") || tree.contains("main.rs"), "tree should show files: {tree}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } // ── Phase 2: Codebase Research (Read Modes) ──────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase2_read_full_and_cache_stub() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let full1 = engine .call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"}))) .await .expect("full read 1"); assert!( full1.contains("load_config"), "full read should contain fn body: {full1}" ); let full2 = engine .call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"}))) .await .expect("full read 2"); assert!( full2.len() <= full1.len(), "re-read should be stub or same size (full1={}, full2={})", full1.len(), full2.len() ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase2_read_signatures() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let sigs = engine .call_tool_text( "ctx_read", Some(json!({"path": main_path, "mode": "signatures"})), ) .await .expect("signatures read"); assert!( sigs.contains("fn main") || sigs.contains("load_config") || sigs.contains("helper_a"), "signatures should contain fn names: {sigs}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase2_read_map() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let lib_path = project.join("src/lib.rs").to_string_lossy().to_string(); let map = engine .call_tool_text("ctx_read", Some(json!({"path": lib_path, "mode": "map"}))) .await .expect("map read"); assert!(!map.is_empty(), "map should not be empty"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase2_read_lines_range() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let lines = engine .call_tool_text( "ctx_read", Some(json!({"path": main_path, "mode": "lines:1-5"})), ) .await .expect("lines read"); assert!( lines.contains("fn main"), "lines 1-5 should contain fn main: {lines}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase2_read_auto() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let auto = engine .call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "auto"}))) .await .expect("auto read"); assert!(!auto.is_empty(), "auto mode should return content"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase2_read_aggressive() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let aggressive = engine .call_tool_text( "ctx_read", Some(json!({"path": main_path, "mode": "aggressive"})), ) .await .expect("aggressive read"); assert!( !aggressive.is_empty(), "aggressive mode should return content" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase2_read_diff_after_edit() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let _ = engine .call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"}))) .await .expect("initial read"); let content = std::fs::read_to_string(project.join("src/main.rs")).unwrap(); let modified = content.replace("debug: false", "debug: true"); std::fs::write(project.join("src/main.rs"), &modified).unwrap(); let diff = engine .call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "diff"}))) .await .expect("diff read"); assert!( diff.contains("debug") || diff.contains("changed") || diff.contains("modified") || diff.contains("true"), "diff should show changes: {diff}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } // ── Phase 3: Code Search ─────────────────────────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase3_search_regex() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let out = engine .call_tool_text("ctx_search", Some(json!({"pattern": "fn main"}))) .await .expect("search"); assert!( out.contains("main") || out.contains("src/main.rs"), "search should find fn main: {out}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase3_search_with_path_filter() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let out = engine .call_tool_text( "ctx_search", Some(json!({"pattern": "pub fn", "path": "src/utils"})), ) .await .expect("search with path"); assert!( out.contains("format_bytes"), "filtered search should find format_bytes: {out}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } // ── Phase 4: Shell & Execution ───────────────────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase4_shell_echo() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let out = engine .call_tool_text( "ctx_shell", Some(json!({"command": "echo hello-from-shell"})), ) .await .expect("shell echo"); assert!( out.contains("hello-from-shell"), "shell should contain echo output: {out}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase4_shell_raw_mode() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let out = engine .call_tool_text( "ctx_shell", Some(json!({"command": "echo raw-test-value", "raw": true})), ) .await .expect("shell raw"); assert!( out.contains("raw-test-value"), "raw shell should contain output: {out}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } // ── Phase 5: Edit & Iteration ────────────────────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase5_edit_and_verify() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let lib_path = project.join("src/lib.rs").to_string_lossy().to_string(); let edit_result = engine .call_tool_text( "ctx_edit", Some(json!({ "path": lib_path, "old_string": "pub fn greet(name: &str) -> String {\n format!(\"Hello, {name}!\")\n}", "new_string": "pub fn greet(name: &str) -> String {\n format!(\"Hi there, {name}!\")\n}", })), ) .await .expect("edit"); assert!( edit_result.contains("applied") || edit_result.contains("ok") || edit_result.contains("✓") || edit_result.contains("success") || !edit_result.is_empty(), "edit should succeed: {edit_result}" ); let content = std::fs::read_to_string(project.join("src/lib.rs")).unwrap(); assert!( content.contains("Hi there"), "file should contain edited text" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } // ── Phase 6: Context Management ──────────────────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase6_compress_and_cache_status() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let _ = engine .call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"}))) .await .expect("prime cache"); let compress = engine.call_tool_text("ctx_compress", None).await; match compress { Ok(out) => assert!(!out.is_empty(), "compress should return output"), Err(e) => { let msg = e.to_string(); assert!( msg.contains("timeout") || msg.contains("timed out"), "compress unexpected error: {msg}" ); } } let cache = engine .call_tool_text("ctx_cache", Some(json!({"action": "status"}))) .await .expect("cache status"); let cache_lower = cache.to_lowercase(); assert!( cache_lower.contains("cache") || cache_lower.contains("entries") || cache_lower.contains("file"), "cache status should show info: {cache}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase6_ledger_status() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let ledger = engine .call_tool_text("ctx_ledger", Some(json!({"action": "status"}))) .await .expect("ledger status"); assert!(!ledger.is_empty(), "ledger status should return output"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase6_dedup() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let lib_path = project.join("src/lib.rs").to_string_lossy().to_string(); let _ = engine .call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"}))) .await .unwrap(); let _ = engine .call_tool_text("ctx_read", Some(json!({"path": lib_path, "mode": "full"}))) .await .unwrap(); let dedup = engine .call_tool_text("ctx_dedup", None) .await .expect("dedup"); assert!(!dedup.is_empty(), "dedup should return output"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase6_plan() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let plan = engine .call_tool_text( "ctx_plan", Some(json!({"task": "Add a caching layer to the Config struct"})), ) .await .expect("plan"); assert!(!plan.is_empty(), "plan should return output"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } // ── Phase 7: Knowledge & Session ─────────────────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase7_knowledge_remember_and_recall() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let remember = engine .call_tool_text( "ctx_knowledge", Some(json!({ "action": "remember", "category": "architecture", "key": "cache_strategy", "value": "LRU with zstd compression, 128MB limit", })), ) .await .expect("knowledge remember"); assert!(!remember.is_empty(), "remember should return confirmation"); let recall = engine .call_tool_text( "ctx_knowledge", Some(json!({"action": "recall", "category": "architecture"})), ) .await .expect("knowledge recall"); assert!( recall.contains("cache_strategy") || recall.contains("LRU"), "recall should contain remembered fact: {recall}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase7_knowledge_timeline_and_rooms() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let _ = engine .call_tool_text( "ctx_knowledge", Some(json!({ "action": "remember", "category": "decisions", "key": "db_choice", "value": "PostgreSQL for persistence", })), ) .await .expect("remember decision"); let timeline = engine .call_tool_text( "ctx_knowledge", Some(json!({"action": "timeline", "category": "decisions"})), ) .await .expect("timeline"); assert!(!timeline.is_empty(), "timeline should return entries"); let rooms = engine .call_tool_text("ctx_knowledge", Some(json!({"action": "rooms"}))) .await .expect("rooms"); assert!( rooms.contains("decisions"), "rooms should list the decisions category: {rooms}" ); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase7_session_finding_and_decision() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let finding = engine .call_tool_text( "ctx_session", Some(json!({ "action": "finding", "content": "Config struct lacks validation for port range", })), ) .await .expect("session finding"); assert!(!finding.is_empty(), "finding should return confirmation"); let decision = engine .call_tool_text( "ctx_session", Some(json!({ "action": "decision", "content": "Use builder pattern for Config validation", })), ) .await .expect("session decision"); assert!(!decision.is_empty(), "decision should return confirmation"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } // ── Phase 8: Multi-Agent Coordination ────────────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase8_agent_register_and_diary() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let reg = engine .call_tool_text( "ctx_agent", Some(json!({ "action": "register", "name": "backend-agent", "role": "Implements API endpoints", })), ) .await .expect("agent register"); assert!(!reg.is_empty(), "register should return confirmation"); let diary = engine .call_tool_text( "ctx_agent", Some(json!({ "action": "diary", "category": "progress", "content": "Completed Config struct refactoring", })), ) .await .expect("agent diary"); assert!(!diary.is_empty(), "diary should return confirmation"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } // ── Phase 9: Metrics & Verification ──────────────────────── #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase9_metrics() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let project = _dir.path().join("project"); let main_path = project.join("src/main.rs").to_string_lossy().to_string(); let _ = engine .call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"}))) .await .unwrap(); let metrics = engine .call_tool_text("ctx_metrics", None) .await .expect("metrics"); assert!(!metrics.is_empty(), "metrics should return data"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn phase9_session_save() { let _lock = lean_ctx::core::data_dir::test_env_lock(); let (_dir, engine) = setup_project(); let _ = engine .call_tool_text( "ctx_session", Some(json!({"action": "task", "content": "Final verification"})), ) .await .expect("set task"); let save = engine .call_tool_text("ctx_session", Some(json!({"action": "save"}))) .await .expect("session save"); assert!(!save.is_empty(), "save should return confirmation"); unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR"); } }