//! End-to-end coverage for the `ctx_compose` task composer. //! //! The library unit tests only cover keyword extraction; these exercise the //! full `handle()` path (semantic ranking + exact match + symbol body) and the //! H1 hardening: the semantic stage must never stall the call beyond its budget. use std::sync::Mutex; use std::time::{Duration, Instant}; use lean_ctx::tools::CrpMode; use lean_ctx::tools::ctx_compose; /// `LEAN_CTX_COMPOSE_BUDGET_MS` is process-global; serialize tests that set it. static ENV_GUARD: Mutex<()> = Mutex::new(()); fn write_corpus() -> tempfile::TempDir { let dir = tempfile::tempdir().unwrap(); std::fs::write( dir.path().join("auth.rs"), "pub fn authenticate_user(token: &str) -> bool {\n \ validate_token(token) && !token.is_empty()\n}\n\n\ fn validate_token(token: &str) -> bool {\n token.len() > 8\n}\n", ) .unwrap(); std::fs::write( dir.path().join("config.rs"), "pub fn parse_config(path: &str) -> String {\n \ std::fs::read_to_string(path).unwrap_or_default()\n}\n", ) .unwrap(); dir } #[test] fn compose_returns_all_sections_with_symbol_body() { let _guard = ENV_GUARD .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_COMPOSE_BUDGET_MS") }; let dir = write_corpus(); let (out, tokens) = ctx_compose::handle( "how does authenticate_user validate the token", &dir.path().to_string_lossy(), CrpMode::Off, ); assert!(out.contains("TASK:"), "must echo the task header"); assert!( out.contains("## Ranked files (semantic)"), "must contain the semantic ranking section" ); assert!( out.contains("## Exact matches"), "must contain the exact-match section" ); assert!( out.contains("authenticate_user"), "exact matches / symbol body must surface the queried symbol:\n{out}" ); assert!(tokens > 0, "token count must be reported"); } #[test] fn compose_degrades_under_tight_budget_without_stalling() { let _guard = ENV_GUARD .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); // A 1 ms budget guarantees the semantic worker cannot finish in time, so the // call must degrade gracefully instead of blocking on the (cold) build. // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_COMPOSE_BUDGET_MS", "1") }; let dir = write_corpus(); let start = Instant::now(); let (out, _tokens) = ctx_compose::handle( "how does authenticate_user validate the token", &dir.path().to_string_lossy(), CrpMode::Off, ); let elapsed = start.elapsed(); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_COMPOSE_BUDGET_MS") }; // The exact-match + symbol stages are synchronous and index-backed, so the // whole call should still return promptly even when ranking is deferred. assert!( elapsed < Duration::from_secs(10), "tight budget must not stall the call (took {elapsed:?})" ); assert!( out.contains("## Ranked files (semantic)"), "section header is always present" ); assert!( out.contains("## Exact matches"), "exact matches remain authoritative under degradation" ); } #[test] fn compose_rejects_empty_task() { let _guard = ENV_GUARD .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let (out, tokens) = ctx_compose::handle(" ", "/tmp", CrpMode::Off); assert!(out.starts_with("ERROR")); assert_eq!(tokens, 0); } #[test] fn compose_surfaces_associative_neighbours() { let _guard = ENV_GUARD .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_COMPOSE_BUDGET_MS") }; // Generous graph budget so the (tiny) index build never times out here. // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_COMPOSE_GRAPH_BUDGET_MS", "8000") }; let dir = write_corpus(); // `authenticate_user` lives in auth.rs; config.rs is a same-dir sibling, so // the graph connects them and spreading activation from the auth.rs seed // must surface config.rs as an associative neighbour. let (out, _tokens) = ctx_compose::handle( "explain authenticate_user", &dir.path().to_string_lossy(), CrpMode::Off, ); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_COMPOSE_GRAPH_BUDGET_MS") }; assert!( out.contains("## Related (associative"), "associative section should appear when the graph connects files:\n{out}" ); assert!( out.contains("config.rs"), "the sibling neighbour should be surfaced via spreading activation:\n{out}" ); }