use std::fs;
use super::{
BranchRefError, BranchRefRecord, BranchRefStore, BranchShardRef, decode_record, ref_file_name,
};
use crate::branch::persist::{push_bytes, push_u64};
use crate::branch::policy::BranchKind;
use crate::tree::Hash;
fn hash(byte: u8) -> Hash {
Hash::from_bytes([byte; 32])
}
fn record(name: &str, kind: BranchKind, namespace_lineage: Option<String>) -> BranchRefRecord {
BranchRefRecord {
name: name.to_owned(),
created: 10,
kind,
namespace_lineage,
seq: 0,
timestamp: 10,
shards: vec![BranchShardRef {
shard_id: 0,
fork_anchor: hash(1),
head: hash(1),
}],
parents: Vec::new(),
}
}
fn hbk1_namespace_with_lineage_bytes() -> Vec<u8> {
let mut bytes = b"HBR1".to_vec();
push_bytes(&mut bytes, b"invalid-namespace");
push_u64(&mut bytes, 10); push_u64(&mut bytes, 0); push_u64(&mut bytes, 10); push_u64(&mut bytes, 1); push_u64(&mut bytes, 0); bytes.extend_from_slice(hash(1).as_bytes());
bytes.extend_from_slice(hash(1).as_bytes());
push_u64(&mut bytes, 0); bytes.extend_from_slice(b"HBK1");
push_u64(&mut bytes, 1); push_u64(&mut bytes, 1); push_bytes(&mut bytes, b"parent");
bytes
}
#[test]
fn hbk1_decode_rejects_namespace_with_lineage() {
let result = decode_record(&hbk1_namespace_with_lineage_bytes());
assert!(matches!(
result,
Err(BranchRefError::Corrupt(reason))
if reason == "HBK1 record has kind=Namespace with lineage_tag=1"
));
}
#[test]
fn branch_advance_preserves_kind_and_lineage() -> Result<(), BranchRefError> {
let dir = tempfile::tempdir().map_err(BranchRefError::Io)?;
let mut store = BranchRefStore::open(dir.path())?;
store.create(record(
"namespace-work",
BranchKind::Work,
Some("namespace-a".to_owned()),
))?;
store.advance("namespace-work", 10, 0, &[(0, hash(2))], vec![hash(1)], 11)?;
drop(store);
let reopened = BranchRefStore::open(dir.path())?;
let advanced = reopened
.get("namespace-work")
.ok_or_else(|| BranchRefError::BranchRemoved("namespace-work".to_owned()))?;
assert_eq!(advanced.kind, BranchKind::Work);
assert_eq!(advanced.namespace_lineage.as_deref(), Some("namespace-a"));
assert_eq!(advanced.shards[0].head, hash(2));
Ok(())
}
#[test]
fn refstore_create_refuses_namespace_with_stored_lineage() -> Result<(), BranchRefError> {
let dir = tempfile::tempdir().map_err(BranchRefError::Io)?;
let mut store = BranchRefStore::open(dir.path())?;
let result = store.create(record(
"invalid-namespace",
BranchKind::Namespace,
Some("parent".to_owned()),
));
assert!(matches!(
result,
Err(BranchRefError::InvalidKindLineage { name, kind: BranchKind::Namespace })
if name == "invalid-namespace"
));
assert!(store.get("invalid-namespace").is_none());
assert!(!dir.path().join(ref_file_name("invalid-namespace")).exists());
assert_eq!(
fs::read_dir(dir.path())
.map_err(BranchRefError::Io)?
.count(),
0,
"the rejected create must leave no temp or ref file"
);
Ok(())
}