//! Cache-control tests count provider calls; they do not measure model relevance. //! Production still loads Google and root runs separate real CPU benchmarks. use anyhow::Result; use searchi_core::{SearchIndex, SearchOptions}; use searchi_semantic::TextWindow; use searchi_service::{EmbeddingProvider, HybridSearch}; use std::{ collections::BTreeMap, sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }, }; #[derive(Default)] struct Calls { queries: Mutex>, fail: AtomicBool, invalid: AtomicBool, new_fingerprint: AtomicBool, } struct CountingProvider(Arc); impl EmbeddingProvider for CountingProvider { fn fingerprint(&self) -> &str { if self.0.new_fingerprint.load(Ordering::Relaxed) { "cache-control-v2" } else { "cache-control-v1" } } fn dimensions(&self) -> usize { 768 } fn split_windows(&mut self, _: &str, _: &str) -> Result> { anyhow::bail!("Not used by cache-control tests") } fn embed_documents(&mut self, _: &[&str], _: &str) -> Result>> { anyhow::bail!("Not used by cache-control tests") } fn embed_query(&mut self, query: &str) -> Result> { self.0.queries.lock().unwrap().push(query.into()); if self.0.fail.load(Ordering::Relaxed) { anyhow::bail!("Injected provider failure"); } let mut vector = vec![0.0; 768]; if !self.0.invalid.load(Ordering::Relaxed) { vector[0] = 1.0; } Ok(vector) } fn embed_code_query(&mut self, query: &str) -> Result> { self.embed_query(&format!("code-task:{query}")) } fn query_diagnostics(&self) -> BTreeMap { BTreeMap::from([("worker_inference_us".into(), 123)]) } } fn fixture() -> Result<(tempfile::TempDir, HybridSearch, Arc)> { let directory = tempfile::tempdir()?; let database = directory.path().join("index.db"); let calls = Arc::new(Calls::default()); let service = HybridSearch::with_provider(database, Box::new(CountingProvider(calls.clone())))?; Ok((directory, service, calls)) } fn count(calls: &Calls) -> usize { calls.queries.lock().unwrap().len() } #[test] fn query_task_scopes_cache_and_code_debug_never_replays_old_worker_counters() -> Result<()> { let (_directory, mut service, calls) = fixture()?; let normal = SearchOptions::default(); let code = SearchOptions { code_only: true, ..Default::default() }; service.search("parse JSON", 5, &normal)?; service.search("parse JSON", 5, &code)?; assert_eq!( *calls.queries.lock().unwrap(), vec!["parse JSON", "code-task:parse JSON"] ); let cached = service.search_measured("parse JSON", 5, &code, true)?; assert_eq!(count(&calls), 2); let embedding = cached .metrics .trace .unwrap() .into_iter() .find(|s| s.name == "semantic.query_embedding") .unwrap(); assert_eq!(embedding.counters["code_task"], 1); assert_eq!(embedding.counters["query_cache_hit"], 1); assert!(!embedding.counters.contains_key("worker_inference_us")); service.search("parse JSON", 5, &normal)?; assert_eq!(count(&calls), 2); Ok(()) } #[test] fn exact_trimmed_queries_reuse_vectors_but_case_and_model_fingerprint_do_not() -> Result<()> { let (_directory, mut service, calls) = fixture()?; let options = SearchOptions::default(); service.search(" supplier ", 5, &options)?; service.search("supplier", 5, &options)?; assert_eq!(*calls.queries.lock().unwrap(), vec!["supplier"]); service.search("Supplier", 5, &options)?; assert_eq!(count(&calls), 2); calls.new_fingerprint.store(true, Ordering::Relaxed); service.search("supplier", 5, &options)?; assert_eq!(count(&calls), 3); Ok(()) } #[test] fn filters_and_changed_index_are_recomputed_even_when_vector_is_reused() -> Result<()> { let (directory, mut service, calls) = fixture()?; std::fs::write(directory.path().join("first.txt"), "supplier notice")?; std::fs::write(directory.path().join("second.md"), "supplier notice")?; let mut index = SearchIndex::open(directory.path().join("index.db"))?; index.index_folder(directory.path())?; let txt = SearchOptions { extension: Some("txt".into()), ..Default::default() }; let md = SearchOptions { extension: Some("md".into()), ..Default::default() }; assert_eq!( service.search("supplier", 5, &txt)?[0].filename, "first.txt" ); assert_eq!(service.search("supplier", 5, &md)?[0].filename, "second.md"); assert_eq!(count(&calls), 1); std::fs::remove_file(directory.path().join("first.txt"))?; index.index_folder(directory.path())?; assert!(service.search("supplier", 5, &txt)?.is_empty()); assert_eq!(count(&calls), 1); Ok(()) } #[test] fn cache_hit_trace_never_replays_prior_worker_diagnostics() -> Result<()> { let (_directory, mut service, _calls) = fixture()?; let miss = service.search_measured("supplier", 5, &SearchOptions::default(), true)?; let hit = service.search_measured("supplier", 5, &SearchOptions::default(), true)?; let miss = miss .metrics .trace .unwrap() .into_iter() .find(|span| span.name == "semantic.query_embedding") .unwrap(); let hit = hit .metrics .trace .unwrap() .into_iter() .find(|span| span.name == "semantic.query_embedding") .unwrap(); assert_eq!(miss.counters["query_cache_miss"], 1); assert_eq!(miss.counters["worker_inference_us"], 123); assert_eq!(hit.counters["query_cache_hit"], 1); assert_eq!(hit.counters["query_cache_miss"], 0); assert!(!hit.counters.contains_key("worker_inference_us")); assert_eq!(hit.counters["failed"], 0); Ok(()) } #[test] fn failures_and_invalid_vectors_are_not_cached() -> Result<()> { let (_directory, mut service, calls) = fixture()?; assert!(service .search_semantic(&"界".repeat(2000), 5, &SearchOptions::default()) .is_err()); assert_eq!( count(&calls), 0, "Oversized query must not reach the provider or cache" ); calls.fail.store(true, Ordering::Relaxed); for _ in 0..2 { assert!(service .search("supplier", 5, &SearchOptions::default()) .is_err()); } calls.fail.store(false, Ordering::Relaxed); calls.invalid.store(true, Ordering::Relaxed); for _ in 0..2 { assert!(service .search("supplier", 5, &SearchOptions::default()) .is_err()); } calls.invalid.store(false, Ordering::Relaxed); service.search("supplier", 5, &SearchOptions::default())?; service.search("supplier", 5, &SearchOptions::default())?; assert_eq!(count(&calls), 5); Ok(()) } #[test] fn cache_evicts_least_recently_used_query_at_32_entries() -> Result<()> { let (_directory, mut service, calls) = fixture()?; for number in 0..32 { service.search(&format!("query{number}"), 5, &SearchOptions::default())?; } service.search("query0", 5, &SearchOptions::default())?; service.search("query32", 5, &SearchOptions::default())?; service.search("query0", 5, &SearchOptions::default())?; assert_eq!(count(&calls), 33); service.search("query1", 5, &SearchOptions::default())?; assert_eq!(count(&calls), 34); Ok(()) } #[test] fn warmup_bypasses_cache_and_never_seeds_an_unseen_measured_query() -> Result<()> { let (_directory, mut service, calls) = fixture()?; service.warmup()?; assert_eq!(count(&calls), 1); let first = service.search_measured( "warm up local file search", 5, &SearchOptions::default(), true, )?; assert_eq!(count(&calls), 2); let span = first .metrics .trace .unwrap() .into_iter() .find(|span| span.name == "semantic.query_embedding") .unwrap(); assert_eq!(span.counters["query_cache_miss"], 1); service.warmup()?; assert_eq!(count(&calls), 3); service.search("warm up local file search", 5, &SearchOptions::default())?; assert_eq!(count(&calls), 3); Ok(()) }