pulith-fs 0.1.0

Cross-platform atomic filesystem primitives
Documentation
use pulith_fs::primitives::{hardlink, rw};
use tempfile::tempdir;

#[test]
fn test_atomic_write_basic() {
    let dir = tempdir().unwrap();
    let path = dir.path().join("test.txt");

    rw::atomic_write(&path, b"hello world", rw::Options::new()).unwrap();

    assert!(path.exists());
    assert_eq!(rw::atomic_read(&path).unwrap(), b"hello world");
}

#[test]
fn test_atomic_write_preserves_content_on_failure() {
    let dir = tempdir().unwrap();
    let path = dir.path().join("existing.txt");

    std::fs::write(&path, "original").unwrap();

    let result = rw::atomic_write(&path, b"new content", rw::Options::new());

    assert!(result.is_ok());
    assert_eq!(rw::atomic_read(&path).unwrap(), b"new content");
}

#[cfg(unix)]
#[test]
fn test_hardlink_or_copy_hardlink() {
    use pulith_fs::hardlink_or_copy;
    use std::os::unix::fs::MetadataExt;

    let dir = tempdir().unwrap();
    let src = dir.path().join("source.txt");
    let dest = dir.path().join("hardlink.txt");

    std::fs::write(&src, "shared content").unwrap();

    hardlink_or_copy(&src, &dest, hardlink::Options::new()).unwrap();

    assert!(dest.exists());

    let src_meta = std::fs::metadata(&src).unwrap();
    let dest_meta = std::fs::metadata(&dest).unwrap();

    assert_eq!(src_meta.ino(), dest_meta.ino());
}

#[cfg(not(unix))]
#[test]
fn test_hardlink_or_copy_hardlink() {
    let dir = tempdir().unwrap();
    let src = dir.path().join("source.txt");
    let dest = dir.path().join("hardlink.txt");

    std::fs::write(&src, "shared content").unwrap();

    hardlink::hardlink_or_copy(&src, &dest, hardlink::Options::new()).unwrap();

    assert!(dest.exists());
    assert_eq!(rw::atomic_read(&dest).unwrap(), b"shared content");
}

#[test]
fn test_hardlink_or_copy_fallback_copy() {
    let dir = tempdir().unwrap();
    let src = dir.path().join("source.txt");
    let dest = dir.path().join("copy.txt");

    std::fs::write(&src, "content to copy").unwrap();

    let options = hardlink::Options::new().fallback(hardlink::FallBack::Copy);
    hardlink::hardlink_or_copy(&src, &dest, options).unwrap();

    assert!(dest.exists());
    assert_eq!(rw::atomic_read(&dest).unwrap(), b"content to copy");
}

#[cfg(unix)]
#[test]
fn test_atomic_write_with_permissions() {
    use pulith_fs::{AtomicWriteOptions, PermissionMode, atomic_write};
    use std::os::unix::fs::PermissionsExt;

    let dir = tempdir().unwrap();
    let path = dir.path().join("executable.sh");

    atomic_write(
        &path,
        b"#!/bin/bash\necho hello",
        AtomicWriteOptions::new().permissions(PermissionMode::Custom(0o755)),
    )
    .unwrap();

    let metadata = std::fs::metadata(&path).unwrap();
    let perms = metadata.permissions().mode();

    assert_eq!(perms & 0o777, 0o755);
}

#[cfg(unix)]
#[test]
fn test_symlink_functionality() {
    use pulith_fs::atomic_read;
    use pulith_fs::atomic_symlink;

    let dir = tempdir().unwrap();
    let target = dir.path().join("target_file");
    let link = dir.path().join("symlink");

    std::fs::write(&target, "target content").unwrap();
    atomic_symlink(&target, &link).unwrap();

    assert!(link.is_symlink());
    assert_eq!(atomic_read(&link).unwrap(), b"target content");
}

#[cfg(unix)]
#[test]
fn test_hardlink_or_copy_directory() {
    use pulith_fs::{FallBack, hardlink_or_copy};

    let dir = tempdir().unwrap();
    let src = dir.path().join("source_dir");
    let dest = dir.path().join("dest_dir");

    std::fs::create_dir_all(&src).unwrap();
    std::fs::write(src.join("file1.txt"), "content1").unwrap();
    std::fs::write(src.join("file2.txt"), "content2").unwrap();

    let options = hardlink::Options::new().fallback(FallBack::Copy);
    hardlink_or_copy(&src, &dest, options).unwrap();

    assert!(dest.is_dir());
    assert!(dest.join("file1.txt").exists());
    assert!(dest.join("file2.txt").exists());
}

#[cfg(windows)]
#[test]
fn test_junction_creation() {
    use pulith_fs::primitives::symlink;

    let dir = tempdir().unwrap();
    let target = dir.path().join("target_dir");
    let junction = dir.path().join("junction_link");

    std::fs::create_dir_all(&target).unwrap();
    std::fs::write(target.join("file.txt"), "test").unwrap();

    if symlink::atomic_symlink(&target, &junction).is_ok() {
        assert!(junction.exists());
        assert!(junction.is_dir());
        assert!(junction.join("file.txt").exists());
    }
}

#[cfg(windows)]
#[test]
fn test_replace_directory() {
    use pulith_fs::primitives::replace_dir;

    let dir = tempdir().unwrap();
    let src = dir.path().join("new_version");
    let dest = dir.path().join("current");

    std::fs::create_dir_all(&src).unwrap();
    std::fs::write(src.join("bin.exe"), "binary").unwrap();

    replace_dir::replace_dir(&src, &dest, replace_dir::Options::new()).unwrap();

    assert!(dest.exists());
    assert!(dest.join("bin.exe").exists());
    assert!(!src.exists());
}