agentsec-core 0.5.0

AgentSec core library — scan / web / paste logic, pure Rust
Documentation
//! Backup helper for installer mutations.
//!
//! Before any write the installer calls [`backup`] to create a timestamped
//! copy of the original file. If the file does not yet exist, no backup is
//! created and an empty [`PathBuf`] is returned so callers can detect the
//! "no backup needed" case.

use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

use crate::error::Result;

/// Copy `path` to `<path>.bak.<epoch_ms>` and return the backup path.
///
/// If `path` does not exist (i.e. the file will be created fresh by the
/// installer), no copy is performed and `Ok(PathBuf::new())` is returned.
/// Callers can detect this sentinel by calling `.as_os_str().is_empty()`.
pub fn backup(path: &Path) -> Result<PathBuf> {
    if !path.exists() {
        return Ok(PathBuf::new());
    }
    let epoch_ms = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system clock before UNIX epoch")
        .as_millis();
    let bak = PathBuf::from(format!("{}.bak.{}", path.display(), epoch_ms));
    std::fs::copy(path, &bak)?;
    Ok(bak)
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn backup_creates_bak_file_with_same_content() {
        let tmp = TempDir::new().unwrap();
        let src = tmp.path().join("test.json");
        std::fs::write(&src, b"hello").unwrap();

        let bak = backup(&src).unwrap();
        assert!(
            !bak.as_os_str().is_empty(),
            "backup path should not be empty"
        );
        assert!(bak.exists(), "backup file should exist");

        // Verify filename pattern: ends with .bak.<digits>
        let fname = bak.file_name().unwrap().to_str().unwrap();
        assert!(fname.starts_with("test.json.bak."), "got: {fname}");
        let suffix = fname.trim_start_matches("test.json.bak.");
        assert!(
            suffix.chars().all(|c| c.is_ascii_digit()),
            "epoch part must be digits, got: {suffix}"
        );

        // Same content
        let content = std::fs::read(&bak).unwrap();
        assert_eq!(content, b"hello");
    }

    #[test]
    fn backup_nonexistent_returns_empty_path() {
        let tmp = TempDir::new().unwrap();
        let missing = tmp.path().join("does_not_exist.json");

        let result = backup(&missing).unwrap();
        assert!(result.as_os_str().is_empty(), "should return empty PathBuf");
    }
}