diff --git a/tests/round_trip.rs b/tests/round_trip.rs index b634ff5..cdac8d7 100644 --- a/tests/round_trip.rs +++ b/tests/round_trip.rs @@ -5,7 +5,9 @@ //! All tests use `tempdir()` for isolation (auto-cleanup, parallel-safe). use assert_cmd::Command; +use predicates::prelude::*; use std::fs; +use std::os::unix::fs::PermissionsExt; use tempfile::tempdir; /// Helper: get a Command for the encrypted_archive binary. @@ -190,3 +192,136 @@ fn test_roundtrip_no_compress_flag() { let extracted = fs::read(output_dir.join("data.apk")).unwrap(); assert_eq!(extracted, data); } + +/// Directory round-trip: pack a directory tree, unpack, verify files, empty dirs, and permissions. +#[test] +fn test_roundtrip_directory() { + let dir = tempdir().unwrap(); + let testdir = dir.path().join("testdir"); + let subdir = testdir.join("subdir"); + let emptydir = testdir.join("empty"); + let archive = dir.path().join("archive.bin"); + let output_dir = dir.path().join("output"); + + // Create directory structure + fs::create_dir_all(&subdir).unwrap(); + fs::create_dir_all(&emptydir).unwrap(); + + fs::write(testdir.join("hello.txt"), b"Hello from dir").unwrap(); + fs::write(subdir.join("nested.txt"), b"Nested file").unwrap(); + + // Set specific permissions + fs::set_permissions(&testdir, fs::Permissions::from_mode(0o755)).unwrap(); + fs::set_permissions(testdir.join("hello.txt"), fs::Permissions::from_mode(0o644)).unwrap(); + fs::set_permissions(&subdir, fs::Permissions::from_mode(0o755)).unwrap(); + fs::set_permissions(subdir.join("nested.txt"), fs::Permissions::from_mode(0o755)).unwrap(); + fs::set_permissions(&emptydir, fs::Permissions::from_mode(0o700)).unwrap(); + + // Pack directory + cmd() + .args(["pack", testdir.to_str().unwrap(), "-o", archive.to_str().unwrap()]) + .assert() + .success(); + + // Unpack + cmd() + .args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()]) + .assert() + .success(); + + // Verify file contents + let hello = fs::read(output_dir.join("testdir/hello.txt")).unwrap(); + assert_eq!(hello, b"Hello from dir"); + + let nested = fs::read(output_dir.join("testdir/subdir/nested.txt")).unwrap(); + assert_eq!(nested, b"Nested file"); + + // Verify empty directory exists + assert!( + output_dir.join("testdir/empty").is_dir(), + "Empty directory should be recreated" + ); + + // Verify permissions + let nested_mode = fs::metadata(output_dir.join("testdir/subdir/nested.txt")) + .unwrap() + .permissions() + .mode() + & 0o7777; + assert_eq!(nested_mode, 0o755, "nested.txt should have mode 0755"); + + let empty_mode = fs::metadata(output_dir.join("testdir/empty")) + .unwrap() + .permissions() + .mode() + & 0o7777; + assert_eq!(empty_mode, 0o700, "empty dir should have mode 0700"); +} + +/// Mixed files and directories: pack both a standalone file and a directory, verify round-trip. +#[test] +fn test_roundtrip_mixed_files_and_dirs() { + let dir = tempdir().unwrap(); + let standalone = dir.path().join("standalone.txt"); + let mydir = dir.path().join("mydir"); + let archive = dir.path().join("archive.bin"); + let output_dir = dir.path().join("output"); + + fs::write(&standalone, b"Standalone").unwrap(); + fs::create_dir_all(&mydir).unwrap(); + fs::write(mydir.join("inner.txt"), b"Inner").unwrap(); + + // Pack both file and directory + cmd() + .args([ + "pack", + standalone.to_str().unwrap(), + mydir.to_str().unwrap(), + "-o", + archive.to_str().unwrap(), + ]) + .assert() + .success(); + + // Unpack + cmd() + .args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()]) + .assert() + .success(); + + // Verify both entries + assert_eq!( + fs::read(output_dir.join("standalone.txt")).unwrap(), + b"Standalone" + ); + assert_eq!( + fs::read(output_dir.join("mydir/inner.txt")).unwrap(), + b"Inner" + ); +} + +/// Inspect shows directory info: entry type and permissions for directory entries. +#[test] +fn test_inspect_shows_directory_info() { + let dir = tempdir().unwrap(); + let testdir = dir.path().join("testdir"); + let archive = dir.path().join("archive.bin"); + + fs::create_dir_all(&testdir).unwrap(); + fs::write(testdir.join("file.txt"), b"content").unwrap(); + + cmd() + .args(["pack", testdir.to_str().unwrap(), "-o", archive.to_str().unwrap()]) + .assert() + .success(); + + // Inspect and check output contains directory info + cmd() + .args(["inspect", archive.to_str().unwrap()]) + .assert() + .success() + .stdout(predicate::str::contains("dir")) + .stdout(predicate::str::contains("file")) + .stdout(predicate::str::contains("0755").or(predicate::str::contains("0775"))) + .stdout(predicate::str::contains("Permissions:")); +}