presentar_core/
cache.rs

1#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
2// Data Caching Layer - WASM-first caching for API/network data
3//
4// Provides:
5// - In-memory LRU cache
6// - Time-based expiration
7// - Cache invalidation patterns
8// - Stale-while-revalidate strategy
9// - Memory pressure handling
10
11use std::collections::HashMap;
12use std::hash::Hash;
13use std::sync::Arc;
14use std::time::Duration;
15
16/// Unique identifier for a cache entry
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct CacheKey(u64);
19
20impl CacheKey {
21    pub fn from_str(s: &str) -> Self {
22        // Simple hash for string keys
23        let mut hash: u64 = 0;
24        for byte in s.bytes() {
25            hash = hash.wrapping_mul(31).wrapping_add(u64::from(byte));
26        }
27        Self(hash)
28    }
29
30    pub fn as_u64(self) -> u64 {
31        self.0
32    }
33}
34
35impl From<u64> for CacheKey {
36    fn from(v: u64) -> Self {
37        Self(v)
38    }
39}
40
41impl From<&str> for CacheKey {
42    fn from(s: &str) -> Self {
43        Self::from_str(s)
44    }
45}
46
47/// Cache entry state
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum CacheState {
50    /// Entry is fresh and valid
51    Fresh,
52    /// Entry is stale but can be used while revalidating
53    Stale,
54    /// Entry has expired and should not be used
55    Expired,
56}
57
58/// Cache entry metadata
59#[derive(Debug, Clone)]
60pub struct CacheMetadata {
61    /// When the entry was created
62    pub created_at: u64,
63    /// When the entry was last accessed
64    pub last_accessed: u64,
65    /// Time to live in milliseconds
66    pub ttl_ms: u64,
67    /// Stale time in milliseconds (for stale-while-revalidate)
68    pub stale_ms: u64,
69    /// Number of times this entry was accessed
70    pub access_count: u64,
71    /// Size of the cached data in bytes
72    pub size_bytes: usize,
73    /// Tags for invalidation
74    pub tags: Vec<String>,
75}
76
77impl CacheMetadata {
78    /// Check current state based on timestamp
79    pub fn state(&self, now: u64) -> CacheState {
80        let age = now.saturating_sub(self.created_at);
81        if age <= self.ttl_ms {
82            CacheState::Fresh
83        } else if age <= self.ttl_ms + self.stale_ms {
84            CacheState::Stale
85        } else {
86            CacheState::Expired
87        }
88    }
89
90    /// Check if entry is expired
91    pub fn is_expired(&self, now: u64) -> bool {
92        self.state(now) == CacheState::Expired
93    }
94}
95
96/// Configuration for cache
97#[derive(Debug, Clone)]
98pub struct CacheConfig {
99    /// Maximum number of entries
100    pub max_entries: usize,
101    /// Maximum memory in bytes
102    pub max_memory: usize,
103    /// Default TTL in milliseconds
104    pub default_ttl_ms: u64,
105    /// Default stale time in milliseconds
106    pub default_stale_ms: u64,
107    /// Enable LRU eviction
108    pub enable_lru: bool,
109    /// Cleanup interval in milliseconds
110    pub cleanup_interval_ms: u64,
111}
112
113impl Default for CacheConfig {
114    fn default() -> Self {
115        Self {
116            max_entries: 1000,
117            max_memory: 50 * 1024 * 1024,  // 50 MB
118            default_ttl_ms: 5 * 60 * 1000, // 5 minutes
119            default_stale_ms: 60 * 1000,   // 1 minute stale
120            enable_lru: true,
121            cleanup_interval_ms: 60 * 1000, // 1 minute
122        }
123    }
124}
125
126/// Cache entry options
127#[derive(Debug, Clone, Default)]
128pub struct CacheOptions {
129    /// Custom TTL (None = use default)
130    pub ttl: Option<Duration>,
131    /// Custom stale time (None = use default)
132    pub stale: Option<Duration>,
133    /// Tags for invalidation
134    pub tags: Vec<String>,
135    /// Priority (higher = less likely to be evicted)
136    pub priority: u8,
137}
138
139impl CacheOptions {
140    pub fn new() -> Self {
141        Self::default()
142    }
143
144    pub fn with_ttl(mut self, ttl: Duration) -> Self {
145        self.ttl = Some(ttl);
146        self
147    }
148
149    pub fn with_stale(mut self, stale: Duration) -> Self {
150        self.stale = Some(stale);
151        self
152    }
153
154    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
155        self.tags.push(tag.into());
156        self
157    }
158
159    pub fn with_priority(mut self, priority: u8) -> Self {
160        self.priority = priority;
161        self
162    }
163}
164
165/// Cache event for notifications
166#[derive(Debug, Clone)]
167pub enum CacheEvent {
168    /// Entry was added
169    Added(CacheKey),
170    /// Entry was accessed
171    Hit(CacheKey),
172    /// Entry was not found
173    Miss(CacheKey),
174    /// Entry was evicted
175    Evicted(CacheKey),
176    /// Entry was invalidated
177    Invalidated(CacheKey),
178    /// Entries were invalidated by tag
179    TagInvalidated(String, usize),
180    /// Cache was cleared
181    Cleared,
182}
183
184/// Callback for cache events
185pub type CacheCallback = Arc<dyn Fn(CacheEvent) + Send + Sync>;
186
187/// Generic cache entry
188struct CacheEntry<V> {
189    value: V,
190    metadata: CacheMetadata,
191    #[allow(dead_code)]
192    priority: u8,
193}
194
195/// In-memory data cache
196pub struct DataCache<K, V>
197where
198    K: Eq + Hash + Clone,
199{
200    config: CacheConfig,
201    entries: HashMap<K, CacheEntry<V>>,
202    /// Access order for LRU
203    access_order: Vec<K>,
204    /// Current memory usage
205    current_memory: usize,
206    /// Current timestamp
207    timestamp: u64,
208    /// Last cleanup timestamp
209    last_cleanup: u64,
210    /// Event listeners
211    listeners: Vec<CacheCallback>,
212    /// Statistics
213    stats: CacheStats,
214}
215
216/// Cache statistics
217#[derive(Debug, Clone, Default)]
218pub struct CacheStats {
219    pub hits: u64,
220    pub misses: u64,
221    pub evictions: u64,
222    pub invalidations: u64,
223    pub current_entries: usize,
224    pub current_memory: usize,
225}
226
227impl CacheStats {
228    pub fn hit_rate(&self) -> f64 {
229        let total = self.hits + self.misses;
230        if total == 0 {
231            0.0
232        } else {
233            self.hits as f64 / total as f64
234        }
235    }
236}
237
238impl<K, V> Default for DataCache<K, V>
239where
240    K: Eq + Hash + Clone,
241{
242    fn default() -> Self {
243        Self::new(CacheConfig::default())
244    }
245}
246
247impl<K, V> DataCache<K, V>
248where
249    K: Eq + Hash + Clone,
250{
251    pub fn new(config: CacheConfig) -> Self {
252        Self {
253            config,
254            entries: HashMap::new(),
255            access_order: Vec::new(),
256            current_memory: 0,
257            timestamp: 0,
258            last_cleanup: 0,
259            listeners: Vec::new(),
260            stats: CacheStats::default(),
261        }
262    }
263
264    /// Get a value from cache
265    pub fn get(&mut self, key: &K) -> Option<&V> {
266        // Check if cleanup needed
267        self.maybe_cleanup();
268
269        if let Some(entry) = self.entries.get_mut(key) {
270            // Check expiration
271            if entry.metadata.is_expired(self.timestamp) {
272                return None;
273            }
274
275            // Update access time
276            entry.metadata.last_accessed = self.timestamp;
277            entry.metadata.access_count += 1;
278
279            // Update LRU order
280            if self.config.enable_lru {
281                self.access_order.retain(|k| k != key);
282                self.access_order.push(key.clone());
283            }
284
285            self.stats.hits += 1;
286            Some(&entry.value)
287        } else {
288            self.stats.misses += 1;
289            None
290        }
291    }
292
293    /// Get a value and its state
294    pub fn get_with_state(&mut self, key: &K) -> Option<(&V, CacheState)> {
295        self.maybe_cleanup();
296
297        if let Some(entry) = self.entries.get_mut(key) {
298            let state = entry.metadata.state(self.timestamp);
299
300            // Don't return expired entries
301            if state == CacheState::Expired {
302                return None;
303            }
304
305            entry.metadata.last_accessed = self.timestamp;
306            entry.metadata.access_count += 1;
307
308            if self.config.enable_lru {
309                self.access_order.retain(|k| k != key);
310                self.access_order.push(key.clone());
311            }
312
313            self.stats.hits += 1;
314            Some((&entry.value, state))
315        } else {
316            self.stats.misses += 1;
317            None
318        }
319    }
320
321    /// Check if key exists and is not expired
322    pub fn contains(&self, key: &K) -> bool {
323        self.entries
324            .get(key)
325            .is_some_and(|e| !e.metadata.is_expired(self.timestamp))
326    }
327
328    /// Insert a value into cache
329    pub fn insert(&mut self, key: K, value: V, options: CacheOptions)
330    where
331        V: CacheSize,
332    {
333        let size = value.cache_size();
334        let ttl = options.ttl.map(|d| d.as_millis() as u64);
335        let stale = options.stale.map(|d| d.as_millis() as u64);
336
337        let metadata = CacheMetadata {
338            created_at: self.timestamp,
339            last_accessed: self.timestamp,
340            ttl_ms: ttl.unwrap_or(self.config.default_ttl_ms),
341            stale_ms: stale.unwrap_or(self.config.default_stale_ms),
342            access_count: 0,
343            size_bytes: size,
344            tags: options.tags,
345        };
346
347        // Remove old entry if exists
348        if let Some(old) = self.entries.remove(&key) {
349            self.current_memory = self.current_memory.saturating_sub(old.metadata.size_bytes);
350            self.access_order.retain(|k| k != &key);
351        }
352
353        // Evict if needed
354        while self.entries.len() >= self.config.max_entries
355            || self.current_memory + size > self.config.max_memory
356        {
357            if !self.evict_one() {
358                break;
359            }
360        }
361
362        self.current_memory += size;
363
364        let entry = CacheEntry {
365            value,
366            metadata,
367            priority: options.priority,
368        };
369
370        self.entries.insert(key.clone(), entry);
371        self.access_order.push(key);
372
373        self.stats.current_entries = self.entries.len();
374        self.stats.current_memory = self.current_memory;
375    }
376
377    /// Insert with default options
378    pub fn insert_default(&mut self, key: K, value: V)
379    where
380        V: CacheSize,
381    {
382        self.insert(key, value, CacheOptions::default());
383    }
384
385    /// Remove a value from cache
386    pub fn remove(&mut self, key: &K) -> Option<V> {
387        if let Some(entry) = self.entries.remove(key) {
388            self.current_memory = self
389                .current_memory
390                .saturating_sub(entry.metadata.size_bytes);
391            self.access_order.retain(|k| k != key);
392            self.stats.current_entries = self.entries.len();
393            self.stats.current_memory = self.current_memory;
394            self.stats.invalidations += 1;
395            Some(entry.value)
396        } else {
397            None
398        }
399    }
400
401    /// Invalidate entries by tag
402    pub fn invalidate_tag(&mut self, tag: &str) -> usize {
403        let keys_to_remove: Vec<K> = self
404            .entries
405            .iter()
406            .filter(|(_, e)| e.metadata.tags.iter().any(|t| t == tag))
407            .map(|(k, _)| k.clone())
408            .collect();
409
410        let count = keys_to_remove.len();
411        for key in keys_to_remove {
412            self.remove(&key);
413        }
414
415        self.emit(CacheEvent::TagInvalidated(tag.to_string(), count));
416        count
417    }
418
419    /// Clear all entries
420    pub fn clear(&mut self) {
421        self.entries.clear();
422        self.access_order.clear();
423        self.current_memory = 0;
424        self.stats.current_entries = 0;
425        self.stats.current_memory = 0;
426        self.emit(CacheEvent::Cleared);
427    }
428
429    /// Get cache statistics
430    pub fn stats(&self) -> &CacheStats {
431        &self.stats
432    }
433
434    /// Get number of entries
435    pub fn len(&self) -> usize {
436        self.entries.len()
437    }
438
439    /// Check if cache is empty
440    pub fn is_empty(&self) -> bool {
441        self.entries.is_empty()
442    }
443
444    /// Get current memory usage
445    pub fn memory_usage(&self) -> usize {
446        self.current_memory
447    }
448
449    /// Update timestamp (call each frame or periodically)
450    pub fn tick(&mut self, delta_ms: u64) {
451        self.timestamp += delta_ms;
452        self.maybe_cleanup();
453    }
454
455    /// Set timestamp directly
456    pub fn set_timestamp(&mut self, timestamp: u64) {
457        self.timestamp = timestamp;
458    }
459
460    /// Get current timestamp
461    pub fn timestamp(&self) -> u64 {
462        self.timestamp
463    }
464
465    /// Add event listener
466    pub fn on_event(&mut self, callback: CacheCallback) {
467        self.listeners.push(callback);
468    }
469
470    fn emit(&self, event: CacheEvent) {
471        for listener in &self.listeners {
472            listener(event.clone());
473        }
474    }
475
476    fn maybe_cleanup(&mut self) {
477        if self.timestamp - self.last_cleanup >= self.config.cleanup_interval_ms {
478            self.cleanup_expired();
479            self.last_cleanup = self.timestamp;
480        }
481    }
482
483    fn cleanup_expired(&mut self) {
484        let now = self.timestamp;
485        let keys_to_remove: Vec<K> = self
486            .entries
487            .iter()
488            .filter(|(_, e)| e.metadata.is_expired(now))
489            .map(|(k, _)| k.clone())
490            .collect();
491
492        for key in keys_to_remove {
493            if let Some(entry) = self.entries.remove(&key) {
494                self.current_memory = self
495                    .current_memory
496                    .saturating_sub(entry.metadata.size_bytes);
497                self.access_order.retain(|k| k != &key);
498                self.stats.evictions += 1;
499            }
500        }
501
502        self.stats.current_entries = self.entries.len();
503        self.stats.current_memory = self.current_memory;
504    }
505
506    fn evict_one(&mut self) -> bool {
507        if self.config.enable_lru && !self.access_order.is_empty() {
508            // LRU eviction
509            let key = self.access_order.remove(0);
510            if let Some(entry) = self.entries.remove(&key) {
511                self.current_memory = self
512                    .current_memory
513                    .saturating_sub(entry.metadata.size_bytes);
514                self.stats.evictions += 1;
515                return true;
516            }
517        } else if !self.entries.is_empty() {
518            // Random eviction as fallback
519            if let Some(key) = self.entries.keys().next().cloned() {
520                if let Some(entry) = self.entries.remove(&key) {
521                    self.current_memory = self
522                        .current_memory
523                        .saturating_sub(entry.metadata.size_bytes);
524                    self.access_order.retain(|k| k != &key);
525                    self.stats.evictions += 1;
526                    return true;
527                }
528            }
529        }
530        false
531    }
532}
533
534/// Trait for getting size of cached values
535pub trait CacheSize {
536    fn cache_size(&self) -> usize;
537}
538
539// Implement CacheSize for common types
540impl CacheSize for String {
541    fn cache_size(&self) -> usize {
542        self.len()
543    }
544}
545
546impl<T> CacheSize for Vec<T> {
547    fn cache_size(&self) -> usize {
548        self.len() * std::mem::size_of::<T>()
549    }
550}
551
552impl<T> CacheSize for Box<T> {
553    fn cache_size(&self) -> usize {
554        std::mem::size_of::<T>()
555    }
556}
557
558impl CacheSize for () {
559    fn cache_size(&self) -> usize {
560        0
561    }
562}
563
564impl CacheSize for i32 {
565    fn cache_size(&self) -> usize {
566        std::mem::size_of::<Self>()
567    }
568}
569
570impl CacheSize for i64 {
571    fn cache_size(&self) -> usize {
572        std::mem::size_of::<Self>()
573    }
574}
575
576impl CacheSize for f32 {
577    fn cache_size(&self) -> usize {
578        std::mem::size_of::<Self>()
579    }
580}
581
582impl CacheSize for f64 {
583    fn cache_size(&self) -> usize {
584        std::mem::size_of::<Self>()
585    }
586}
587
588/// Simple string-keyed cache
589pub type StringCache<V> = DataCache<String, V>;
590
591/// Builder for cache entries
592pub struct CacheBuilder<V> {
593    value: V,
594    options: CacheOptions,
595}
596
597impl<V> CacheBuilder<V> {
598    pub fn new(value: V) -> Self {
599        Self {
600            value,
601            options: CacheOptions::default(),
602        }
603    }
604
605    pub fn ttl(mut self, ttl: Duration) -> Self {
606        self.options.ttl = Some(ttl);
607        self
608    }
609
610    pub fn stale(mut self, stale: Duration) -> Self {
611        self.options.stale = Some(stale);
612        self
613    }
614
615    pub fn tag(mut self, tag: impl Into<String>) -> Self {
616        self.options.tags.push(tag.into());
617        self
618    }
619
620    pub fn priority(mut self, priority: u8) -> Self {
621        self.options.priority = priority;
622        self
623    }
624
625    pub fn build(self) -> (V, CacheOptions) {
626        (self.value, self.options)
627    }
628}
629
630#[cfg(test)]
631#[allow(clippy::unwrap_used)]
632mod tests {
633    use super::*;
634
635    #[test]
636    fn test_cache_key_from_str() {
637        let key1 = CacheKey::from_str("test");
638        let key2 = CacheKey::from_str("test");
639        let key3 = CacheKey::from_str("different");
640
641        assert_eq!(key1, key2);
642        assert_ne!(key1, key3);
643    }
644
645    #[test]
646    fn test_cache_key_from_u64() {
647        let key: CacheKey = 42u64.into();
648        assert_eq!(key.as_u64(), 42);
649    }
650
651    #[test]
652    fn test_cache_default() {
653        let cache: DataCache<String, String> = DataCache::default();
654        assert!(cache.is_empty());
655        assert_eq!(cache.len(), 0);
656    }
657
658    #[test]
659    fn test_cache_insert_get() {
660        let mut cache: DataCache<String, String> = DataCache::default();
661
662        cache.insert_default("key".to_string(), "value".to_string());
663
664        let result = cache.get(&"key".to_string());
665        assert_eq!(result, Some(&"value".to_string()));
666    }
667
668    #[test]
669    fn test_cache_miss() {
670        let mut cache: DataCache<String, String> = DataCache::default();
671
672        let result = cache.get(&"missing".to_string());
673        assert_eq!(result, None);
674    }
675
676    #[test]
677    fn test_cache_contains() {
678        let mut cache: DataCache<String, String> = DataCache::default();
679
680        cache.insert_default("key".to_string(), "value".to_string());
681
682        assert!(cache.contains(&"key".to_string()));
683        assert!(!cache.contains(&"missing".to_string()));
684    }
685
686    #[test]
687    fn test_cache_remove() {
688        let mut cache: DataCache<String, String> = DataCache::default();
689
690        cache.insert_default("key".to_string(), "value".to_string());
691        let removed = cache.remove(&"key".to_string());
692
693        assert_eq!(removed, Some("value".to_string()));
694        assert!(!cache.contains(&"key".to_string()));
695    }
696
697    #[test]
698    fn test_cache_clear() {
699        let mut cache: DataCache<String, String> = DataCache::default();
700
701        cache.insert_default("key1".to_string(), "value1".to_string());
702        cache.insert_default("key2".to_string(), "value2".to_string());
703
704        cache.clear();
705
706        assert!(cache.is_empty());
707        assert_eq!(cache.len(), 0);
708    }
709
710    #[test]
711    fn test_cache_expiration() {
712        let config = CacheConfig {
713            default_ttl_ms: 100,
714            default_stale_ms: 0,
715            ..Default::default()
716        };
717        let mut cache: DataCache<String, String> = DataCache::new(config);
718
719        cache.insert_default("key".to_string(), "value".to_string());
720
721        // Should be fresh
722        assert!(cache.contains(&"key".to_string()));
723
724        // Advance time past TTL
725        cache.set_timestamp(200);
726
727        // Should be expired
728        assert!(!cache.contains(&"key".to_string()));
729        assert!(cache.get(&"key".to_string()).is_none());
730    }
731
732    #[test]
733    fn test_cache_stale_while_revalidate() {
734        let config = CacheConfig {
735            default_ttl_ms: 100,
736            default_stale_ms: 50,
737            ..Default::default()
738        };
739        let mut cache: DataCache<String, String> = DataCache::new(config);
740
741        cache.insert_default("key".to_string(), "value".to_string());
742
743        // Should be fresh
744        let (_, state) = cache.get_with_state(&"key".to_string()).unwrap();
745        assert_eq!(state, CacheState::Fresh);
746
747        // Advance into stale period
748        cache.set_timestamp(120);
749        let (_, state) = cache.get_with_state(&"key".to_string()).unwrap();
750        assert_eq!(state, CacheState::Stale);
751
752        // Advance past stale period
753        cache.set_timestamp(200);
754        assert!(cache.get_with_state(&"key".to_string()).is_none());
755    }
756
757    #[test]
758    fn test_cache_custom_ttl() {
759        let config = CacheConfig {
760            default_ttl_ms: 1000,
761            default_stale_ms: 0, // No stale period for this test
762            ..Default::default()
763        };
764        let mut cache: DataCache<String, String> = DataCache::new(config);
765
766        let options = CacheOptions::new().with_ttl(Duration::from_millis(50));
767        cache.insert("key".to_string(), "value".to_string(), options);
768
769        cache.set_timestamp(100);
770        assert!(cache.get(&"key".to_string()).is_none());
771    }
772
773    #[test]
774    fn test_cache_tags() {
775        let mut cache: DataCache<String, String> = DataCache::default();
776
777        let options1 = CacheOptions::new().with_tag("user").with_tag("profile");
778        let options2 = CacheOptions::new().with_tag("user");
779        let options3 = CacheOptions::new().with_tag("settings");
780
781        cache.insert("key1".to_string(), "value1".to_string(), options1);
782        cache.insert("key2".to_string(), "value2".to_string(), options2);
783        cache.insert("key3".to_string(), "value3".to_string(), options3);
784
785        // Invalidate by tag
786        let count = cache.invalidate_tag("user");
787        assert_eq!(count, 2);
788
789        assert!(!cache.contains(&"key1".to_string()));
790        assert!(!cache.contains(&"key2".to_string()));
791        assert!(cache.contains(&"key3".to_string()));
792    }
793
794    #[test]
795    fn test_cache_lru_eviction() {
796        let config = CacheConfig {
797            max_entries: 3,
798            enable_lru: true,
799            ..Default::default()
800        };
801        let mut cache: DataCache<String, i32> = DataCache::new(config);
802
803        cache.insert_default("key1".to_string(), 1);
804        cache.insert_default("key2".to_string(), 2);
805        cache.insert_default("key3".to_string(), 3);
806
807        // Access key1 to make it recently used
808        cache.get(&"key1".to_string());
809
810        // Insert new entry, should evict key2 (LRU)
811        cache.insert_default("key4".to_string(), 4);
812
813        assert!(cache.contains(&"key1".to_string()));
814        assert!(!cache.contains(&"key2".to_string())); // Evicted
815        assert!(cache.contains(&"key3".to_string()));
816        assert!(cache.contains(&"key4".to_string()));
817    }
818
819    #[test]
820    fn test_cache_memory_limit() {
821        let config = CacheConfig {
822            max_memory: 20, // Very small
823            ..Default::default()
824        };
825        let mut cache: DataCache<String, String> = DataCache::new(config);
826
827        cache.insert_default("key1".to_string(), "12345".to_string()); // 5 bytes
828        cache.insert_default("key2".to_string(), "12345".to_string()); // 5 bytes
829        cache.insert_default("key3".to_string(), "12345".to_string()); // 5 bytes
830        cache.insert_default("key4".to_string(), "12345".to_string()); // 5 bytes
831
832        // Should have evicted some entries
833        assert!(cache.memory_usage() <= 20);
834    }
835
836    #[test]
837    fn test_cache_stats() {
838        let mut cache: DataCache<String, String> = DataCache::default();
839
840        cache.insert_default("key".to_string(), "value".to_string());
841
842        cache.get(&"key".to_string()); // Hit
843        cache.get(&"key".to_string()); // Hit
844        cache.get(&"missing".to_string()); // Miss
845
846        let stats = cache.stats();
847        assert_eq!(stats.hits, 2);
848        assert_eq!(stats.misses, 1);
849        assert!((stats.hit_rate() - 0.666).abs() < 0.01);
850    }
851
852    #[test]
853    fn test_cache_tick() {
854        let config = CacheConfig {
855            cleanup_interval_ms: 100,
856            default_ttl_ms: 50,
857            default_stale_ms: 0, // No stale period for this test
858            ..Default::default()
859        };
860        let mut cache: DataCache<String, String> = DataCache::new(config);
861
862        cache.insert_default("key".to_string(), "value".to_string());
863
864        // Tick forward past TTL and cleanup interval
865        cache.tick(150);
866
867        // Entry should be cleaned up
868        assert!(!cache.contains(&"key".to_string()));
869    }
870
871    #[test]
872    fn test_cache_metadata_state() {
873        let meta = CacheMetadata {
874            created_at: 0,
875            last_accessed: 0,
876            ttl_ms: 100,
877            stale_ms: 50,
878            access_count: 0,
879            size_bytes: 0,
880            tags: vec![],
881        };
882
883        assert_eq!(meta.state(50), CacheState::Fresh);
884        assert_eq!(meta.state(120), CacheState::Stale);
885        assert_eq!(meta.state(200), CacheState::Expired);
886
887        assert!(!meta.is_expired(100));
888        assert!(meta.is_expired(200));
889    }
890
891    #[test]
892    fn test_cache_options_builder() {
893        let options = CacheOptions::new()
894            .with_ttl(Duration::from_secs(60))
895            .with_stale(Duration::from_secs(30))
896            .with_tag("test")
897            .with_priority(5);
898
899        assert_eq!(options.ttl, Some(Duration::from_secs(60)));
900        assert_eq!(options.stale, Some(Duration::from_secs(30)));
901        assert_eq!(options.tags, vec!["test"]);
902        assert_eq!(options.priority, 5);
903    }
904
905    #[test]
906    fn test_cache_builder() {
907        let (value, options) = CacheBuilder::new("test".to_string())
908            .ttl(Duration::from_secs(60))
909            .stale(Duration::from_secs(30))
910            .tag("user")
911            .priority(3)
912            .build();
913
914        assert_eq!(value, "test");
915        assert_eq!(options.ttl, Some(Duration::from_secs(60)));
916        assert_eq!(options.tags, vec!["user"]);
917    }
918
919    #[test]
920    fn test_cache_config_default() {
921        let config = CacheConfig::default();
922        assert_eq!(config.max_entries, 1000);
923        assert_eq!(config.max_memory, 50 * 1024 * 1024);
924        assert_eq!(config.default_ttl_ms, 5 * 60 * 1000);
925        assert_eq!(config.default_stale_ms, 60 * 1000);
926        assert!(config.enable_lru);
927    }
928
929    #[test]
930    fn test_cache_size_string() {
931        let s = "hello".to_string();
932        assert_eq!(s.cache_size(), 5);
933    }
934
935    #[test]
936    fn test_cache_size_vec() {
937        let v: Vec<u8> = vec![1, 2, 3, 4, 5];
938        assert_eq!(v.cache_size(), 5);
939    }
940
941    #[test]
942    fn test_cache_size_i32() {
943        let n: i32 = 42;
944        assert_eq!(n.cache_size(), 4);
945    }
946
947    #[test]
948    fn test_cache_update_entry() {
949        let mut cache: DataCache<String, String> = DataCache::default();
950
951        cache.insert_default("key".to_string(), "value1".to_string());
952        cache.insert_default("key".to_string(), "value2".to_string());
953
954        let result = cache.get(&"key".to_string());
955        assert_eq!(result, Some(&"value2".to_string()));
956        assert_eq!(cache.len(), 1);
957    }
958
959    #[test]
960    fn test_cache_event_callback() {
961        use std::sync::atomic::{AtomicUsize, Ordering};
962
963        let mut cache: DataCache<String, String> = DataCache::default();
964        let event_count = Arc::new(AtomicUsize::new(0));
965
966        let ec = event_count.clone();
967        cache.on_event(Arc::new(move |_event| {
968            ec.fetch_add(1, Ordering::SeqCst);
969        }));
970
971        cache.invalidate_tag("test");
972        cache.clear();
973
974        assert!(event_count.load(Ordering::SeqCst) >= 1);
975    }
976
977    #[test]
978    fn test_cache_state_variants() {
979        assert_eq!(CacheState::Fresh, CacheState::Fresh);
980        assert_ne!(CacheState::Fresh, CacheState::Stale);
981        assert_ne!(CacheState::Stale, CacheState::Expired);
982    }
983
984    #[test]
985    fn test_stats_hit_rate_empty() {
986        let stats = CacheStats::default();
987        assert_eq!(stats.hit_rate(), 0.0);
988    }
989
990    #[test]
991    fn test_cache_timestamp() {
992        let mut cache: DataCache<String, String> = DataCache::default();
993        assert_eq!(cache.timestamp(), 0);
994
995        cache.set_timestamp(1000);
996        assert_eq!(cache.timestamp(), 1000);
997
998        cache.tick(500);
999        assert_eq!(cache.timestamp(), 1500);
1000    }
1001
1002    // ========== Additional CacheKey tests ==========
1003
1004    #[test]
1005    fn test_cache_key_empty_string() {
1006        let key = CacheKey::from_str("");
1007        assert_eq!(key.as_u64(), 0);
1008    }
1009
1010    #[test]
1011    fn test_cache_key_unicode() {
1012        let key1 = CacheKey::from_str("日本語");
1013        let key2 = CacheKey::from_str("日本語");
1014        let key3 = CacheKey::from_str("中文");
1015        assert_eq!(key1, key2);
1016        assert_ne!(key1, key3);
1017    }
1018
1019    #[test]
1020    fn test_cache_key_long_string() {
1021        let long_str: String = "a".repeat(10000);
1022        let key = CacheKey::from_str(&long_str);
1023        assert!(key.as_u64() > 0);
1024    }
1025
1026    #[test]
1027    fn test_cache_key_from_str_trait() {
1028        let key: CacheKey = "test".into();
1029        assert_eq!(key, CacheKey::from_str("test"));
1030    }
1031
1032    #[test]
1033    fn test_cache_key_hash_distribution() {
1034        // Different short strings should produce different hashes
1035        let keys: Vec<CacheKey> = (0..100)
1036            .map(|i| CacheKey::from_str(&format!("key{i}")))
1037            .collect();
1038        let unique: std::collections::HashSet<_> = keys.iter().map(|k| k.as_u64()).collect();
1039        assert_eq!(unique.len(), 100);
1040    }
1041
1042    #[test]
1043    fn test_cache_key_special_chars() {
1044        let key = CacheKey::from_str("!@#$%^&*()_+-=[]{}|;':\",./<>?");
1045        assert!(key.as_u64() > 0);
1046    }
1047
1048    #[test]
1049    fn test_cache_key_whitespace() {
1050        let key1 = CacheKey::from_str("  ");
1051        let key2 = CacheKey::from_str("\t\n");
1052        assert_ne!(key1, key2);
1053    }
1054
1055    #[test]
1056    fn test_cache_key_debug() {
1057        let key = CacheKey::from_str("test");
1058        let debug = format!("{key:?}");
1059        assert!(debug.contains("CacheKey"));
1060    }
1061
1062    #[test]
1063    fn test_cache_key_clone() {
1064        let key1 = CacheKey::from_str("test");
1065        let key2 = key1;
1066        assert_eq!(key1, key2);
1067    }
1068
1069    // ========== Additional CacheMetadata tests ==========
1070
1071    #[test]
1072    fn test_cache_metadata_boundary_fresh() {
1073        let meta = CacheMetadata {
1074            created_at: 0,
1075            last_accessed: 0,
1076            ttl_ms: 100,
1077            stale_ms: 50,
1078            access_count: 0,
1079            size_bytes: 0,
1080            tags: vec![],
1081        };
1082        // Exactly at ttl boundary should still be fresh
1083        assert_eq!(meta.state(100), CacheState::Fresh);
1084    }
1085
1086    #[test]
1087    fn test_cache_metadata_boundary_stale() {
1088        let meta = CacheMetadata {
1089            created_at: 0,
1090            last_accessed: 0,
1091            ttl_ms: 100,
1092            stale_ms: 50,
1093            access_count: 0,
1094            size_bytes: 0,
1095            tags: vec![],
1096        };
1097        // At ttl+1 should be stale
1098        assert_eq!(meta.state(101), CacheState::Stale);
1099        // At ttl+stale boundary should still be stale
1100        assert_eq!(meta.state(150), CacheState::Stale);
1101    }
1102
1103    #[test]
1104    fn test_cache_metadata_zero_ttl() {
1105        let meta = CacheMetadata {
1106            created_at: 0,
1107            last_accessed: 0,
1108            ttl_ms: 0,
1109            stale_ms: 0,
1110            access_count: 0,
1111            size_bytes: 0,
1112            tags: vec![],
1113        };
1114        assert_eq!(meta.state(0), CacheState::Fresh);
1115        assert_eq!(meta.state(1), CacheState::Expired);
1116    }
1117
1118    #[test]
1119    fn test_cache_metadata_zero_stale() {
1120        let meta = CacheMetadata {
1121            created_at: 0,
1122            last_accessed: 0,
1123            ttl_ms: 100,
1124            stale_ms: 0,
1125            access_count: 0,
1126            size_bytes: 0,
1127            tags: vec![],
1128        };
1129        assert_eq!(meta.state(100), CacheState::Fresh);
1130        assert_eq!(meta.state(101), CacheState::Expired);
1131    }
1132
1133    #[test]
1134    fn test_cache_metadata_large_ttl() {
1135        let meta = CacheMetadata {
1136            created_at: 0,
1137            last_accessed: 0,
1138            ttl_ms: u64::MAX / 2,
1139            stale_ms: 1000,
1140            access_count: 0,
1141            size_bytes: 0,
1142            tags: vec![],
1143        };
1144        assert_eq!(meta.state(1_000_000), CacheState::Fresh);
1145    }
1146
1147    #[test]
1148    fn test_cache_metadata_created_in_future() {
1149        let meta = CacheMetadata {
1150            created_at: 1000,
1151            last_accessed: 1000,
1152            ttl_ms: 100,
1153            stale_ms: 50,
1154            access_count: 0,
1155            size_bytes: 0,
1156            tags: vec![],
1157        };
1158        // now < created_at, saturating_sub gives 0
1159        assert_eq!(meta.state(500), CacheState::Fresh);
1160    }
1161
1162    #[test]
1163    fn test_cache_metadata_with_tags() {
1164        let meta = CacheMetadata {
1165            created_at: 0,
1166            last_accessed: 0,
1167            ttl_ms: 100,
1168            stale_ms: 50,
1169            access_count: 5,
1170            size_bytes: 1024,
1171            tags: vec!["user".to_string(), "profile".to_string()],
1172        };
1173        assert_eq!(meta.tags.len(), 2);
1174        assert_eq!(meta.access_count, 5);
1175        assert_eq!(meta.size_bytes, 1024);
1176    }
1177
1178    #[test]
1179    fn test_cache_metadata_clone() {
1180        let meta = CacheMetadata {
1181            created_at: 100,
1182            last_accessed: 200,
1183            ttl_ms: 1000,
1184            stale_ms: 500,
1185            access_count: 10,
1186            size_bytes: 256,
1187            tags: vec!["test".to_string()],
1188        };
1189        let cloned = meta.clone();
1190        assert_eq!(cloned.created_at, 100);
1191        assert_eq!(cloned.tags, vec!["test"]);
1192    }
1193
1194    // ========== Additional CacheState tests ==========
1195
1196    #[test]
1197    fn test_cache_state_debug() {
1198        assert_eq!(format!("{:?}", CacheState::Fresh), "Fresh");
1199        assert_eq!(format!("{:?}", CacheState::Stale), "Stale");
1200        assert_eq!(format!("{:?}", CacheState::Expired), "Expired");
1201    }
1202
1203    #[test]
1204    fn test_cache_state_clone() {
1205        let state = CacheState::Fresh;
1206        let cloned = state;
1207        assert_eq!(state, cloned);
1208    }
1209
1210    // ========== Additional CacheConfig tests ==========
1211
1212    #[test]
1213    fn test_cache_config_custom() {
1214        let config = CacheConfig {
1215            max_entries: 500,
1216            max_memory: 10 * 1024 * 1024,
1217            default_ttl_ms: 60_000,
1218            default_stale_ms: 10_000,
1219            enable_lru: false,
1220            cleanup_interval_ms: 30_000,
1221        };
1222        assert_eq!(config.max_entries, 500);
1223        assert!(!config.enable_lru);
1224    }
1225
1226    #[test]
1227    fn test_cache_config_clone() {
1228        let config = CacheConfig::default();
1229        let cloned = config.clone();
1230        assert_eq!(cloned.max_entries, 1000);
1231    }
1232
1233    // ========== Additional CacheOptions tests ==========
1234
1235    #[test]
1236    fn test_cache_options_default() {
1237        let options = CacheOptions::default();
1238        assert!(options.ttl.is_none());
1239        assert!(options.stale.is_none());
1240        assert!(options.tags.is_empty());
1241        assert_eq!(options.priority, 0);
1242    }
1243
1244    #[test]
1245    fn test_cache_options_multiple_tags() {
1246        let options = CacheOptions::new()
1247            .with_tag("user")
1248            .with_tag("profile")
1249            .with_tag("admin");
1250        assert_eq!(options.tags.len(), 3);
1251    }
1252
1253    #[test]
1254    fn test_cache_options_zero_duration() {
1255        let options = CacheOptions::new()
1256            .with_ttl(Duration::ZERO)
1257            .with_stale(Duration::ZERO);
1258        assert_eq!(options.ttl, Some(Duration::ZERO));
1259        assert_eq!(options.stale, Some(Duration::ZERO));
1260    }
1261
1262    #[test]
1263    fn test_cache_options_max_priority() {
1264        let options = CacheOptions::new().with_priority(255);
1265        assert_eq!(options.priority, 255);
1266    }
1267
1268    #[test]
1269    fn test_cache_options_clone() {
1270        let options = CacheOptions::new()
1271            .with_ttl(Duration::from_secs(60))
1272            .with_tag("test");
1273        let cloned = options.clone();
1274        assert_eq!(cloned.ttl, Some(Duration::from_secs(60)));
1275        assert_eq!(cloned.tags, vec!["test"]);
1276    }
1277
1278    // ========== Additional CacheEvent tests ==========
1279
1280    #[test]
1281    fn test_cache_event_all_variants() {
1282        let key = CacheKey::from_str("test");
1283        let events = vec![
1284            CacheEvent::Added(key),
1285            CacheEvent::Hit(key),
1286            CacheEvent::Miss(key),
1287            CacheEvent::Evicted(key),
1288            CacheEvent::Invalidated(key),
1289            CacheEvent::TagInvalidated("user".to_string(), 5),
1290            CacheEvent::Cleared,
1291        ];
1292        for event in events {
1293            let _ = format!("{event:?}");
1294        }
1295    }
1296
1297    #[test]
1298    fn test_cache_event_clone() {
1299        let event = CacheEvent::TagInvalidated("test".to_string(), 10);
1300        let cloned = event.clone();
1301        if let CacheEvent::TagInvalidated(tag, count) = cloned {
1302            assert_eq!(tag, "test");
1303            assert_eq!(count, 10);
1304        } else {
1305            panic!("Clone failed");
1306        }
1307    }
1308
1309    // ========== Additional DataCache tests ==========
1310
1311    #[test]
1312    fn test_cache_lru_disabled() {
1313        let config = CacheConfig {
1314            max_entries: 3,
1315            enable_lru: false,
1316            ..Default::default()
1317        };
1318        let mut cache: DataCache<String, i32> = DataCache::new(config);
1319
1320        cache.insert_default("key1".to_string(), 1);
1321        cache.insert_default("key2".to_string(), 2);
1322        cache.insert_default("key3".to_string(), 3);
1323        cache.insert_default("key4".to_string(), 4);
1324
1325        // Should evict something (random eviction)
1326        assert_eq!(cache.len(), 3);
1327    }
1328
1329    #[test]
1330    fn test_cache_get_with_state_fresh() {
1331        let mut cache: DataCache<String, String> = DataCache::default();
1332        cache.insert_default("key".to_string(), "value".to_string());
1333
1334        let result = cache.get_with_state(&"key".to_string());
1335        assert!(result.is_some());
1336        let (value, state) = result.unwrap();
1337        assert_eq!(value, "value");
1338        assert_eq!(state, CacheState::Fresh);
1339    }
1340
1341    #[test]
1342    fn test_cache_get_with_state_miss() {
1343        let mut cache: DataCache<String, String> = DataCache::default();
1344        let result = cache.get_with_state(&"missing".to_string());
1345        assert!(result.is_none());
1346        assert_eq!(cache.stats().misses, 1);
1347    }
1348
1349    #[test]
1350    fn test_cache_multiple_removes() {
1351        let mut cache: DataCache<String, String> = DataCache::default();
1352        cache.insert_default("key".to_string(), "value".to_string());
1353
1354        let removed1 = cache.remove(&"key".to_string());
1355        let removed2 = cache.remove(&"key".to_string());
1356
1357        assert_eq!(removed1, Some("value".to_string()));
1358        assert_eq!(removed2, None);
1359    }
1360
1361    #[test]
1362    fn test_cache_invalidate_nonexistent_tag() {
1363        let mut cache: DataCache<String, String> = DataCache::default();
1364        cache.insert_default("key".to_string(), "value".to_string());
1365
1366        let count = cache.invalidate_tag("nonexistent");
1367        assert_eq!(count, 0);
1368        assert!(cache.contains(&"key".to_string()));
1369    }
1370
1371    #[test]
1372    fn test_cache_clear_empty() {
1373        let mut cache: DataCache<String, String> = DataCache::default();
1374        cache.clear();
1375        assert!(cache.is_empty());
1376    }
1377
1378    #[test]
1379    fn test_cache_memory_accounting() {
1380        let mut cache: DataCache<String, String> = DataCache::default();
1381
1382        cache.insert_default("key1".to_string(), "12345".to_string()); // 5 bytes
1383        assert_eq!(cache.memory_usage(), 5);
1384
1385        cache.insert_default("key2".to_string(), "12345678".to_string()); // 8 bytes
1386        assert_eq!(cache.memory_usage(), 13);
1387
1388        cache.remove(&"key1".to_string());
1389        assert_eq!(cache.memory_usage(), 8);
1390
1391        cache.clear();
1392        assert_eq!(cache.memory_usage(), 0);
1393    }
1394
1395    #[test]
1396    fn test_cache_replace_updates_memory() {
1397        let mut cache: DataCache<String, String> = DataCache::default();
1398
1399        cache.insert_default("key".to_string(), "12345".to_string()); // 5 bytes
1400        assert_eq!(cache.memory_usage(), 5);
1401
1402        cache.insert_default("key".to_string(), "12345678901234567890".to_string()); // 20 bytes
1403        assert_eq!(cache.memory_usage(), 20);
1404    }
1405
1406    #[test]
1407    fn test_cache_lru_order_updates_on_get() {
1408        let config = CacheConfig {
1409            max_entries: 2,
1410            enable_lru: true,
1411            ..Default::default()
1412        };
1413        let mut cache: DataCache<String, i32> = DataCache::new(config);
1414
1415        cache.insert_default("key1".to_string(), 1);
1416        cache.insert_default("key2".to_string(), 2);
1417
1418        // Access key1 to move it to end of LRU
1419        cache.get(&"key1".to_string());
1420
1421        // Insert key3, should evict key2 (least recently used)
1422        cache.insert_default("key3".to_string(), 3);
1423
1424        assert!(cache.contains(&"key1".to_string()));
1425        assert!(!cache.contains(&"key2".to_string()));
1426        assert!(cache.contains(&"key3".to_string()));
1427    }
1428
1429    #[test]
1430    fn test_cache_access_count_increments() {
1431        let config = CacheConfig {
1432            default_ttl_ms: 10000,
1433            ..Default::default()
1434        };
1435        let mut cache: DataCache<String, String> = DataCache::new(config);
1436        cache.insert_default("key".to_string(), "value".to_string());
1437
1438        for _ in 0..10 {
1439            cache.get(&"key".to_string());
1440        }
1441
1442        assert_eq!(cache.stats().hits, 10);
1443    }
1444
1445    #[test]
1446    fn test_cache_cleanup_triggered_by_tick() {
1447        let config = CacheConfig {
1448            cleanup_interval_ms: 50,
1449            default_ttl_ms: 25,
1450            default_stale_ms: 0,
1451            ..Default::default()
1452        };
1453        let mut cache: DataCache<String, String> = DataCache::new(config);
1454
1455        cache.insert_default("key".to_string(), "value".to_string());
1456        assert_eq!(cache.len(), 1);
1457
1458        // Tick past TTL but not cleanup interval
1459        cache.tick(30);
1460        // Entry exists but expired
1461        assert!(cache.get(&"key".to_string()).is_none());
1462        // Entry still in storage until cleanup
1463        assert_eq!(cache.entries.len(), 1);
1464
1465        // Tick past cleanup interval
1466        cache.tick(30);
1467        // Now entry should be cleaned up
1468        assert_eq!(cache.entries.len(), 0);
1469    }
1470
1471    #[test]
1472    fn test_cache_multiple_listeners() {
1473        use std::sync::atomic::{AtomicUsize, Ordering};
1474
1475        let mut cache: DataCache<String, String> = DataCache::default();
1476        let count1 = Arc::new(AtomicUsize::new(0));
1477        let count2 = Arc::new(AtomicUsize::new(0));
1478
1479        let c1 = count1.clone();
1480        cache.on_event(Arc::new(move |_| {
1481            c1.fetch_add(1, Ordering::SeqCst);
1482        }));
1483
1484        let c2 = count2.clone();
1485        cache.on_event(Arc::new(move |_| {
1486            c2.fetch_add(1, Ordering::SeqCst);
1487        }));
1488
1489        cache.clear();
1490
1491        assert_eq!(count1.load(Ordering::SeqCst), 1);
1492        assert_eq!(count2.load(Ordering::SeqCst), 1);
1493    }
1494
1495    #[test]
1496    fn test_cache_eviction_updates_stats() {
1497        let config = CacheConfig {
1498            max_entries: 2,
1499            ..Default::default()
1500        };
1501        let mut cache: DataCache<String, i32> = DataCache::new(config);
1502
1503        cache.insert_default("key1".to_string(), 1);
1504        cache.insert_default("key2".to_string(), 2);
1505        cache.insert_default("key3".to_string(), 3);
1506
1507        assert_eq!(cache.stats().evictions, 1);
1508    }
1509
1510    #[test]
1511    fn test_cache_contains_expired_entry() {
1512        let config = CacheConfig {
1513            default_ttl_ms: 50,
1514            default_stale_ms: 0,
1515            ..Default::default()
1516        };
1517        let mut cache: DataCache<String, String> = DataCache::new(config);
1518
1519        cache.insert_default("key".to_string(), "value".to_string());
1520        assert!(cache.contains(&"key".to_string()));
1521
1522        cache.set_timestamp(100);
1523        assert!(!cache.contains(&"key".to_string()));
1524    }
1525
1526    #[test]
1527    fn test_cache_stats_current_entries() {
1528        let mut cache: DataCache<String, i32> = DataCache::default();
1529
1530        cache.insert_default("k1".to_string(), 1);
1531        assert_eq!(cache.stats().current_entries, 1);
1532
1533        cache.insert_default("k2".to_string(), 2);
1534        assert_eq!(cache.stats().current_entries, 2);
1535
1536        cache.remove(&"k1".to_string());
1537        assert_eq!(cache.stats().current_entries, 1);
1538    }
1539
1540    #[test]
1541    fn test_cache_integer_keys() {
1542        let mut cache: DataCache<u64, String> = DataCache::default();
1543
1544        cache.insert_default(1, "one".to_string());
1545        cache.insert_default(2, "two".to_string());
1546
1547        assert_eq!(cache.get(&1), Some(&"one".to_string()));
1548        assert_eq!(cache.get(&2), Some(&"two".to_string()));
1549    }
1550
1551    #[test]
1552    fn test_cache_with_custom_stale() {
1553        let config = CacheConfig {
1554            default_ttl_ms: 1000,
1555            default_stale_ms: 500,
1556            ..Default::default()
1557        };
1558        let mut cache: DataCache<String, String> = DataCache::new(config);
1559
1560        let options = CacheOptions::new().with_stale(Duration::from_millis(100));
1561        cache.insert("key".to_string(), "value".to_string(), options);
1562
1563        // At ttl+50, should be stale (custom stale is 100)
1564        cache.set_timestamp(1050);
1565        let result = cache.get_with_state(&"key".to_string());
1566        assert!(result.is_some());
1567        let (_, state) = result.unwrap();
1568        assert_eq!(state, CacheState::Stale);
1569
1570        // At ttl+150, should be expired
1571        cache.set_timestamp(1150);
1572        assert!(cache.get_with_state(&"key".to_string()).is_none());
1573    }
1574
1575    // ========== Additional CacheStats tests ==========
1576
1577    #[test]
1578    fn test_cache_stats_hit_rate_all_hits() {
1579        let mut stats = CacheStats::default();
1580        stats.hits = 100;
1581        stats.misses = 0;
1582        assert_eq!(stats.hit_rate(), 1.0);
1583    }
1584
1585    #[test]
1586    fn test_cache_stats_hit_rate_all_misses() {
1587        let mut stats = CacheStats::default();
1588        stats.hits = 0;
1589        stats.misses = 100;
1590        assert_eq!(stats.hit_rate(), 0.0);
1591    }
1592
1593    #[test]
1594    fn test_cache_stats_hit_rate_half() {
1595        let mut stats = CacheStats::default();
1596        stats.hits = 50;
1597        stats.misses = 50;
1598        assert!((stats.hit_rate() - 0.5).abs() < 0.001);
1599    }
1600
1601    #[test]
1602    fn test_cache_stats_debug() {
1603        let stats = CacheStats::default();
1604        let debug = format!("{stats:?}");
1605        assert!(debug.contains("hits"));
1606        assert!(debug.contains("misses"));
1607    }
1608
1609    #[test]
1610    fn test_cache_stats_clone() {
1611        let mut stats = CacheStats::default();
1612        stats.hits = 42;
1613        stats.evictions = 5;
1614        let cloned = stats.clone();
1615        assert_eq!(cloned.hits, 42);
1616        assert_eq!(cloned.evictions, 5);
1617    }
1618
1619    // ========== Additional CacheSize tests ==========
1620
1621    #[test]
1622    fn test_cache_size_unit() {
1623        let unit = ();
1624        assert_eq!(unit.cache_size(), 0);
1625    }
1626
1627    #[test]
1628    fn test_cache_size_i64() {
1629        let n: i64 = 42;
1630        assert_eq!(n.cache_size(), 8);
1631    }
1632
1633    #[test]
1634    fn test_cache_size_f32() {
1635        let n: f32 = 3.14;
1636        assert_eq!(n.cache_size(), 4);
1637    }
1638
1639    #[test]
1640    fn test_cache_size_f64() {
1641        let n: f64 = 3.14159;
1642        assert_eq!(n.cache_size(), 8);
1643    }
1644
1645    #[test]
1646    fn test_cache_size_box() {
1647        let b: Box<i32> = Box::new(42);
1648        assert_eq!(b.cache_size(), 4);
1649    }
1650
1651    #[test]
1652    fn test_cache_size_empty_string() {
1653        let s = String::new();
1654        assert_eq!(s.cache_size(), 0);
1655    }
1656
1657    #[test]
1658    fn test_cache_size_empty_vec() {
1659        let v: Vec<u8> = Vec::new();
1660        assert_eq!(v.cache_size(), 0);
1661    }
1662
1663    #[test]
1664    fn test_cache_size_vec_of_structs() {
1665        #[derive(Clone)]
1666        struct Data {
1667            _a: i32,
1668            _b: i32,
1669        }
1670        let v: Vec<Data> = vec![Data { _a: 1, _b: 2 }, Data { _a: 3, _b: 4 }];
1671        // 2 * size_of::<Data>() = 2 * 8 = 16
1672        assert_eq!(v.cache_size(), 16);
1673    }
1674
1675    // ========== Additional CacheBuilder tests ==========
1676
1677    #[test]
1678    fn test_cache_builder_default_options() {
1679        let (value, options) = CacheBuilder::new(42i32).build();
1680        assert_eq!(value, 42);
1681        assert!(options.ttl.is_none());
1682        assert!(options.tags.is_empty());
1683    }
1684
1685    #[test]
1686    fn test_cache_builder_multiple_tags() {
1687        let (_, options) = CacheBuilder::new("test".to_string())
1688            .tag("a")
1689            .tag("b")
1690            .tag("c")
1691            .build();
1692        assert_eq!(options.tags, vec!["a", "b", "c"]);
1693    }
1694
1695    #[test]
1696    fn test_cache_builder_chaining() {
1697        let (value, options) = CacheBuilder::new(vec![1, 2, 3])
1698            .ttl(Duration::from_secs(120))
1699            .stale(Duration::from_secs(60))
1700            .tag("numbers")
1701            .priority(10)
1702            .build();
1703
1704        assert_eq!(value, vec![1, 2, 3]);
1705        assert_eq!(options.ttl, Some(Duration::from_secs(120)));
1706        assert_eq!(options.stale, Some(Duration::from_secs(60)));
1707        assert_eq!(options.tags, vec!["numbers"]);
1708        assert_eq!(options.priority, 10);
1709    }
1710
1711    #[test]
1712    fn test_cache_builder_with_cache() {
1713        let mut cache: DataCache<String, String> = DataCache::default();
1714        let (value, options) = CacheBuilder::new("cached_value".to_string())
1715            .ttl(Duration::from_secs(300))
1716            .tag("test")
1717            .build();
1718
1719        cache.insert("key".to_string(), value, options);
1720        assert!(cache.contains(&"key".to_string()));
1721    }
1722
1723    // ========== Edge case and stress tests ==========
1724
1725    #[test]
1726    fn test_cache_rapid_insert_remove() {
1727        let mut cache: DataCache<i32, i32> = DataCache::default();
1728
1729        for i in 0..1000 {
1730            cache.insert_default(i, i);
1731            if i % 2 == 0 {
1732                cache.remove(&i);
1733            }
1734        }
1735
1736        assert_eq!(cache.len(), 500);
1737    }
1738
1739    #[test]
1740    fn test_cache_same_key_multiple_times() {
1741        let mut cache: DataCache<String, i32> = DataCache::default();
1742
1743        for i in 0..100 {
1744            cache.insert_default("key".to_string(), i);
1745        }
1746
1747        assert_eq!(cache.len(), 1);
1748        assert_eq!(cache.get(&"key".to_string()), Some(&99));
1749    }
1750
1751    #[test]
1752    fn test_cache_evict_all() {
1753        let config = CacheConfig {
1754            max_entries: 5,
1755            ..Default::default()
1756        };
1757        let mut cache: DataCache<i32, i32> = DataCache::new(config);
1758
1759        // Insert more than max_entries
1760        for i in 0..10 {
1761            cache.insert_default(i, i);
1762        }
1763
1764        assert_eq!(cache.len(), 5);
1765        assert!(cache.stats().evictions >= 5);
1766    }
1767
1768    #[test]
1769    fn test_cache_memory_eviction_large_item() {
1770        let config = CacheConfig {
1771            max_memory: 100,
1772            ..Default::default()
1773        };
1774        let mut cache: DataCache<String, String> = DataCache::new(config);
1775
1776        // Insert item larger than max_memory
1777        cache.insert_default("key".to_string(), "a".repeat(200));
1778
1779        // Should either not insert or evict everything
1780        assert!(cache.memory_usage() <= 200);
1781    }
1782
1783    #[test]
1784    fn test_cache_get_updates_lru_order() {
1785        let config = CacheConfig {
1786            max_entries: 3,
1787            enable_lru: true,
1788            ..Default::default()
1789        };
1790        let mut cache: DataCache<String, i32> = DataCache::new(config);
1791
1792        cache.insert_default("a".to_string(), 1);
1793        cache.insert_default("b".to_string(), 2);
1794        cache.insert_default("c".to_string(), 3);
1795
1796        // Access a, then b
1797        cache.get(&"a".to_string());
1798        cache.get(&"b".to_string());
1799
1800        // Insert d, should evict c (least recently used)
1801        cache.insert_default("d".to_string(), 4);
1802
1803        assert!(cache.contains(&"a".to_string()));
1804        assert!(cache.contains(&"b".to_string()));
1805        assert!(!cache.contains(&"c".to_string()));
1806        assert!(cache.contains(&"d".to_string()));
1807    }
1808
1809    #[test]
1810    fn test_cache_invalidate_multiple_tags_same_entry() {
1811        let mut cache: DataCache<String, String> = DataCache::default();
1812
1813        let options = CacheOptions::new().with_tag("tag1").with_tag("tag2");
1814        cache.insert("key".to_string(), "value".to_string(), options);
1815
1816        // Invalidate by first tag
1817        let count1 = cache.invalidate_tag("tag1");
1818        assert_eq!(count1, 1);
1819
1820        // Second invalidation should find nothing
1821        let count2 = cache.invalidate_tag("tag2");
1822        assert_eq!(count2, 0);
1823    }
1824
1825    #[test]
1826    fn test_cache_tick_zero() {
1827        let mut cache: DataCache<String, String> = DataCache::default();
1828        cache.tick(0);
1829        assert_eq!(cache.timestamp(), 0);
1830    }
1831
1832    #[test]
1833    fn test_cache_tick_large_values() {
1834        let mut cache: DataCache<String, String> = DataCache::default();
1835        cache.set_timestamp(u64::MAX / 2);
1836        cache.tick(1000);
1837        assert_eq!(cache.timestamp(), u64::MAX / 2 + 1000);
1838    }
1839
1840    #[test]
1841    fn test_cache_remove_nonexistent() {
1842        let mut cache: DataCache<String, String> = DataCache::default();
1843        let result = cache.remove(&"nonexistent".to_string());
1844        assert!(result.is_none());
1845    }
1846
1847    #[test]
1848    fn test_string_cache_type_alias() {
1849        let mut cache: StringCache<i32> = StringCache::default();
1850        cache.insert_default("key".to_string(), 42);
1851        assert_eq!(cache.get(&"key".to_string()), Some(&42));
1852    }
1853}