ricecoder_refactoring/safety/
rollback.rs

1//! Rollback handling for refactoring operations
2
3use crate::error::{RefactoringError, Result};
4use crate::types::BackupInfo;
5use chrono::Utc;
6use std::collections::HashMap;
7use std::path::PathBuf;
8use uuid::Uuid;
9
10/// Handles rollback of refactoring operations
11pub struct RollbackHandler;
12
13impl RollbackHandler {
14    /// Create a backup of files before refactoring
15    pub fn create_backup(files: &[(PathBuf, String)]) -> Result<BackupInfo> {
16        let mut backup_files = HashMap::new();
17
18        for (path, content) in files {
19            backup_files.insert(path.clone(), content.clone());
20        }
21
22        Ok(BackupInfo {
23            id: Uuid::new_v4().to_string(),
24            timestamp: Utc::now().to_string(),
25            files: backup_files,
26        })
27    }
28
29    /// Restore files from a backup
30    pub fn restore_from_backup(backup: &BackupInfo) -> Result<()> {
31        for (path, content) in &backup.files {
32            std::fs::write(path, content).map_err(|e| {
33                RefactoringError::RollbackFailed(format!("Failed to restore {}: {}", path.display(), e))
34            })?;
35        }
36
37        Ok(())
38    }
39
40    /// Verify backup integrity
41    pub fn verify_backup(backup: &BackupInfo) -> Result<bool> {
42        if backup.files.is_empty() {
43            return Err(RefactoringError::RollbackFailed(
44                "Backup contains no files".to_string(),
45            ));
46        }
47
48        Ok(true)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use tempfile::NamedTempFile;
56    use std::io::Write;
57
58    #[test]
59    fn test_create_backup() -> Result<()> {
60        let files = vec![
61            (PathBuf::from("file1.rs"), "content1".to_string()),
62            (PathBuf::from("file2.rs"), "content2".to_string()),
63        ];
64
65        let backup = RollbackHandler::create_backup(&files)?;
66        assert_eq!(backup.files.len(), 2);
67        assert!(!backup.id.is_empty());
68
69        Ok(())
70    }
71
72    #[test]
73    fn test_verify_backup() -> Result<()> {
74        let files = vec![(PathBuf::from("file1.rs"), "content1".to_string())];
75        let backup = RollbackHandler::create_backup(&files)?;
76
77        assert!(RollbackHandler::verify_backup(&backup)?);
78
79        Ok(())
80    }
81
82    #[test]
83    fn test_verify_backup_empty() -> Result<()> {
84        let backup = BackupInfo {
85            id: "test".to_string(),
86            timestamp: Utc::now().to_string(),
87            files: HashMap::new(),
88        };
89
90        assert!(RollbackHandler::verify_backup(&backup).is_err());
91
92        Ok(())
93    }
94
95    #[test]
96    fn test_restore_from_backup() -> Result<()> {
97        let mut file = NamedTempFile::new()?;
98        file.write_all(b"original")?;
99        file.flush()?;
100
101        let path = file.path().to_path_buf();
102        let mut backup_files = HashMap::new();
103        backup_files.insert(path.clone(), "restored".to_string());
104
105        let backup = BackupInfo {
106            id: "test".to_string(),
107            timestamp: Utc::now().to_string(),
108            files: backup_files,
109        };
110
111        RollbackHandler::restore_from_backup(&backup)?;
112
113        let content = std::fs::read_to_string(&path)?;
114        assert_eq!(content, "restored");
115
116        Ok(())
117    }
118}