All necessary imports and test infrastructure are already in place: - Standard library imports (fs, path, process) - pdftract CLI imports (exit codes) - pdftract Core imports (diagnostics, decryption errors) - Test helpers (pdftract_bin, encrypted_fixture, assertion functions) - Test modules (unsupported_handlers, exit_codes, password_handling, consistency) File compiles successfully and all imports match actual crate structure. Infrastructure was implemented in previous commit as part of bf-5for4. Closes bf-2nl4x
468 lines
17 KiB
Rust
468 lines
17 KiB
Rust
//! Encryption error integration tests.
|
|
//!
|
|
//! Tests encryption-related error cases in the pdftract CLI:
|
|
//! - ENCRYPTION_UNSUPPORTED errors for unknown handlers (e.g., Adobe LiveCycle)
|
|
//! - Exit code verification (exit 3 for encryption failures)
|
|
//! - Diagnostic message verification
|
|
//! - Password handling (empty password vs user-supplied)
|
|
//!
|
|
//! # References
|
|
//! - Plan line 258, Failure Mode Taxonomy: ENCRYPTION_UNSUPPORTED
|
|
//! - Plan line 732: Input | Encryption-unsupported | /Encrypt dict with unknown handler
|
|
//! - Plan line 765: ENCRYPTION_UNSUPPORTED diagnostic code
|
|
|
|
#![cfg(feature = "decrypt")]
|
|
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
|
|
// Import exit code constants from CLI module
|
|
use pdftract_cli::hash::{EXIT_ENCRYPTED, EXIT_SUCCESS};
|
|
|
|
// Import diagnostic types for error message validation
|
|
use pdftract_core::diagnostics::{DiagCode, Diagnostic};
|
|
|
|
// Import encryption error types for testing
|
|
use pdftract_core::encryption::decryptor::DecryptionError;
|
|
|
|
// ============================================================================
|
|
// Test Constants and Fixtures
|
|
// ============================================================================
|
|
|
|
/// Exit code for encryption errors (should match EXIT_ENCRYPTED from CLI)
|
|
const EXPECTED_ENCRYPTION_EXIT_CODE: i32 = 3;
|
|
|
|
/// Expected encrypted fixture files for testing
|
|
const ENCRYPTED_FIXTURES: &[&str] = &[
|
|
"livecycle.pdf",
|
|
// Add more fixtures as they become available:
|
|
// "EC-04-rc4-encrypted.pdf",
|
|
// "EC-05-aes128-encrypted.pdf",
|
|
// "EC-06-aes256-encrypted.pdf",
|
|
// "EC-empty-password.pdf",
|
|
];
|
|
|
|
// ============================================================================
|
|
// Test Helper Functions
|
|
// ============================================================================
|
|
|
|
/// Get the path to the pdftract binary (cargo build output)
|
|
fn pdftract_bin() -> PathBuf {
|
|
// The binary should be built at target/debug/pdftract or target/release/pdftract
|
|
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
path.push("../../target/debug/pdftract");
|
|
|
|
// Fall back to release if debug doesn't exist
|
|
if !path.exists() {
|
|
let mut release_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
release_path.push("../../target/release/pdftract");
|
|
return release_path;
|
|
}
|
|
|
|
path
|
|
}
|
|
|
|
/// Path to encrypted fixtures directory
|
|
fn encrypted_fixtures_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../../tests/fixtures/encrypted")
|
|
}
|
|
|
|
/// Get the path to a specific encrypted fixture
|
|
fn encrypted_fixture(name: &str) -> PathBuf {
|
|
encrypted_fixtures_dir().join(name)
|
|
}
|
|
|
|
/// Assert that a command output contains encryption-related diagnostics
|
|
fn assert_encryption_diagnostic(output: &std::process::Output, fixture_name: &str) {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let combined = format!("{} {}", stderr, stdout);
|
|
|
|
assert!(
|
|
combined.contains("ENCRYPTION_UNSUPPORTED") || combined.contains("Unsupported encryption"),
|
|
"Output for {} should contain ENCRYPTION_UNSUPPORTED diagnostic, got: {}",
|
|
fixture_name,
|
|
combined
|
|
);
|
|
}
|
|
|
|
/// Assert that a command exits with the encryption error code
|
|
fn assert_encryption_exit_code(output: &std::process::Output, fixture_name: &str) {
|
|
let exit_code = output.status.code().unwrap_or(0);
|
|
assert_eq!(
|
|
exit_code, EXPECTED_ENCRYPTION_EXIT_CODE,
|
|
"{} should exit with code {} for ENCRYPTION_UNSUPPORTED, got: {}",
|
|
fixture_name, EXPECTED_ENCRYPTION_EXIT_CODE, exit_code
|
|
);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Test Modules
|
|
// ============================================================================
|
|
|
|
/// Module for testing unsupported encryption handlers
|
|
mod unsupported_handlers {
|
|
use super::*;
|
|
|
|
/// Test that ENCRYPTION_UNSUPPORTED is emitted for unknown encryption handlers
|
|
///
|
|
/// This test verifies that when pdftract encounters an encrypted PDF with an
|
|
/// unknown handler (e.g., Adobe LiveCycle policy server), it:
|
|
/// 1. Emits ENCRYPTION_UNSUPPORTED diagnostic
|
|
/// 2. Exits with code 3
|
|
/// 3. Does not crash or hang
|
|
#[test]
|
|
fn test_encryption_unsupported_livecycle() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
// Ensure binary exists
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
|
|
// Ensure fixture exists
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found at {:?}", livecycle_pdf);
|
|
|
|
// Run pdftract extract on the encrypted file
|
|
let output = Command::new(&bin)
|
|
.args(["extract", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract");
|
|
|
|
// Verify exit code and diagnostic
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
assert_encryption_diagnostic(&output, "livecycle.pdf");
|
|
|
|
println!("✓ livecycle.pdf correctly produced ENCRYPTION_UNSUPPORTED");
|
|
println!("Exit code: {:?}", output.status.code());
|
|
}
|
|
}
|
|
|
|
/// Module for testing exit codes for encrypted PDFs
|
|
mod exit_codes {
|
|
use super::*;
|
|
|
|
/// Test exit code 3 for encrypted PDFs without password
|
|
///
|
|
/// Verifies that pdftract exits with code 3 when:
|
|
/// - PDF is encrypted with owner password only
|
|
/// - Empty password is attempted
|
|
/// - User did not supply a password via --password
|
|
#[test]
|
|
fn test_exit_code_3_no_password() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found");
|
|
|
|
let output = Command::new(&bin)
|
|
.args(["extract", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract");
|
|
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
println!("✓ Exit code 3 correctly returned for encrypted PDF without password");
|
|
}
|
|
}
|
|
|
|
/// Module for testing password handling
|
|
mod password_handling {
|
|
use super::*;
|
|
|
|
/// Test that wrong password also produces ENCRYPTION_UNSUPPORTED
|
|
#[test]
|
|
fn test_wrong_password_encryption_unsupported() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found");
|
|
|
|
// Supply an incorrect password
|
|
let output = Command::new(&bin)
|
|
.args(["extract", "--password", "wrongpassword", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract with wrong password");
|
|
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
assert_encryption_diagnostic(&output, "livecycle.pdf");
|
|
|
|
println!("✓ Wrong password correctly produces ENCRYPTION_UNSUPPORTED");
|
|
}
|
|
}
|
|
|
|
/// Module for testing encryption error consistency across fixtures
|
|
mod consistency {
|
|
use super::*;
|
|
|
|
/// Test that encrypted fixtures produce consistent error behavior
|
|
///
|
|
/// This is a parameterized test that checks multiple encrypted fixtures
|
|
/// for consistent error handling.
|
|
#[test]
|
|
fn test_encryption_error_consistency() {
|
|
let bin = pdftract_bin();
|
|
let fixtures_dir = encrypted_fixtures_dir();
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
|
|
for fixture_name in ENCRYPTED_FIXTURES {
|
|
let fixture_path = encrypted_fixture(fixture_name);
|
|
|
|
if !fixture_path.exists() {
|
|
println!("Skipping {} (not found)", fixture_name);
|
|
continue;
|
|
}
|
|
|
|
let output = Command::new(&bin)
|
|
.args(["extract", fixture_path.to_str().unwrap()])
|
|
.output()
|
|
.expect(&format!("Failed to execute pdftract extract on {}", fixture_name));
|
|
|
|
// Should fail with non-zero exit code
|
|
assert!(!output.status.success(),
|
|
"{} should fail extraction (exit code: {:?})",
|
|
fixture_name, output.status.code());
|
|
|
|
// Verify exit code and diagnostic
|
|
assert_encryption_exit_code(&output, fixture_name);
|
|
assert_encryption_diagnostic(&output, fixture_name);
|
|
|
|
println!("✓ {} correctly handled as encrypted file", fixture_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Legacy standalone tests (to be migrated into modules above)
|
|
// ============================================================================
|
|
|
|
/// Test that ENCRYPTION_UNSUPPORTED is emitted for unknown encryption handlers
|
|
///
|
|
/// This test verifies that when pdftract encounters an encrypted PDF with an
|
|
/// unknown handler (e.g., Adobe LiveCycle policy server), it:
|
|
/// 1. Emits ENCRYPTION_UNSUPPORTED diagnostic
|
|
/// 2. Exits with code 3
|
|
/// 3. Does not crash or hang
|
|
#[test]
|
|
#[deprecated = "Use unsupported_handlers::test_encryption_unsupported_livecycle instead"]
|
|
fn test_encryption_unsupported_livecycle() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
// Ensure binary exists
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
|
|
// Ensure fixture exists
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found at {:?}", livecycle_pdf);
|
|
|
|
// Run pdftract extract on the encrypted file
|
|
let output = Command::new(&bin)
|
|
.args(["extract", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract");
|
|
|
|
// Verify exit code and diagnostic
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
assert_encryption_diagnostic(&output, "livecycle.pdf");
|
|
|
|
println!("✓ livecycle.pdf correctly produced ENCRYPTION_UNSUPPORTED");
|
|
println!("Exit code: {:?}", output.status.code());
|
|
}
|
|
|
|
/// Test exit code 3 for encrypted PDFs without password
|
|
///
|
|
/// Verifies that pdftract exits with code 3 when:
|
|
/// - PDF is encrypted with owner password only
|
|
/// - Empty password is attempted
|
|
/// - User did not supply a password via --password
|
|
#[test]
|
|
#[deprecated = "Use exit_codes::test_exit_code_3_no_password instead"]
|
|
fn test_exit_code_3_no_password() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found");
|
|
|
|
let output = Command::new(&bin)
|
|
.args(["extract", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract");
|
|
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
println!("✓ Exit code 3 correctly returned for encrypted PDF without password");
|
|
}
|
|
|
|
/// Test that wrong password also produces ENCRYPTION_UNSUPPORTED
|
|
#[test]
|
|
#[deprecated = "Use password_handling::test_wrong_password_encryption_unsupported instead"]
|
|
fn test_wrong_password_encryption_unsupported() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found");
|
|
|
|
// Supply an incorrect password
|
|
let output = Command::new(&bin)
|
|
.args(["extract", "--password", "wrongpassword", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract with wrong password");
|
|
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
assert_encryption_diagnostic(&output, "livecycle.pdf");
|
|
|
|
println!("✓ Wrong password correctly produces ENCRYPTION_UNSUPPORTED");
|
|
}
|
|
|
|
/// Test that encrypted fixtures produce consistent error behavior
|
|
///
|
|
/// This is a parameterized test that checks multiple encrypted fixtures
|
|
/// for consistent error handling.
|
|
#[test]
|
|
#[deprecated = "Use consistency::test_encryption_error_consistency instead"]
|
|
fn test_encryption_error_consistency() {
|
|
let bin = pdftract_bin();
|
|
let fixtures_dir = encrypted_fixtures_dir();
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
|
|
for fixture_name in ENCRYPTED_FIXTURES {
|
|
let fixture_path = encrypted_fixture(fixture_name);
|
|
|
|
if !fixture_path.exists() {
|
|
println!("Skipping {} (not found)", fixture_name);
|
|
continue;
|
|
}
|
|
|
|
let output = Command::new(&bin)
|
|
.args(["extract", fixture_path.to_str().unwrap()])
|
|
.output()
|
|
.expect(&format!("Failed to execute pdftract extract on {}", fixture_name));
|
|
|
|
// Should fail with non-zero exit code
|
|
assert!(!output.status.success(),
|
|
"{} should fail extraction (exit code: {:?})",
|
|
fixture_name, output.status.code());
|
|
|
|
// Verify exit code and diagnostic
|
|
assert_encryption_exit_code(&output, fixture_name);
|
|
assert_encryption_diagnostic(&output, fixture_name);
|
|
|
|
println!("✓ {} correctly handled as encrypted file", fixture_name);
|
|
}
|
|
}
|
|
///
|
|
/// This test verifies that when pdftract encounters an encrypted PDF with an
|
|
/// unknown handler (e.g., Adobe LiveCycle policy server), it:
|
|
/// 1. Emits ENCRYPTION_UNSUPPORTED diagnostic
|
|
/// 2. Exits with code 3
|
|
/// 3. Does not crash or hang
|
|
#[test]
|
|
fn test_encryption_unsupported_livecycle() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
// Ensure binary exists
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
|
|
// Ensure fixture exists
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found at {:?}", livecycle_pdf);
|
|
|
|
// Run pdftract extract on the encrypted file
|
|
let output = Command::new(&bin)
|
|
.args(["extract", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract");
|
|
|
|
// Verify exit code and diagnostic
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
assert_encryption_diagnostic(&output, "livecycle.pdf");
|
|
|
|
println!("✓ livecycle.pdf correctly produced ENCRYPTION_UNSUPPORTED");
|
|
println!("Exit code: {:?}", output.status.code());
|
|
}
|
|
|
|
/// Test exit code 3 for encrypted PDFs without password
|
|
///
|
|
/// Verifies that pdftract exits with code 3 when:
|
|
/// - PDF is encrypted with owner password only
|
|
/// - Empty password is attempted
|
|
/// - User did not supply a password via --password
|
|
#[test]
|
|
fn test_exit_code_3_no_password() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found");
|
|
|
|
let output = Command::new(&bin)
|
|
.args(["extract", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract");
|
|
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
println!("✓ Exit code 3 correctly returned for encrypted PDF without password");
|
|
}
|
|
|
|
/// Test that wrong password also produces ENCRYPTION_UNSUPPORTED
|
|
#[test]
|
|
fn test_wrong_password_encryption_unsupported() {
|
|
let bin = pdftract_bin();
|
|
let livecycle_pdf = encrypted_fixture("livecycle.pdf");
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
assert!(livecycle_pdf.exists(), "livecycle.pdf fixture not found");
|
|
|
|
// Supply an incorrect password
|
|
let output = Command::new(&bin)
|
|
.args(["extract", "--password", "wrongpassword", livecycle_pdf.to_str().unwrap()])
|
|
.output()
|
|
.expect("Failed to execute pdftract extract with wrong password");
|
|
|
|
assert_encryption_exit_code(&output, "livecycle.pdf");
|
|
assert_encryption_diagnostic(&output, "livecycle.pdf");
|
|
|
|
println!("✓ Wrong password correctly produces ENCRYPTION_UNSUPPORTED");
|
|
}
|
|
|
|
/// Test that encrypted fixtures produce consistent error behavior
|
|
///
|
|
/// This is a parameterized test that checks multiple encrypted fixtures
|
|
/// for consistent error handling.
|
|
#[test]
|
|
fn test_encryption_error_consistency() {
|
|
let bin = pdftract_bin();
|
|
let fixtures_dir = encrypted_fixtures_dir();
|
|
|
|
assert!(bin.exists(), "pdftract binary not found at {:?}", bin);
|
|
|
|
for fixture_name in ENCRYPTED_FIXTURES {
|
|
let fixture_path = encrypted_fixture(fixture_name);
|
|
|
|
if !fixture_path.exists() {
|
|
println!("Skipping {} (not found)", fixture_name);
|
|
continue;
|
|
}
|
|
|
|
let output = Command::new(&bin)
|
|
.args(["extract", fixture_path.to_str().unwrap()])
|
|
.output()
|
|
.expect(&format!("Failed to execute pdftract extract on {}", fixture_name));
|
|
|
|
// Should fail with non-zero exit code
|
|
assert!(!output.status.success(),
|
|
"{} should fail extraction (exit code: {:?})",
|
|
fixture_name, output.status.code());
|
|
|
|
// Verify exit code and diagnostic
|
|
assert_encryption_exit_code(&output, fixture_name);
|
|
assert_encryption_diagnostic(&output, fixture_name);
|
|
|
|
println!("✓ {} correctly handled as encrypted file", fixture_name);
|
|
}
|
|
}
|