Skip to main content

hashtree_core/
store.rs

1//! Content-addressed key-value store interfaces and implementations
2
3use async_trait::async_trait;
4use std::collections::HashMap;
5use std::sync::{Arc, RwLock};
6
7use crate::types::{to_hex, Hash};
8
9/// Storage statistics
10#[derive(Debug, Clone, Default)]
11pub struct StoreStats {
12    /// Number of items in store
13    pub count: u64,
14    /// Total bytes stored
15    pub bytes: u64,
16    /// Number of pinned items
17    pub pinned_count: u64,
18    /// Bytes used by pinned items
19    pub pinned_bytes: u64,
20}
21
22/// Content-addressed key-value store interface
23#[async_trait]
24pub trait Store: Send + Sync {
25    /// Store data by its hash
26    /// Returns true if newly stored, false if already existed
27    async fn put(&self, hash: Hash, data: Vec<u8>) -> Result<bool, StoreError>;
28
29    /// Store multiple blobs.
30    /// Returns the number of newly stored items.
31    async fn put_many(&self, items: Vec<(Hash, Vec<u8>)>) -> Result<usize, StoreError> {
32        let mut inserted = 0usize;
33        for (hash, data) in items {
34            if self.put(hash, data).await? {
35                inserted += 1;
36            }
37        }
38        Ok(inserted)
39    }
40
41    /// Retrieve data by hash
42    /// Returns data or None if not found
43    async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError>;
44
45    /// Check if hash exists
46    async fn has(&self, hash: &Hash) -> Result<bool, StoreError>;
47
48    /// Delete by hash
49    /// Returns true if deleted, false if didn't exist
50    async fn delete(&self, hash: &Hash) -> Result<bool, StoreError>;
51
52    // ========================================================================
53    // Optional: Storage limits and eviction (default no-op implementations)
54    // ========================================================================
55
56    /// Set maximum storage size in bytes. 0 = unlimited.
57    fn set_max_bytes(&self, _max: u64) {}
58
59    /// Get maximum storage size. None = unlimited.
60    fn max_bytes(&self) -> Option<u64> {
61        None
62    }
63
64    /// Get storage statistics
65    async fn stats(&self) -> StoreStats {
66        StoreStats::default()
67    }
68
69    /// Evict unpinned items if over storage limit.
70    /// Returns number of bytes freed.
71    async fn evict_if_needed(&self) -> Result<u64, StoreError> {
72        Ok(0)
73    }
74
75    // ========================================================================
76    // Optional: Pinning (default no-op implementations)
77    // ========================================================================
78
79    /// Pin a hash (increment ref count). Pinned items are not evicted.
80    async fn pin(&self, _hash: &Hash) -> Result<(), StoreError> {
81        Ok(())
82    }
83
84    /// Unpin a hash (decrement ref count). Item can be evicted when count reaches 0.
85    async fn unpin(&self, _hash: &Hash) -> Result<(), StoreError> {
86        Ok(())
87    }
88
89    /// Get pin count for a hash. 0 = not pinned.
90    fn pin_count(&self, _hash: &Hash) -> u32 {
91        0
92    }
93
94    /// Check if hash is pinned (pin count > 0)
95    fn is_pinned(&self, hash: &Hash) -> bool {
96        self.pin_count(hash) > 0
97    }
98}
99
100/// Store error type
101#[derive(Debug, thiserror::Error)]
102pub enum StoreError {
103    #[error("IO error: {0}")]
104    Io(#[from] std::io::Error),
105    #[error("Store error: {0}")]
106    Other(String),
107}
108
109#[derive(Debug, Default)]
110struct BufferedStoreInner {
111    pending: HashMap<Hash, Vec<u8>>,
112    order: Vec<Hash>,
113}
114
115#[derive(Debug, Clone, Copy)]
116struct BufferedStoreOptions {
117    check_base_on_put: bool,
118}
119
120impl Default for BufferedStoreOptions {
121    fn default() -> Self {
122        Self {
123            check_base_on_put: true,
124        }
125    }
126}
127
128/// Buffered overlay store that keeps writes in memory until flushed.
129#[derive(Debug, Clone)]
130pub struct BufferedStore<S: Store> {
131    base: Arc<S>,
132    inner: Arc<RwLock<BufferedStoreInner>>,
133    options: BufferedStoreOptions,
134}
135
136impl<S: Store> BufferedStore<S> {
137    pub fn new(base: Arc<S>) -> Self {
138        Self::with_options(base, BufferedStoreOptions::default())
139    }
140
141    pub fn new_optimistic(base: Arc<S>) -> Self {
142        Self::with_options(
143            base,
144            BufferedStoreOptions {
145                check_base_on_put: false,
146            },
147        )
148    }
149
150    fn with_options(base: Arc<S>, options: BufferedStoreOptions) -> Self {
151        Self {
152            base,
153            inner: Arc::new(RwLock::new(BufferedStoreInner::default())),
154            options,
155        }
156    }
157
158    pub async fn flush(&self) -> Result<usize, StoreError> {
159        let items = {
160            let mut inner = self.inner.write().unwrap();
161            if inner.order.is_empty() {
162                return Ok(0);
163            }
164
165            let order = std::mem::take(&mut inner.order);
166            let mut items = Vec::with_capacity(order.len());
167            for hash in order {
168                if let Some(data) = inner.pending.remove(&hash) {
169                    items.push((hash, data));
170                }
171            }
172            items
173        };
174
175        self.base.put_many(items).await
176    }
177}
178
179#[async_trait]
180impl<S: Store> Store for BufferedStore<S> {
181    async fn put(&self, hash: Hash, data: Vec<u8>) -> Result<bool, StoreError> {
182        {
183            let inner = self.inner.read().unwrap();
184            if inner.pending.contains_key(&hash) {
185                return Ok(false);
186            }
187        }
188
189        if self.options.check_base_on_put && self.base.has(&hash).await? {
190            return Ok(false);
191        }
192
193        let mut inner = self.inner.write().unwrap();
194        if inner.pending.contains_key(&hash) {
195            return Ok(false);
196        }
197        inner.order.push(hash);
198        inner.pending.insert(hash, data);
199        Ok(true)
200    }
201
202    async fn put_many(&self, items: Vec<(Hash, Vec<u8>)>) -> Result<usize, StoreError> {
203        let mut inserted = 0usize;
204        for (hash, data) in items {
205            if self.put(hash, data).await? {
206                inserted += 1;
207            }
208        }
209        Ok(inserted)
210    }
211
212    async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError> {
213        if let Some(data) = self.inner.read().unwrap().pending.get(hash).cloned() {
214            return Ok(Some(data));
215        }
216        self.base.get(hash).await
217    }
218
219    async fn has(&self, hash: &Hash) -> Result<bool, StoreError> {
220        if self.inner.read().unwrap().pending.contains_key(hash) {
221            return Ok(true);
222        }
223        self.base.has(hash).await
224    }
225
226    async fn delete(&self, hash: &Hash) -> Result<bool, StoreError> {
227        let removed = {
228            let mut inner = self.inner.write().unwrap();
229            let removed = inner.pending.remove(hash).is_some();
230            if removed {
231                inner.order.retain(|queued| queued != hash);
232            }
233            removed
234        };
235
236        if removed {
237            return Ok(true);
238        }
239
240        self.base.delete(hash).await
241    }
242
243    async fn stats(&self) -> StoreStats {
244        let mut stats = self.base.stats().await;
245        let pending_bytes = self
246            .inner
247            .read()
248            .unwrap()
249            .pending
250            .values()
251            .map(|data| data.len() as u64)
252            .sum::<u64>();
253        stats.count += self.inner.read().unwrap().pending.len() as u64;
254        stats.bytes += pending_bytes;
255        stats
256    }
257
258    async fn evict_if_needed(&self) -> Result<u64, StoreError> {
259        self.base.evict_if_needed().await
260    }
261
262    async fn pin(&self, hash: &Hash) -> Result<(), StoreError> {
263        self.base.pin(hash).await
264    }
265
266    async fn unpin(&self, hash: &Hash) -> Result<(), StoreError> {
267        self.base.unpin(hash).await
268    }
269
270    fn pin_count(&self, hash: &Hash) -> u32 {
271        self.base.pin_count(hash)
272    }
273}
274
275/// Entry in the memory store with metadata for LRU
276#[derive(Debug, Clone)]
277struct MemoryEntry {
278    data: Vec<u8>,
279    /// Insertion order for LRU (lower = older)
280    order: u64,
281}
282
283/// Internal state for MemoryStore
284#[derive(Debug, Default)]
285struct MemoryStoreInner {
286    data: HashMap<String, MemoryEntry>,
287    pins: HashMap<String, u32>,
288    next_order: u64,
289    max_bytes: Option<u64>,
290}
291
292/// In-memory content-addressed store with LRU eviction and pinning
293#[derive(Debug, Clone, Default)]
294pub struct MemoryStore {
295    inner: Arc<RwLock<MemoryStoreInner>>,
296}
297
298impl MemoryStore {
299    pub fn new() -> Self {
300        Self {
301            inner: Arc::new(RwLock::new(MemoryStoreInner::default())),
302        }
303    }
304
305    /// Create a new store with a maximum size limit
306    pub fn with_max_bytes(max_bytes: u64) -> Self {
307        Self {
308            inner: Arc::new(RwLock::new(MemoryStoreInner {
309                max_bytes: if max_bytes > 0 { Some(max_bytes) } else { None },
310                ..Default::default()
311            })),
312        }
313    }
314
315    /// Get number of stored items
316    pub fn size(&self) -> usize {
317        self.inner.read().unwrap().data.len()
318    }
319
320    /// Get total bytes stored
321    pub fn total_bytes(&self) -> usize {
322        self.inner
323            .read()
324            .unwrap()
325            .data
326            .values()
327            .map(|e| e.data.len())
328            .sum()
329    }
330
331    /// Clear all data (but not pins)
332    pub fn clear(&self) {
333        self.inner.write().unwrap().data.clear();
334    }
335
336    /// List all hashes
337    pub fn keys(&self) -> Vec<Hash> {
338        self.inner
339            .read()
340            .unwrap()
341            .data
342            .keys()
343            .filter_map(|hex| {
344                let bytes = hex::decode(hex).ok()?;
345                if bytes.len() != 32 {
346                    return None;
347                }
348                let mut hash = [0u8; 32];
349                hash.copy_from_slice(&bytes);
350                Some(hash)
351            })
352            .collect()
353    }
354
355    /// Evict oldest unpinned entries until under target bytes
356    fn evict_to_target(&self, target_bytes: u64) -> u64 {
357        let mut inner = self.inner.write().unwrap();
358
359        let current_bytes: u64 = inner.data.values().map(|e| e.data.len() as u64).sum();
360        if current_bytes <= target_bytes {
361            return 0;
362        }
363
364        // Collect unpinned entries sorted by order (oldest first)
365        let mut unpinned: Vec<(String, u64, u64)> = inner
366            .data
367            .iter()
368            .filter(|(key, _)| inner.pins.get(*key).copied().unwrap_or(0) == 0)
369            .map(|(key, entry)| (key.clone(), entry.order, entry.data.len() as u64))
370            .collect();
371
372        unpinned.sort_by_key(|(_, order, _)| *order);
373
374        let mut freed = 0u64;
375        let to_free = current_bytes - target_bytes;
376
377        for (key, _, size) in unpinned {
378            if freed >= to_free {
379                break;
380            }
381            inner.data.remove(&key);
382            freed += size;
383        }
384
385        freed
386    }
387}
388
389#[async_trait]
390impl Store for MemoryStore {
391    async fn put(&self, hash: Hash, data: Vec<u8>) -> Result<bool, StoreError> {
392        let key = to_hex(&hash);
393        let mut inner = self.inner.write().unwrap();
394        if inner.data.contains_key(&key) {
395            return Ok(false);
396        }
397        let order = inner.next_order;
398        inner.next_order += 1;
399        inner.data.insert(key, MemoryEntry { data, order });
400        Ok(true)
401    }
402
403    async fn put_many(&self, items: Vec<(Hash, Vec<u8>)>) -> Result<usize, StoreError> {
404        let mut inserted = 0usize;
405        let mut inner = self.inner.write().unwrap();
406        for (hash, data) in items {
407            let key = to_hex(&hash);
408            if inner.data.contains_key(&key) {
409                continue;
410            }
411            let order = inner.next_order;
412            inner.next_order += 1;
413            inner.data.insert(key, MemoryEntry { data, order });
414            inserted += 1;
415        }
416        Ok(inserted)
417    }
418
419    async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError> {
420        let key = to_hex(hash);
421        let inner = self.inner.read().unwrap();
422        Ok(inner.data.get(&key).map(|e| e.data.clone()))
423    }
424
425    async fn has(&self, hash: &Hash) -> Result<bool, StoreError> {
426        let key = to_hex(hash);
427        Ok(self.inner.read().unwrap().data.contains_key(&key))
428    }
429
430    async fn delete(&self, hash: &Hash) -> Result<bool, StoreError> {
431        let key = to_hex(hash);
432        let mut inner = self.inner.write().unwrap();
433        // Also remove pin entry if exists
434        inner.pins.remove(&key);
435        Ok(inner.data.remove(&key).is_some())
436    }
437
438    fn set_max_bytes(&self, max: u64) {
439        self.inner.write().unwrap().max_bytes = if max > 0 { Some(max) } else { None };
440    }
441
442    fn max_bytes(&self) -> Option<u64> {
443        self.inner.read().unwrap().max_bytes
444    }
445
446    async fn stats(&self) -> StoreStats {
447        let inner = self.inner.read().unwrap();
448        let mut count = 0u64;
449        let mut bytes = 0u64;
450        let mut pinned_count = 0u64;
451        let mut pinned_bytes = 0u64;
452
453        for (key, entry) in &inner.data {
454            count += 1;
455            bytes += entry.data.len() as u64;
456            if inner.pins.get(key).copied().unwrap_or(0) > 0 {
457                pinned_count += 1;
458                pinned_bytes += entry.data.len() as u64;
459            }
460        }
461
462        StoreStats {
463            count,
464            bytes,
465            pinned_count,
466            pinned_bytes,
467        }
468    }
469
470    async fn evict_if_needed(&self) -> Result<u64, StoreError> {
471        let max = match self.inner.read().unwrap().max_bytes {
472            Some(m) => m,
473            None => return Ok(0), // No limit set
474        };
475
476        let current: u64 = self
477            .inner
478            .read()
479            .unwrap()
480            .data
481            .values()
482            .map(|e| e.data.len() as u64)
483            .sum();
484
485        if current <= max {
486            return Ok(0);
487        }
488
489        // Evict to 90% of max to avoid frequent evictions
490        let target = max * 9 / 10;
491        Ok(self.evict_to_target(target))
492    }
493
494    async fn pin(&self, hash: &Hash) -> Result<(), StoreError> {
495        let key = to_hex(hash);
496        let mut inner = self.inner.write().unwrap();
497        *inner.pins.entry(key).or_insert(0) += 1;
498        Ok(())
499    }
500
501    async fn unpin(&self, hash: &Hash) -> Result<(), StoreError> {
502        let key = to_hex(hash);
503        let mut inner = self.inner.write().unwrap();
504        if let Some(count) = inner.pins.get_mut(&key) {
505            if *count > 0 {
506                *count -= 1;
507            }
508            if *count == 0 {
509                inner.pins.remove(&key);
510            }
511        }
512        Ok(())
513    }
514
515    fn pin_count(&self, hash: &Hash) -> u32 {
516        let key = to_hex(hash);
517        self.inner
518            .read()
519            .unwrap()
520            .pins
521            .get(&key)
522            .copied()
523            .unwrap_or(0)
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530    use crate::hash::sha256;
531
532    #[tokio::test]
533    async fn test_put_returns_true_for_new() {
534        let store = MemoryStore::new();
535        let data = vec![1u8, 2, 3];
536        let hash = sha256(&data);
537
538        let result = store.put(hash, data).await.unwrap();
539        assert!(result);
540    }
541
542    #[tokio::test]
543    async fn test_put_returns_false_for_duplicate() {
544        let store = MemoryStore::new();
545        let data = vec![1u8, 2, 3];
546        let hash = sha256(&data);
547
548        store.put(hash, data.clone()).await.unwrap();
549        let result = store.put(hash, data).await.unwrap();
550        assert!(!result);
551    }
552
553    #[tokio::test]
554    async fn test_put_many_counts_only_new_items() {
555        let store = MemoryStore::new();
556        let data1 = vec![1u8, 2, 3];
557        let data2 = vec![4u8, 5, 6];
558        let hash1 = sha256(&data1);
559        let hash2 = sha256(&data2);
560
561        store.put(hash1, data1.clone()).await.unwrap();
562        let inserted = store
563            .put_many(vec![(hash1, data1), (hash2, data2.clone())])
564            .await
565            .unwrap();
566
567        assert_eq!(inserted, 1);
568        assert_eq!(store.get(&hash2).await.unwrap(), Some(data2));
569    }
570
571    #[tokio::test]
572    async fn test_buffered_store_flushes_pending_writes() {
573        let base = std::sync::Arc::new(MemoryStore::new());
574        let buffered = BufferedStore::new(std::sync::Arc::clone(&base));
575        let data = vec![9u8, 8, 7];
576        let hash = sha256(&data);
577
578        assert!(buffered.put(hash, data.clone()).await.unwrap());
579        assert_eq!(buffered.get(&hash).await.unwrap(), Some(data.clone()));
580        assert_eq!(base.get(&hash).await.unwrap(), None);
581
582        let flushed = buffered.flush().await.unwrap();
583
584        assert_eq!(flushed, 1);
585        assert_eq!(base.get(&hash).await.unwrap(), Some(data));
586    }
587
588    #[tokio::test]
589    async fn test_optimistic_buffered_store_avoids_base_probe_but_preserves_contents() {
590        let base = std::sync::Arc::new(MemoryStore::new());
591        let buffered = BufferedStore::new_optimistic(std::sync::Arc::clone(&base));
592        let data = vec![4u8, 5, 6];
593        let hash = sha256(&data);
594
595        base.put(hash, data.clone()).await.unwrap();
596
597        assert!(buffered.put(hash, data.clone()).await.unwrap());
598        assert_eq!(buffered.get(&hash).await.unwrap(), Some(data.clone()));
599
600        let flushed = buffered.flush().await.unwrap();
601
602        assert_eq!(flushed, 0);
603        assert_eq!(base.get(&hash).await.unwrap(), Some(data));
604    }
605
606    #[tokio::test]
607    async fn test_get_returns_data() {
608        let store = MemoryStore::new();
609        let data = vec![1u8, 2, 3];
610        let hash = sha256(&data);
611
612        store.put(hash, data.clone()).await.unwrap();
613        let result = store.get(&hash).await.unwrap();
614
615        assert_eq!(result, Some(data));
616    }
617
618    #[tokio::test]
619    async fn test_get_returns_none_for_missing() {
620        let store = MemoryStore::new();
621        let hash = [0u8; 32];
622
623        let result = store.get(&hash).await.unwrap();
624        assert!(result.is_none());
625    }
626
627    #[tokio::test]
628    async fn test_has_returns_true() {
629        let store = MemoryStore::new();
630        let data = vec![1u8, 2, 3];
631        let hash = sha256(&data);
632
633        store.put(hash, data).await.unwrap();
634        assert!(store.has(&hash).await.unwrap());
635    }
636
637    #[tokio::test]
638    async fn test_has_returns_false() {
639        let store = MemoryStore::new();
640        let hash = [0u8; 32];
641
642        assert!(!store.has(&hash).await.unwrap());
643    }
644
645    #[tokio::test]
646    async fn test_delete_returns_true() {
647        let store = MemoryStore::new();
648        let data = vec![1u8, 2, 3];
649        let hash = sha256(&data);
650
651        store.put(hash, data).await.unwrap();
652        let result = store.delete(&hash).await.unwrap();
653
654        assert!(result);
655        assert!(!store.has(&hash).await.unwrap());
656    }
657
658    #[tokio::test]
659    async fn test_delete_returns_false() {
660        let store = MemoryStore::new();
661        let hash = [0u8; 32];
662
663        let result = store.delete(&hash).await.unwrap();
664        assert!(!result);
665    }
666
667    #[tokio::test]
668    async fn test_size() {
669        let store = MemoryStore::new();
670        assert_eq!(store.size(), 0);
671
672        let data1 = vec![1u8];
673        let data2 = vec![2u8];
674        let hash1 = sha256(&data1);
675        let hash2 = sha256(&data2);
676
677        store.put(hash1, data1).await.unwrap();
678        store.put(hash2, data2).await.unwrap();
679
680        assert_eq!(store.size(), 2);
681    }
682
683    #[tokio::test]
684    async fn test_total_bytes() {
685        let store = MemoryStore::new();
686        assert_eq!(store.total_bytes(), 0);
687
688        let data1 = vec![1u8, 2, 3];
689        let data2 = vec![4u8, 5];
690        let hash1 = sha256(&data1);
691        let hash2 = sha256(&data2);
692
693        store.put(hash1, data1).await.unwrap();
694        store.put(hash2, data2).await.unwrap();
695
696        assert_eq!(store.total_bytes(), 5);
697    }
698
699    #[tokio::test]
700    async fn test_clear() {
701        let store = MemoryStore::new();
702        let data = vec![1u8, 2, 3];
703        let hash = sha256(&data);
704
705        store.put(hash, data).await.unwrap();
706        store.clear();
707
708        assert_eq!(store.size(), 0);
709        assert!(!store.has(&hash).await.unwrap());
710    }
711
712    #[tokio::test]
713    async fn test_keys() {
714        let store = MemoryStore::new();
715        assert!(store.keys().is_empty());
716
717        let data1 = vec![1u8];
718        let data2 = vec![2u8];
719        let hash1 = sha256(&data1);
720        let hash2 = sha256(&data2);
721
722        store.put(hash1, data1).await.unwrap();
723        store.put(hash2, data2).await.unwrap();
724
725        let keys = store.keys();
726        assert_eq!(keys.len(), 2);
727
728        let mut hex_keys: Vec<_> = keys.iter().map(to_hex).collect();
729        hex_keys.sort();
730        let mut expected: Vec<_> = vec![to_hex(&hash1), to_hex(&hash2)];
731        expected.sort();
732        assert_eq!(hex_keys, expected);
733    }
734
735    #[tokio::test]
736    async fn test_pin_and_unpin() {
737        let store = MemoryStore::new();
738        let data = vec![1u8, 2, 3];
739        let hash = sha256(&data);
740
741        store.put(hash, data).await.unwrap();
742
743        // Initially not pinned
744        assert!(!store.is_pinned(&hash));
745        assert_eq!(store.pin_count(&hash), 0);
746
747        // Pin
748        store.pin(&hash).await.unwrap();
749        assert!(store.is_pinned(&hash));
750        assert_eq!(store.pin_count(&hash), 1);
751
752        // Unpin
753        store.unpin(&hash).await.unwrap();
754        assert!(!store.is_pinned(&hash));
755        assert_eq!(store.pin_count(&hash), 0);
756    }
757
758    #[tokio::test]
759    async fn test_pin_count_ref_counting() {
760        let store = MemoryStore::new();
761        let data = vec![1u8, 2, 3];
762        let hash = sha256(&data);
763
764        store.put(hash, data).await.unwrap();
765
766        // Pin multiple times
767        store.pin(&hash).await.unwrap();
768        store.pin(&hash).await.unwrap();
769        store.pin(&hash).await.unwrap();
770        assert_eq!(store.pin_count(&hash), 3);
771
772        // Unpin once
773        store.unpin(&hash).await.unwrap();
774        assert_eq!(store.pin_count(&hash), 2);
775        assert!(store.is_pinned(&hash));
776
777        // Unpin remaining
778        store.unpin(&hash).await.unwrap();
779        store.unpin(&hash).await.unwrap();
780        assert_eq!(store.pin_count(&hash), 0);
781        assert!(!store.is_pinned(&hash));
782
783        // Extra unpin shouldn't go negative
784        store.unpin(&hash).await.unwrap();
785        assert_eq!(store.pin_count(&hash), 0);
786    }
787
788    #[tokio::test]
789    async fn test_stats() {
790        let store = MemoryStore::new();
791
792        let data1 = vec![1u8, 2, 3]; // 3 bytes
793        let data2 = vec![4u8, 5]; // 2 bytes
794        let hash1 = sha256(&data1);
795        let hash2 = sha256(&data2);
796
797        store.put(hash1, data1).await.unwrap();
798        store.put(hash2, data2).await.unwrap();
799
800        // Pin one item
801        store.pin(&hash1).await.unwrap();
802
803        let stats = store.stats().await;
804        assert_eq!(stats.count, 2);
805        assert_eq!(stats.bytes, 5);
806        assert_eq!(stats.pinned_count, 1);
807        assert_eq!(stats.pinned_bytes, 3);
808    }
809
810    #[tokio::test]
811    async fn test_max_bytes() {
812        let store = MemoryStore::new();
813        assert!(store.max_bytes().is_none());
814
815        store.set_max_bytes(1000);
816        assert_eq!(store.max_bytes(), Some(1000));
817
818        // 0 means unlimited
819        store.set_max_bytes(0);
820        assert!(store.max_bytes().is_none());
821    }
822
823    #[tokio::test]
824    async fn test_with_max_bytes() {
825        let store = MemoryStore::with_max_bytes(500);
826        assert_eq!(store.max_bytes(), Some(500));
827
828        let store_unlimited = MemoryStore::with_max_bytes(0);
829        assert!(store_unlimited.max_bytes().is_none());
830    }
831
832    #[tokio::test]
833    async fn test_eviction_respects_pins() {
834        // Store with 10 byte limit
835        let store = MemoryStore::with_max_bytes(10);
836
837        // Insert 3 items: 3 + 3 + 3 = 9 bytes
838        let data1 = vec![1u8, 1, 1]; // oldest
839        let data2 = vec![2u8, 2, 2];
840        let data3 = vec![3u8, 3, 3]; // newest
841        let hash1 = sha256(&data1);
842        let hash2 = sha256(&data2);
843        let hash3 = sha256(&data3);
844
845        store.put(hash1, data1).await.unwrap();
846        store.put(hash2, data2).await.unwrap();
847        store.put(hash3, data3).await.unwrap();
848
849        // Pin the oldest item
850        store.pin(&hash1).await.unwrap();
851
852        // Add more data to exceed limit: 9 + 3 = 12 bytes > 10
853        let data4 = vec![4u8, 4, 4];
854        let hash4 = sha256(&data4);
855        store.put(hash4, data4).await.unwrap();
856
857        // Evict - should remove hash2 (oldest unpinned)
858        let freed = store.evict_if_needed().await.unwrap();
859        assert!(freed > 0);
860
861        // hash1 should still exist (pinned)
862        assert!(store.has(&hash1).await.unwrap());
863        // hash2 should be gone (oldest unpinned)
864        assert!(!store.has(&hash2).await.unwrap());
865        // hash3 and hash4 should exist
866        assert!(store.has(&hash3).await.unwrap());
867        assert!(store.has(&hash4).await.unwrap());
868    }
869
870    #[tokio::test]
871    async fn test_eviction_lru_order() {
872        // Store with 15 byte limit
873        let store = MemoryStore::with_max_bytes(15);
874
875        // Insert items in order (oldest first)
876        let data1 = vec![1u8; 5]; // oldest
877        let data2 = vec![2u8; 5];
878        let data3 = vec![3u8; 5];
879        let data4 = vec![4u8; 5]; // newest
880        let hash1 = sha256(&data1);
881        let hash2 = sha256(&data2);
882        let hash3 = sha256(&data3);
883        let hash4 = sha256(&data4);
884
885        store.put(hash1, data1).await.unwrap();
886        store.put(hash2, data2).await.unwrap();
887        store.put(hash3, data3).await.unwrap();
888        store.put(hash4, data4).await.unwrap();
889
890        // Now at 20 bytes, limit is 15
891        assert_eq!(store.total_bytes(), 20);
892
893        // Evict - should remove oldest items first
894        let freed = store.evict_if_needed().await.unwrap();
895        assert!(freed >= 5); // At least one item evicted
896
897        // Oldest should be gone
898        assert!(!store.has(&hash1).await.unwrap());
899        // Newest should still exist
900        assert!(store.has(&hash4).await.unwrap());
901    }
902
903    #[tokio::test]
904    async fn test_no_eviction_when_under_limit() {
905        let store = MemoryStore::with_max_bytes(100);
906
907        let data = vec![1u8, 2, 3];
908        let hash = sha256(&data);
909        store.put(hash, data).await.unwrap();
910
911        let freed = store.evict_if_needed().await.unwrap();
912        assert_eq!(freed, 0);
913        assert!(store.has(&hash).await.unwrap());
914    }
915
916    #[tokio::test]
917    async fn test_no_eviction_without_limit() {
918        let store = MemoryStore::new();
919
920        // Add lots of data
921        for i in 0..100u8 {
922            let data = vec![i; 100];
923            let hash = sha256(&data);
924            store.put(hash, data).await.unwrap();
925        }
926
927        let freed = store.evict_if_needed().await.unwrap();
928        assert_eq!(freed, 0);
929        assert_eq!(store.size(), 100);
930    }
931
932    #[tokio::test]
933    async fn test_delete_removes_pin() {
934        let store = MemoryStore::new();
935        let data = vec![1u8, 2, 3];
936        let hash = sha256(&data);
937
938        store.put(hash, data).await.unwrap();
939        store.pin(&hash).await.unwrap();
940        assert!(store.is_pinned(&hash));
941
942        store.delete(&hash).await.unwrap();
943        // Pin should be gone after delete
944        assert_eq!(store.pin_count(&hash), 0);
945    }
946}