1use std::collections::HashMap;
7use std::hash::Hash;
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10use tokio::sync::RwLock;
11
12#[derive(Debug, Clone)]
14struct CacheEntry<V> {
15 value: V,
16 expires_at: Option<Instant>,
17 access_count: u64,
18 last_accessed: Instant,
19}
20
21impl<V> CacheEntry<V> {
22 fn new(value: V, ttl: Option<Duration>) -> Self {
23 let now = Instant::now();
24 Self {
25 value,
26 expires_at: ttl.map(|duration| now + duration),
27 access_count: 0,
28 last_accessed: now,
29 }
30 }
31
32 fn is_expired(&self) -> bool {
33 self.expires_at.is_some_and(|expires_at| Instant::now() > expires_at)
34 }
35
36 fn access(&mut self) -> &V {
37 self.access_count += 1;
38 self.last_accessed = Instant::now();
39 &self.value
40 }
41}
42
43#[derive(Debug)]
45pub struct Cache<K, V> {
46 storage: Arc<RwLock<HashMap<K, CacheEntry<V>>>>,
47 max_size: usize,
48 default_ttl: Option<Duration>,
49 stats: Arc<RwLock<CacheStats>>,
50}
51
52#[derive(Debug, Default, Clone)]
54pub struct CacheStats {
55 pub hits: u64,
57 pub misses: u64,
59 pub evictions: u64,
61 pub expirations: u64,
63 pub insertions: u64,
65}
66
67impl<K: Hash + Eq + Clone, V: Clone> Cache<K, V> {
68 pub fn new(max_size: usize) -> Self {
70 Self {
71 storage: Arc::new(RwLock::new(HashMap::new())),
72 max_size,
73 default_ttl: None,
74 stats: Arc::new(RwLock::new(CacheStats::default())),
75 }
76 }
77
78 pub fn with_ttl(max_size: usize, default_ttl: Duration) -> Self {
80 Self {
81 storage: Arc::new(RwLock::new(HashMap::new())),
82 max_size,
83 default_ttl: Some(default_ttl),
84 stats: Arc::new(RwLock::new(CacheStats::default())),
85 }
86 }
87
88 pub async fn insert(&self, key: K, value: V, ttl: Option<Duration>) {
90 let mut storage = self.storage.write().await;
91 let mut stats = self.stats.write().await;
92
93 let effective_ttl = ttl.or(self.default_ttl);
95
96 self.cleanup_expired(&mut storage, &mut stats).await;
98
99 if storage.len() >= self.max_size && !storage.contains_key(&key) {
101 self.evict_lru(&mut storage, &mut stats).await;
102 }
103
104 storage.insert(key, CacheEntry::new(value, effective_ttl));
105 stats.insertions += 1;
106 }
107
108 pub async fn get(&self, key: &K) -> Option<V> {
110 let mut storage = self.storage.write().await;
111 let mut stats = self.stats.write().await;
112
113 if let Some(entry) = storage.get_mut(key) {
114 if entry.is_expired() {
115 storage.remove(key);
116 stats.expirations += 1;
117 stats.misses += 1;
118 return None;
119 }
120
121 stats.hits += 1;
122 Some(entry.access().clone())
123 } else {
124 stats.misses += 1;
125 None
126 }
127 }
128
129 pub async fn contains_key(&self, key: &K) -> bool {
131 let storage = self.storage.read().await;
132 if let Some(entry) = storage.get(key) {
133 !entry.is_expired()
134 } else {
135 false
136 }
137 }
138
139 pub async fn remove(&self, key: &K) -> Option<V> {
141 let mut storage = self.storage.write().await;
142 storage.remove(key).map(|entry| entry.value)
143 }
144
145 pub async fn clear(&self) {
147 let mut storage = self.storage.write().await;
148 storage.clear();
149 }
150
151 pub async fn len(&self) -> usize {
153 let storage = self.storage.read().await;
154 storage.len()
155 }
156
157 pub async fn is_empty(&self) -> bool {
159 let storage = self.storage.read().await;
160 storage.is_empty()
161 }
162
163 pub async fn stats(&self) -> CacheStats {
165 let stats = self.stats.read().await;
166 stats.clone()
167 }
168
169 pub async fn reset_stats(&self) {
171 let mut stats = self.stats.write().await;
172 *stats = CacheStats::default();
173 }
174
175 pub async fn get_or_insert<F, Fut>(&self, key: K, f: F) -> V
177 where
178 F: FnOnce() -> Fut,
179 Fut: std::future::Future<Output = V>,
180 {
181 if let Some(value) = self.get(&key).await {
182 return value;
183 }
184
185 let value = f().await;
186 self.insert(key, value.clone(), None).await;
187 value
188 }
189
190 pub async fn get_or_insert_with_ttl<F, Fut>(&self, key: K, f: F, ttl: Duration) -> V
192 where
193 F: FnOnce() -> Fut,
194 Fut: std::future::Future<Output = V>,
195 {
196 if let Some(value) = self.get(&key).await {
197 return value;
198 }
199
200 let value = f().await;
201 self.insert(key, value.clone(), Some(ttl)).await;
202 value
203 }
204
205 async fn cleanup_expired(
207 &self,
208 storage: &mut HashMap<K, CacheEntry<V>>,
209 stats: &mut CacheStats,
210 ) {
211 let expired_keys: Vec<K> = storage
212 .iter()
213 .filter_map(|(k, v)| {
214 if v.is_expired() {
215 Some(k.clone())
216 } else {
217 None
218 }
219 })
220 .collect();
221
222 for key in expired_keys {
223 storage.remove(&key);
224 stats.expirations += 1;
225 }
226 }
227
228 async fn evict_lru(&self, storage: &mut HashMap<K, CacheEntry<V>>, stats: &mut CacheStats) {
230 if let Some((lru_key, _)) = storage
231 .iter()
232 .min_by_key(|(_, entry)| entry.last_accessed)
233 .map(|(k, v)| (k.clone(), v.clone()))
234 {
235 storage.remove(&lru_key);
236 stats.evictions += 1;
237 }
238 }
239}
240
241#[derive(Debug)]
243pub struct ResponseCache {
244 cache: Cache<String, CachedResponse>,
245}
246
247#[derive(Debug, Clone)]
249pub struct CachedResponse {
250 pub status_code: u16,
252 pub headers: HashMap<String, String>,
254 pub body: String,
256 pub content_type: Option<String>,
258}
259
260impl ResponseCache {
261 pub fn new(max_size: usize, ttl: Duration) -> Self {
263 Self {
264 cache: Cache::with_ttl(max_size, ttl),
265 }
266 }
267
268 pub fn generate_key(
270 method: &str,
271 path: &str,
272 query: &str,
273 headers: &HashMap<String, String>,
274 ) -> String {
275 use std::collections::hash_map::DefaultHasher;
276 use std::hash::Hasher;
277
278 let mut hasher = DefaultHasher::new();
279 hasher.write(method.as_bytes());
280 hasher.write(path.as_bytes());
281 hasher.write(query.as_bytes());
282
283 let mut sorted_headers: Vec<_> = headers.iter().collect();
285 sorted_headers.sort_by_key(|(k, _)| *k);
286 for (key, value) in sorted_headers {
287 if key.to_lowercase() != "authorization" && !key.to_lowercase().starts_with("x-") {
288 hasher.write(key.as_bytes());
289 hasher.write(value.as_bytes());
290 }
291 }
292
293 format!("resp_{}_{}", hasher.finish(), path.len())
294 }
295
296 pub async fn cache_response(&self, key: String, response: CachedResponse) {
298 self.cache.insert(key, response, None).await;
299 }
300
301 pub async fn get_response(&self, key: &str) -> Option<CachedResponse> {
303 self.cache.get(&key.to_string()).await
304 }
305
306 pub async fn clear(&self) {
308 self.cache.clear().await;
309 }
310
311 pub async fn stats(&self) -> CacheStats {
313 self.cache.stats().await
314 }
315}
316
317#[derive(Debug)]
319pub struct TemplateCache {
320 cache: Cache<String, CompiledTemplate>,
321}
322
323#[derive(Debug, Clone)]
325pub struct CompiledTemplate {
326 pub template: String,
328 pub variables: Vec<String>,
330 pub compiled_at: Instant,
332}
333
334impl TemplateCache {
335 pub fn new(max_size: usize) -> Self {
337 Self {
338 cache: Cache::new(max_size),
339 }
340 }
341
342 pub async fn cache_template(&self, key: String, template: String, variables: Vec<String>) {
344 let compiled = CompiledTemplate {
345 template,
346 variables,
347 compiled_at: Instant::now(),
348 };
349 self.cache.insert(key, compiled, None).await;
350 }
351
352 pub async fn get_template(&self, key: &str) -> Option<CompiledTemplate> {
354 self.cache.get(&key.to_string()).await
355 }
356
357 pub async fn clear(&self) {
359 self.cache.clear().await;
360 }
361
362 pub async fn stats(&self) -> CacheStats {
364 self.cache.stats().await
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371 use tokio::time::sleep;
372
373 #[tokio::test]
376 async fn test_basic_cache_operations() {
377 let cache = Cache::new(3);
378
379 cache.insert("key1".to_string(), "value1".to_string(), None).await;
380 cache.insert("key2".to_string(), "value2".to_string(), None).await;
381
382 assert_eq!(cache.get(&"key1".to_string()).await, Some("value1".to_string()));
383 assert_eq!(cache.get(&"key2".to_string()).await, Some("value2".to_string()));
384 assert_eq!(cache.get(&"key3".to_string()).await, None);
385
386 assert_eq!(cache.len().await, 2);
387 assert!(!cache.is_empty().await);
388 }
389
390 #[tokio::test]
391 async fn test_cache_new() {
392 let cache: Cache<String, String> = Cache::new(100);
393 assert!(cache.is_empty().await);
394 assert_eq!(cache.len().await, 0);
395 }
396
397 #[tokio::test]
398 async fn test_cache_with_ttl() {
399 let cache: Cache<String, String> = Cache::with_ttl(100, Duration::from_secs(60));
400 assert!(cache.is_empty().await);
401 }
402
403 #[tokio::test]
404 async fn test_cache_contains_key() {
405 let cache = Cache::new(10);
406 cache.insert("key1".to_string(), "value1".to_string(), None).await;
407
408 assert!(cache.contains_key(&"key1".to_string()).await);
409 assert!(!cache.contains_key(&"key2".to_string()).await);
410 }
411
412 #[tokio::test]
413 async fn test_cache_remove() {
414 let cache = Cache::new(10);
415 cache.insert("key1".to_string(), "value1".to_string(), None).await;
416
417 let removed = cache.remove(&"key1".to_string()).await;
418 assert_eq!(removed, Some("value1".to_string()));
419 assert!(!cache.contains_key(&"key1".to_string()).await);
420
421 let removed2 = cache.remove(&"key2".to_string()).await;
423 assert_eq!(removed2, None);
424 }
425
426 #[tokio::test]
427 async fn test_cache_clear() {
428 let cache = Cache::new(10);
429 cache.insert("key1".to_string(), "value1".to_string(), None).await;
430 cache.insert("key2".to_string(), "value2".to_string(), None).await;
431
432 assert_eq!(cache.len().await, 2);
433 cache.clear().await;
434 assert_eq!(cache.len().await, 0);
435 assert!(cache.is_empty().await);
436 }
437
438 #[tokio::test]
439 async fn test_cache_overwrite() {
440 let cache = Cache::new(10);
441 cache.insert("key1".to_string(), "value1".to_string(), None).await;
442 cache.insert("key1".to_string(), "value2".to_string(), None).await;
443
444 assert_eq!(cache.get(&"key1".to_string()).await, Some("value2".to_string()));
445 assert_eq!(cache.len().await, 1);
446 }
447
448 #[tokio::test]
451 async fn test_ttl_expiration() {
452 let cache = Cache::with_ttl(10, Duration::from_millis(200));
453
454 cache.insert("key1".to_string(), "value1".to_string(), None).await;
455 assert_eq!(cache.get(&"key1".to_string()).await, Some("value1".to_string()));
456
457 sleep(Duration::from_millis(300)).await;
458 assert_eq!(cache.get(&"key1".to_string()).await, None);
459 }
460
461 #[tokio::test]
462 async fn test_custom_ttl_per_entry() {
463 let cache = Cache::new(10);
464
465 cache
467 .insert(
468 "short".to_string(),
469 "short_lived".to_string(),
470 Some(Duration::from_millis(200)),
471 )
472 .await;
473 cache
474 .insert("long".to_string(), "long_lived".to_string(), Some(Duration::from_secs(60)))
475 .await;
476
477 assert_eq!(cache.get(&"short".to_string()).await, Some("short_lived".to_string()));
478 assert_eq!(cache.get(&"long".to_string()).await, Some("long_lived".to_string()));
479
480 sleep(Duration::from_millis(300)).await;
482
483 assert_eq!(cache.get(&"short".to_string()).await, None);
484 assert_eq!(cache.get(&"long".to_string()).await, Some("long_lived".to_string()));
485 }
486
487 #[tokio::test]
488 async fn test_contains_key_respects_ttl() {
489 let cache = Cache::with_ttl(10, Duration::from_millis(200));
490 cache.insert("key".to_string(), "value".to_string(), None).await;
491
492 assert!(cache.contains_key(&"key".to_string()).await);
493
494 sleep(Duration::from_millis(300)).await;
495
496 assert!(!cache.contains_key(&"key".to_string()).await);
497 }
498
499 #[tokio::test]
502 async fn test_lru_eviction() {
503 let cache = Cache::new(2);
504
505 cache.insert("key1".to_string(), "value1".to_string(), None).await;
506 cache.insert("key2".to_string(), "value2".to_string(), None).await;
507
508 cache.get(&"key1".to_string()).await;
510
511 cache.insert("key3".to_string(), "value3".to_string(), None).await;
513
514 assert_eq!(cache.get(&"key1".to_string()).await, Some("value1".to_string()));
515 assert_eq!(cache.get(&"key2".to_string()).await, None);
516 assert_eq!(cache.get(&"key3".to_string()).await, Some("value3".to_string()));
517 }
518
519 #[tokio::test]
520 async fn test_eviction_stats() {
521 let cache = Cache::new(2);
522
523 cache.insert("key1".to_string(), "value1".to_string(), None).await;
524 cache.insert("key2".to_string(), "value2".to_string(), None).await;
525 cache.insert("key3".to_string(), "value3".to_string(), None).await;
526
527 let stats = cache.stats().await;
528 assert_eq!(stats.evictions, 1);
529 }
530
531 #[tokio::test]
532 async fn test_no_eviction_when_replacing() {
533 let cache = Cache::new(2);
534
535 cache.insert("key1".to_string(), "value1".to_string(), None).await;
536 cache.insert("key2".to_string(), "value2".to_string(), None).await;
537 cache.insert("key1".to_string(), "updated".to_string(), None).await;
539
540 let stats = cache.stats().await;
541 assert_eq!(stats.evictions, 0);
542 assert_eq!(cache.len().await, 2);
543 }
544
545 #[tokio::test]
548 async fn test_cache_stats() {
549 let cache = Cache::new(10);
550
551 cache.insert("key1".to_string(), "value1".to_string(), None).await;
552 cache.get(&"key1".to_string()).await; cache.get(&"key2".to_string()).await; let stats = cache.stats().await;
556 assert_eq!(stats.hits, 1);
557 assert_eq!(stats.misses, 1);
558 assert_eq!(stats.insertions, 1);
559 }
560
561 #[tokio::test]
562 async fn test_reset_stats() {
563 let cache = Cache::new(10);
564
565 cache.insert("key1".to_string(), "value1".to_string(), None).await;
566 cache.get(&"key1".to_string()).await;
567 cache.get(&"key2".to_string()).await;
568
569 let stats = cache.stats().await;
570 assert_eq!(stats.hits, 1);
571 assert_eq!(stats.misses, 1);
572
573 cache.reset_stats().await;
574
575 let stats_after = cache.stats().await;
576 assert_eq!(stats_after.hits, 0);
577 assert_eq!(stats_after.misses, 0);
578 assert_eq!(stats_after.insertions, 0);
579 }
580
581 #[tokio::test]
582 async fn test_expiration_stats() {
583 let cache = Cache::with_ttl(10, Duration::from_millis(20));
584
585 cache.insert("key".to_string(), "value".to_string(), None).await;
586 sleep(Duration::from_millis(30)).await;
587 cache.get(&"key".to_string()).await; let stats = cache.stats().await;
590 assert_eq!(stats.expirations, 1);
591 }
592
593 #[tokio::test]
596 async fn test_get_or_insert_miss() {
597 let cache = Cache::new(10);
598
599 let value = cache
600 .get_or_insert("key".to_string(), || async { "computed_value".to_string() })
601 .await;
602
603 assert_eq!(value, "computed_value".to_string());
604 assert_eq!(cache.get(&"key".to_string()).await, Some("computed_value".to_string()));
605 }
606
607 #[tokio::test]
608 async fn test_get_or_insert_hit() {
609 let cache = Cache::new(10);
610 cache.insert("key".to_string(), "existing_value".to_string(), None).await;
611
612 let value = cache
613 .get_or_insert("key".to_string(), || async { "should_not_be_used".to_string() })
614 .await;
615
616 assert_eq!(value, "existing_value".to_string());
617 }
618
619 #[tokio::test]
620 async fn test_get_or_insert_with_ttl() {
621 let cache = Cache::new(10);
622
623 let value = cache
624 .get_or_insert_with_ttl(
625 "key".to_string(),
626 || async { "computed".to_string() },
627 Duration::from_millis(30),
628 )
629 .await;
630
631 assert_eq!(value, "computed".to_string());
632
633 assert!(cache.contains_key(&"key".to_string()).await);
635
636 sleep(Duration::from_millis(50)).await;
638
639 assert!(!cache.contains_key(&"key".to_string()).await);
640 }
641
642 #[tokio::test]
645 async fn test_response_cache() {
646 let response_cache = ResponseCache::new(100, Duration::from_secs(300));
647
648 let headers = HashMap::new();
649 let key = ResponseCache::generate_key("GET", "/api/users", "", &headers);
650
651 let response = CachedResponse {
652 status_code: 200,
653 headers: HashMap::new(),
654 body: "test response".to_string(),
655 content_type: Some("application/json".to_string()),
656 };
657
658 response_cache.cache_response(key.clone(), response.clone()).await;
659 let cached = response_cache.get_response(&key).await;
660
661 assert!(cached.is_some());
662 assert_eq!(cached.unwrap().body, "test response");
663 }
664
665 #[tokio::test]
666 async fn test_response_cache_key_generation() {
667 let headers1 = HashMap::new();
668 let headers2 = HashMap::new();
669
670 let key1 = ResponseCache::generate_key("GET", "/api/users", "page=1", &headers1);
672 let key2 = ResponseCache::generate_key("GET", "/api/users", "page=1", &headers2);
673 assert_eq!(key1, key2);
674
675 let key3 = ResponseCache::generate_key("POST", "/api/users", "page=1", &headers1);
677 assert_ne!(key1, key3);
678
679 let key4 = ResponseCache::generate_key("GET", "/api/items", "page=1", &headers1);
681 assert_ne!(key1, key4);
682
683 let key5 = ResponseCache::generate_key("GET", "/api/users", "page=2", &headers1);
685 assert_ne!(key1, key5);
686 }
687
688 #[tokio::test]
689 async fn test_response_cache_key_excludes_auth_headers() {
690 let mut headers_without_auth = HashMap::new();
691 headers_without_auth.insert("accept".to_string(), "application/json".to_string());
692
693 let mut headers_with_auth = headers_without_auth.clone();
694 headers_with_auth.insert("authorization".to_string(), "Bearer token123".to_string());
695
696 let key1 = ResponseCache::generate_key("GET", "/api/users", "", &headers_without_auth);
698 let key2 = ResponseCache::generate_key("GET", "/api/users", "", &headers_with_auth);
699
700 assert_eq!(key1, key2);
701 }
702
703 #[tokio::test]
704 async fn test_response_cache_key_excludes_x_headers() {
705 let mut headers1 = HashMap::new();
706 headers1.insert("accept".to_string(), "application/json".to_string());
707
708 let mut headers2 = headers1.clone();
709 headers2.insert("x-request-id".to_string(), "unique-id-123".to_string());
710 headers2.insert("x-correlation-id".to_string(), "corr-456".to_string());
711
712 let key1 = ResponseCache::generate_key("GET", "/api/users", "", &headers1);
713 let key2 = ResponseCache::generate_key("GET", "/api/users", "", &headers2);
714
715 assert_eq!(key1, key2);
716 }
717
718 #[tokio::test]
719 async fn test_response_cache_stats() {
720 let response_cache = ResponseCache::new(10, Duration::from_secs(60));
721
722 let response = CachedResponse {
723 status_code: 200,
724 headers: HashMap::new(),
725 body: "test".to_string(),
726 content_type: None,
727 };
728
729 response_cache.cache_response("key1".to_string(), response).await;
730 response_cache.get_response("key1").await; response_cache.get_response("key2").await; let stats = response_cache.stats().await;
734 assert_eq!(stats.hits, 1);
735 assert_eq!(stats.misses, 1);
736 }
737
738 #[tokio::test]
741 async fn test_template_cache_new() {
742 let template_cache = TemplateCache::new(100);
743 assert_eq!(template_cache.stats().await.insertions, 0);
744 }
745
746 #[tokio::test]
747 async fn test_template_cache_operations() {
748 let template_cache = TemplateCache::new(100);
749
750 template_cache
751 .cache_template(
752 "greeting".to_string(),
753 "Hello, {{name}}!".to_string(),
754 vec!["name".to_string()],
755 )
756 .await;
757
758 let cached = template_cache.get_template("greeting").await;
759 assert!(cached.is_some());
760
761 let template = cached.unwrap();
762 assert_eq!(template.template, "Hello, {{name}}!");
763 assert_eq!(template.variables, vec!["name".to_string()]);
764 }
765
766 #[tokio::test]
767 async fn test_template_cache_miss() {
768 let template_cache = TemplateCache::new(100);
769
770 let cached = template_cache.get_template("nonexistent").await;
771 assert!(cached.is_none());
772 }
773
774 #[tokio::test]
775 async fn test_template_cache_stats() {
776 let template_cache = TemplateCache::new(10);
777
778 template_cache
779 .cache_template("key".to_string(), "template".to_string(), vec![])
780 .await;
781
782 template_cache.get_template("key").await; template_cache.get_template("missing").await; let stats = template_cache.stats().await;
786 assert_eq!(stats.hits, 1);
787 assert_eq!(stats.misses, 1);
788 assert_eq!(stats.insertions, 1);
789 }
790
791 #[test]
794 fn test_cache_stats_default() {
795 let stats = CacheStats::default();
796 assert_eq!(stats.hits, 0);
797 assert_eq!(stats.misses, 0);
798 assert_eq!(stats.evictions, 0);
799 assert_eq!(stats.expirations, 0);
800 assert_eq!(stats.insertions, 0);
801 }
802
803 #[test]
804 fn test_cache_stats_clone() {
805 let stats = CacheStats {
806 hits: 10,
807 misses: 5,
808 ..Default::default()
809 };
810
811 let cloned = stats.clone();
812 assert_eq!(cloned.hits, 10);
813 assert_eq!(cloned.misses, 5);
814 }
815
816 #[test]
817 fn test_cache_stats_debug() {
818 let stats = CacheStats::default();
819 let debug_str = format!("{:?}", stats);
820 assert!(debug_str.contains("CacheStats"));
821 assert!(debug_str.contains("hits"));
822 }
823
824 #[test]
827 fn test_cached_response_clone() {
828 let response = CachedResponse {
829 status_code: 200,
830 headers: HashMap::new(),
831 body: "test".to_string(),
832 content_type: Some("application/json".to_string()),
833 };
834
835 let cloned = response.clone();
836 assert_eq!(cloned.status_code, 200);
837 assert_eq!(cloned.body, "test");
838 assert_eq!(cloned.content_type, Some("application/json".to_string()));
839 }
840
841 #[test]
842 fn test_cached_response_debug() {
843 let response = CachedResponse {
844 status_code: 404,
845 headers: HashMap::new(),
846 body: "not found".to_string(),
847 content_type: None,
848 };
849
850 let debug_str = format!("{:?}", response);
851 assert!(debug_str.contains("CachedResponse"));
852 assert!(debug_str.contains("404"));
853 }
854
855 #[test]
858 fn test_compiled_template_clone() {
859 let template = CompiledTemplate {
860 template: "Hello, {{name}}!".to_string(),
861 variables: vec!["name".to_string()],
862 compiled_at: Instant::now(),
863 };
864
865 let cloned = template.clone();
866 assert_eq!(cloned.template, "Hello, {{name}}!");
867 assert_eq!(cloned.variables, vec!["name".to_string()]);
868 }
869
870 #[test]
871 fn test_compiled_template_debug() {
872 let template = CompiledTemplate {
873 template: "test".to_string(),
874 variables: vec![],
875 compiled_at: Instant::now(),
876 };
877
878 let debug_str = format!("{:?}", template);
879 assert!(debug_str.contains("CompiledTemplate"));
880 assert!(debug_str.contains("test"));
881 }
882
883 #[tokio::test]
886 async fn test_cache_with_zero_size() {
887 let cache = Cache::new(0);
889 cache.insert("key".to_string(), "value".to_string(), None).await;
890 }
892
893 #[tokio::test]
894 async fn test_cache_with_numeric_keys() {
895 let cache = Cache::new(10);
896 cache.insert(1, "one".to_string(), None).await;
897 cache.insert(2, "two".to_string(), None).await;
898
899 assert_eq!(cache.get(&1).await, Some("one".to_string()));
900 assert_eq!(cache.get(&2).await, Some("two".to_string()));
901 }
902
903 #[tokio::test]
904 async fn test_cache_with_complex_values() {
905 let cache: Cache<String, Vec<u8>> = Cache::new(10);
906 cache.insert("bytes".to_string(), vec![1, 2, 3, 4, 5], None).await;
907
908 let retrieved = cache.get(&"bytes".to_string()).await;
909 assert_eq!(retrieved, Some(vec![1, 2, 3, 4, 5]));
910 }
911
912 #[tokio::test]
913 async fn test_multiple_expirations_cleanup() {
914 let cache = Cache::with_ttl(10, Duration::from_millis(20));
915
916 cache.insert("key1".to_string(), "v1".to_string(), None).await;
917 cache.insert("key2".to_string(), "v2".to_string(), None).await;
918 cache.insert("key3".to_string(), "v3".to_string(), None).await;
919
920 sleep(Duration::from_millis(30)).await;
921
922 cache.insert("new".to_string(), "new_val".to_string(), None).await;
924
925 let stats = cache.stats().await;
926 assert!(stats.expirations >= 3);
927 }
928}