1mod config;
83mod object;
84mod cache;
85mod error;
86
87pub use config::{CacheConfig, CachePathConfig, CacheFormatConfig};
89pub use object::CacheObject;
90pub use cache::Cache;
91pub use error::CacheError;
92
93pub 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 assert!(config.path.windows.contains("%temp%"));
108 assert!(config.path.windows.contains("Rust/Cache"));
109
110 assert_eq!(config.path.linux, "/tmp/Rust/Cache");
112
113 assert!(config.format.filename.contains("{name}"));
115 assert!(config.format.filename.contains("{time}"));
116
117 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 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 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 assert!(result.is_err());
180
181 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 let content = "Hello, Cache!";
217 cache_object.write_string(content).expect("Failed to write to cache");
218
219 assert!(test_path.exists());
221 assert!(test_path.is_file());
222
223 let read_content = cache_object.get_string().expect("Failed to read from cache");
225 assert_eq!(read_content, content);
226
227 let file = cache_object.get_file().expect("Failed to open cache file");
229 assert!(file.metadata().is_ok());
230
231 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 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 let size = cache_object.size().expect("Failed to get file size");
256 assert_eq!(size, 5);
257
258 assert!(cache_object.exists());
260
261 cache_object.delete().expect("Failed to delete");
263 assert!(!cache_object.exists());
264
265 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 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 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 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 assert_eq!(cache.len(), 2);
328 assert!(!cache.is_empty());
329
330 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 cache.remove("test_cache_1").expect("Failed to remove cache object");
341 assert_eq!(cache.len(), 1);
342
343 assert!(cache.get("test_cache_1").is_err());
345
346 assert!(cache.get("test_cache_2").is_ok());
348
349 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 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 let result = cache.create("", None);
380 assert!(result.is_err());
381 assert_eq!(result.err().unwrap().kind(), "invalid_name");
382
383 let cache_obj = cache.create("test_cache", None)
385 .expect("Failed to create cache object");
386
387 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 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 let result = cache.create("", None);
408 assert!(result.is_err(), "Should reject empty names");
409
410 let result = cache.create("../evil", None);
412 assert!(result.is_err(), "Should reject path traversal names");
413
414 let result = cache.create("path/to/cache", None);
416 assert!(result.is_err(), "Should reject names with slashes");
417
418 let result = cache.create("path\\to\\cache", None);
420 assert!(result.is_err(), "Should reject names with backslashes");
421
422 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 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 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 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 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 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 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 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 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 let new_config = CacheConfig::default();
545 cache.set_config(new_config.clone());
546
547 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 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 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 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 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 let cache_obj = cache.create("deprecated_test", None)
609 .expect("Failed to create cache object");
610
611 #[allow(deprecated)]
613 let expired = cache_obj.is_expired();
614 assert!(!expired, "is_expired should always return false");
615
616 #[allow(deprecated)]
618 let cleanup_result = cache.cleanup();
619 assert!(cleanup_result.is_ok());
620
621 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 let expanded = cache.expand_path(&temp_path);
650 assert_eq!(expanded, temp_path);
651
652 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 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 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); 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 let long_name = "a".repeat(300);
691 let result = cache.create(&long_name, None);
692
693 assert!(result.is_err());
695 assert_eq!(result.err().unwrap().kind(), "invalid_name");
696
697 let limit_name = "a".repeat(255);
699 let result = cache.create(&limit_name, None);
700
701 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 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 std::fs::remove_file(obj1.path()).expect("Failed to delete file");
733
734 let result = cache.clear();
736 assert!(result.is_ok()); 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 let result1 = cache.create("duplicate", None);
749 assert!(result1.is_ok());
750
751 let result2 = cache.create("duplicate", None);
753 assert!(result2.is_ok());
754
755 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 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 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}