//! The load-bearing interop test (ADR-002; POC #1 findings 4/5's //! standard): byte-exact derivation against the `git hash-object` CLI //! for both algorithms — validated against git, not just hardcoded //! vectors — plus the external-oid round-trip into pool keys. //! //! Harness detail (POC finding 4, empirically learned): in current //! git, `--object-format` is not a `hash-object` flag; the object //! format is set on the repo (`git init --object-format=sha256`) and //! the hash-object runs inside that repo. Repos are created fresh in //! a per-call temp directory, with global/system config detached. use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; use alkblobs::{Algorithm, Digest, Key}; /// Content cases: empty, short text, binary-with-NULs, and a /// multi-KiB case (75 KiB, deterministic). fn content_cases() -> Vec<(&'static str, Vec)> { vec![ ("empty", Vec::new()), ("short", b"hello".to_vec()), ( "binary-nuls", vec![0u8, 1, 255, 0, 10, 13, 0, 7, 255, 255, 0, 0], ), ( "multi-kib", (0..75 * 1024).map(|i| (i % 251) as u8).collect::>(), ), ] } fn scratch_dir(tag: &str) -> PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system clock before epoch") .as_nanos(); let dir = std::env::temp_dir().join(format!( "alkblobs-git-interop-{}-{}-{}", tag, std::process::id(), nanos )); fs::create_dir_all(&dir).expect("temp dir creation"); dir } fn detached_config(command: &mut Command) { let devnull = if cfg!(windows) { "NUL" } else { "/dev/null" }; command .env("GIT_CONFIG_GLOBAL", devnull) .env("GIT_CONFIG_SYSTEM", devnull); } /// Initialize a fresh repo of the requested object format and return /// the oid hex `git hash-object --stdin` prints for `content`. fn git_hash_object(content: &[u8], sha256: bool) -> (PathBuf, String) { let dir = scratch_dir("repo"); let mut init = Command::new("git"); detached_config(&mut init); init.arg("init").arg("-q").current_dir(&dir); if sha256 { init.arg("--object-format=sha256"); } let out = init.output().expect("git init spawn"); assert!( out.status.success(), "git init failed: {}", String::from_utf8_lossy(&out.stderr) ); let mut hash = Command::new("git"); detached_config(&mut hash); hash.arg("-C").arg(&dir).arg("hash-object").arg("--stdin"); use std::io::Write as _; let mut child = hash .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .spawn() .expect("git hash-object spawn"); child .stdin .as_mut() .expect("stdin piped") .write_all(content) .expect("stdin write"); let out = child.wait_with_output().expect("git hash-object output"); assert!( out.status.success(), "git hash-object failed: {}", String::from_utf8_lossy(&out.stderr) ); let oid = String::from_utf8(out.stdout) .expect("git output utf8") .trim() .to_owned(); (dir, oid) } fn drop_dir(dir: &Path) { let _ = fs::remove_dir_all(dir); } /// The load-bearing test: `Digest`'s derivation matches the git CLI /// byte-exact, both algorithms, small and multi-KiB content. #[test] fn derivation_is_byte_exact_against_git_hash_object() { for (tag, content) in content_cases() { let (dir, oid) = git_hash_object(&content, false); drop_dir(&dir); assert_eq!(oid.len(), 40, "git printed a sha1 oid"); assert_eq!( Digest::from_git_oid_hex(&oid), Ok(Algorithm::GitBlobSha1.derive(&content, content.len() as u64)), "sha1 {tag}: derived digest != git hash-object", ); let (dir, oid) = git_hash_object(&content, true); drop_dir(&dir); assert_eq!(oid.len(), 64, "git printed a sha256 oid"); assert_eq!( Digest::from_git_oid_hex(&oid), Ok(Digest::derive(&content, content.len() as u64)), "sha256 {tag}: derived digest != git hash-object", ); } } /// The external-oid round-trip (POC finding 4 corollary, key level): /// an oid produced by external git (`from_git_oid_hex` → /// `as_bytes` → `from_bytes`) becomes exactly the pool key the store /// core would derive — no mapping layer. #[test] fn external_oid_round_trips_into_pool_keys() { for sha256 in [false, true] { let content = b"external producer blob \0 bytes"; let (dir, oid) = git_hash_object(content, sha256); drop_dir(&dir); let external: Key = Digest::from_git_oid_hex(&oid) .expect("external oid decodes") .into(); let derived: Key = { let len = content.len() as u64; Digest::derive(content, len).into() }; if sha256 { assert_eq!(external, derived, "sha256: external oid == derived key"); } else { assert_eq!( external, Key::from(Algorithm::GitBlobSha1.derive(content, content.len() as u64)), "sha1: external oid == derived key" ); } let decoded = Key::from_bytes(&external.as_bytes()).expect("physical encoding decodes"); assert_eq!(decoded, external); assert_eq!(decoded.as_bytes(), external.as_bytes()); } } /// Tagged-key coexistence (POC finding 5): git oids of both formats /// address the same flat key universe from one external producer. #[test] fn both_oid_formats_coexist_in_one_pool() { let content = b"same content, two formats"; let (dir1, oid1) = git_hash_object(content, false); drop_dir(&dir1); let (dir2, oid2) = git_hash_object(content, true); drop_dir(&dir2); let mut pool = std::collections::BTreeSet::new(); pool.insert( Key::from_bytes( &Key::from(Digest::from_git_oid_hex(&oid1).expect("sha1 oid decodes")).as_bytes(), ) .expect("sha1 external key decodes"), ); pool.insert( Key::from_bytes( &Key::from(Digest::from_git_oid_hex(&oid2).expect("sha256 oid decodes")).as_bytes(), ) .expect("sha256 external key decodes"), ); assert_eq!(pool.len(), 2, "both algorithms coexist in one flat pool"); }