ontoref-code/crates/ontoref-blobs/tests/basic.rs
2026-07-10 01:44:59 +01:00

117 lines
3.4 KiB
Rust

use std::sync::Arc;
use std::thread;
use ontoref_blobs::{BlobId, BlobStore, Error};
#[test]
fn put_get_round_trip() {
let dir = tempfile::tempdir().unwrap();
let store = BlobStore::open(dir.path()).unwrap();
let id = store.put(b"hello world").unwrap();
assert_eq!(store.get(&id).unwrap(), b"hello world");
}
#[test]
fn put_get_empty_bytes() {
let dir = tempfile::tempdir().unwrap();
let store = BlobStore::open(dir.path()).unwrap();
let id = store.put(b"").unwrap();
assert_eq!(store.get(&id).unwrap(), Vec::<u8>::new());
}
#[test]
fn put_get_is_idempotent() {
let dir = tempfile::tempdir().unwrap();
let store = BlobStore::open(dir.path()).unwrap();
let id1 = store.put(b"same content").unwrap();
let id2 = store.put(b"same content").unwrap();
assert_eq!(id1, id2);
assert_eq!(store.get(&id1).unwrap(), b"same content");
}
#[test]
fn put_get_missing_returns_not_found() {
let dir = tempfile::tempdir().unwrap();
let store = BlobStore::open(dir.path()).unwrap();
let dummy = BlobId([0u8; 32]);
match store.get(&dummy) {
Err(Error::NotFound(_)) => {}
other => panic!("expected NotFound, got {other:?}"),
}
}
#[test]
fn concurrent_puts_of_same_blob() {
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(BlobStore::open(dir.path()).unwrap());
let payload = b"shared content".to_vec();
let handles: Vec<_> = (0..16)
.map(|_| {
let store = Arc::clone(&store);
let payload = payload.clone();
thread::spawn(move || store.put(&payload).unwrap())
})
.collect();
let ids: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
let first = ids[0];
for id in &ids {
assert_eq!(*id, first);
}
assert_eq!(store.get(&first).unwrap(), payload);
}
#[test]
fn concurrent_puts_of_different_blobs() {
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(BlobStore::open(dir.path()).unwrap());
let handles: Vec<_> = (0..32)
.map(|i| {
let store = Arc::clone(&store);
let payload = format!("blob #{i}").into_bytes();
thread::spawn(move || {
let id = store.put(&payload).unwrap();
(id, payload)
})
})
.collect();
for handle in handles {
let (id, payload) = handle.join().unwrap();
assert_eq!(store.get(&id).unwrap(), payload);
}
}
#[test]
fn crash_safe_no_orphan_tmp_after_successful_put() {
let dir = tempfile::tempdir().unwrap();
let store = BlobStore::open(dir.path()).unwrap();
let id = store.put(b"normal content").unwrap();
let parent = store.path_for(&id).parent().unwrap().to_path_buf();
let entries: Vec<String> = std::fs::read_dir(&parent)
.unwrap()
.filter_map(|entry| entry.ok())
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.collect();
assert_eq!(
entries,
vec![id.to_hex()],
"blob directory should contain exactly the finalised blob"
);
}
#[test]
fn crash_safe_stale_tmp_does_not_break_get() {
let dir = tempfile::tempdir().unwrap();
let store = BlobStore::open(dir.path()).unwrap();
let id = store.put(b"real").unwrap();
let parent = store.path_for(&id).parent().unwrap().to_path_buf();
std::fs::write(parent.join(".tmp.garbage"), b"garbage from a crashed put").unwrap();
assert_eq!(store.get(&id).unwrap(), b"real");
}