use std::fs::OpenOptions;
use std::io::Write;
use std::time::SystemTime;
use mod_tempdir::NamedTempFile;
fn unique_target(tag: &str) -> std::path::PathBuf {
let nanos = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let pid = std::process::id();
std::env::temp_dir().join(format!("persist-atomic-{tag}-{pid}-{nanos}"))
}
fn write_payload(path: &std::path::Path, data: &[u8]) {
let mut h = OpenOptions::new()
.write(true)
.open(path)
.expect("open temp file for write");
h.write_all(data).expect("write payload");
}
#[test]
fn moves_temp_file_to_target_and_preserves_content() {
let f = NamedTempFile::new().expect("NamedTempFile::new failed");
let source = f.path().to_path_buf();
write_payload(&source, b"persist_atomic preserves bytes");
let target = unique_target("preserve");
let _ = std::fs::remove_file(&target);
let landed = f
.persist_atomic(&target)
.expect("persist_atomic failed on same-filesystem move");
assert_eq!(landed, target, "persist_atomic returned wrong path");
assert!(
target.is_file(),
"target does not exist after persist_atomic"
);
assert!(
!source.exists(),
"source temp file still exists after persist_atomic"
);
let contents = std::fs::read(&target).expect("read target");
assert_eq!(contents, b"persist_atomic preserves bytes");
let _ = std::fs::remove_file(&target);
}
#[test]
fn replaces_existing_target() {
let target = unique_target("replace");
std::fs::write(&target, b"stale").expect("seed target");
let f = NamedTempFile::new().expect("NamedTempFile::new failed");
write_payload(f.path(), b"fresh");
let landed = f
.persist_atomic(&target)
.expect("persist_atomic should replace existing target");
let contents = std::fs::read(&landed).expect("read target");
assert_eq!(
contents, b"fresh",
"target was not replaced by persist_atomic"
);
let _ = std::fs::remove_file(&landed);
}
#[test]
fn errors_when_target_parent_missing_and_preserves_source() {
let f = NamedTempFile::new().expect("NamedTempFile::new failed");
let source = f.path().to_path_buf();
let target = std::env::temp_dir()
.join("nonexistent-parent-for-persist-atomic")
.join("file.bin");
let err = f
.persist_atomic(&target)
.expect_err("persist_atomic should error when target parent missing");
assert!(
source.exists(),
"source temp file should survive a failed persist_atomic"
);
assert_eq!(
err.file.path(),
source.as_path(),
"recovered NamedTempFile should point at the original temp path"
);
}
#[test]
fn original_path_no_longer_exists_after_success() {
let f = NamedTempFile::new().expect("NamedTempFile::new failed");
let source = f.path().to_path_buf();
let target = unique_target("gone");
let _ = std::fs::remove_file(&target);
let landed = f.persist_atomic(&target).expect("persist_atomic failed");
assert!(!source.exists(), "source temp file should be gone");
assert!(target.is_file(), "target should exist");
assert_eq!(landed, target);
let _ = std::fs::remove_file(&target);
}