test(03-02): add CLI round-trip integration tests
- 6 integration tests via assert_cmd: single file, multiple files, empty, Cyrillic filename, 11MB large, APK no-compress - All tests verify byte-identical extraction (pack -> unpack -> assert_eq) - Uses tempdir() for isolation and parallel safety - Uses cargo_bin! macro (non-deprecated assert_cmd API) - Covers Phase 3 edge cases: 0-byte PKCS7, >10MB file, UTF-8 non-ASCII names Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
192
tests/round_trip.rs
Normal file
192
tests/round_trip.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
//! CLI round-trip integration tests.
|
||||
//!
|
||||
//! Each test runs the actual `encrypted_archive` binary via `assert_cmd`,
|
||||
//! packs files into an archive, unpacks them, and verifies byte-identical output.
|
||||
//! All tests use `tempdir()` for isolation (auto-cleanup, parallel-safe).
|
||||
|
||||
use assert_cmd::Command;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// Helper: get a Command for the encrypted_archive binary.
|
||||
fn cmd() -> Command {
|
||||
Command::new(assert_cmd::cargo::cargo_bin!("encrypted_archive"))
|
||||
}
|
||||
|
||||
/// Single text file: pack "Hello", unpack, verify byte-identical.
|
||||
#[test]
|
||||
fn test_roundtrip_single_text_file() {
|
||||
let dir = tempdir().unwrap();
|
||||
let input_file = dir.path().join("hello.txt");
|
||||
let archive = dir.path().join("archive.bin");
|
||||
let output_dir = dir.path().join("output");
|
||||
|
||||
fs::write(&input_file, b"Hello").unwrap();
|
||||
|
||||
cmd()
|
||||
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
cmd()
|
||||
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let extracted = fs::read(output_dir.join("hello.txt")).unwrap();
|
||||
assert_eq!(extracted, b"Hello");
|
||||
}
|
||||
|
||||
/// Multiple files: pack two files, unpack, verify both byte-identical.
|
||||
#[test]
|
||||
fn test_roundtrip_multiple_files() {
|
||||
let dir = tempdir().unwrap();
|
||||
let text_file = dir.path().join("text.txt");
|
||||
let binary_file = dir.path().join("binary.dat");
|
||||
let archive = dir.path().join("archive.bin");
|
||||
let output_dir = dir.path().join("output");
|
||||
|
||||
fs::write(&text_file, b"Some text content").unwrap();
|
||||
fs::write(&binary_file, &[0x42u8; 256]).unwrap();
|
||||
|
||||
cmd()
|
||||
.args([
|
||||
"pack",
|
||||
text_file.to_str().unwrap(),
|
||||
binary_file.to_str().unwrap(),
|
||||
"-o",
|
||||
archive.to_str().unwrap(),
|
||||
])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
cmd()
|
||||
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
assert_eq!(
|
||||
fs::read(output_dir.join("text.txt")).unwrap(),
|
||||
b"Some text content"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read(output_dir.join("binary.dat")).unwrap(),
|
||||
vec![0x42u8; 256]
|
||||
);
|
||||
}
|
||||
|
||||
/// Empty file: pack 0-byte file, unpack, verify extracted file is exactly 0 bytes.
|
||||
/// This tests PKCS7 full-block padding on 0-byte input (encrypted_size = 16).
|
||||
#[test]
|
||||
fn test_roundtrip_empty_file() {
|
||||
let dir = tempdir().unwrap();
|
||||
let input_file = dir.path().join("empty.txt");
|
||||
let archive = dir.path().join("archive.bin");
|
||||
let output_dir = dir.path().join("output");
|
||||
|
||||
fs::write(&input_file, b"").unwrap();
|
||||
|
||||
cmd()
|
||||
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
cmd()
|
||||
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let extracted = fs::read(output_dir.join("empty.txt")).unwrap();
|
||||
assert!(extracted.is_empty(), "Empty file should extract as 0 bytes");
|
||||
}
|
||||
|
||||
/// Cyrillic filename: pack file with UTF-8 non-ASCII name, unpack, verify content.
|
||||
/// This tests non-ASCII (UTF-8) filename handling in TOC entries.
|
||||
#[test]
|
||||
fn test_roundtrip_cyrillic_filename() {
|
||||
let dir = tempdir().unwrap();
|
||||
let input_file = dir.path().join("файл.txt");
|
||||
let archive = dir.path().join("archive.bin");
|
||||
let output_dir = dir.path().join("output");
|
||||
|
||||
let content = "Содержимое".as_bytes();
|
||||
fs::write(&input_file, content).unwrap();
|
||||
|
||||
cmd()
|
||||
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
cmd()
|
||||
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let extracted = fs::read(output_dir.join("файл.txt")).unwrap();
|
||||
assert_eq!(extracted, content);
|
||||
}
|
||||
|
||||
/// Large file (11MB): pack, unpack, verify byte-identical.
|
||||
/// Tests large file handling (>10MB) per Phase 3 success criteria.
|
||||
#[test]
|
||||
fn test_roundtrip_large_file() {
|
||||
let dir = tempdir().unwrap();
|
||||
let input_file = dir.path().join("large.bin");
|
||||
let archive = dir.path().join("archive.bin");
|
||||
let output_dir = dir.path().join("output");
|
||||
|
||||
// Generate 11MB of deterministic pseudo-random data
|
||||
let data: Vec<u8> = (0..11_000_000u32)
|
||||
.map(|i| i.wrapping_mul(2654435761) as u8)
|
||||
.collect();
|
||||
fs::write(&input_file, &data).unwrap();
|
||||
|
||||
// Pack with --no-compress bin (skip compression for binary extension)
|
||||
cmd()
|
||||
.args([
|
||||
"pack",
|
||||
input_file.to_str().unwrap(),
|
||||
"-o",
|
||||
archive.to_str().unwrap(),
|
||||
"--no-compress",
|
||||
"bin",
|
||||
])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
cmd()
|
||||
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let extracted = fs::read(output_dir.join("large.bin")).unwrap();
|
||||
assert_eq!(extracted.len(), data.len(), "Extracted file size must match original");
|
||||
assert_eq!(extracted, data, "Extracted file content must be byte-identical");
|
||||
}
|
||||
|
||||
/// No-compress flag: pack APK file (auto-detected as no-compress), unpack, verify.
|
||||
/// APK extension is in the known compressed extensions list, so compression_flag=0.
|
||||
#[test]
|
||||
fn test_roundtrip_no_compress_flag() {
|
||||
let dir = tempdir().unwrap();
|
||||
let input_file = dir.path().join("data.apk");
|
||||
let archive = dir.path().join("archive.bin");
|
||||
let output_dir = dir.path().join("output");
|
||||
|
||||
// 100 bytes of pattern data
|
||||
let data: Vec<u8> = (0..100u8).collect();
|
||||
fs::write(&input_file, &data).unwrap();
|
||||
|
||||
cmd()
|
||||
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
cmd()
|
||||
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let extracted = fs::read(output_dir.join("data.apk")).unwrap();
|
||||
assert_eq!(extracted, data);
|
||||
}
|
||||
Reference in New Issue
Block a user