Skip to main content

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