pdftract/tests/encryption_fixtures.rs
jedarden b13db18929 fix(bf-4wbf2): fix UNMAPPED_GLYPH_NAMES visibility and verify TestExecutionResult
- Fix build.rs to generate pub static UNMAPPED_GLYPH_NAMES (was private)
- Remove redundant pub use in unmapped.rs (definition already included)
- Verify TestExecutionResult infrastructure is complete and functional
- Add comprehensive verification note at notes/bf-4wbf2.md

Closes bf-4wbf2. Verification: notes/bf-4wbf2.md.

Acceptance criteria:
- All TestExecutionResult fields present and working ✓
- All assertion methods functional (11 general + 5 encryption) ✓
- Encryption-specific assertions work correctly ✓
- Clear error messages on assertion failure ✓
- Code compiles successfully ✓
2026-07-06 13:03:02 -04:00

1012 lines
32 KiB
Rust

//! Common test fixtures and helper functions for encryption testing
//!
//! This module provides shared utilities for encryption-related tests across
//! the pdftract codebase. It includes:
//! - Test fixture path resolution
//! - Binary path resolution
//! - Common test constants
//! - Helper functions for running pdftract and validating output
//! - Mock data builders for encryption dictionaries
//!
//! # Usage
//!
//! ```rust
//! use pdftract_tests::encryption_fixtures::*;
//!
//! let bin = pdftract_bin();
//! let fixture = encrypted_fixture("livecycle.pdf");
//! let output = run_pdftract_extract(&bin, &fixture, None);
//! assert_encryption_exit_code(&output, &fixture);
//! ```
use std::path::{Path, PathBuf};
use std::process::Command;
use std::fs;
// Re-export exit code constants from CLI module
pub use pdftract_cli::hash::{EXIT_ENCRYPTED, EXIT_SUCCESS};
// Re-export diagnostic types for error message validation
pub use pdftract_core::diagnostics::{DiagCode, Diagnostic};
// Re-export encryption error types for testing
pub use pdftract_core::encryption::decryptor::DecryptionError;
// ============================================================================
// Test Constants
// ============================================================================
/// Exit code for encryption errors (should match EXIT_ENCRYPTED from CLI)
pub const EXPECTED_ENCRYPTION_EXIT_CODE: i32 = 3;
/// Expected encrypted fixture files for testing
pub const ENCRYPTED_FIXTURES: &[&str] = &[
"livecycle.pdf",
"EC-04-rc4-encrypted.pdf",
"EC-05-aes128-encrypted.pdf",
"EC-06-aes256-encrypted.pdf",
"EC-empty-password.pdf",
];
/// Test password for encrypted fixtures (when available)
pub const TEST_PASSWORD: &str = "test123";
/// Wrong password for testing error handling
pub const WRONG_PASSWORD: &str = "wrongpassword";
// ============================================================================
// Path Resolution Functions
// ============================================================================
/// Get the workspace root directory
pub fn workspace_root() -> PathBuf {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.unwrap_or_else(|_| ".".to_string());
let path = PathBuf::from(manifest_dir);
// If we're in tests/ directory, parent() is workspace root
// If we're in crates/pdftract-cli/tests/, go up two levels
if path.ends_with("tests") {
path.parent().unwrap().to_path_buf()
} else if path.ends_with("pdftract-cli") {
path.parent().unwrap().parent().unwrap().to_path_buf()
} else if path.ends_with("pdftract-core") {
path.parent().unwrap().parent().unwrap().to_path_buf()
} else {
path
}
}
/// Get the path to the pdftract binary (cargo build output)
pub fn pdftract_bin() -> PathBuf {
let mut path = workspace_root();
path.push("target/debug/pdftract");
// Fall back to release if debug doesn't exist
if !path.exists() {
let mut release_path = workspace_root();
release_path.push("target/release/pdftract");
return release_path;
}
path
}
/// Path to encrypted fixtures directory
pub fn encrypted_fixtures_dir() -> PathBuf {
workspace_root().join("tests/fixtures/encrypted")
}
/// Get the path to a specific encrypted fixture
pub fn encrypted_fixture(name: &str) -> PathBuf {
encrypted_fixtures_dir().join(name)
}
/// Path to general fixtures directory
pub fn fixtures_dir() -> PathBuf {
workspace_root().join("tests/fixtures")
}
/// Get the path to a general fixture (not encrypted)
pub fn fixture(name: &str) -> PathBuf {
fixtures_dir().join(name)
}
// ============================================================================
// Test Execution Helpers
// ============================================================================
/// Run pdftract extract on a file with optional password
pub fn run_pdftract_extract(
bin: &Path,
pdf_path: &Path,
password: Option<&str>,
) -> std::process::Output {
let mut cmd = Command::new(bin);
cmd.args(["extract", pdf_path.to_str().unwrap()]);
if let Some(pwd) = password {
cmd.args(["--password", pwd]);
}
cmd.output().expect("Failed to execute pdftract extract")
}
/// Run pdftract extract and return TestExecutionResult
pub fn run_pdftract_extract_test(
bin: &Path,
pdf_path: &Path,
password: Option<&str>,
) -> TestExecutionResult {
let fixture_name = pdf_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
let output = run_pdftract_extract(bin, pdf_path, password);
TestExecutionResult::with_fixture(output, fixture_name)
}
/// Run pdftract extract with stdin password
pub fn run_pdftract_extract_with_stdin_password(
bin: &Path,
pdf_path: &Path,
password: &str,
) -> std::process::Output {
Command::new(bin)
.args(["extract", pdf_path.to_str().unwrap()])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.output()
.expect("Failed to execute pdftract extract with stdin password")
}
/// Run pdftract extract with stdin password and return TestExecutionResult
pub fn run_pdftract_extract_stdin_test(
bin: &Path,
pdf_path: &Path,
password: &str,
) -> TestExecutionResult {
let fixture_name = pdf_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
let output = run_pdftract_extract_with_stdin_password(bin, pdf_path, password);
TestExecutionResult::with_fixture(output, fixture_name)
}
// ============================================================================
// Test Execution Result Struct
// ============================================================================
/// Result of running a CLI command for testing purposes.
///
/// This struct wraps `std::process::Output` and provides convenient
/// assertion methods for validating command execution results in tests.
#[derive(Debug, Clone)]
pub struct TestExecutionResult {
/// The underlying process output
pub output: std::process::Output,
/// Optional fixture name for better error messages
pub fixture_name: Option<String>,
}
impl TestExecutionResult {
/// Create a new TestExecutionResult from process output
pub fn new(output: std::process::Output) -> Self {
Self {
output,
fixture_name: None,
}
}
/// Create a new TestExecutionResult with associated fixture name
pub fn with_fixture(output: std::process::Output, fixture_name: &str) -> Self {
Self {
output,
fixture_name: Some(fixture_name.to_string()),
}
}
/// Get the exit code (returns None if process terminated by signal)
pub fn exit_code(&self) -> Option<i32> {
self.output.status.code()
}
/// Get stdout as a String
pub fn stdout(&self) -> String {
String::from_utf8_lossy(&self.output.stdout).to_string()
}
/// Get stderr as a String
pub fn stderr(&self) -> String {
String::from_utf8_lossy(&self.output.stderr).to_string()
}
/// Check if the command succeeded (exit code 0)
pub fn success(&self) -> bool {
self.output.status.success()
}
/// Get combined stdout and stderr
pub fn combined_output(&self) -> String {
format!("{} {}", self.stdout(), self.stderr())
}
// ========================================================================
// Assertion Methods
// ========================================================================
/// Assert that stderr contains specific text
pub fn assert_stderr_contains(&self, text: &str) -> &Self {
let stderr = self.stderr();
assert!(
stderr.contains(text),
"Expected stderr to contain '{}', got: {}",
text,
stderr
);
self
}
/// Assert that stdout contains specific text
pub fn assert_stdout_contains(&self, text: &str) -> &Self {
let stdout = self.stdout();
assert!(
stdout.contains(text),
"Expected stdout to contain '{}', got: {}",
text,
stdout
);
self
}
/// Assert that the exit code matches a specific value
pub fn assert_exit_code(&self, expected: i32) -> &Self {
let actual = self.exit_code();
let context = self.fixture_name.as_deref().unwrap_or("command");
assert_eq!(
actual,
Some(expected),
"Expected {} to exit with code {}, got {:?}",
context,
expected,
actual
);
self
}
/// Assert that the command succeeded (exit code 0)
pub fn assert_success(&self) -> &Self {
let context = self.fixture_name.as_deref().unwrap_or("command");
assert!(
self.success(),
"Expected {} to succeed, got exit code: {:?}",
context,
self.exit_code()
);
self
}
/// Assert that the command failed (non-zero exit code)
pub fn assert_failure(&self) -> &Self {
let context = self.fixture_name.as_deref().unwrap_or("command");
assert!(
!self.success(),
"Expected {} to fail, got exit code: {:?}",
context,
self.exit_code()
);
self
}
/// Assert that combined output contains specific text
pub fn assert_output_contains(&self, text: &str) -> &Self {
let combined = self.combined_output();
assert!(
combined.contains(text),
"Expected output to contain '{}', got: {}",
text,
combined
);
self
}
// ========================================================================
// Encryption-Specific Assertion Methods
// ========================================================================
/// Assert that this is an unsupported encryption error
///
/// Checks for:
/// - Exit code 3 (EXPECTED_ENCRYPTION_EXIT_CODE)
/// - Error message containing "ENCRYPTION_UNSUPPORTED" or "Unsupported encryption"
pub fn assert_unsupported_encryption(&self) -> &Self {
let context = self.fixture_name.as_deref().unwrap_or("encrypted PDF");
let combined = self.combined_output();
// Check exit code
self.assert_exit_code(EXPECTED_ENCRYPTION_EXIT_CODE);
// Check error message
assert!(
combined.contains("ENCRYPTION_UNSUPPORTED")
|| combined.contains("Unsupported encryption")
|| combined.contains("unsupported encryption handler"),
"Expected {} to emit ENCRYPTION_UNSUPPORTED diagnostic, got: {}",
context,
combined
);
self
}
/// Assert that a password is required/missing error
///
/// Checks for:
/// - Exit code 3 (EXPECTED_ENCRYPTION_EXIT_CODE)
/// - Error message indicating password requirement
pub fn assert_password_required(&self) -> &Self {
let context = self.fixture_name.as_deref().unwrap_or("encrypted PDF");
let combined = self.combined_output();
// Check exit code
self.assert_exit_code(EXPECTED_ENCRYPTION_EXIT_CODE);
// Check for password-related error message
assert!(
combined.contains("password")
|| combined.contains("Password")
|| combined.contains("PASSWORD_REQUIRED"),
"Expected {} to emit password-required diagnostic, got: {}",
context,
combined
);
self
}
/// Assert that the provided password was wrong
///
/// Checks for:
/// - Exit code 3 (EXPECTED_ENCRYPTION_EXIT_CODE)
/// - Error message indicating wrong/invalid password
pub fn assert_wrong_password(&self) -> &Self {
let context = self.fixture_name.as_deref().unwrap_or("encrypted PDF");
let combined = self.combined_output();
// Check exit code
self.assert_exit_code(EXPECTED_ENCRYPTION_EXIT_CODE);
// Check for wrong password error message
assert!(
combined.contains("wrong password")
|| combined.contains("incorrect password")
|| combined.contains("invalid password")
|| combined.contains("Password incorrect"),
"Expected {} to emit wrong-password diagnostic, got: {}",
context,
combined
);
self
}
/// Assert that the command produced encryption-related diagnostics
///
/// This is a more general check that looks for any encryption-related
/// error messages in the output.
pub fn assert_encryption_diagnostic(&self) -> &Self {
let context = self.fixture_name.as_deref().unwrap_or("encrypted PDF");
let combined = self.combined_output();
assert!(
combined.contains("ENCRYPTION_UNSUPPORTED")
|| combined.contains("Unsupported encryption")
|| combined.contains("encrypted")
|| combined.contains("ENCRYPTION"),
"Expected {} to contain encryption-related diagnostic, got: {}",
context,
combined
);
self
}
/// Assert that the output is empty (no extraction occurred)
///
/// Useful for verifying that encrypted PDFs don't leak content
pub fn assert_empty_output(&self) -> &Self {
let stdout = self.stdout();
let context = self.fixture_name.as_deref().unwrap_or("command");
assert!(
stdout.is_empty() || !stdout.contains("\"pages\""),
"Expected {} to produce no extraction output, got: {}",
context,
stdout
);
self
}
}
impl From<std::process::Output> for TestExecutionResult {
fn from(output: std::process::Output) -> Self {
Self::new(output)
}
}
// ============================================================================
// Legacy Assertion Helpers (backwards compatibility)
// ============================================================================
/// Assert that a command output contains encryption-related diagnostics
pub fn assert_encryption_diagnostic(output: &std::process::Output, fixture_name: &str) {
let result = TestExecutionResult::with_fixture(output.clone(), fixture_name);
result.assert_encryption_diagnostic();
}
/// Assert that a command exits with the encryption error code
pub fn assert_encryption_exit_code(output: &std::process::Output, fixture_name: &str) {
let result = TestExecutionResult::with_fixture(output.clone(), fixture_name);
result.assert_exit_code(EXPECTED_ENCRYPTION_EXIT_CODE);
}
/// Assert that a command exits with success code
pub fn assert_success_exit_code(output: &std::process::Output) {
let result = TestExecutionResult::new(output.clone());
result.assert_success();
}
/// Assert that a command failed with non-zero exit code
pub fn assert_failure_exit_code(output: &std::process::Output) {
let result = TestExecutionResult::new(output.clone());
result.assert_failure();
}
/// Assert output contains specific message
pub fn assert_output_contains(output: &std::process::Output, message: &str) {
let result = TestExecutionResult::new(output.clone());
result.assert_output_contains(message);
}
// ============================================================================
// Fixture Validation Helpers
// ============================================================================
/// Verify a fixture file exists
pub fn assert_fixture_exists(fixture_path: &Path) {
assert!(
fixture_path.exists(),
"Fixture not found: {}",
fixture_path.display()
);
}
/// Verify a fixture file is a valid PDF (basic structure check)
pub fn assert_valid_pdf_structure(fixture_path: &Path) {
let content = fs::read(fixture_path)
.expect(&format!("Failed to read fixture: {}", fixture_path.display()));
// Check for PDF magic number (%PDF-)
assert!(
content.starts_with(b"%PDF-"),
"Fixture {} is not a valid PDF (missing %PDF- header)",
fixture_path.display()
);
// Check for EOF marker
let content_str = String::from_utf8_lossy(&content);
assert!(
content_str.contains("%%EOF"),
"Fixture {} is not a valid PDF (missing %%EOF marker)",
fixture_path.display()
);
}
/// Verify all encrypted fixture files exist
pub fn assert_encrypted_fixtures_exist() {
let fixture_dir = encrypted_fixtures_dir();
assert!(
fixture_dir.exists(),
"Encrypted fixtures directory not found: {}",
fixture_dir.display()
);
for fixture_name in ENCRYPTED_FIXTURES {
let path = encrypted_fixture(fixture_name);
if !path.exists() {
println!("Skipping {} (not found)", fixture_name);
}
}
}
// ============================================================================
// Mock Data Builders (for core encryption tests)
// ============================================================================
#[cfg(feature = "decrypt")]
use pdftract_core::parser::object::{PdfDict, PdfObject};
/// Create a PdfDict from a vector of key-value pairs
#[cfg(feature = "decrypt")]
pub fn make_dict(entries: Vec<(&str, PdfObject)>) -> PdfDict {
entries.into_iter().map(|(k, v)| (k.into(), v)).collect()
}
/// Create a mock trailer dictionary with encryption
#[cfg(feature = "decrypt")]
pub fn make_trailer(encrypt_dict: PdfDict, id: Option<Vec<u8>>) -> PdfDict {
let mut trailer = make_dict(vec![
("/Root", PdfObject::Ref(pdftract_core::parser::object::ObjRef::new(1, 0))),
("/Encrypt", PdfObject::Dict(Box::new(encrypt_dict))),
]);
if let Some(id_bytes) = id {
trailer.insert(
"/ID".into(),
PdfObject::Array(Box::new(vec![PdfObject::String(Box::new(id_bytes))]))
);
}
trailer
}
/// Create a mock RC4-40 encryption dictionary (V=1, R=2)
#[cfg(feature = "decrypt")]
pub fn make_rc4_encryption_dict() -> PdfDict {
make_dict(vec![
("/Filter", PdfObject::Name("Standard".into())),
("/V", PdfObject::Integer(1)),
("/R", PdfObject::Integer(2)),
("/O", PdfObject::String(Box::new(vec![0u8; 32]))),
("/U", PdfObject::String(Box::new(vec![0u8; 32]))),
("/P", PdfObject::Integer(0xFFFFFFFF_i64)),
])
}
/// Create a mock AES-128 encryption dictionary (V=4, R=4)
#[cfg(feature = "decrypt")]
pub fn make_aes128_encryption_dict() -> PdfDict {
make_dict(vec![
("/Filter", PdfObject::Name("Standard".into())),
("/V", PdfObject::Integer(4)),
("/R", PdfObject::Integer(4)),
("/O", PdfObject::String(Box::new(vec![0u8; 32]))),
("/U", PdfObject::String(Box::new(vec![0u8; 32]))),
("/P", PdfObject::Integer(0xFFFFFFFF_i64)),
("/StmF", PdfObject::Name("/Identity".into())),
("/StrF", PdfObject::Name("/Identity".into())),
])
}
/// Create a mock AES-256 encryption dictionary (V=5, R=6)
#[cfg(feature = "decrypt")]
pub fn make_aes256_encryption_dict() -> PdfDict {
make_dict(vec![
("/Filter", PdfObject::Name("Standard".into())),
("/V", PdfObject::Integer(5)),
("/R", PdfObject::Integer(6)),
("/O", PdfObject::String(Box::new(vec![0u8; 48]))),
("/U", PdfObject::String(Box::new(vec![0u8; 48]))),
("/P", PdfObject::Integer(0xFFFFFFFF_i64)),
("/UE", PdfObject::String(Box::new(vec![0u8; 32]))),
("/OE", PdfObject::String(Box::new(vec![0u8; 32]))),
("/Perms", PdfObject::String(Box::new({
let mut perms = [0u8; 16];
perms[0..4].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
perms.to_vec()
}))),
])
}
/// Create a mock unsupported encryption dictionary
#[cfg(feature = "decrypt")]
pub fn make_unsupported_encryption_dict(filter_name: &str) -> PdfDict {
make_dict(vec![
("/Filter", PdfObject::Name(filter_name.into())),
("/V", PdfObject::Integer(1)),
("/R", PdfObject::Integer(2)),
])
}
// ============================================================================
// Test Suite Builders
// ============================================================================
/// Macro to generate parameterized tests for multiple fixtures
#[macro_export]
macro_rules! test_all_fixtures {
($test_name:ident, $test_body:expr) => {
pub fn $test_name() {
let bin = pdftract_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;
}
$test_body(&bin, &fixture_path, fixture_name);
}
}
};
}
// ============================================================================
// Module Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_workspace_root_resolution() {
let root = workspace_root();
assert!(root.exists(), "Workspace root should exist: {}", root.display());
}
#[test]
fn test_encrypted_fixtures_dir_exists() {
let dir = encrypted_fixtures_dir();
assert!(dir.exists(), "Encrypted fixtures directory should exist: {}", dir.display());
}
#[test]
fn test_pdftract_bin_path() {
let bin = pdftract_bin();
// Don't assert existence here since binary might not be built yet
assert!(bin.ends_with("pdftract"), "Binary path should end with 'pdftract'");
}
#[test]
fn test_livecycle_fixture_path() {
let fixture = encrypted_fixture("livecycle.pdf");
assert!(
fixture.ends_with("tests/fixtures/encrypted/livecycle.pdf"),
"Fixture path should resolve correctly: {}",
fixture.display()
);
}
#[test]
fn test_constants_defined() {
assert_eq!(EXPECTED_ENCRYPTION_EXIT_CODE, 3);
assert!(!ENCRYPTED_FIXTURES.is_empty());
assert!(!TEST_PASSWORD.is_empty());
assert!(!WRONG_PASSWORD.is_empty());
}
#[test]
#[cfg(feature = "decrypt")]
fn test_make_dict_creates_valid_dict() {
let dict = make_dict(vec![
("/Filter", PdfObject::Name("Standard".into())),
("/V", PdfObject::Integer(1)),
]);
assert!(dict.contains_key("/Filter"));
assert!(dict.contains_key("/V"));
}
#[test]
#[cfg(feature = "decrypt")]
fn test_rc4_encryption_dict_builder() {
let dict = make_rc4_encryption_dict();
assert!(dict.contains_key("/Filter"));
assert!(dict.contains_key("/V"));
assert!(dict.contains_key("/R"));
}
#[test]
#[cfg(feature = "decrypt")]
fn test_aes128_encryption_dict_builder() {
let dict = make_aes128_encryption_dict();
assert!(dict.contains_key("/Filter"));
assert!(dict.contains_key("/V"));
assert!(dict.contains_key("/StmF"));
}
#[test]
#[cfg(feature = "decrypt")]
fn test_aes256_encryption_dict_builder() {
let dict = make_aes256_encryption_dict();
assert!(dict.contains_key("/Filter"));
assert!(dict.contains_key("/V"));
assert!(dict.contains_key("/UE"));
assert!(dict.contains_key("/OE"));
}
// ========================================================================
// TestExecutionResult Tests
// ========================================================================
#[test]
fn test_execution_result_creation() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(0),
stdout: b"test output".to_vec(),
stderr: b"test error".to_vec(),
};
let result = TestExecutionResult::new(output.clone());
assert_eq!(result.exit_code(), Some(0));
assert_eq!(result.stdout(), "test output");
assert_eq!(result.stderr(), "test error");
assert!(result.success());
}
#[test]
fn test_execution_result_with_fixture() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(3),
stdout: b"".to_vec(),
stderr: b"Unsupported encryption".to_vec(),
};
let result = TestExecutionResult::with_fixture(output, "test.pdf");
assert_eq!(result.exit_code(), Some(3));
assert_eq!(result.fixture_name, Some("test.pdf".to_string()));
}
#[test]
fn test_execution_result_assert_exit_code() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(3),
stdout: b"".to_vec(),
stderr: b"error".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_exit_code(3); // Should not panic
}
#[test]
#[should_panic(expected = "Expected command to exit with code 0")]
fn test_execution_result_assert_exit_code_failure() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(3),
stdout: b"".to_vec(),
stderr: b"error".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_exit_code(0); // Should panic
}
#[test]
fn test_execution_result_assert_success() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(0),
stdout: b"success".to_vec(),
stderr: b"".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_success(); // Should not panic
}
#[test]
#[should_panic(expected = "Expected command to succeed")]
fn test_execution_result_assert_success_failure() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(1),
stdout: b"error".to_vec(),
stderr: b"error".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_success(); // Should panic
}
#[test]
fn test_execution_result_assert_failure() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(1),
stdout: b"error".to_vec(),
stderr: b"error".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_failure(); // Should not panic
}
#[test]
fn test_execution_result_assert_stderr_contains() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(1),
stdout: b"".to_vec(),
stderr: b"Unsupported encryption handler".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_stderr_contains("Unsupported encryption"); // Should not panic
}
#[test]
#[should_panic(expected = "Expected stderr to contain")]
fn test_execution_result_assert_stderr_contains_failure() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(1),
stdout: b"".to_vec(),
stderr: b"some error".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_stderr_contains("encryption"); // Should panic
}
#[test]
fn test_execution_result_assert_stdout_contains() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(0),
stdout: b"test output".to_vec(),
stderr: b"".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_stdout_contains("test"); // Should not panic
}
#[test]
fn test_execution_result_assert_output_contains() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(1),
stdout: b"stdout message".to_vec(),
stderr: b"stderr message".to_vec(),
};
let result = TestExecutionResult::new(output);
result.assert_output_contains("stdout message"); // Should not panic
result.assert_output_contains("stderr message"); // Should not panic
}
#[test]
fn test_execution_result_combined_output() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(0),
stdout: b"stdout".to_vec(),
stderr: b"stderr".to_vec(),
};
let result = TestExecutionResult::new(output);
let combined = result.combined_output();
assert!(combined.contains("stdout"));
assert!(combined.contains("stderr"));
}
#[test]
fn test_execution_result_assert_unsupported_encryption() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(3),
stdout: b"".to_vec(),
stderr: b"Unsupported encryption handler Adobe.APS".to_vec(),
};
let result = TestExecutionResult::with_fixture(output, "test.pdf");
result.assert_unsupported_encryption(); // Should not panic
}
#[test]
fn test_execution_result_assert_password_required() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(3),
stdout: b"".to_vec(),
stderr: b"password required".to_vec(),
};
let result = TestExecutionResult::with_fixture(output, "encrypted.pdf");
result.assert_password_required(); // Should not panic
}
#[test]
fn test_execution_result_assert_wrong_password() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(3),
stdout: b"".to_vec(),
stderr: b"incorrect password".to_vec(),
};
let result = TestExecutionResult::with_fixture(output, "encrypted.pdf");
result.assert_wrong_password(); // Should not panic
}
#[test]
fn test_execution_result_assert_empty_output() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(3),
stdout: b"".to_vec(),
stderr: b"error".to_vec(),
};
let result = TestExecutionResult::with_fixture(output, "encrypted.pdf");
result.assert_empty_output(); // Should not panic
}
#[test]
#[should_panic(expected = "Expected no extraction output")]
fn test_execution_result_assert_empty_output_failure() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(0),
stdout: br#"{"pages": []}"#.to_vec(),
stderr: b"".to_vec(),
};
let result = TestExecutionResult::with_fixture(output, "test.pdf");
result.assert_empty_output(); // Should panic
}
#[test]
fn test_execution_result_method_chaining() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(3),
stdout: b"".to_vec(),
stderr: b"Unsupported encryption".to_vec(),
};
let result = TestExecutionResult::with_fixture(output, "test.pdf");
// Test method chaining
result
.assert_failure()
.assert_exit_code(3)
.assert_stderr_contains("Unsupported")
.assert_empty_output();
}
#[test]
fn test_execution_result_from_impl() {
use std::process::Output;
let output = Output {
status: std::process::ExitStatus::from_raw(0),
stdout: b"test".to_vec(),
stderr: b"".to_vec(),
};
let result: TestExecutionResult = output.into();
assert_eq!(result.exit_code(), Some(0));
assert_eq!(result.stdout(), "test");
}
}