#[path = "support/corpus_harness.rs"]
mod corpus_harness;
use std::fs;
use corpus_harness::CorpusFixture;
#[test]
fn corpus_harness_invariants() {
let cache = tempfile::tempdir().expect("tmp cache");
let fx = CorpusFixture::small().with_cache_dir(cache.path());
let Some((_dir, engine)) = fx.open_or_skip() else { return };
let report = fx.ingest_into(&engine);
assert!(report.nodes > 0, "ingest wrote 0 nodes");
assert!(!report.embed_cache_hit, "expected a cold-cache miss on first ingest");
assert!(report.embedded_live > 0, "cold ingest embedded nothing live");
assert_eq!(
report.cache_miss_reason.as_deref(),
Some("cold"),
"cold ingest must surface a loud cold-miss reason"
);
fx.assert_vec0_row_count_matches_ingest(&engine);
fx.assert_fts_index_populated(&engine);
let qs = fx.query_set(25, 0xC0FFEE);
assert!(!qs.is_empty(), "query_set produced no queries");
for q in &qs {
assert_ne!(q.text.trim(), q.target_body.trim(), "query equals its source body");
}
fx.assert_search_returns_non_empty_for_each(&engine, &qs);
}
#[test]
fn corpus_harness_determinism() {
let dir_a = tempfile::tempdir().expect("tmp a");
let dir_b = tempfile::tempdir().expect("tmp b");
let fx_a = CorpusFixture::small().with_cache_dir(dir_a.path());
let fx_b = CorpusFixture::small().with_cache_dir(dir_b.path());
if fx_a.skip_reason().is_some() {
eprintln!("SKIP: {:?}", fx_a.skip_reason());
return;
}
let (_da, ea) = fx_a.open_engine();
let ra = fx_a.ingest_into(&ea);
let (_db, eb) = fx_b.open_engine();
let rb = fx_b.ingest_into(&eb);
assert_eq!(
ra.cache_path.file_name(),
rb.cache_path.file_name(),
"equivalent fixtures produced different cache keys"
);
let bytes_a = fs::read(&ra.cache_path).expect("read cache a");
let bytes_b = fs::read(&rb.cache_path).expect("read cache b");
assert_eq!(
bytes_a,
bytes_b,
"cache blobs are not byte-identical across equivalent constructions ({} vs {} bytes)",
bytes_a.len(),
bytes_b.len()
);
assert!(!bytes_a.is_empty(), "cache blob is empty");
let fx_warm = CorpusFixture::small().with_cache_dir(dir_a.path());
let (_dw, ew) = fx_warm.open_engine();
let rw = fx_warm.ingest_into(&ew);
assert!(rw.embed_cache_hit, "warm run did not hit the cache");
assert_eq!(rw.embedded_live, 0, "warm run still embedded live");
assert!(rw.cache_miss_reason.is_none(), "warm hit must not report a miss reason");
}
#[test]
fn corpus_harness_cache_invalidation() {
let dir = tempfile::tempdir().expect("tmp");
let fx_a = CorpusFixture::small().with_synthetic_revision("rev-A").with_cache_dir(dir.path());
if fx_a.skip_reason().is_some() {
eprintln!("SKIP: {:?}", fx_a.skip_reason());
return;
}
let fx_b = CorpusFixture::small().with_synthetic_revision("rev-B").with_cache_dir(dir.path());
let (_da, ea) = fx_a.open_engine();
let ra = fx_a.ingest_into(&ea);
let (_db, eb) = fx_b.open_engine();
let rb = fx_b.ingest_into(&eb);
assert_ne!(
ra.cache_path, rb.cache_path,
"identity change did not change the cache key — STALE-CACHE RISK"
);
assert!(!rb.embed_cache_hit, "identity change reused a stale cache");
assert!(rb.embedded_live > 0, "identity change did not re-embed");
assert!(rb.cache_miss_reason.is_some(), "identity-change miss must be loud (reason surfaced)");
let fx_a2 = CorpusFixture::small().with_synthetic_revision("rev-A").with_cache_dir(dir.path());
let (_da2, ea2) = fx_a2.open_engine();
let ra2 = fx_a2.ingest_into(&ea2);
assert!(ra2.embed_cache_hit, "rev-A warm re-run was not a cache hit");
assert!(ra2.cache_miss_reason.is_none(), "rev-A warm re-run wrongly reported a miss");
let meta = ra.cache_path.with_extension("meta.json");
fs::remove_file(&meta).expect("remove sidecar");
let fx_a3 = CorpusFixture::small().with_synthetic_revision("rev-A").with_cache_dir(dir.path());
let (_da3, ea3) = fx_a3.open_engine();
let ra3 = fx_a3.ingest_into(&ea3);
assert!(!ra3.embed_cache_hit, "partial cache was wrongly treated as a hit");
assert!(
ra3.cache_miss_reason.as_deref().is_some_and(|r| r.contains("partial")),
"partial-cache miss must be loud with a 'partial' reason, got {:?}",
ra3.cache_miss_reason
);
let dir_stale = tempfile::tempdir().expect("tmp stale");
let fx_s1 =
CorpusFixture::small().with_synthetic_revision("rev-S").with_cache_dir(dir_stale.path());
let (_ds1, es1) = fx_s1.open_engine();
let rs1 = fx_s1.ingest_into(&es1);
assert!(!rs1.embed_cache_hit, "first rev-S ingest should be a cold write");
let meta_path = rs1.cache_path.with_extension("meta.json");
let mut meta: serde_json::Value =
serde_json::from_slice(&fs::read(&meta_path).expect("read meta")).expect("parse meta");
meta["doc_manifest_sha"] = serde_json::Value::String("0".repeat(64));
fs::write(&meta_path, serde_json::to_vec_pretty(&meta).expect("ser meta")).expect("write meta");
let fx_s2 =
CorpusFixture::small().with_synthetic_revision("rev-S").with_cache_dir(dir_stale.path());
let (_ds2, es2) = fx_s2.open_engine();
let rs2 = fx_s2.ingest_into(&es2);
assert!(!rs2.embed_cache_hit, "present-but-stale cache was wrongly trusted");
assert!(
rs2.cache_miss_reason.as_deref().is_some_and(|r| r.contains("mismatch")),
"stale-cache (manifest) miss must be loud with a mismatch reason, got {:?}",
rs2.cache_miss_reason
);
let dir_id = tempfile::tempdir().expect("tmp id");
let fx_i1 =
CorpusFixture::small().with_synthetic_revision("rev-I").with_cache_dir(dir_id.path());
let (_di1, ei1) = fx_i1.open_engine();
let ri1 = fx_i1.ingest_into(&ei1);
let meta_i = ri1.cache_path.with_extension("meta.json");
let mut mi: serde_json::Value =
serde_json::from_slice(&fs::read(&meta_i).expect("read meta")).expect("parse meta");
mi["identity"]["revision"] = serde_json::Value::String("rev-TAMPERED".to_string());
fs::write(&meta_i, serde_json::to_vec_pretty(&mi).expect("ser meta")).expect("write meta");
let fx_i2 =
CorpusFixture::small().with_synthetic_revision("rev-I").with_cache_dir(dir_id.path());
let (_di2, ei2) = fx_i2.open_engine();
let ri2 = fx_i2.ingest_into(&ei2);
assert!(!ri2.embed_cache_hit, "identity-tampered cache was wrongly trusted");
assert!(
ri2.cache_miss_reason.as_deref().is_some_and(|r| r.contains("identity")),
"stale-cache (identity) miss must be loud with an identity reason, got {:?}",
ri2.cache_miss_reason
);
let dir2 = tempfile::tempdir().expect("tmp2");
let all = fx_a.docs().to_vec();
assert!(all.len() >= 2, "need >=2 docs to drop one");
let dropped = all[1..].to_vec();
let fx_full = CorpusFixture::from_docs("sub", all).with_cache_dir(dir2.path());
let fx_drop = CorpusFixture::from_docs("sub", dropped).with_cache_dir(dir2.path());
let (_df, ef) = fx_full.open_engine();
let rf = fx_full.ingest_into(&ef);
let (_dd, ed) = fx_drop.open_engine();
let rd = fx_drop.ingest_into(&ed);
assert_ne!(
rf.cache_path, rd.cache_path,
"subset-content change did not change the cache key — STALE-CACHE RISK"
);
assert!(!rd.embed_cache_hit, "subset-content change reused a stale cache");
}
#[test]
fn corpus_harness_skip_paths() {
let empty = CorpusFixture::from_docs("empty", Vec::new());
assert!(empty.skip_reason().is_some(), "empty fixture must report a skip reason");
assert!(empty.open_or_skip().is_none(), "empty fixture must skip open");
let real = CorpusFixture::small().with_real_embedder();
#[cfg(not(feature = "default-embedder"))]
{
assert!(
real.skip_reason().is_some(),
"with_real_embedder must SKIP when default-embedder is off"
);
assert!(real.open_or_skip().is_none(), "real path must skip without the feature");
}
#[cfg(feature = "default-embedder")]
{
if !real.docs().is_empty() {
assert!(
real.skip_reason().is_none(),
"real embedder unexpectedly unavailable with feature on: {:?}",
real.skip_reason()
);
}
}
}
#[cfg(feature = "default-embedder")]
#[test]
fn corpus_harness_real_embedder_smoke() {
if std::env::var("AGENT_LONG").is_err() {
eprintln!("SKIP: corpus_harness_real_embedder_smoke requires AGENT_LONG=1");
return;
}
let cache = tempfile::tempdir().expect("tmp cache");
let fx = CorpusFixture::small().with_real_embedder().with_cache_dir(cache.path());
let Some((_dir, engine)) = fx.open_or_skip() else { return };
let cold = fx.ingest_into(&engine);
assert!(cold.nodes > 0, "real ingest wrote 0 nodes");
assert!(!cold.embed_cache_hit, "first real ingest should be a cold miss");
assert_eq!(cold.cache_miss_reason.as_deref(), Some("cold"), "cold real miss not loud");
assert!(cold.embedded_live > 0, "cold real ingest embedded nothing live");
assert!(cold.cache_path.exists(), "real cache blob not written to disk");
fx.assert_vec0_row_count_matches_ingest(&engine);
fx.assert_fts_index_populated(&engine);
let fx2 = CorpusFixture::small().with_real_embedder().with_cache_dir(cache.path());
let (_dir2, engine2) = fx2.open_engine();
let warm = fx2.ingest_into(&engine2);
assert!(warm.embed_cache_hit, "warm real reopen did not hit the cache");
assert_eq!(warm.embedded_live, 0, "warm real reopen still embedded bodies live");
assert!(warm.cache_miss_reason.is_none(), "warm real reopen reported a miss");
let qs = fx2.query_set(10, 0xBEEF);
fx2.assert_search_returns_non_empty_for_each(&engine2, &qs);
}