use alpha_id::model::{AlphaIdEntry, Config, IndexStatus}; use alpha_id::pipeline::load_packed_or_store; use std::path::Path; use std::process::Command; // Write a minimal config TOML pointing the corpus + index dir at temp paths. // The `--pack` branch only reads `alpha_id_path` (corpus) and `index_dir` // (store); `claml_path` etc. are present for parse but unused on this path. fn write_config(dir: &Path, corpus: &Path, index_dir: &Path) -> std::path::PathBuf { let toml = format!( r#"ionos_base_url = "http://127.0.0.1:1" token_path = "/nonexistent" embed_model = "test/model" rerank_model = "test/rerank" alpha_id_path = "{corpus}" claml_path = "/nonexistent.xml" index_dir = "{index}" pool_size = 10 top_k = 10 "#, corpus = corpus.to_str().unwrap(), index = index_dir.to_str().unwrap(), ); let p = dir.join("config.toml"); std::fs::write(&p, toml).unwrap(); p } fn corpus_line(text: &str) -> String { // Alpha-ID format: valid|alpha_id|primary|star|addon|primary2|orpha|text format!("1|A0001|A00|||||{text}") } #[test] fn pack_refuses_incomplete_store_exit_2() { let dir = tempfile::tempdir().unwrap(); let corpus = dir.path().join("corpus.txt"); std::fs::write(&corpus, format!("{}\n{}\n", corpus_line("alpha"), corpus_line("beta"))).unwrap(); let index_dir = dir.path().join("index"); std::fs::create_dir_all(&index_dir).unwrap(); let config = write_config(dir.path(), &corpus, &index_dir); let out = Command::new(env!("CARGO_BIN_EXE_alpha-id")) .args(["--config", config.to_str().unwrap(), "index", "--pack"]) .output() .unwrap(); assert_eq!(out.status.code(), Some(2), "incomplete store must exit 2"); let stderr = String::from_utf8_lossy(&out.stderr); assert!(stderr.contains("store incomplete"), "stderr was: {stderr}"); } #[test] fn pack_writes_file_from_complete_store() { let dir = tempfile::tempdir().unwrap(); let corpus = dir.path().join("corpus.txt"); std::fs::write(&corpus, format!("{}\n{}\n", corpus_line("alpha"), corpus_line("beta"))).unwrap(); let index_dir = dir.path().join("index"); std::fs::create_dir_all(&index_dir).unwrap(); // Populate the per-file store for both entry texts so the pack succeeds. let store = alpha_id::embed::EmbeddingStore::open(index_dir.to_str().unwrap(), "test/model").unwrap(); store.put("alpha", &[1.0f32, 0.0]).unwrap(); store.put("beta", &[0.0f32, 1.0]).unwrap(); let config = write_config(dir.path(), &corpus, &index_dir); let out = Command::new(env!("CARGO_BIN_EXE_alpha-id")) .args(["--config", config.to_str().unwrap(), "index", "--pack"]) .output() .unwrap(); assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr)); let packed = index_dir.join("packed.test_model.bin"); assert!(packed.exists(), "packed file should be written at {}", packed.display()); } fn entry(text: &str) -> AlphaIdEntry { AlphaIdEntry { alpha_id: "A0001".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(), } } /// End-to-end seam: a packed file produced by the `index --pack` CLI loads back /// through the production `load_packed_or_store` path as `IndexStatus::Ok` with a /// usable index, with no per-file store consulted. This pins the cross-boundary /// round trip (CLI writer ↔ pipeline reader) that the two halves' unit tests /// each cover only one side of. #[test] fn cli_packed_file_loads_back_as_ok_index() { let dir = tempfile::tempdir().unwrap(); let corpus = dir.path().join("corpus.txt"); std::fs::write(&corpus, format!("{}\n{}\n", corpus_line("alpha"), corpus_line("beta"))).unwrap(); let index_dir = dir.path().join("index"); std::fs::create_dir_all(&index_dir).unwrap(); let store = alpha_id::embed::EmbeddingStore::open(index_dir.to_str().unwrap(), "test/model").unwrap(); store.put("alpha", &[1.0f32, 0.0]).unwrap(); store.put("beta", &[0.0f32, 1.0]).unwrap(); let config = write_config(dir.path(), &corpus, &index_dir); let out = Command::new(env!("CARGO_BIN_EXE_alpha-id")) .args(["--config", config.to_str().unwrap(), "index", "--pack"]) .output() .unwrap(); assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr)); // Load the CLI-written packed file through the production path. The store // directory is removed first to prove the index comes from the packed file // alone, not the per-file store. std::fs::remove_dir_all(index_dir.join("embeddings")).unwrap(); let mut cfg = Config::load(config.to_str().unwrap()).unwrap(); cfg.index_dir = index_dir.to_str().unwrap().to_string(); let entries = vec![entry("alpha"), entry("beta")]; let (vector, status) = load_packed_or_store(&cfg, &entries); assert!(vector.is_some(), "packed file written by the CLI must yield an index"); assert_eq!(status, IndexStatus::Ok); }