Skip to main content

cache_lite/
lib.rs

1/*
2 * @filename: lib.rs
3 * @description: A cross-platform caching library for Rust with configurable storage, lifecycle, and file formatting.
4 * @author: TaimWay <taimway@gmail.com>
5 * 
6 * Copyright (C) 2026 TaimWay
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27//! # Rust Cache Library
28//! 
29//! A lightweight, cross-platform caching library for Rust applications.
30//! Provides configurable cache storage, lifecycle management, and file formatting.
31//! 
32//! # Features
33//! 
34//! - **Cross-platform**: Automatic path handling for Windows and Linux
35//! - **Configurable**: JSON-based configuration with runtime overrides
36//! - **Simple API**: Easy-to-use interface for cache operations
37//! - **File-based**: Persistent cache storage on disk
38//! 
39//! # Quick Start
40//! 
41//! ```no_run
42//! use cache_lite::{Cache, CacheConfig};
43//! 
44//! // Create cache with default configuration
45//! let config = CacheConfig::default();
46//! let mut cache = Cache::new(config);
47//! 
48//! // Create a new cache object
49//! let cache_obj = cache.create("my_cache", None).unwrap();
50//! 
51//! // Write data to cache
52//! cache_obj.write_string("Cached data").unwrap();
53//! 
54//! // Read data from cache
55//! let data = cache_obj.get_string().unwrap();
56//! assert_eq!(data, "Cached data");
57//! ```
58//! 
59//! # Configuration
60//! 
61//! The library supports JSON configuration for customizing cache behavior:
62//! 
63//! ```json
64//! {
65//!   "path": {
66//!     "windows": "%temp%/MyApp/Cache",
67//!     "linux": "/tmp/myapp/cache"
68//!   },
69//!   "format": {
70//!     "filename": "{name}_{time}.cache",
71//!     "time": "%Y%m%d_%H%M%S"
72//!   }
73//! }
74//! ```
75//! 
76//! # Error Handling
77//! 
78//! The library provides a comprehensive error type `CacheError` for handling
79//! various failure scenarios including I/O errors, invalid configurations,
80//! permission issues, and more.
81
82mod config;
83mod object;
84mod cache;
85mod error;
86
87// Re-export public API
88pub use config::{CacheConfig, CachePathConfig, CacheFormatConfig};
89pub use object::CacheObject;
90pub use cache::Cache;
91pub use error::CacheError;
92
93/// Result type alias for cache operations
94pub type CacheResult<T> = std::result::Result<T, CacheError>;
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use std::fs;
100    use tempfile::tempdir;
101    
102    #[test]
103    fn test_cache_config_default() {
104        let config = CacheConfig::default();
105        
106        // Check default Windows path
107        assert!(config.path.windows.contains("%temp%"));
108        assert!(config.path.windows.contains("Rust/Cache"));
109        
110        // Check default Linux path
111        assert_eq!(config.path.linux, "/tmp/Rust/Cache");
112        
113        // Check default filename format
114        assert!(config.format.filename.contains("{name}"));
115        assert!(config.format.filename.contains("{time}"));
116        
117        // Check default time format
118        assert_eq!(config.format.time, "%Y+%m+%d-%H+%M+%S");
119    }
120    
121    #[test]
122    fn test_cache_config_from_json() {
123        let json_config = r#"
124        {
125            "path": {
126                "windows": "C:/Temp/Cache",
127                "linux": "/var/cache"
128            },
129            "format": {
130                "filename": "cache_{name}.dat",
131                "time": "%Y%m%d"
132            }
133        }
134        "#;
135        
136        let config = CacheConfig::new(json_config).expect("Failed to parse config");
137        
138        assert_eq!(config.path.windows, "C:/Temp/Cache");
139        assert_eq!(config.path.linux, "/var/cache");
140        assert_eq!(config.format.filename, "cache_{name}.dat");
141        assert_eq!(config.format.time, "%Y%m%d");
142    }
143    
144    #[test]
145    fn test_cache_config_new_or_default() {
146        // Test valid JSON
147        let valid_json = r#"
148        {
149            "path": {
150                "windows": "C:/Custom/Cache",
151                "linux": "/custom/cache"
152            },
153            "format": {
154                "filename": "custom_{name}.dat",
155                "time": "%Y%m"
156            }
157        }
158        "#;
159        
160        let config = CacheConfig::new_or_default(valid_json);
161        assert_eq!(config.path.windows, "C:/Custom/Cache");
162        assert_eq!(config.path.linux, "/custom/cache");
163        assert_eq!(config.format.filename, "custom_{name}.dat");
164        assert_eq!(config.format.time, "%Y%m");
165        
166        // Test invalid JSON - should fall back to default
167        let invalid_json = "{invalid json";
168        let config = CacheConfig::new_or_default(invalid_json);
169        assert!(config.path.windows.contains("%temp%"));
170        assert_eq!(config.path.linux, "/tmp/Rust/Cache");
171    }
172    
173    #[test]
174    fn test_cache_config_invalid_json() {
175        let invalid_json = "{invalid json";
176        let result = CacheConfig::new(invalid_json);
177        
178        // Should return error
179        assert!(result.is_err());
180        
181        // Check error type
182        let error = result.err().unwrap();
183        assert_eq!(error.kind(), "config_parse");
184        assert!(error.message().contains("Failed to parse config"));
185    }
186    
187    #[test]
188    fn test_cache_object_creation_and_properties() {
189        let temp_dir = tempdir().expect("Failed to create temp directory");
190        let test_path = temp_dir.path().join("test_cache.cache");
191        
192        let cache_object = CacheObject::new(
193            "test_object".to_string(),
194            test_path.clone(),
195            1
196        );
197        
198        assert_eq!(cache_object.name(), "test_object");
199        assert_eq!(cache_object.id(), 1);
200        assert_eq!(cache_object.path(), test_path);
201        assert!(cache_object.created_at().elapsed().is_ok());
202    }
203    
204    #[test]
205    fn test_cache_object_file_operations() {
206        let temp_dir = tempdir().expect("Failed to create temp directory");
207        let test_path = temp_dir.path().join("test_operations.cache");
208        
209        let cache_object = CacheObject::new(
210            "test_ops".to_string(),
211            test_path.clone(),
212            2
213        );
214        
215        // Test write operation
216        let content = "Hello, Cache!";
217        cache_object.write_string(content).expect("Failed to write to cache");
218        
219        // Verify file was created
220        assert!(test_path.exists());
221        assert!(test_path.is_file());
222        
223        // Test read operation
224        let read_content = cache_object.get_string().expect("Failed to read from cache");
225        assert_eq!(read_content, content);
226        
227        // Test file handle retrieval
228        let file = cache_object.get_file().expect("Failed to open cache file");
229        assert!(file.metadata().is_ok());
230        
231        // Test delete operation
232        cache_object.delete().expect("Failed to delete cache file");
233        assert!(!test_path.exists());
234    }
235    
236    #[test]
237    fn test_cache_object_advanced_operations() {
238        let temp_dir = tempdir().expect("Failed to create temp directory");
239        let test_path = temp_dir.path().join("test_advanced.cache");
240        
241        let cache_object = CacheObject::new(
242            "test_advanced".to_string(),
243            test_path.clone(),
244            1
245        );
246        
247        // Test writing and reading bytes
248        let bytes = vec![1, 2, 3, 4, 5];
249        cache_object.write_bytes(&bytes).expect("Failed to write bytes");
250        
251        let read_bytes = cache_object.get_bytes().expect("Failed to read bytes");
252        assert_eq!(read_bytes, bytes);
253        
254        // Test file size
255        let size = cache_object.size().expect("Failed to get file size");
256        assert_eq!(size, 5);
257        
258        // Test file existence
259        assert!(cache_object.exists());
260        
261        // Test delete
262        cache_object.delete().expect("Failed to delete");
263        assert!(!cache_object.exists());
264        
265        // Test getting size of non-existent file
266        let result = cache_object.size();
267        assert!(result.is_err());
268    }
269    
270    #[test]
271    fn test_cache_object_clone() {
272        let temp_dir = tempdir().expect("Failed to create temp directory");
273        let test_path = temp_dir.path().join("test_clone.cache");
274        
275        let original = CacheObject::new(
276            "original".to_string(),
277            test_path.clone(),
278            3
279        );
280        
281        let cloned = original.clone();
282        
283        assert_eq!(original.name(), cloned.name());
284        assert_eq!(original.id(), cloned.id());
285        assert_eq!(original.path(), cloned.path());
286        
287        // Cloned object should have same properties but be a separate instance
288        assert_eq!(original.name(), "original");
289        assert_eq!(cloned.name(), "original");
290    }
291    
292    #[test]
293    fn test_cache_management() {
294        let temp_dir = tempdir().expect("Failed to create temp directory");
295        
296        // Create custom configuration for testing
297        let config_json = format!(r#"
298        {{
299            "path": {{
300                "windows": "{}",
301                "linux": "{}"
302            }},
303            "format": {{
304                "filename": "{{name}}.cache",
305                "time": "%Y%m%d"
306            }}
307        }}"#, 
308        temp_dir.path().to_string_lossy().replace("\\", "\\\\"),
309        temp_dir.path().to_string_lossy()
310        );
311        
312        let config = CacheConfig::new(&config_json).expect("Failed to parse config");
313        let mut cache = Cache::new(config);
314        
315        // Test creating cache objects
316        let cache_obj1 = cache.create("test_cache_1", None)
317            .expect("Failed to create cache object 1");
318        assert_eq!(cache_obj1.name(), "test_cache_1");
319        assert_eq!(cache_obj1.id(), 1);
320        
321        let cache_obj2 = cache.create("test_cache_2", None)
322            .expect("Failed to create cache object 2");
323        assert_eq!(cache_obj2.name(), "test_cache_2");
324        assert_eq!(cache_obj2.id(), 2);
325        
326        // Test cache size
327        assert_eq!(cache.len(), 2);
328        assert!(!cache.is_empty());
329        
330        // Test retrieving cache objects
331        let retrieved1 = cache.get("test_cache_1")
332            .expect("Failed to get cache object 1");
333        assert_eq!(retrieved1.name(), "test_cache_1");
334        
335        let retrieved2 = cache.get("test_cache_2")
336            .expect("Failed to get cache object 2");
337        assert_eq!(retrieved2.name(), "test_cache_2");
338        
339        // Test removing a cache object
340        cache.remove("test_cache_1").expect("Failed to remove cache object");
341        assert_eq!(cache.len(), 1);
342        
343        // Verify removed object can't be retrieved
344        assert!(cache.get("test_cache_1").is_err());
345        
346        // Verify remaining object is still accessible
347        assert!(cache.get("test_cache_2").is_ok());
348        
349        // Test clearing all cache objects
350        cache.clear().expect("Failed to clear cache");
351        assert_eq!(cache.len(), 0);
352        assert!(cache.is_empty());
353    }
354    
355    #[test]
356    fn test_cache_management_with_error_handling() {
357        let temp_dir = tempdir().expect("Failed to create temp directory");
358        
359        // Create custom configuration for testing
360        let config_json = format!(r#"
361        {{
362            "path": {{
363                "windows": "{}",
364                "linux": "{}"
365            }},
366            "format": {{
367                "filename": "{{name}}.cache",
368                "time": "%Y%m%d"
369            }}
370        }}"#, 
371        temp_dir.path().to_string_lossy().replace("\\", "\\\\"),
372        temp_dir.path().to_string_lossy()
373        );
374        
375        let config = CacheConfig::new(&config_json).expect("Failed to parse config");
376        let mut cache = Cache::new(config);
377        
378        // Test creating cache objects with invalid names
379        let result = cache.create("", None);
380        assert!(result.is_err());
381        assert_eq!(result.err().unwrap().kind(), "invalid_name");
382        
383        // Test creating valid cache object
384        let cache_obj = cache.create("test_cache", None)
385            .expect("Failed to create cache object");
386        
387        // Test writing and reading with error handling
388        let write_result = cache_obj.write_string("Test content");
389        assert!(write_result.is_ok());
390        
391        let read_result = cache_obj.get_string();
392        assert!(read_result.is_ok());
393        assert_eq!(read_result.unwrap(), "Test content");
394        
395        // Test getting non-existent cache
396        let result = cache.get("non_existent");
397        assert!(result.is_err());
398        assert_eq!(result.err().unwrap().kind(), "not_found");
399    }
400    
401    #[test]
402    fn test_cache_invalid_names() {
403        let config = CacheConfig::default();
404        let mut cache = Cache::new(config);
405        
406        // Test with empty name
407        let result = cache.create("", None);
408        assert!(result.is_err(), "Should reject empty names");
409        
410        // Test with path traversal attempt
411        let result = cache.create("../evil", None);
412        assert!(result.is_err(), "Should reject path traversal names");
413        
414        // Test with slash in name
415        let result = cache.create("path/to/cache", None);
416        assert!(result.is_err(), "Should reject names with slashes");
417        
418        // Test with backslash in name (Windows)
419        let result = cache.create("path\\to\\cache", None);
420        assert!(result.is_err(), "Should reject names with backslashes");
421        
422        // Test with valid name
423        let result = cache.create("valid_cache_name", None);
424        assert!(result.is_ok(), "Should accept valid cache names");
425    }
426    
427    #[test]
428    fn test_cache_custom_configuration() {
429        let temp_dir = tempdir().expect("Failed to create temp directory");
430        
431        // Base configuration
432        let base_config_json = format!(r#"
433        {{
434            "path": {{
435                "windows": "{}",
436                "linux": "{}"
437            }},
438            "format": {{
439                "filename": "default_{{name}}.cache",
440                "time": "%Y%m%d"
441            }}
442        }}"#,
443        temp_dir.path().to_string_lossy().replace("\\", "\\\\"),
444        temp_dir.path().to_string_lossy()
445        );
446        
447        let base_config = CacheConfig::new(&base_config_json).expect("Failed to parse config");
448        let mut cache = Cache::new(base_config);
449        
450        // Custom configuration to override filename format
451        let custom_config = r#"
452        {
453            "path": {
454                "windows": "",
455                "linux": ""
456            },
457            "format": {
458                "filename": "custom_{name}_{id}.dat",
459                "time": "%H%M%S"
460            }
461        }
462        "#;
463        
464        let cache_obj = cache.create("custom_cache", Some(custom_config))
465            .expect("Failed to create cache with custom config");
466        
467        let path_str = cache_obj.path().to_string_lossy();
468        
469        // Should use custom filename format
470        assert!(path_str.contains("custom_custom_cache_"), 
471                "Path should contain custom pattern: {}", path_str);
472        assert!(path_str.ends_with(".dat"), 
473                "Path should end with .dat: {}", path_str);
474    }
475    
476    #[test]
477    fn test_cache_iterator() {
478        let temp_dir = tempdir().expect("Failed to create temp directory");
479        
480        let config_json = format!(r#"
481        {{
482            "path": {{
483                "windows": "{}",
484                "linux": "{}"
485            }},
486            "format": {{
487                "filename": "{{name}}.cache",
488                "time": "%Y%m%d"
489            }}
490        }}"#,
491        temp_dir.path().to_string_lossy().replace("\\", "\\\\"),
492        temp_dir.path().to_string_lossy()
493        );
494        
495        let config = CacheConfig::new(&config_json).expect("Failed to parse config");
496        let mut cache = Cache::new(config);
497        
498        // Create multiple cache objects
499        let _ = cache.create("cache_a", None).expect("Failed to create cache_a");
500        let _ = cache.create("cache_b", None).expect("Failed to create cache_b");
501        let _ = cache.create("cache_c", None).expect("Failed to create cache_c");
502        
503        // Test iterator collects all names
504        let names: Vec<String> = cache.iter()
505            .map(|obj| obj.name().to_string())
506            .collect();
507        
508        assert_eq!(names.len(), 3, "Should have 3 cache objects");
509        assert!(names.contains(&"cache_a".to_string()));
510        assert!(names.contains(&"cache_b".to_string()));
511        assert!(names.contains(&"cache_c".to_string()));
512        
513        // Test iterator order (should be arbitrary for HashMap)
514        let ids: Vec<u32> = cache.iter()
515            .map(|obj| obj.id())
516            .collect();
517        
518        assert_eq!(ids.len(), 3);
519        assert!(ids.contains(&1));
520        assert!(ids.contains(&2));
521        assert!(ids.contains(&3));
522    }
523    
524    #[test]
525    fn test_cache_configuration_get_set() {
526        let mut config = CacheConfig::default();
527        
528        // Modify configuration
529        config.path.windows = "C:/Custom/Path".to_string();
530        config.path.linux = "/custom/path".to_string();
531        config.format.filename = "custom_{name}.dat".to_string();
532        config.format.time = "%Y".to_string();
533        
534        let mut cache = Cache::new(config.clone());
535        
536        // Verify initial configuration
537        let retrieved_config = cache.get_config();
538        assert_eq!(retrieved_config.path.windows, "C:/Custom/Path");
539        assert_eq!(retrieved_config.path.linux, "/custom/path");
540        assert_eq!(retrieved_config.format.filename, "custom_{name}.dat");
541        assert_eq!(retrieved_config.format.time, "%Y");
542        
543        // Update configuration
544        let new_config = CacheConfig::default();
545        cache.set_config(new_config.clone());
546        
547        // Verify updated configuration
548        let updated_config = cache.get_config();
549        assert_eq!(updated_config.path.windows, new_config.path.windows);
550        assert_eq!(updated_config.path.linux, new_config.path.linux);
551        assert_eq!(updated_config.format.filename, new_config.format.filename);
552        assert_eq!(updated_config.format.time, new_config.format.time);
553    }
554    
555    #[test]
556    fn test_cache_error_types() {
557        use std::io::{Error, ErrorKind};
558        
559        // Test CacheError display implementation
560        let io_error = CacheError::Io(Error::new(ErrorKind::NotFound, "File not found"));
561        assert_eq!(format!("{}", io_error), "I/O error: File not found");
562        
563        let invalid_name_error = CacheError::InvalidName("test".to_string());
564        assert_eq!(format!("{}", invalid_name_error), "Invalid cache name: test");
565        
566        let config_error = CacheError::ConfigParse("Invalid JSON".to_string());
567        assert_eq!(format!("{}", config_error), "Configuration parse error: Invalid JSON");
568        
569        // Test error kind method
570        assert_eq!(io_error.kind(), "io");
571        assert_eq!(invalid_name_error.kind(), "invalid_name");
572        assert_eq!(config_error.kind(), "config_parse");
573        
574        // Test error message method
575        assert_eq!(io_error.message(), "File not found");
576        assert_eq!(invalid_name_error.message(), "test");
577        assert_eq!(config_error.message(), "Invalid JSON");
578        
579        // Test error type checks
580        assert!(io_error.is_io_error());
581        assert!(!invalid_name_error.is_io_error());
582        assert!(!config_error.is_io_error());
583    }
584    
585    #[test]
586    fn test_cache_deprecated_functions() {
587        let temp_dir = tempdir().expect("Failed to create temp directory");
588        
589        let config_json = format!(r#"
590        {{
591            "path": {{
592                "windows": "{}",
593                "linux": "{}"
594            }},
595            "format": {{
596                "filename": "{{name}}.cache",
597                "time": "%Y%m%d"
598            }}
599        }}"#,
600        temp_dir.path().to_string_lossy().replace("\\", "\\\\"),
601        temp_dir.path().to_string_lossy()
602        );
603        
604        let config = CacheConfig::new(&config_json).expect("Failed to parse config");
605        let mut cache = Cache::new(config);
606        
607        // Create a cache object to test deprecated functions
608        let cache_obj = cache.create("deprecated_test", None)
609            .expect("Failed to create cache object");
610        
611        // Test deprecated is_expired method (always returns false)
612        #[allow(deprecated)]
613        let expired = cache_obj.is_expired();
614        assert!(!expired, "is_expired should always return false");
615        
616        // Test deprecated cleanup method
617        #[allow(deprecated)]
618        let cleanup_result = cache.cleanup();
619        assert!(cleanup_result.is_ok());
620        
621        // After cleanup, cache should be empty
622        assert_eq!(cache.len(), 0);
623    }
624    
625    #[test]
626    fn test_cache_path_expansion() {
627        let temp_dir = tempdir().expect("Failed to create temp directory");
628        let temp_path = temp_dir.path().to_string_lossy().to_string();
629        
630        let config_json = format!(r#"
631        {{
632            "path": {{
633                "windows": "{}",
634                "linux": "{}"
635            }},
636            "format": {{
637                "filename": "test.cache",
638                "time": "%Y%m%d"
639            }}
640        }}"#,
641        temp_path.replace("\\", "\\\\"),
642        temp_path
643        );
644        
645        let config = CacheConfig::new(&config_json).expect("Failed to parse config");
646        let cache = Cache::new(config);
647        
648        // Test path expansion - should not modify already absolute paths
649        let expanded = cache.expand_path(&temp_path);
650        assert_eq!(expanded, temp_path);
651        
652        // Test tilde expansion (if home directory is available)
653        if dirs::home_dir().is_some() {
654            let expanded = cache.expand_path("~/test/cache");
655            assert!(!expanded.contains('~'), "Tilde should be expanded");
656        }
657    }
658    
659    #[test]
660    fn test_cache_concurrent_ids() {
661        let config = CacheConfig::default();
662        let mut cache = Cache::new(config);
663        
664        // Create multiple cache objects and verify they get sequential IDs
665        let obj1 = cache.create("obj1", None).expect("Failed to create obj1");
666        assert_eq!(obj1.id(), 1);
667        
668        let obj2 = cache.create("obj2", None).expect("Failed to create obj2");
669        assert_eq!(obj2.id(), 2);
670        
671        let obj3 = cache.create("obj3", None).expect("Failed to create obj3");
672        assert_eq!(obj3.id(), 3);
673        
674        // Remove one and create another
675        cache.remove("obj2").expect("Failed to remove obj2");
676        let obj4 = cache.create("obj4", None).expect("Failed to create obj4");
677        assert_eq!(obj4.id(), 4); // IDs should continue incrementing
678        
679        // Create more objects
680        let obj5 = cache.create("obj5", None).expect("Failed to create obj5");
681        assert_eq!(obj5.id(), 5);
682    }
683    
684    #[test]
685    fn test_cache_large_name_rejection() {
686        let config = CacheConfig::default();
687        let mut cache = Cache::new(config);
688        
689        // Create a name that's too long
690        let long_name = "a".repeat(300);
691        let result = cache.create(&long_name, None);
692        
693        // Should reject names longer than 255 characters
694        assert!(result.is_err());
695        assert_eq!(result.err().unwrap().kind(), "invalid_name");
696        
697        // Create a name at the limit
698        let limit_name = "a".repeat(255);
699        let result = cache.create(&limit_name, None);
700        
701        // Should accept names at exactly 255 characters
702        assert!(result.is_ok());
703    }
704    
705    #[test]
706    fn test_cache_clear_with_errors() {
707        let temp_dir = tempdir().expect("Failed to create temp directory");
708        
709        let config_json = format!(r#"
710        {{
711            "path": {{
712                "windows": "{}",
713                "linux": "{}"
714            }},
715            "format": {{
716                "filename": "{{name}}.cache",
717                "time": "%Y%m%d"
718            }}
719        }}"#,
720        temp_dir.path().to_string_lossy().replace("\\", "\\\\"),
721        temp_dir.path().to_string_lossy()
722        );
723        
724        let config = CacheConfig::new(&config_json).expect("Failed to parse config");
725        let mut cache = Cache::new(config);
726        
727        // Create cache objects
728        let obj1 = cache.create("obj1", None).expect("Failed to create obj1");
729        let obj2 = cache.create("obj2", None).expect("Failed to create obj2");
730        
731        // Manually delete one file to simulate error
732        std::fs::remove_file(obj1.path()).expect("Failed to delete file");
733        
734        // Try to clear cache - should still work even with one error
735        let result = cache.clear();
736        assert!(result.is_ok()); // clear() should still succeed even with deletion errors
737        
738        // Cache should be empty
739        assert_eq!(cache.len(), 0);
740    }
741    
742    #[test]
743    fn test_cache_duplicate_names() {
744        let config = CacheConfig::default();
745        let mut cache = Cache::new(config);
746        
747        // Create first cache object
748        let result1 = cache.create("duplicate", None);
749        assert!(result1.is_ok());
750        
751        // Try to create another with same name - should overwrite (not error)
752        let result2 = cache.create("duplicate", None);
753        assert!(result2.is_ok());
754        
755        // Should have only one object with that name
756        assert_eq!(cache.len(), 1);
757    }
758    
759    #[test]
760    fn test_cache_empty_config_override() {
761        let temp_dir = tempdir().expect("Failed to create temp directory");
762        
763        let base_config_json = format!(r#"
764        {{
765            "path": {{
766                "windows": "{}",
767                "linux": "{}"
768            }},
769            "format": {{
770                "filename": "base_{{name}}.cache",
771                "time": "%Y%m%d"
772            }}
773        }}"#,
774        temp_dir.path().to_string_lossy().replace("\\", "\\\\"),
775        temp_dir.path().to_string_lossy()
776        );
777        
778        let base_config = CacheConfig::new(&base_config_json).expect("Failed to parse config");
779        let mut cache = Cache::new(base_config);
780        
781        // Custom config with empty strings (should not override base config)
782        let custom_config = r#"
783        {
784            "path": {
785                "windows": "",
786                "linux": ""
787            },
788            "format": {
789                "filename": "",
790                "time": ""
791            }
792        }
793        "#;
794        
795        let cache_obj = cache.create("test", Some(custom_config))
796            .expect("Failed to create cache object");
797        
798        let path_str = cache_obj.path().to_string_lossy();
799        
800        // Should use base config since custom config has empty strings
801        assert!(path_str.contains("base_test"), 
802                "Path should contain base pattern: {}", path_str);
803        assert!(path_str.ends_with(".cache"), 
804                "Path should end with .cache: {}", path_str);
805    }
806}