use alpha_id::model::{Config, Filter, IndexStatus, Mode}; use alpha_id::packed::{packed_path, write_packed, PackedHeader}; use alpha_id::pipeline::Pipeline; use std::process::Command; // A structurally-mismatched packed file at the config's derived packed path. // n_rows=1 will not equal the real corpus entry count, so load reports a // Mismatch — enough to exercise the warning and the diagnostics token. Uses // the real corpus/claml from config/default.toml with index_dir redirected to // a temp dir, so the real index is never touched. fn write_mismatched_packed(cfg: &Config) { let header = PackedHeader { n_rows: 1, dim: 1, embed_model: cfg.embed_model.clone(), corpus_sha256: "x".to_string(), corpus_name: "x".to_string(), }; write_packed(&packed_path(&cfg.index_dir, &cfg.embed_model), &header, &[vec![0.0f32]]).unwrap(); } #[test] fn hybrid_suggest_under_mismatch_is_degraded_and_index_mismatch() { let dir = tempfile::tempdir().unwrap(); let mut cfg = Config::load("config/default.toml").unwrap(); cfg.index_dir = dir.path().to_str().unwrap().to_string(); cfg.token_path = "/nonexistent".to_string(); write_mismatched_packed(&cfg); let p = Pipeline::load(&cfg).unwrap(); let res = p.suggest("knee pain", Mode::Hybrid, &Filter::default(), 5); assert!(res.diagnostics.degraded, "hybrid degrades when the index is mismatched"); assert!( matches!(res.diagnostics.index_status, IndexStatus::Mismatch(_)), "index_status names the structural problem, not an anonymous degrade" ); } #[test] fn cli_suggest_under_mismatch_warns_and_shows_index_token() { let dir = tempfile::tempdir().unwrap(); let mut cfg = Config::load("config/default.toml").unwrap(); cfg.index_dir = dir.path().to_str().unwrap().to_string(); write_mismatched_packed(&cfg); // Build a temp config TOML reusing the real data paths but the temp index dir. let toml = format!( r#"ionos_base_url = "http://127.0.0.1:1" token_path = "/nonexistent" embed_model = "{embed_model}" rerank_model = "{rerank_model}" alpha_id_path = "{alpha_id_path}" claml_path = "{claml_path}" index_dir = "{index_dir}" pool_size = {pool_size} top_k = {top_k} "#, embed_model = cfg.embed_model, rerank_model = cfg.rerank_model, alpha_id_path = cfg.alpha_id_path, claml_path = cfg.claml_path, index_dir = cfg.index_dir, pool_size = cfg.pool_size, top_k = cfg.top_k, ); let config_path = dir.path().join("config.toml"); std::fs::write(&config_path, toml).unwrap(); // Mode Lexical needs no IONOS; the warning + token come from load, not the mode. let out = Command::new(env!("CARGO_BIN_EXE_alpha-id")) .args(["--config", config_path.to_str().unwrap(), "suggest", "-", "--mode", "lexical"]) .env("ALPHA_ID_STDIN", "knee pain") .output() .unwrap(); let stderr = String::from_utf8_lossy(&out.stderr); assert!(stderr.contains("does not match"), "load warning missing; stderr: {stderr}"); assert!(stderr.contains("index=mismatch"), "diagnostics token missing; stderr: {stderr}"); }