1use anyhow::Result;
2use chrono::{DateTime, Utc};
3use handlebars::Handlebars;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::{HashMap, HashSet};
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11#[derive(Debug, Clone)]
13pub struct VirtualFileSystem {
14 #[allow(dead_code)]
15 root: PathBuf,
16 files: Arc<RwLock<HashMap<PathBuf, VirtualFile>>>,
17 fixtures: Arc<RwLock<HashMap<PathBuf, FileFixture>>>,
18 directories: Arc<RwLock<HashSet<PathBuf>>>,
19}
20
21impl VirtualFileSystem {
22 pub fn new(root: PathBuf) -> Self {
23 let mut dirs = HashSet::new();
24 dirs.insert(PathBuf::from("/"));
25 Self {
26 root,
27 files: Arc::new(RwLock::new(HashMap::new())),
28 fixtures: Arc::new(RwLock::new(HashMap::new())),
29 directories: Arc::new(RwLock::new(dirs)),
30 }
31 }
32
33 pub fn add_file(&self, path: PathBuf, file: VirtualFile) -> Result<()> {
34 let mut files = self.files.blocking_write();
35 files.insert(path, file);
36 Ok(())
37 }
38
39 pub fn get_file(&self, path: &Path) -> Option<VirtualFile> {
40 let files = self.files.blocking_read();
41 if let Some(file) = files.get(path) {
42 return Some(file.clone());
43 }
44
45 let fixtures = self.fixtures.blocking_read();
47 if let Some(fixture) = fixtures.get(path) {
48 return Some(fixture.clone().to_virtual_file());
49 }
50
51 None
52 }
53
54 pub fn remove_file(&self, path: &Path) -> Result<()> {
55 let mut files = self.files.blocking_write();
56 files.remove(path);
57 Ok(())
58 }
59
60 pub fn list_files(&self, path: &Path) -> Vec<VirtualFile> {
61 let files = self.files.blocking_read();
62 files
63 .iter()
64 .filter(|(file_path, _)| file_path.starts_with(path))
65 .map(|(_, file)| file.clone())
66 .collect()
67 }
68
69 pub fn clear(&self) -> Result<()> {
70 let mut files = self.files.blocking_write();
71 files.clear();
72 Ok(())
73 }
74
75 pub fn add_fixture(&self, fixture: FileFixture) -> Result<()> {
76 let mut fixtures = self.fixtures.blocking_write();
77 fixtures.insert(fixture.path.clone(), fixture);
78 Ok(())
79 }
80
81 pub fn load_fixtures(&self, fixtures: Vec<FileFixture>) -> Result<()> {
82 for fixture in fixtures {
83 self.add_fixture(fixture)?;
84 }
85 Ok(())
86 }
87
88 pub fn create_directory(&self, path: PathBuf) -> Result<()> {
90 let mut dirs = self.directories.blocking_write();
91 dirs.insert(path);
92 Ok(())
93 }
94
95 pub fn remove_directory(&self, path: &Path) -> Result<()> {
97 if self.is_directory_empty(path) {
98 let mut dirs = self.directories.blocking_write();
99 dirs.remove(path);
100 Ok(())
101 } else {
102 Err(anyhow::anyhow!("Directory is not empty"))
103 }
104 }
105
106 pub fn directory_exists(&self, path: &Path) -> bool {
108 let dirs = self.directories.blocking_read();
109 dirs.contains(path)
110 }
111
112 pub fn is_directory_empty(&self, path: &Path) -> bool {
114 let files = self.files.blocking_read();
115 let has_files =
116 files.keys().any(|file_path| file_path != path && file_path.starts_with(path));
117 if has_files {
118 return false;
119 }
120
121 let dirs = self.directories.blocking_read();
122 let has_subdirs =
123 dirs.iter().any(|dir_path| dir_path != path && dir_path.starts_with(path));
124 !has_subdirs
125 }
126
127 pub async fn add_file_async(&self, path: PathBuf, file: VirtualFile) -> Result<()> {
129 let mut files = self.files.write().await;
130 files.insert(path, file);
131 Ok(())
132 }
133
134 pub async fn list_files_async(&self, path: &Path) -> Vec<VirtualFile> {
136 let files = self.files.read().await;
137 files
138 .iter()
139 .filter(|(file_path, _)| file_path.starts_with(path))
140 .map(|(_, file)| file.clone())
141 .collect()
142 }
143
144 pub async fn remove_file_async(&self, path: &Path) -> Result<()> {
146 let mut files = self.files.write().await;
147 files.remove(path);
148 Ok(())
149 }
150
151 pub async fn directory_exists_async(&self, path: &Path) -> bool {
153 let dirs = self.directories.read().await;
154 dirs.contains(path)
155 }
156
157 pub async fn create_directory_async(&self, path: PathBuf) -> Result<()> {
159 let mut dirs = self.directories.write().await;
160 dirs.insert(path);
161 Ok(())
162 }
163
164 pub async fn is_directory_empty_async(&self, path: &Path) -> bool {
166 let files = self.files.read().await;
167 let has_files =
168 files.keys().any(|file_path| file_path != path && file_path.starts_with(path));
169 if has_files {
170 return false;
171 }
172
173 let dirs = self.directories.read().await;
174 !dirs.iter().any(|dir_path| dir_path != path && dir_path.starts_with(path))
175 }
176
177 pub async fn remove_directory_async(&self, path: &Path) -> Result<()> {
179 if self.is_directory_empty_async(path).await {
180 let mut dirs = self.directories.write().await;
181 dirs.remove(path);
182 Ok(())
183 } else {
184 Err(anyhow::anyhow!("Directory is not empty"))
185 }
186 }
187
188 pub async fn get_file_async(&self, path: &Path) -> Option<VirtualFile> {
190 let files = self.files.read().await;
191 if let Some(file) = files.get(path) {
192 return Some(file.clone());
193 }
194
195 let fixtures = self.fixtures.read().await;
197 if let Some(fixture) = fixtures.get(path) {
198 return Some(fixture.clone().to_virtual_file());
199 }
200
201 None
202 }
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct VirtualFile {
208 pub path: PathBuf,
209 pub content: FileContent,
210 pub metadata: FileMetadata,
211 pub created_at: DateTime<Utc>,
212 pub modified_at: DateTime<Utc>,
213}
214
215impl VirtualFile {
216 pub fn new(path: PathBuf, content: FileContent, metadata: FileMetadata) -> Self {
217 let now = Utc::now();
218 Self {
219 path,
220 content,
221 metadata,
222 created_at: now,
223 modified_at: now,
224 }
225 }
226
227 pub fn render_content(&self) -> Result<Vec<u8>> {
228 match &self.content {
229 FileContent::Static(data) => Ok(data.clone()),
230 FileContent::Template(template) => {
231 let handlebars = Handlebars::new();
233 let context = create_template_context();
234 let rendered = handlebars.render_template(template, &context)?;
235 Ok(rendered.into_bytes())
236 }
237 FileContent::Generated { size, pattern } => match pattern {
238 GenerationPattern::Random => Ok((0..*size).map(|_| rand::random::<u8>()).collect()),
239 GenerationPattern::Zeros => Ok(vec![0; *size]),
240 GenerationPattern::Ones => Ok(vec![1; *size]),
241 GenerationPattern::Incremental => Ok((0..*size).map(|i| (i % 256) as u8).collect()),
242 },
243 }
244 }
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub enum FileContent {
250 Static(Vec<u8>),
251 Template(String),
252 Generated {
253 size: usize,
254 pattern: GenerationPattern,
255 },
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct FileMetadata {
261 pub permissions: String,
262 pub owner: String,
263 pub group: String,
264 pub size: u64,
265}
266
267impl Default for FileMetadata {
268 fn default() -> Self {
269 Self {
270 permissions: "644".to_string(),
271 owner: "mockforge".to_string(),
272 group: "users".to_string(),
273 size: 0,
274 }
275 }
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
280pub enum GenerationPattern {
281 Random,
282 Zeros,
283 Ones,
284 Incremental,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct FileFixture {
290 pub path: PathBuf,
291 pub content: FileContent,
292 pub metadata: FileMetadata,
293}
294
295impl FileFixture {
296 pub fn to_virtual_file(self) -> VirtualFile {
297 VirtualFile::new(self.path, self.content, self.metadata)
298 }
299}
300
301fn create_template_context() -> Value {
303 let mut context = serde_json::Map::new();
304
305 let now = Utc::now();
307 context.insert("now".to_string(), Value::String(now.to_rfc3339()));
308 context.insert("timestamp".to_string(), Value::Number(now.timestamp().into()));
309 context.insert("date".to_string(), Value::String(now.format("%Y-%m-%d").to_string()));
310 context.insert("time".to_string(), Value::String(now.format("%H:%M:%S").to_string()));
311
312 context.insert("random_int".to_string(), Value::Number(rand::random::<i64>().into()));
314 context.insert(
315 "random_float".to_string(),
316 Value::String(format!("{:.6}", rand::random::<f64>())),
317 );
318
319 context.insert("uuid".to_string(), Value::String(uuid::Uuid::new_v4().to_string()));
321
322 let mut faker = serde_json::Map::new();
324 faker.insert("name".to_string(), Value::String("John Doe".to_string()));
325 faker.insert("email".to_string(), Value::String("john.doe@example.com".to_string()));
326 faker.insert("age".to_string(), Value::Number(30.into()));
327 context.insert("faker".to_string(), Value::Object(faker));
328
329 Value::Object(context)
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn test_file_metadata_default() {
338 let metadata = FileMetadata::default();
339 assert_eq!(metadata.permissions, "644");
340 assert_eq!(metadata.owner, "mockforge");
341 assert_eq!(metadata.group, "users");
342 assert_eq!(metadata.size, 0);
343 }
344
345 #[test]
346 fn test_file_metadata_clone() {
347 let metadata = FileMetadata {
348 permissions: "755".to_string(),
349 owner: "root".to_string(),
350 group: "root".to_string(),
351 size: 1024,
352 };
353
354 let cloned = metadata.clone();
355 assert_eq!(metadata.permissions, cloned.permissions);
356 assert_eq!(metadata.owner, cloned.owner);
357 assert_eq!(metadata.size, cloned.size);
358 }
359
360 #[test]
361 fn test_generation_pattern_clone() {
362 let pattern = GenerationPattern::Random;
363 let _cloned = pattern.clone();
364 }
366
367 #[test]
368 fn test_generation_pattern_debug() {
369 let pattern = GenerationPattern::Zeros;
370 let debug = format!("{:?}", pattern);
371 assert!(debug.contains("Zeros"));
372 }
373
374 #[test]
375 fn test_file_content_static() {
376 let content = FileContent::Static(b"hello world".to_vec());
377 let debug = format!("{:?}", content);
378 assert!(debug.contains("Static"));
379 }
380
381 #[test]
382 fn test_file_content_template() {
383 let content = FileContent::Template("Hello {{name}}".to_string());
384 let debug = format!("{:?}", content);
385 assert!(debug.contains("Template"));
386 }
387
388 #[test]
389 fn test_file_content_generated() {
390 let content = FileContent::Generated {
391 size: 100,
392 pattern: GenerationPattern::Ones,
393 };
394 let debug = format!("{:?}", content);
395 assert!(debug.contains("Generated"));
396 }
397
398 #[test]
399 fn test_virtual_file_new() {
400 let file = VirtualFile::new(
401 PathBuf::from("/test.txt"),
402 FileContent::Static(b"content".to_vec()),
403 FileMetadata::default(),
404 );
405
406 assert_eq!(file.path, PathBuf::from("/test.txt"));
407 }
408
409 #[test]
410 fn test_virtual_file_render_static() {
411 let file = VirtualFile::new(
412 PathBuf::from("/test.txt"),
413 FileContent::Static(b"hello".to_vec()),
414 FileMetadata::default(),
415 );
416
417 let content = file.render_content().unwrap();
418 assert_eq!(content, b"hello".to_vec());
419 }
420
421 #[test]
422 fn test_virtual_file_render_generated_zeros() {
423 let file = VirtualFile::new(
424 PathBuf::from("/zeros.bin"),
425 FileContent::Generated {
426 size: 10,
427 pattern: GenerationPattern::Zeros,
428 },
429 FileMetadata::default(),
430 );
431
432 let content = file.render_content().unwrap();
433 assert_eq!(content.len(), 10);
434 assert!(content.iter().all(|&b| b == 0));
435 }
436
437 #[test]
438 fn test_virtual_file_render_generated_ones() {
439 let file = VirtualFile::new(
440 PathBuf::from("/ones.bin"),
441 FileContent::Generated {
442 size: 10,
443 pattern: GenerationPattern::Ones,
444 },
445 FileMetadata::default(),
446 );
447
448 let content = file.render_content().unwrap();
449 assert_eq!(content.len(), 10);
450 assert!(content.iter().all(|&b| b == 1));
451 }
452
453 #[test]
454 fn test_virtual_file_render_generated_incremental() {
455 let file = VirtualFile::new(
456 PathBuf::from("/inc.bin"),
457 FileContent::Generated {
458 size: 256,
459 pattern: GenerationPattern::Incremental,
460 },
461 FileMetadata::default(),
462 );
463
464 let content = file.render_content().unwrap();
465 assert_eq!(content.len(), 256);
466 for (i, &b) in content.iter().enumerate() {
467 assert_eq!(b, i as u8);
468 }
469 }
470
471 #[test]
472 fn test_virtual_file_render_generated_random() {
473 let file = VirtualFile::new(
474 PathBuf::from("/random.bin"),
475 FileContent::Generated {
476 size: 100,
477 pattern: GenerationPattern::Random,
478 },
479 FileMetadata::default(),
480 );
481
482 let content = file.render_content().unwrap();
483 assert_eq!(content.len(), 100);
484 }
485
486 #[test]
487 fn test_virtual_file_clone() {
488 let file = VirtualFile::new(
489 PathBuf::from("/test.txt"),
490 FileContent::Static(b"test".to_vec()),
491 FileMetadata::default(),
492 );
493
494 let cloned = file.clone();
495 assert_eq!(file.path, cloned.path);
496 }
497
498 #[test]
499 fn test_virtual_file_debug() {
500 let file = VirtualFile::new(
501 PathBuf::from("/test.txt"),
502 FileContent::Static(vec![]),
503 FileMetadata::default(),
504 );
505
506 let debug = format!("{:?}", file);
507 assert!(debug.contains("VirtualFile"));
508 }
509
510 #[test]
511 fn test_file_fixture_to_virtual_file() {
512 let fixture = FileFixture {
513 path: PathBuf::from("/fixture.txt"),
514 content: FileContent::Static(b"fixture content".to_vec()),
515 metadata: FileMetadata::default(),
516 };
517
518 let file = fixture.to_virtual_file();
519 assert_eq!(file.path, PathBuf::from("/fixture.txt"));
520 }
521
522 #[test]
523 fn test_file_fixture_clone() {
524 let fixture = FileFixture {
525 path: PathBuf::from("/test.txt"),
526 content: FileContent::Static(vec![]),
527 metadata: FileMetadata::default(),
528 };
529
530 let cloned = fixture.clone();
531 assert_eq!(fixture.path, cloned.path);
532 }
533
534 #[test]
535 fn test_vfs_new() {
536 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
537 let files = vfs.list_files(&PathBuf::from("/"));
538 assert!(files.is_empty());
539 }
540
541 #[test]
542 fn test_vfs_add_file() {
543 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
544 let file = VirtualFile::new(
545 PathBuf::from("/test.txt"),
546 FileContent::Static(b"hello".to_vec()),
547 FileMetadata::default(),
548 );
549
550 vfs.add_file(PathBuf::from("/test.txt"), file).unwrap();
551
552 let retrieved = vfs.get_file(&PathBuf::from("/test.txt"));
553 assert!(retrieved.is_some());
554 }
555
556 #[test]
557 fn test_vfs_get_file_not_found() {
558 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
559 let retrieved = vfs.get_file(&PathBuf::from("/nonexistent.txt"));
560 assert!(retrieved.is_none());
561 }
562
563 #[test]
564 fn test_vfs_remove_file() {
565 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
566 let file = VirtualFile::new(
567 PathBuf::from("/test.txt"),
568 FileContent::Static(vec![]),
569 FileMetadata::default(),
570 );
571
572 vfs.add_file(PathBuf::from("/test.txt"), file).unwrap();
573 vfs.remove_file(&PathBuf::from("/test.txt")).unwrap();
574
575 let retrieved = vfs.get_file(&PathBuf::from("/test.txt"));
576 assert!(retrieved.is_none());
577 }
578
579 #[test]
580 fn test_vfs_list_files() {
581 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
582
583 let file1 = VirtualFile::new(
584 PathBuf::from("/dir/file1.txt"),
585 FileContent::Static(vec![]),
586 FileMetadata::default(),
587 );
588 let file2 = VirtualFile::new(
589 PathBuf::from("/dir/file2.txt"),
590 FileContent::Static(vec![]),
591 FileMetadata::default(),
592 );
593
594 vfs.add_file(PathBuf::from("/dir/file1.txt"), file1).unwrap();
595 vfs.add_file(PathBuf::from("/dir/file2.txt"), file2).unwrap();
596
597 let files = vfs.list_files(&PathBuf::from("/dir"));
598 assert_eq!(files.len(), 2);
599 }
600
601 #[test]
602 fn test_vfs_clear() {
603 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
604
605 let file = VirtualFile::new(
606 PathBuf::from("/test.txt"),
607 FileContent::Static(vec![]),
608 FileMetadata::default(),
609 );
610 vfs.add_file(PathBuf::from("/test.txt"), file).unwrap();
611
612 vfs.clear().unwrap();
613
614 let files = vfs.list_files(&PathBuf::from("/"));
615 assert!(files.is_empty());
616 }
617
618 #[test]
619 fn test_vfs_add_fixture() {
620 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
621 let fixture = FileFixture {
622 path: PathBuf::from("/fixture.txt"),
623 content: FileContent::Static(b"fixture".to_vec()),
624 metadata: FileMetadata::default(),
625 };
626
627 vfs.add_fixture(fixture).unwrap();
628
629 let file = vfs.get_file(&PathBuf::from("/fixture.txt"));
630 assert!(file.is_some());
631 }
632
633 #[test]
634 fn test_vfs_load_fixtures() {
635 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
636 let fixtures = vec![
637 FileFixture {
638 path: PathBuf::from("/f1.txt"),
639 content: FileContent::Static(vec![]),
640 metadata: FileMetadata::default(),
641 },
642 FileFixture {
643 path: PathBuf::from("/f2.txt"),
644 content: FileContent::Static(vec![]),
645 metadata: FileMetadata::default(),
646 },
647 ];
648
649 vfs.load_fixtures(fixtures).unwrap();
650
651 assert!(vfs.get_file(&PathBuf::from("/f1.txt")).is_some());
652 assert!(vfs.get_file(&PathBuf::from("/f2.txt")).is_some());
653 }
654
655 #[test]
656 fn test_vfs_clone() {
657 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
658 let _cloned = vfs.clone();
659 }
661
662 #[test]
663 fn test_vfs_debug() {
664 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
665 let debug = format!("{:?}", vfs);
666 assert!(debug.contains("VirtualFileSystem"));
667 }
668
669 #[test]
670 fn test_template_context_has_expected_fields() {
671 let context = create_template_context();
672 assert!(context.get("now").is_some());
673 assert!(context.get("timestamp").is_some());
674 assert!(context.get("date").is_some());
675 assert!(context.get("uuid").is_some());
676 assert!(context.get("faker").is_some());
677 }
678
679 #[test]
680 fn test_virtual_file_render_template() {
681 let file = VirtualFile::new(
682 PathBuf::from("/template.txt"),
683 FileContent::Template("Hello {{faker.name}}!".to_string()),
684 FileMetadata::default(),
685 );
686
687 let content = file.render_content().unwrap();
688 let text = String::from_utf8(content).unwrap();
689 assert!(text.contains("Hello"));
690 assert!(text.contains("John Doe")); }
692
693 #[test]
694 fn test_virtual_file_render_template_with_timestamp() {
695 let file = VirtualFile::new(
696 PathBuf::from("/timestamp.txt"),
697 FileContent::Template("Current timestamp: {{timestamp}}".to_string()),
698 FileMetadata::default(),
699 );
700
701 let content = file.render_content().unwrap();
702 let text = String::from_utf8(content).unwrap();
703 assert!(text.contains("Current timestamp:"));
704 }
705
706 #[test]
707 fn test_virtual_file_render_template_with_uuid() {
708 let file = VirtualFile::new(
709 PathBuf::from("/uuid.txt"),
710 FileContent::Template("ID: {{uuid}}".to_string()),
711 FileMetadata::default(),
712 );
713
714 let content = file.render_content().unwrap();
715 let text = String::from_utf8(content).unwrap();
716 assert!(text.starts_with("ID: "));
717 let uuid_part = text.trim_start_matches("ID: ");
719 assert!(!uuid_part.is_empty());
720 }
721
722 #[test]
723 fn test_vfs_get_file_from_fixtures() {
724 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
725 let fixture = FileFixture {
726 path: PathBuf::from("/fixture.txt"),
727 content: FileContent::Static(b"fixture content".to_vec()),
728 metadata: FileMetadata::default(),
729 };
730
731 vfs.add_fixture(fixture).unwrap();
732
733 let file = vfs.get_file(&PathBuf::from("/fixture.txt"));
734 assert!(file.is_some());
735 let content = file.unwrap().render_content().unwrap();
736 assert_eq!(content, b"fixture content");
737 }
738
739 #[test]
740 fn test_vfs_files_priority_over_fixtures() {
741 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
742
743 let fixture = FileFixture {
745 path: PathBuf::from("/test.txt"),
746 content: FileContent::Static(b"fixture".to_vec()),
747 metadata: FileMetadata::default(),
748 };
749 vfs.add_fixture(fixture).unwrap();
750
751 let file = VirtualFile::new(
753 PathBuf::from("/test.txt"),
754 FileContent::Static(b"file".to_vec()),
755 FileMetadata::default(),
756 );
757 vfs.add_file(PathBuf::from("/test.txt"), file).unwrap();
758
759 let retrieved = vfs.get_file(&PathBuf::from("/test.txt")).unwrap();
761 let content = retrieved.render_content().unwrap();
762 assert_eq!(content, b"file");
763 }
764
765 #[test]
766 fn test_vfs_list_files_empty_path() {
767 let vfs = VirtualFileSystem::new(PathBuf::from("/"));
768
769 let file1 = VirtualFile::new(
770 PathBuf::from("/file1.txt"),
771 FileContent::Static(vec![]),
772 FileMetadata::default(),
773 );
774 let file2 = VirtualFile::new(
775 PathBuf::from("/subdir/file2.txt"),
776 FileContent::Static(vec![]),
777 FileMetadata::default(),
778 );
779
780 vfs.add_file(PathBuf::from("/file1.txt"), file1).unwrap();
781 vfs.add_file(PathBuf::from("/subdir/file2.txt"), file2).unwrap();
782
783 let files = vfs.list_files(&PathBuf::from("/"));
785 assert_eq!(files.len(), 2);
786 }
787
788 #[test]
789 fn test_virtual_file_serialization() {
790 let file = VirtualFile::new(
791 PathBuf::from("/test.txt"),
792 FileContent::Static(b"test".to_vec()),
793 FileMetadata::default(),
794 );
795
796 let serialized = serde_json::to_string(&file);
798 assert!(serialized.is_ok());
799
800 let deserialized: Result<VirtualFile, _> = serde_json::from_str(&serialized.unwrap());
802 assert!(deserialized.is_ok());
803 }
804
805 #[test]
806 fn test_file_metadata_serialization() {
807 let metadata = FileMetadata {
808 permissions: "755".to_string(),
809 owner: "root".to_string(),
810 group: "admin".to_string(),
811 size: 2048,
812 };
813
814 let serialized = serde_json::to_string(&metadata);
815 assert!(serialized.is_ok());
816
817 let deserialized: Result<FileMetadata, _> = serde_json::from_str(&serialized.unwrap());
818 assert!(deserialized.is_ok());
819 }
820
821 #[test]
822 fn test_file_content_serialization() {
823 let content = FileContent::Static(b"test content".to_vec());
824 let serialized = serde_json::to_string(&content);
825 assert!(serialized.is_ok());
826 }
827
828 #[test]
829 fn test_generation_pattern_serialization() {
830 let pattern = GenerationPattern::Random;
831 let serialized = serde_json::to_string(&pattern);
832 assert!(serialized.is_ok());
833 }
834}