use alpha_id::model::{AlphaIdEntry, Config, IndexStatus}; use alpha_id::packed::{packed_path, write_packed, PackedHeader}; use alpha_id::packed::corpus_sha256; use alpha_id::pipeline::load_packed_or_store; fn entry(text: &str) -> AlphaIdEntry { AlphaIdEntry { alpha_id: "X".to_string(), valid: true, icd_primary: "A00".to_string(), icd_star: String::new(), icd_addon: String::new(), icd_primary2: String::new(), orpha: String::new(), text: text.to_string(), } } fn cfg_in(dir: &std::path::Path) -> Config { let mut cfg = Config::load("config/default.toml").unwrap(); cfg.index_dir = dir.to_str().unwrap().to_string(); cfg.embed_model = "test/model".to_string(); cfg } #[test] fn index_status_defaults_to_absent() { assert_eq!(IndexStatus::default(), IndexStatus::Absent); } #[test] fn index_status_serializes_externally_tagged() { let j = serde_json::to_string(&IndexStatus::Mismatch("x".to_string())).unwrap(); assert!(j.contains("Mismatch"), "got {j}"); } #[test] fn packed_beats_store_builds_without_store() { let dir = tempfile::tempdir().unwrap(); let mut cfg = cfg_in(dir.path()); // Hermetic corpus so the header's corpus_sha256 can match the live config // under iteration-2 semantic enforcement. let corpus = dir.path().join("corpus.txt"); std::fs::write(&corpus, b"some corpus bytes").unwrap(); cfg.alpha_id_path = corpus.to_str().unwrap().to_string(); let entries = vec![entry("a"), entry("b")]; let rows = vec![vec![1.0f32, 0.0], vec![0.0f32, 1.0]]; let h = PackedHeader { n_rows: 2, dim: 2, embed_model: cfg.embed_model.clone(), corpus_sha256: corpus_sha256(&cfg.alpha_id_path).unwrap(), corpus_name: "c.txt".to_string(), }; write_packed(&packed_path(&cfg.index_dir, &cfg.embed_model), &h, &rows).unwrap(); // No per-file store populated in `dir`; the index must come from the packed file. let (vector, status) = load_packed_or_store(&cfg, &entries); assert!(vector.is_some()); assert_eq!(status, IndexStatus::Ok); } #[test] fn n_rows_mismatch_yields_mismatch_none() { let dir = tempfile::tempdir().unwrap(); let cfg = cfg_in(dir.path()); let entries = vec![entry("a"), entry("b"), entry("c")]; // 3 entries let rows = vec![vec![1.0f32, 0.0], vec![0.0f32, 1.0]]; // packed claims 2 rows let h = PackedHeader { n_rows: 2, dim: 2, embed_model: cfg.embed_model.clone(), corpus_sha256: "x".to_string(), corpus_name: "c.txt".to_string(), }; write_packed(&packed_path(&cfg.index_dir, &cfg.embed_model), &h, &rows).unwrap(); let (vector, status) = load_packed_or_store(&cfg, &entries); assert!(vector.is_none()); assert!(matches!(status, IndexStatus::Mismatch(_))); } fn header_for(cfg: &Config, n_rows: u32, model: &str, corpus_hash: &str) -> PackedHeader { let _ = cfg; PackedHeader { n_rows, dim: 2, embed_model: model.to_string(), corpus_sha256: corpus_hash.to_string(), corpus_name: "c.txt".to_string(), } } #[test] fn index_status_display_renders_spec_tokens() { assert_eq!(format!("{}", IndexStatus::Ok), "ok"); assert_eq!(format!("{}", IndexStatus::Absent), "absent"); assert_eq!( format!("{}", IndexStatus::Mismatch("corpus sha256".to_string())), "mismatch: corpus sha256" ); } #[test] fn model_mismatch_yields_mismatch_none() { let dir = tempfile::tempdir().unwrap(); let cfg = cfg_in(dir.path()); // cfg.embed_model == "test/model" let entries = vec![entry("a"), entry("b")]; let rows = vec![vec![1.0f32, 0.0], vec![0.0f32, 1.0]]; // n_rows matches (2) so the structural check passes; the header declares a // different model, so the semantic check must reject it. let h = header_for(&cfg, 2, "other/model", "irrelevant"); write_packed(&packed_path(&cfg.index_dir, &cfg.embed_model), &h, &rows).unwrap(); let (vector, status) = load_packed_or_store(&cfg, &entries); assert!(vector.is_none()); assert!(matches!(status, IndexStatus::Mismatch(_))); } #[test] fn corpus_hash_mismatch_yields_mismatch_none() { let dir = tempfile::tempdir().unwrap(); let mut cfg = cfg_in(dir.path()); // Point the corpus at a tiny temp file so the hash is hermetic. let corpus = dir.path().join("corpus.txt"); std::fs::write(&corpus, b"some corpus bytes").unwrap(); cfg.alpha_id_path = corpus.to_str().unwrap().to_string(); let entries = vec![entry("a"), entry("b")]; let rows = vec![vec![1.0f32, 0.0], vec![0.0f32, 1.0]]; // Model matches, but the header's corpus hash does not match the file's. let h = header_for(&cfg, 2, &cfg.embed_model, "deadbeef-not-the-real-hash"); write_packed(&packed_path(&cfg.index_dir, &cfg.embed_model), &h, &rows).unwrap(); let (vector, status) = load_packed_or_store(&cfg, &entries); assert!(vector.is_none()); assert!(matches!(status, IndexStatus::Mismatch(_))); } #[test] fn matching_model_and_corpus_yields_ok() { let dir = tempfile::tempdir().unwrap(); let mut cfg = cfg_in(dir.path()); let corpus = dir.path().join("corpus.txt"); std::fs::write(&corpus, b"some corpus bytes").unwrap(); cfg.alpha_id_path = corpus.to_str().unwrap().to_string(); let entries = vec![entry("a"), entry("b")]; let rows = vec![vec![1.0f32, 0.0], vec![0.0f32, 1.0]]; let real_hash = corpus_sha256(&cfg.alpha_id_path).unwrap(); let h = header_for(&cfg, 2, &cfg.embed_model, &real_hash); write_packed(&packed_path(&cfg.index_dir, &cfg.embed_model), &h, &rows).unwrap(); let (vector, status) = load_packed_or_store(&cfg, &entries); assert!(vector.is_some()); assert_eq!(status, IndexStatus::Ok); }