use syntheca::{
Cella, DepositError, GetError, GetPinaxError, Hash, Name, Options, SetPinaxOutcome, StatError,
};
use sha2::Digest;
use tempfile::TempDir;
fn fresh_cella() -> (TempDir, Cella) {
let dir = TempDir::new().unwrap();
let cella = Cella::open(dir.path()).unwrap();
(dir, cella)
}
fn sha256(bytes: &[u8]) -> [u8; 32] {
let mut h = sha2::Sha256::new();
h.update(bytes);
h.finalize().into()
}
#[test]
fn deposit_get_round_trip() {
let (_dir, cella) = fresh_cella();
let bytes = b"hello world";
let h = cella.deposit(bytes).unwrap();
assert_eq!(h, Hash::of(bytes));
let got = cella.get(&h).unwrap();
assert_eq!(got.as_slice(), bytes);
}
#[test]
fn deposit_is_idempotent() {
let (_dir, cella) = fresh_cella();
let bytes = b"twice";
let h1 = cella.deposit(bytes).unwrap();
let h2 = cella.deposit(bytes).unwrap();
assert_eq!(h1, h2);
}
#[test]
fn empty_bytes_round_trip() {
let (_dir, cella) = fresh_cella();
let h = cella.deposit(b"").unwrap();
assert_eq!(h, Hash::of(b""));
assert_eq!(cella.get(&h).unwrap(), Vec::<u8>::new());
assert_eq!(cella.stat(&h).unwrap().size, 0);
}
#[test]
fn get_unknown_is_not_found() {
let (_dir, cella) = fresh_cella();
let h = Hash::of(b"never deposited");
match cella.get(&h) {
Err(GetError::NotFound) => {}
other => panic!("expected NotFound, got {other:?}"),
}
}
#[test]
fn stat_unknown_is_not_found() {
let (_dir, cella) = fresh_cella();
let h = Hash::of(b"never deposited");
match cella.stat(&h) {
Err(StatError::NotFound) => {}
other => panic!("expected NotFound, got {other:?}"),
}
}
#[test]
fn stat_returns_size_and_sha256() {
let (_dir, cella) = fresh_cella();
let bytes = b"abc";
let h = cella.deposit(bytes).unwrap();
let s = cella.stat(&h).unwrap();
assert_eq!(s.size, bytes.len() as u64);
assert_eq!(s.sha256, sha256(bytes));
}
#[test]
fn hash_collision_surfaces_when_bytes_differ_but_name_matches() {
let dir = TempDir::new().unwrap();
let apo = apotheca::Cella::open(dir.path()).unwrap();
let b2: &[u8] = b"the bytes the caller would deposit";
let b1: &[u8] = b"different bytes already squatting on that name";
let name_hex = Hash::of(b2).to_hex();
let name = apotheca::Name::new(name_hex.as_bytes()).unwrap();
assert_eq!(
apo.deposit(&name, b1).unwrap(),
apotheca::DepositOutcome::Ok,
"test setup: first deposit should succeed"
);
let cella = Cella::from_apotheca(apo, Options::default());
match cella.deposit(b2) {
Err(DepositError::HashCollision) => {}
other => panic!("expected HashCollision, got {other:?}"),
}
}
#[test]
fn verify_on_read_catches_mismatched_name() {
let dir = TempDir::new().unwrap();
let apo = apotheca::Cella::open(dir.path()).unwrap();
let claimed: &[u8] = b"what the hash claims to name";
let actual: &[u8] = b"what actually got stored";
let claimed_hash = Hash::of(claimed);
let name_hex = claimed_hash.to_hex();
let name = apotheca::Name::new(name_hex.as_bytes()).unwrap();
apo.deposit(&name, actual).unwrap();
let cella = Cella::from_apotheca(apo, Options::default());
match cella.get(&claimed_hash) {
Err(GetError::IntegrityError) => {}
other => panic!("expected IntegrityError, got {other:?}"),
}
}
#[test]
fn verify_on_read_can_be_disabled() {
let dir = TempDir::new().unwrap();
let apo = apotheca::Cella::open(dir.path()).unwrap();
let claimed: &[u8] = b"what the hash claims to name";
let actual: &[u8] = b"what actually got stored";
let claimed_hash = Hash::of(claimed);
let name_hex = claimed_hash.to_hex();
let name = apotheca::Name::new(name_hex.as_bytes()).unwrap();
apo.deposit(&name, actual).unwrap();
let cella = Cella::from_apotheca(
apo,
Options {
verify_on_read: false,
},
);
let got = cella.get(&claimed_hash).unwrap();
assert_eq!(got, actual);
}
#[test]
fn distinct_bytes_get_distinct_hashes() {
let (_dir, cella) = fresh_cella();
let h1 = cella.deposit(b"alpha").unwrap();
let h2 = cella.deposit(b"beta").unwrap();
assert_ne!(h1, h2);
assert_eq!(cella.get(&h1).unwrap(), b"alpha");
assert_eq!(cella.get(&h2).unwrap(), b"beta");
}
#[test]
fn pinax_round_trip() {
let (_dir, cella) = fresh_cella();
let name = Name::new(b"head").unwrap();
match cella.set_pinax(&name, b"v1", None).unwrap() {
SetPinaxOutcome::Ok => {}
other => panic!("expected Ok, got {other:?}"),
}
assert_eq!(cella.get_pinax(&name).unwrap(), b"v1");
let v1_digest = sha256(b"v1");
match cella.set_pinax(&name, b"v2", Some(v1_digest)).unwrap() {
SetPinaxOutcome::Ok => {}
other => panic!("expected Ok on cas, got {other:?}"),
}
assert_eq!(cella.get_pinax(&name).unwrap(), b"v2");
}
#[test]
fn pinax_get_unknown_is_not_found() {
let (_dir, cella) = fresh_cella();
let name = Name::new(b"missing").unwrap();
match cella.get_pinax(&name) {
Err(GetPinaxError::NotFound) => {}
other => panic!("expected NotFound, got {other:?}"),
}
}
#[test]
fn pinax_set_conflict_when_expected_wrong() {
let (_dir, cella) = fresh_cella();
let name = Name::new(b"head").unwrap();
cella.set_pinax(&name, b"v1", None).unwrap();
match cella.set_pinax(&name, b"v2", None).unwrap() {
SetPinaxOutcome::Conflict { actual } => {
assert_eq!(actual, Some(sha256(b"v1")));
}
other => panic!("expected Conflict, got {other:?}"),
}
assert_eq!(cella.get_pinax(&name).unwrap(), b"v1");
}
#[test]
fn pinax_namespace_disjoint_from_deposita() {
let (_dir, cella) = fresh_cella();
let bytes = b"sediment";
let depositum_hash = cella.deposit(bytes).unwrap();
let name_hex = depositum_hash.to_hex();
let pinax_name = Name::new(name_hex.as_bytes()).unwrap();
cella.set_pinax(&pinax_name, b"surface", None).unwrap();
assert_eq!(cella.get(&depositum_hash).unwrap(), bytes);
assert_eq!(cella.get_pinax(&pinax_name).unwrap(), b"surface");
}