1mod config;
89mod object;
90mod cache;
91mod error;
92mod utils;
93
94pub use config::{CacheConfig, CachePathConfig, CacheFormatConfig};
96pub use object::CacheObject;
97pub use cache::Cache;
98pub use error::CacheError;
99
100pub type CacheResult<T> = std::result::Result<T, CacheError>;
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use tempfile::tempdir;
107
108 #[test]
109 fn test_cache_config_default() {
110 let config = CacheConfig::default();
111 assert_eq!(config.max_size, 0);
112 assert_eq!(config.max_files, 0);
113 assert!(!config.path.windows.is_empty());
114 assert!(!config.path.linux.is_empty());
115 assert!(!config.format.filename.is_empty());
116 assert!(!config.format.time.is_empty());
117 }
118
119 #[test]
120 fn test_cache_config_from_json() {
121 let json = r#"{
122 "path": {
123 "windows": "%temp%/TestCache",
124 "linux": "/tmp/testcache"
125 },
126 "format": {
127 "filename": "test_{name}.cache",
128 "time": "%Y%m%d"
129 },
130 "max_size": 1024,
131 "max_files": 10
132 }"#;
133
134 let config = CacheConfig::new(json).unwrap();
135 assert_eq!(config.path.windows, "%temp%/TestCache");
136 assert_eq!(config.path.linux, "/tmp/testcache");
137 assert_eq!(config.format.filename, "test_{name}.cache");
138 assert_eq!(config.format.time, "%Y%m%d");
139 assert_eq!(config.max_size, 1024);
140 assert_eq!(config.max_files, 10);
141 }
142
143 #[test]
144 fn test_cache_config_from_partial_json() {
145 let json = r#"{
146 "path": {
147 "linux": "/custom/path"
148 },
149 "format": {
150 "filename": "custom_{name}.cache"
151 }
152 }"#;
153
154 let config = CacheConfig::new(json).unwrap();
155 assert_eq!(config.path.linux, "/custom/path");
156 assert_eq!(config.format.filename, "custom_{name}.cache");
157 assert!(!config.path.windows.is_empty());
159 assert!(!config.format.time.is_empty());
161 }
162
163 #[test]
164 fn test_cache_config_new_or_default() {
165 let json = "invalid json";
166 let config = CacheConfig::new_or_default(json);
167 assert_eq!(config.max_size, 0);
169 assert_eq!(config.max_files, 0);
170 }
171
172 #[test]
173 fn test_cache_creation() {
174 let temp_dir = tempdir().unwrap();
175 let config_json = format!(
176 r#"{{
177 "path": {{
178 "windows": "{}",
179 "linux": "{}"
180 }},
181 "format": {{
182 "filename": "{{name}}.cache",
183 "time": "%Y%m%d"
184 }},
185 "max_size": 0,
186 "max_files": 0
187 }}"#,
188 temp_dir.path().to_string_lossy(),
189 temp_dir.path().to_string_lossy()
190 );
191
192 let config = CacheConfig::new(&config_json).unwrap();
193 let mut cache = Cache::new(config).unwrap();
194
195 let cache_obj = cache.create("test_cache", None).unwrap();
197 assert_eq!(cache_obj.name(), "test_cache");
198
199 cache_obj.write_string("test data").unwrap();
201 assert!(cache_obj.exists());
202
203 let result = cache.create("test_cache", None);
205 assert!(result.is_err());
206 if let Err(e) = result {
207 assert!(matches!(e, CacheError::AlreadyExists(_)));
208 }
209
210 let retrieved = cache.get("test_cache").unwrap();
212 assert_eq!(retrieved.name(), "test_cache");
213 assert_eq!(retrieved.id(), cache_obj.id());
214
215 let result = cache.get("nonexistent");
217 assert!(result.is_err());
218 if let Err(e) = result {
219 assert!(matches!(e, CacheError::NotFound(_)));
220 }
221
222 assert_eq!(cache.len(), 1);
224 assert!(!cache.is_empty());
225
226 let objects: Vec<_> = cache.iter().collect();
228 assert_eq!(objects.len(), 1);
229 assert_eq!(objects[0].name(), "test_cache");
230 }
231
232 #[test]
233 fn test_cache_operations() {
234 let temp_dir = tempdir().unwrap();
235 let config_json = format!(
236 r#"{{
237 "path": {{
238 "windows": "{}",
239 "linux": "{}"
240 }},
241 "format": {{
242 "filename": "{{name}}.cache",
243 "time": "%Y%m%d"
244 }},
245 "max_size": 0,
246 "max_files": 0
247 }}"#,
248 temp_dir.path().to_string_lossy(),
249 temp_dir.path().to_string_lossy()
250 );
251
252 let config = CacheConfig::new(&config_json).unwrap();
253 let mut cache = Cache::new(config).unwrap();
254
255 cache.create("cache1", None).unwrap();
257 cache.create("cache2", None).unwrap();
258 cache.create("cache3", None).unwrap();
259
260 assert_eq!(cache.len(), 3);
261
262 cache.remove("cache2").unwrap();
264 assert_eq!(cache.len(), 2);
265 assert!(cache.get("cache2").is_err());
266
267 cache.clear().unwrap();
269 assert_eq!(cache.len(), 0);
270 assert!(cache.is_empty());
271 }
272
273 #[test]
274 fn test_cache_object_operations() {
275 let temp_dir = tempdir().unwrap();
276 let config_json = format!(
277 r#"{{
278 "path": {{
279 "windows": "{}",
280 "linux": "{}"
281 }},
282 "format": {{
283 "filename": "{{name}}.cache",
284 "time": "%Y%m%d"
285 }},
286 "max_size": 0,
287 "max_files": 0
288 }}"#,
289 temp_dir.path().to_string_lossy(),
290 temp_dir.path().to_string_lossy()
291 );
292
293 let config = CacheConfig::new(&config_json).unwrap();
294 let mut cache = Cache::new(config).unwrap();
295 let cache_obj = cache.create("test_operations", None).unwrap();
296
297 let test_string = "Hello, World!";
299 cache_obj.write_string(test_string).unwrap();
300
301 let read_string = cache_obj.get_string().unwrap();
302 assert_eq!(read_string, test_string);
303
304 let test_bytes = vec![1, 2, 3, 4, 5];
306 cache_obj.write_bytes(&test_bytes).unwrap();
307
308 let read_bytes = cache_obj.get_bytes().unwrap();
309 assert_eq!(read_bytes, test_bytes);
310
311 let file = cache_obj.get_file().unwrap();
313 assert!(file.metadata().is_ok());
314
315 let size = cache_obj.size().unwrap();
317 assert!(size > 0);
318
319 cache_obj.delete().unwrap();
321 assert!(!cache_obj.exists());
322
323 let new_obj = cache.create("new_cache", None).unwrap();
325 let created_at = new_obj.created_at();
326 assert!(created_at.elapsed().is_ok());
327 }
328
329 #[test]
330 fn test_cache_with_custom_config() {
331 let temp_dir = tempdir().unwrap();
332 let base_config_json = format!(
333 r#"{{
334 "path": {{
335 "windows": "{}",
336 "linux": "{}"
337 }},
338 "format": {{
339 "filename": "base_{{name}}.cache",
340 "time": "%Y%m%d"
341 }},
342 "max_size": 0,
343 "max_files": 0
344 }}"#,
345 temp_dir.path().to_string_lossy(),
346 temp_dir.path().to_string_lossy()
347 );
348
349 let base_config = CacheConfig::new(&base_config_json).unwrap();
350 let mut cache = Cache::new(base_config).unwrap();
351
352 let custom_config = r#"{
353 "path": {
354 "linux": "/custom/path"
355 },
356 "format": {
357 "filename": "custom_{name}_{id}.cache"
358 }
359 }"#;
360
361 let cache_obj = cache.create("custom_cache", Some(custom_config)).unwrap();
362 let path_str = cache_obj.path().to_string_lossy().to_string();
363
364 cache_obj.write_string("test").unwrap();
366
367 assert!(path_str.contains("custom_cache"));
369 assert!(path_str.contains(".cache"));
370 }
371
372 #[test]
373 fn test_cache_config_updates() {
374 let config = CacheConfig::default();
375 let mut cache = Cache::new(config).unwrap();
376
377 let new_config_json = r#"{
378 "path": {
379 "windows": "%temp%/UpdatedCache",
380 "linux": "/tmp/updatedcache"
381 },
382 "format": {
383 "filename": "updated_{name}.cache",
384 "time": "%H%M%S"
385 },
386 "max_size": 2048,
387 "max_files": 20
388 }"#;
389
390 let new_config = CacheConfig::new(new_config_json).unwrap();
391 cache.set_config(new_config.clone());
392
393 let retrieved_config = cache.get_config();
394 assert_eq!(retrieved_config.max_size, 2048);
395 assert_eq!(retrieved_config.max_files, 20);
396 assert_eq!(retrieved_config.format.filename, "updated_{name}.cache");
397 }
398
399 #[test]
400 fn test_validate_name() {
401 assert!(crate::utils::validate_name("valid_name").is_ok());
403 assert!(crate::utils::validate_name("valid123").is_ok());
404 assert!(crate::utils::validate_name("a").is_ok());
405
406 assert!(crate::utils::validate_name("").is_err());
408 assert!(crate::utils::validate_name(&"a".repeat(256)).is_err());
409 assert!(crate::utils::validate_name("test/name").is_err());
410 assert!(crate::utils::validate_name("test\\name").is_err());
411 assert!(crate::utils::validate_name("test..name").is_err());
412
413 #[cfg(windows)]
414 {
415 assert!(crate::utils::validate_name("CON").is_err());
416 assert!(crate::utils::validate_name("test:name").is_err());
417 assert!(crate::utils::validate_name("test<name").is_err());
418 }
419 }
420
421 #[test]
422 fn test_error_handling() {
423 let io_error = CacheError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
425 assert_eq!(io_error.kind(), "io");
426
427 let generic_error = CacheError::new("Test error");
428 assert_eq!(generic_error.kind(), "generic");
429 assert_eq!(generic_error.message(), "Test error");
430
431 let io_err: std::io::Error = std::io::Error::new(std::io::ErrorKind::Other, "test");
433 let cache_err: CacheError = io_err.into();
434 assert!(cache_err.is_io_error());
435
436 let json_err = serde_json::from_str::<CacheConfig>("invalid json");
437 assert!(json_err.is_err());
438 }
439
440 #[test]
441 fn test_cache_object_clone() {
442 let temp_dir = tempdir().unwrap();
443 let config_json = format!(
444 r#"{{
445 "path": {{
446 "windows": "{}",
447 "linux": "{}"
448 }},
449 "format": {{
450 "filename": "{{name}}.cache",
451 "time": "%Y%m%d"
452 }},
453 "max_size": 0,
454 "max_files": 0
455 }}"#,
456 temp_dir.path().to_string_lossy(),
457 temp_dir.path().to_string_lossy()
458 );
459
460 let config = CacheConfig::new(&config_json).unwrap();
461 let mut cache = Cache::new(config).unwrap();
462 let cache_obj = cache.create("clone_test", None).unwrap();
463
464 cache_obj.write_string("test data").unwrap();
466
467 let cloned = cache_obj.clone();
469 assert_eq!(cloned.name(), cache_obj.name());
470 assert_eq!(cloned.id(), cache_obj.id());
471 assert_eq!(cloned.path(), cache_obj.path());
472
473 let cloned_content = cloned.get_string().unwrap();
475 assert_eq!(cloned_content, "test data");
476 }
477
478 #[test]
479 fn test_expand_path() {
480 let path_with_tilde = "~/test/path";
482 let expanded = crate::utils::expand_path(path_with_tilde);
483 if let Some(home) = dirs::home_dir() {
484 let home_str = home.to_string_lossy();
485 assert!(expanded.starts_with(&*home_str));
486 }
487
488 #[cfg(windows)]
490 {
491 let path_with_env = "%temp%/test";
492 let expanded = crate::utils::expand_path(path_with_env);
493 assert!(!expanded.contains("%temp%"));
494 }
495
496 let unix_path = "path/to/file";
498 let expanded = crate::utils::expand_path(unix_path);
499
500 #[cfg(windows)]
501 assert!(expanded.contains('\\'));
502
503 #[cfg(unix)]
504 assert!(expanded.contains('/'));
505 }
506
507 #[test]
508 fn test_cache_clear_with_errors() {
509 let temp_dir = tempdir().unwrap();
510 let config_json = format!(
511 r#"{{
512 "path": {{
513 "windows": "{}",
514 "linux": "{}"
515 }},
516 "format": {{
517 "filename": "{{name}}.cache",
518 "time": "%Y%m%d"
519 }},
520 "max_size": 0,
521 "max_files": 0
522 }}"#,
523 temp_dir.path().to_string_lossy(),
524 temp_dir.path().to_string_lossy()
525 );
526
527 let config = CacheConfig::new(&config_json).unwrap();
528 let mut cache = Cache::new(config).unwrap();
529
530 let cache_obj = cache.create("test_clear", None).unwrap();
532
533 cache_obj.write_string("test data").unwrap();
535 assert!(cache_obj.exists());
536
537 cache.clear().unwrap();
539
540 assert_eq!(cache.len(), 0);
542 assert!(cache.is_empty());
543
544 assert!(!cache_obj.exists());
546 }
547
548 #[test]
549 fn test_error_matches() {
550 let io_error = CacheError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
552 assert!(io_error.is_io_error());
553
554 let not_found_error = CacheError::NotFound("test".to_string());
555 assert!(not_found_error.is_not_found());
556
557 let permission_error = CacheError::PermissionDenied("test".to_string());
558 assert!(permission_error.is_permission_denied());
559 }
560
561 #[test]
562 fn test_config_serde_roundtrip() {
563 let config = CacheConfig::default();
564 let json = serde_json::to_string(&config).unwrap();
565 let parsed_config = CacheConfig::new(&json).unwrap();
566
567 assert_eq!(config.max_size, parsed_config.max_size);
568 assert_eq!(config.max_files, parsed_config.max_files);
569 assert_eq!(config.path.windows, parsed_config.path.windows);
570 assert_eq!(config.path.linux, parsed_config.path.linux);
571 assert_eq!(config.format.filename, parsed_config.format.filename);
572 assert_eq!(config.format.time, parsed_config.format.time);
573 }
574}