Files
alpha-id/tests/packed_tests.rs
T
Brummel 7b9d84be87 refactor: move the index --pack writer into the packed module (closes #5)
The `alpha-id index --pack` writer logic — store-completeness check, row
collection in corpus order, header derivation (dim/corpus_name/corpus_sha256/
n_rows), and the write — lived inline in main, leaving the header construction
outside library test reach (covered only end-to-end via the pack CLI tests).
This extracts it into packed::pack_from_store, returning
PackOutcome { Packed(PackedHeader), Incomplete { cached, total } }. main's pack
branch now just loads entries, calls the fn, and matches the outcome onto the
same stderr messages and exit code 2 as before.

Behaviour is unchanged: the stderr lines ("store incomplete: N of M entries
embedded; run `index --full --confirm` first" and "packed N rows × D dims →
path") and the exit-2 refusal are byte-identical, and the four existing pack
CLI tests (pack_refuses_incomplete_store_exit_2, pack_writes_file_from_complete_store,
cli_packed_file_loads_back_as_ok_index, cli_packed_rows_are_in_corpus_order)
stay green unchanged as the behaviour-parity guard. Two new unit tests now
exercise the header construction directly without spawning the binary: a
complete store yields PackOutcome::Packed with the expected n_rows/dim/
embed_model/corpus_sha256 and writes the file; an incomplete store yields
PackOutcome::Incomplete { cached, total } and writes nothing.

Minor improvement folded in: the old inline `store.get(...).expect("cached
vector")` is now a propagated AppError inside pack_from_store rather than a
panic (the branch stays unreachable — the complete-store precondition is
checked first — so parity holds).

This is the debt tidy the cycle-close audit deferred (architect [low] finding).
87 tests green.

closes #5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:35:06 +02:00

127 lines
4.1 KiB
Rust

use alpha_id::packed::{corpus_sha256, read_packed, write_packed, PackedHeader};
use std::io::Write;
use alpha_id::embed::EmbeddingStore;
use alpha_id::model::AlphaIdEntry;
use alpha_id::packed::{pack_from_store, packed_path, PackOutcome};
fn header(n_rows: u32, dim: u32) -> PackedHeader {
PackedHeader {
n_rows,
dim,
embed_model: "test/model".to_string(),
corpus_sha256: "deadbeef".to_string(),
corpus_name: "corpus.txt".to_string(),
}
}
#[test]
fn write_read_roundtrip_preserves_header_and_rows() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("packed.bin");
let rows = vec![vec![0.0f32, 1.5, -2.25], vec![3.0, 4.0, 5.0]];
let h = header(2, 3);
write_packed(&path, &h, &rows).unwrap();
let (got_h, got_rows) = read_packed(&path).unwrap();
assert_eq!(got_h, h);
assert_eq!(got_rows, rows);
}
#[test]
fn corpus_sha256_stable_and_sensitive() {
let dir = tempfile::tempdir().unwrap();
let p = dir.path().join("c.txt");
std::fs::write(&p, b"alpha|beta").unwrap();
let a = corpus_sha256(p.to_str().unwrap()).unwrap();
let b = corpus_sha256(p.to_str().unwrap()).unwrap();
assert_eq!(a, b, "same bytes hash identically");
std::fs::write(&p, b"alpha|betb").unwrap();
let c = corpus_sha256(p.to_str().unwrap()).unwrap();
assert_ne!(a, c, "one changed byte changes the hash");
}
#[test]
fn read_packed_rejects_bad_magic() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("bad.bin");
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(b"XXXX____________").unwrap();
assert!(read_packed(&path).is_err());
}
fn mk_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(),
}
}
#[test]
fn pack_from_store_complete_builds_header_and_writes_file() {
let dir = tempfile::tempdir().unwrap();
let index_dir = dir.path().join("index");
std::fs::create_dir_all(&index_dir).unwrap();
let corpus = dir.path().join("corpus.txt");
std::fs::write(&corpus, b"corpus bytes").unwrap();
let store = EmbeddingStore::open(index_dir.to_str().unwrap(), "test/model").unwrap();
store.put("alpha", &[1.0f32, 0.0, 0.0]).unwrap();
store.put("beta", &[0.0f32, 1.0, 0.0]).unwrap();
let entries = vec![mk_entry("alpha"), mk_entry("beta")];
let outcome = pack_from_store(
index_dir.to_str().unwrap(),
"test/model",
corpus.to_str().unwrap(),
&entries,
)
.unwrap();
match outcome {
PackOutcome::Packed(h) => {
assert_eq!(h.n_rows, 2);
assert_eq!(h.dim, 3);
assert_eq!(h.embed_model, "test/model");
assert_eq!(
h.corpus_sha256,
alpha_id::packed::corpus_sha256(corpus.to_str().unwrap()).unwrap()
);
}
PackOutcome::Incomplete { .. } => panic!("a complete store must pack"),
}
assert!(packed_path(index_dir.to_str().unwrap(), "test/model").exists());
}
#[test]
fn pack_from_store_incomplete_reports_counts_and_writes_nothing() {
let dir = tempfile::tempdir().unwrap();
let index_dir = dir.path().join("index");
std::fs::create_dir_all(&index_dir).unwrap();
let corpus = dir.path().join("corpus.txt");
std::fs::write(&corpus, b"corpus bytes").unwrap();
let store = EmbeddingStore::open(index_dir.to_str().unwrap(), "test/model").unwrap();
store.put("alpha", &[1.0f32, 0.0]).unwrap(); // only 1 of 2 entries cached
let entries = vec![mk_entry("alpha"), mk_entry("beta")];
let outcome = pack_from_store(
index_dir.to_str().unwrap(),
"test/model",
corpus.to_str().unwrap(),
&entries,
)
.unwrap();
assert!(matches!(outcome, PackOutcome::Incomplete { cached: 1, total: 2 }));
assert!(
!packed_path(index_dir.to_str().unwrap(), "test/model").exists(),
"an incomplete store must not write a packed file"
);
}