chie_core/content/
mod.rs

1//! Content management for CHIE Protocol.
2//!
3//! This module provides content metadata management with LRU caching,
4//! search capabilities, and statistics tracking.
5
6use crate::utils::LruCache;
7use chie_shared::{ContentCategory, ContentMetadata};
8use std::path::PathBuf;
9
10/// Content storage manager with LRU-cached metadata.
11#[derive(Debug)]
12pub struct ContentManager {
13    /// LRU-cached content metadata.
14    metadata_cache: LruCache<String, ContentMetadata>,
15
16    /// Local storage path.
17    storage_path: PathBuf,
18
19    /// Statistics.
20    stats: ContentManagerStats,
21}
22
23/// Statistics for content manager operations.
24#[derive(Debug, Default, Clone)]
25pub struct ContentManagerStats {
26    /// Total cache hits.
27    pub cache_hits: u64,
28
29    /// Total cache misses.
30    pub cache_misses: u64,
31
32    /// Total metadata cached.
33    pub total_cached: u64,
34
35    /// Total searches performed.
36    pub total_searches: u64,
37}
38
39impl ContentManagerStats {
40    /// Calculate cache hit rate.
41    #[inline]
42    #[must_use]
43    pub fn hit_rate(&self) -> f64 {
44        let total = self.cache_hits + self.cache_misses;
45        if total == 0 {
46            0.0
47        } else {
48            self.cache_hits as f64 / total as f64
49        }
50    }
51}
52
53impl Default for ContentManager {
54    fn default() -> Self {
55        Self::new(PathBuf::from("."))
56    }
57}
58
59impl ContentManager {
60    /// Create a new content manager with default cache capacity (1000 entries).
61    #[must_use]
62    pub fn new(storage_path: PathBuf) -> Self {
63        Self::with_capacity(storage_path, 1000)
64    }
65
66    /// Create a new content manager with specified cache capacity.
67    #[must_use]
68    pub fn with_capacity(storage_path: PathBuf, capacity: usize) -> Self {
69        Self {
70            metadata_cache: LruCache::new(capacity),
71            storage_path,
72            stats: ContentManagerStats::default(),
73        }
74    }
75
76    /// Get the storage path.
77    #[inline]
78    pub fn storage_path(&self) -> &std::path::Path {
79        &self.storage_path
80    }
81
82    /// Cache content metadata.
83    #[inline]
84    pub fn cache_metadata(&mut self, cid: String, metadata: ContentMetadata) {
85        self.metadata_cache.put(cid, metadata);
86        self.stats.total_cached += 1;
87    }
88
89    /// Get cached metadata.
90    #[inline]
91    pub fn get_metadata(&mut self, cid: &str) -> Option<&ContentMetadata> {
92        let result = self.metadata_cache.get(&cid.to_string());
93        if result.is_some() {
94            self.stats.cache_hits += 1;
95        } else {
96            self.stats.cache_misses += 1;
97        }
98        result
99    }
100
101    /// Get cached metadata without updating statistics (immutable).
102    #[inline]
103    pub fn peek_metadata(&self, cid: &str) -> Option<&ContentMetadata> {
104        self.metadata_cache.peek(&cid.to_string())
105    }
106
107    /// Remove metadata from cache.
108    #[inline]
109    pub fn remove_metadata(&mut self, cid: &str) -> Option<ContentMetadata> {
110        self.metadata_cache.remove(&cid.to_string())
111    }
112
113    /// Clear all cached metadata.
114    #[inline]
115    pub fn clear_cache(&mut self) {
116        self.metadata_cache.clear();
117    }
118
119    /// Get the number of cached items.
120    #[inline]
121    pub fn cached_count(&self) -> usize {
122        self.metadata_cache.len()
123    }
124
125    /// Calculate total storage used by all cached content.
126    #[inline]
127    pub fn total_storage_used(&self) -> u64 {
128        self.metadata_cache.iter().map(|(_, m)| m.size_bytes).sum()
129    }
130
131    /// Get statistics.
132    #[inline]
133    pub fn stats(&self) -> &ContentManagerStats {
134        &self.stats
135    }
136
137    /// Reset statistics.
138    #[inline]
139    pub fn reset_stats(&mut self) {
140        self.stats = ContentManagerStats::default();
141    }
142
143    /// Search content by category.
144    #[inline]
145    pub fn search_by_category(&mut self, category: ContentCategory) -> Vec<&ContentMetadata> {
146        self.stats.total_searches += 1;
147        self.metadata_cache
148            .iter()
149            .filter(|(_, metadata)| metadata.category == category)
150            .map(|(_, metadata)| metadata)
151            .collect()
152    }
153
154    /// Search content by tag.
155    #[inline]
156    pub fn search_by_tag(&mut self, tag: &str) -> Vec<&ContentMetadata> {
157        self.stats.total_searches += 1;
158        self.metadata_cache
159            .iter()
160            .filter(|(_, metadata)| metadata.tags.iter().any(|t| t == tag))
161            .map(|(_, metadata)| metadata)
162            .collect()
163    }
164
165    /// Search content by text (title or description).
166    #[inline]
167    pub fn search_by_text(&mut self, query: &str) -> Vec<&ContentMetadata> {
168        self.stats.total_searches += 1;
169        let query_lower = query.to_lowercase();
170        self.metadata_cache
171            .iter()
172            .filter(|(_, metadata)| {
173                metadata.title.to_lowercase().contains(&query_lower)
174                    || metadata.description.to_lowercase().contains(&query_lower)
175            })
176            .map(|(_, metadata)| metadata)
177            .collect()
178    }
179
180    /// Get all cached content sorted by size (descending).
181    pub fn get_largest_content(&self, limit: usize) -> Vec<&ContentMetadata> {
182        let mut content: Vec<_> = self.metadata_cache.iter().map(|(_, m)| m).collect();
183        content.sort_by(|a, b| b.size_bytes.cmp(&a.size_bytes));
184        content.into_iter().take(limit).collect()
185    }
186
187    /// Get all cached content sorted by creation time (newest first).
188    pub fn get_newest_content(&self, limit: usize) -> Vec<&ContentMetadata> {
189        let mut content: Vec<_> = self.metadata_cache.iter().map(|(_, m)| m).collect();
190        content.sort_by(|a, b| b.created_at.cmp(&a.created_at));
191        content.into_iter().take(limit).collect()
192    }
193
194    /// Check if content exists in cache.
195    #[inline]
196    pub fn has_metadata(&self, cid: &str) -> bool {
197        self.metadata_cache.peek(&cid.to_string()).is_some()
198    }
199
200    /// Get multiple content metadata by CIDs.
201    ///
202    /// Note: This method updates access statistics for each lookup.
203    pub fn get_multiple(&mut self, cids: &[String]) -> Vec<String> {
204        cids.iter()
205            .filter(|cid| self.get_metadata(cid).is_some())
206            .cloned()
207            .collect()
208    }
209
210    /// Batch cache multiple metadata entries.
211    ///
212    /// More efficient than calling cache_metadata repeatedly.
213    pub fn cache_batch(&mut self, items: Vec<(String, ContentMetadata)>) {
214        let count = items.len() as u64;
215        for (cid, metadata) in items {
216            self.metadata_cache.put(cid, metadata);
217        }
218        self.stats.total_cached += count;
219    }
220
221    /// Batch remove multiple metadata entries.
222    ///
223    /// Returns the number of items actually removed.
224    pub fn remove_batch(&mut self, cids: &[String]) -> usize {
225        cids.iter()
226            .filter_map(|cid| self.metadata_cache.remove(&cid.to_string()))
227            .count()
228    }
229
230    /// Search with multiple filters combined.
231    ///
232    /// Returns content that matches ALL specified criteria.
233    pub fn search_filtered(
234        &mut self,
235        category: Option<ContentCategory>,
236        tag: Option<&str>,
237        min_size: Option<u64>,
238        max_size: Option<u64>,
239    ) -> Vec<&ContentMetadata> {
240        self.stats.total_searches += 1;
241        self.metadata_cache
242            .iter()
243            .filter(|(_, metadata)| {
244                // Category filter
245                if let Some(cat) = category {
246                    if metadata.category != cat {
247                        return false;
248                    }
249                }
250
251                // Tag filter
252                if let Some(t) = tag {
253                    if !metadata.tags.iter().any(|tag| tag == t) {
254                        return false;
255                    }
256                }
257
258                // Size filters
259                if let Some(min) = min_size {
260                    if metadata.size_bytes < min {
261                        return false;
262                    }
263                }
264                if let Some(max) = max_size {
265                    if metadata.size_bytes > max {
266                        return false;
267                    }
268                }
269
270                true
271            })
272            .map(|(_, metadata)| metadata)
273            .collect()
274    }
275
276    /// Get content by price range.
277    pub fn search_by_price_range(
278        &mut self,
279        min_price: u64,
280        max_price: u64,
281    ) -> Vec<&ContentMetadata> {
282        self.stats.total_searches += 1;
283        self.metadata_cache
284            .iter()
285            .filter(|(_, metadata)| metadata.price >= min_price && metadata.price <= max_price)
286            .map(|(_, metadata)| metadata)
287            .collect()
288    }
289
290    /// Get content by size range.
291    pub fn search_by_size_range(
292        &mut self,
293        min_bytes: u64,
294        max_bytes: u64,
295    ) -> Vec<&ContentMetadata> {
296        self.stats.total_searches += 1;
297        self.metadata_cache
298            .iter()
299            .filter(|(_, metadata)| {
300                metadata.size_bytes >= min_bytes && metadata.size_bytes <= max_bytes
301            })
302            .map(|(_, metadata)| metadata)
303            .collect()
304    }
305
306    /// Get all content CIDs currently cached.
307    #[inline]
308    pub fn get_all_cids(&self) -> Vec<String> {
309        self.metadata_cache
310            .iter()
311            .map(|(cid, _)| cid.clone())
312            .collect()
313    }
314
315    /// Get count of content by category.
316    pub fn count_by_category(&self, category: ContentCategory) -> usize {
317        self.metadata_cache
318            .iter()
319            .filter(|(_, metadata)| metadata.category == category)
320            .count()
321    }
322
323    /// Get total size of all cached content.
324    #[inline]
325    pub fn total_content_size(&self) -> u64 {
326        self.total_storage_used()
327    }
328
329    /// Get average content size.
330    pub fn average_content_size(&self) -> u64 {
331        let count = self.cached_count();
332        if count == 0 {
333            0
334        } else {
335            self.total_storage_used() / count as u64
336        }
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use chie_shared::{ContentCategory, ContentStatus};
344    use std::path::PathBuf;
345
346    fn create_test_metadata(cid: &str, size_bytes: u64, chunk_count: u64) -> ContentMetadata {
347        ContentMetadata {
348            id: uuid::Uuid::new_v4(),
349            cid: cid.to_string(),
350            title: format!("Test Content {}", cid),
351            description: "Test description".to_string(),
352            category: ContentCategory::ThreeDModels,
353            tags: vec!["test".to_string()],
354            size_bytes,
355            chunk_count,
356            price: 100,
357            creator_id: uuid::Uuid::new_v4(),
358            status: ContentStatus::Active,
359            preview_images: vec![],
360            created_at: chrono::Utc::now(),
361            updated_at: chrono::Utc::now(),
362        }
363    }
364
365    #[test]
366    fn test_content_manager_new() {
367        let path = PathBuf::from("/tmp/chie-test");
368        let manager = ContentManager::new(path.clone());
369
370        assert_eq!(manager.storage_path(), path.as_path());
371        assert_eq!(manager.total_storage_used(), 0);
372    }
373
374    #[test]
375    fn test_cache_and_get_metadata() {
376        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
377
378        let metadata = create_test_metadata("QmTest123", 1024, 1);
379
380        manager.cache_metadata("QmTest123".to_string(), metadata.clone());
381
382        let retrieved = manager.get_metadata("QmTest123");
383        assert!(retrieved.is_some());
384        assert_eq!(retrieved.unwrap().cid, "QmTest123");
385        assert_eq!(retrieved.unwrap().size_bytes, 1024);
386        assert_eq!(manager.stats().cache_hits, 1);
387        assert_eq!(manager.stats().cache_misses, 0);
388    }
389
390    #[test]
391    fn test_get_nonexistent_metadata() {
392        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
393
394        let result = manager.get_metadata("QmNonexistent");
395        assert!(result.is_none());
396        assert_eq!(manager.stats().cache_misses, 1);
397    }
398
399    #[test]
400    fn test_total_storage_used() {
401        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
402
403        let metadata1 = create_test_metadata("QmTest1", 1024, 1);
404        let metadata2 = create_test_metadata("QmTest2", 2048, 2);
405
406        manager.cache_metadata("QmTest1".to_string(), metadata1);
407        manager.cache_metadata("QmTest2".to_string(), metadata2);
408
409        assert_eq!(manager.total_storage_used(), 1024 + 2048);
410    }
411
412    #[test]
413    fn test_content_manager_default() {
414        let mut manager = ContentManager::default();
415
416        assert_eq!(manager.total_storage_used(), 0);
417        assert!(manager.get_metadata("any").is_none());
418    }
419
420    #[test]
421    fn test_lru_cache_eviction() {
422        let mut manager = ContentManager::with_capacity(PathBuf::from("/tmp/chie-test"), 2);
423
424        let metadata1 = create_test_metadata("QmTest1", 1024, 1);
425        let metadata2 = create_test_metadata("QmTest2", 2048, 2);
426        let metadata3 = create_test_metadata("QmTest3", 3072, 3);
427
428        manager.cache_metadata("QmTest1".to_string(), metadata1);
429        manager.cache_metadata("QmTest2".to_string(), metadata2);
430        manager.cache_metadata("QmTest3".to_string(), metadata3);
431
432        // QmTest1 should be evicted
433        assert_eq!(manager.cached_count(), 2);
434        assert!(manager.peek_metadata("QmTest2").is_some());
435        assert!(manager.peek_metadata("QmTest3").is_some());
436    }
437
438    #[test]
439    fn test_stats_tracking() {
440        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
441
442        let metadata = create_test_metadata("QmTest", 1024, 1);
443        manager.cache_metadata("QmTest".to_string(), metadata);
444
445        assert_eq!(manager.stats().total_cached, 1);
446
447        // Hit
448        manager.get_metadata("QmTest");
449        assert_eq!(manager.stats().cache_hits, 1);
450
451        // Miss
452        manager.get_metadata("QmNonexistent");
453        assert_eq!(manager.stats().cache_misses, 1);
454
455        assert_eq!(manager.stats().hit_rate(), 0.5);
456
457        manager.reset_stats();
458        assert_eq!(manager.stats().cache_hits, 0);
459        assert_eq!(manager.stats().cache_misses, 0);
460    }
461
462    #[test]
463    fn test_search_by_category() {
464        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
465
466        let mut metadata1 = create_test_metadata("QmTest1", 1024, 1);
467        metadata1.category = ContentCategory::ThreeDModels;
468
469        let mut metadata2 = create_test_metadata("QmTest2", 2048, 2);
470        metadata2.category = ContentCategory::Audio;
471
472        let mut metadata3 = create_test_metadata("QmTest3", 3072, 3);
473        metadata3.category = ContentCategory::ThreeDModels;
474
475        manager.cache_metadata("QmTest1".to_string(), metadata1);
476        manager.cache_metadata("QmTest2".to_string(), metadata2);
477        manager.cache_metadata("QmTest3".to_string(), metadata3);
478
479        let results = manager.search_by_category(ContentCategory::ThreeDModels);
480        assert_eq!(results.len(), 2);
481        assert_eq!(manager.stats().total_searches, 1);
482    }
483
484    #[test]
485    fn test_search_by_tag() {
486        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
487
488        let mut metadata1 = create_test_metadata("QmTest1", 1024, 1);
489        metadata1.tags = vec!["rust".to_string(), "programming".to_string()];
490
491        let mut metadata2 = create_test_metadata("QmTest2", 2048, 2);
492        metadata2.tags = vec!["python".to_string(), "programming".to_string()];
493
494        let mut metadata3 = create_test_metadata("QmTest3", 3072, 3);
495        metadata3.tags = vec!["rust".to_string(), "web".to_string()];
496
497        manager.cache_metadata("QmTest1".to_string(), metadata1);
498        manager.cache_metadata("QmTest2".to_string(), metadata2);
499        manager.cache_metadata("QmTest3".to_string(), metadata3);
500
501        let rust_results = manager.search_by_tag("rust");
502        assert_eq!(rust_results.len(), 2);
503
504        let programming_results = manager.search_by_tag("programming");
505        assert_eq!(programming_results.len(), 2);
506    }
507
508    #[test]
509    fn test_search_by_text() {
510        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
511
512        let mut metadata1 = create_test_metadata("QmTest1", 1024, 1);
513        metadata1.title = "Rust Programming Tutorial".to_string();
514        metadata1.description = "Learn Rust from scratch".to_string();
515
516        let mut metadata2 = create_test_metadata("QmTest2", 2048, 2);
517        metadata2.title = "Python Data Science".to_string();
518        metadata2.description = "Data analysis with Python".to_string();
519
520        manager.cache_metadata("QmTest1".to_string(), metadata1);
521        manager.cache_metadata("QmTest2".to_string(), metadata2);
522
523        let rust_results = manager.search_by_text("rust");
524        assert_eq!(rust_results.len(), 1);
525        assert_eq!(rust_results[0].cid, "QmTest1");
526
527        let data_results = manager.search_by_text("data");
528        assert_eq!(data_results.len(), 1);
529        assert_eq!(data_results[0].cid, "QmTest2");
530    }
531
532    #[test]
533    fn test_get_largest_content() {
534        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
535
536        let metadata1 = create_test_metadata("QmTest1", 1024, 1);
537        let metadata2 = create_test_metadata("QmTest2", 3072, 3);
538        let metadata3 = create_test_metadata("QmTest3", 2048, 2);
539
540        manager.cache_metadata("QmTest1".to_string(), metadata1);
541        manager.cache_metadata("QmTest2".to_string(), metadata2);
542        manager.cache_metadata("QmTest3".to_string(), metadata3);
543
544        let largest = manager.get_largest_content(2);
545        assert_eq!(largest.len(), 2);
546        assert_eq!(largest[0].size_bytes, 3072);
547        assert_eq!(largest[1].size_bytes, 2048);
548    }
549
550    #[test]
551    fn test_get_newest_content() {
552        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
553
554        let mut metadata1 = create_test_metadata("QmTest1", 1024, 1);
555        metadata1.created_at = chrono::Utc::now() - chrono::Duration::days(2);
556
557        let mut metadata2 = create_test_metadata("QmTest2", 2048, 2);
558        metadata2.created_at = chrono::Utc::now();
559
560        let mut metadata3 = create_test_metadata("QmTest3", 3072, 3);
561        metadata3.created_at = chrono::Utc::now() - chrono::Duration::days(1);
562
563        manager.cache_metadata("QmTest1".to_string(), metadata1);
564        manager.cache_metadata("QmTest2".to_string(), metadata2);
565        manager.cache_metadata("QmTest3".to_string(), metadata3);
566
567        let newest = manager.get_newest_content(2);
568        assert_eq!(newest.len(), 2);
569        assert_eq!(newest[0].cid, "QmTest2");
570        assert_eq!(newest[1].cid, "QmTest3");
571    }
572
573    #[test]
574    fn test_remove_and_clear() {
575        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
576
577        let metadata1 = create_test_metadata("QmTest1", 1024, 1);
578        let metadata2 = create_test_metadata("QmTest2", 2048, 2);
579
580        manager.cache_metadata("QmTest1".to_string(), metadata1);
581        manager.cache_metadata("QmTest2".to_string(), metadata2);
582
583        assert_eq!(manager.cached_count(), 2);
584
585        let removed = manager.remove_metadata("QmTest1");
586        assert!(removed.is_some());
587        assert_eq!(manager.cached_count(), 1);
588
589        manager.clear_cache();
590        assert_eq!(manager.cached_count(), 0);
591    }
592
593    #[test]
594    fn test_has_metadata() {
595        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
596
597        let metadata = create_test_metadata("QmTest1", 1024, 1);
598        manager.cache_metadata("QmTest1".to_string(), metadata);
599
600        assert!(manager.has_metadata("QmTest1"));
601        assert!(!manager.has_metadata("QmNonexistent"));
602    }
603
604    #[test]
605    fn test_get_multiple() {
606        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
607
608        let metadata1 = create_test_metadata("QmTest1", 1024, 1);
609        let metadata2 = create_test_metadata("QmTest2", 2048, 2);
610
611        manager.cache_metadata("QmTest1".to_string(), metadata1);
612        manager.cache_metadata("QmTest2".to_string(), metadata2);
613
614        let cids = vec![
615            "QmTest1".to_string(),
616            "QmNonexistent".to_string(),
617            "QmTest2".to_string(),
618        ];
619        let found = manager.get_multiple(&cids);
620
621        // Should return only found CIDs (2 out of 3)
622        assert_eq!(found.len(), 2);
623        assert!(found.contains(&"QmTest1".to_string()));
624        assert!(found.contains(&"QmTest2".to_string()));
625    }
626
627    #[test]
628    fn test_cache_batch() {
629        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
630
631        let items = vec![
632            (
633                "QmTest1".to_string(),
634                create_test_metadata("QmTest1", 1024, 1),
635            ),
636            (
637                "QmTest2".to_string(),
638                create_test_metadata("QmTest2", 2048, 2),
639            ),
640            (
641                "QmTest3".to_string(),
642                create_test_metadata("QmTest3", 3072, 3),
643            ),
644        ];
645
646        manager.cache_batch(items);
647
648        assert_eq!(manager.cached_count(), 3);
649        assert_eq!(manager.stats().total_cached, 3);
650        assert!(manager.has_metadata("QmTest1"));
651        assert!(manager.has_metadata("QmTest2"));
652        assert!(manager.has_metadata("QmTest3"));
653    }
654
655    #[test]
656    fn test_remove_batch() {
657        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
658
659        manager.cache_metadata(
660            "QmTest1".to_string(),
661            create_test_metadata("QmTest1", 1024, 1),
662        );
663        manager.cache_metadata(
664            "QmTest2".to_string(),
665            create_test_metadata("QmTest2", 2048, 2),
666        );
667        manager.cache_metadata(
668            "QmTest3".to_string(),
669            create_test_metadata("QmTest3", 3072, 3),
670        );
671
672        let to_remove = vec![
673            "QmTest1".to_string(),
674            "QmTest2".to_string(),
675            "QmNonexistent".to_string(),
676        ];
677        let removed_count = manager.remove_batch(&to_remove);
678
679        assert_eq!(removed_count, 2);
680        assert_eq!(manager.cached_count(), 1);
681        assert!(manager.has_metadata("QmTest3"));
682        assert!(!manager.has_metadata("QmTest1"));
683    }
684
685    #[test]
686    fn test_search_filtered() {
687        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
688
689        let mut metadata1 = create_test_metadata("QmTest1", 1024, 1);
690        metadata1.category = ContentCategory::ThreeDModels;
691        metadata1.tags = vec!["premium".to_string()];
692
693        let mut metadata2 = create_test_metadata("QmTest2", 2048 * 1024, 2);
694        metadata2.category = ContentCategory::ThreeDModels;
695        metadata2.tags = vec!["premium".to_string()];
696
697        let mut metadata3 = create_test_metadata("QmTest3", 5120 * 1024, 3);
698        metadata3.category = ContentCategory::Audio;
699        metadata3.tags = vec!["premium".to_string()];
700
701        manager.cache_metadata("QmTest1".to_string(), metadata1);
702        manager.cache_metadata("QmTest2".to_string(), metadata2);
703        manager.cache_metadata("QmTest3".to_string(), metadata3);
704
705        // Filter by category and size
706        let results = manager.search_filtered(
707            Some(ContentCategory::ThreeDModels),
708            Some("premium"),
709            Some(1024 * 1024),
710            Some(10 * 1024 * 1024),
711        );
712
713        assert_eq!(results.len(), 1);
714        assert_eq!(results[0].cid, "QmTest2");
715    }
716
717    #[test]
718    fn test_search_by_price_range() {
719        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
720
721        let mut metadata1 = create_test_metadata("QmTest1", 1024, 1);
722        metadata1.price = 50;
723
724        let mut metadata2 = create_test_metadata("QmTest2", 2048, 2);
725        metadata2.price = 150;
726
727        let mut metadata3 = create_test_metadata("QmTest3", 3072, 3);
728        metadata3.price = 250;
729
730        manager.cache_metadata("QmTest1".to_string(), metadata1);
731        manager.cache_metadata("QmTest2".to_string(), metadata2);
732        manager.cache_metadata("QmTest3".to_string(), metadata3);
733
734        let results = manager.search_by_price_range(100, 200);
735        assert_eq!(results.len(), 1);
736        assert_eq!(results[0].cid, "QmTest2");
737    }
738
739    #[test]
740    fn test_search_by_size_range() {
741        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
742
743        manager.cache_metadata(
744            "QmTest1".to_string(),
745            create_test_metadata("QmTest1", 1024, 1),
746        );
747        manager.cache_metadata(
748            "QmTest2".to_string(),
749            create_test_metadata("QmTest2", 2048, 2),
750        );
751        manager.cache_metadata(
752            "QmTest3".to_string(),
753            create_test_metadata("QmTest3", 5120, 5),
754        );
755
756        let results = manager.search_by_size_range(2000, 3000);
757        assert_eq!(results.len(), 1);
758        assert_eq!(results[0].cid, "QmTest2");
759    }
760
761    #[test]
762    fn test_get_all_cids() {
763        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
764
765        manager.cache_metadata(
766            "QmTest1".to_string(),
767            create_test_metadata("QmTest1", 1024, 1),
768        );
769        manager.cache_metadata(
770            "QmTest2".to_string(),
771            create_test_metadata("QmTest2", 2048, 2),
772        );
773
774        let cids = manager.get_all_cids();
775        assert_eq!(cids.len(), 2);
776        assert!(cids.contains(&"QmTest1".to_string()));
777        assert!(cids.contains(&"QmTest2".to_string()));
778    }
779
780    #[test]
781    fn test_count_by_category() {
782        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
783
784        let mut metadata1 = create_test_metadata("QmTest1", 1024, 1);
785        metadata1.category = ContentCategory::ThreeDModels;
786
787        let mut metadata2 = create_test_metadata("QmTest2", 2048, 2);
788        metadata2.category = ContentCategory::ThreeDModels;
789
790        let mut metadata3 = create_test_metadata("QmTest3", 3072, 3);
791        metadata3.category = ContentCategory::Audio;
792
793        manager.cache_metadata("QmTest1".to_string(), metadata1);
794        manager.cache_metadata("QmTest2".to_string(), metadata2);
795        manager.cache_metadata("QmTest3".to_string(), metadata3);
796
797        assert_eq!(manager.count_by_category(ContentCategory::ThreeDModels), 2);
798        assert_eq!(manager.count_by_category(ContentCategory::Audio), 1);
799        assert_eq!(manager.count_by_category(ContentCategory::Scripts), 0);
800    }
801
802    #[test]
803    fn test_average_content_size() {
804        let mut manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
805
806        manager.cache_metadata(
807            "QmTest1".to_string(),
808            create_test_metadata("QmTest1", 1000, 1),
809        );
810        manager.cache_metadata(
811            "QmTest2".to_string(),
812            create_test_metadata("QmTest2", 2000, 2),
813        );
814        manager.cache_metadata(
815            "QmTest3".to_string(),
816            create_test_metadata("QmTest3", 3000, 3),
817        );
818
819        assert_eq!(manager.average_content_size(), 2000);
820    }
821
822    #[test]
823    fn test_average_content_size_empty() {
824        let manager = ContentManager::new(PathBuf::from("/tmp/chie-test"));
825        assert_eq!(manager.average_content_size(), 0);
826    }
827}