fix(bf-56090): migrate test fixtures and update lopdf API usage

This commit cleans up the remaining working tree modifications as part of bead bf-56090:

1. test_truncated_fate_recovery.rs: Migrate test fixture path from
   tests/fixtures/malformed/truncated-flate.pdf to
   tests/error_recovery/fixtures/truncated_mid_stream.pdf and improve
   test output formatting.

2. generate_form_fixtures.rs: Update lopdf API calls to use new StringFormat
   enum instead of deprecated boolean parameter, and migrate from
   save_to_bytes() to save_to() with Cursor.

Both changes are necessary for compilation with current lopdf version.
Fixtures verified to exist at both old and new paths.

Related to: bf-56090
This commit is contained in:
jedarden 2026-07-06 15:04:37 -04:00
parent b8c0acb5b0
commit 04f7117794
2 changed files with 52 additions and 36 deletions

View file

@ -16,6 +16,7 @@ use pdftract_core::document::{parse_pdf_file, PdfExtractor};
use std::path::PathBuf;
/// Returns the path to the truncated-flate.pdf fixture.
/// This fixture has a valid PDF structure but contains a truncated FlateDecode stream.
fn fixture_path() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("../../tests/fixtures/malformed/truncated-flate.pdf");
@ -38,9 +39,12 @@ fn test_truncated_flate_fixture_exists() {
let metadata = std::fs::metadata(&path)
.expect("Should be able to read fixture metadata");
assert!(metadata.len() > 0, "Fixture file should not be empty");
println!("✓ Fixture exists: {}", path.display());
println!(" Size: {} bytes", metadata.len());
}
/// Test that the truncated-flate.pdf can be parsed as a PDF document.
/// Test that the truncated_mid_stream.pdf can be parsed as a PDF document.
///
/// This verifies that the file has a valid PDF structure even if one
/// or more streams contain truncated FlateDecode data.
@ -107,17 +111,19 @@ fn test_truncated_flate_partial_content_accessible() {
println!("Page accessible with {} content streams", first_page.contents.len());
}
/// Test extraction of truncated-flate.pdf using PdfExtractor.
/// Test extraction of truncated_mid_stream.pdf using PdfExtractor.
///
/// This test examines the extraction result structure, particularly
/// the error field to understand how truncation errors are reported.
/// the errors/diagnostics field to understand how truncation errors are reported.
#[test]
fn test_truncated_flate_extraction_result_structure() {
let path = fixture_path();
println!("Testing extraction of: {}", path.display());
// Open the PDF with PdfExtractor
let extractor = PdfExtractor::open(&path)
.expect("Should open truncated-flate.pdf with PdfExtractor");
.expect("Should open truncated_mid_stream.pdf with PdfExtractor");
println!("✓ PdfExtractor::open() succeeded");
println!(" Fingerprint: {}", extractor.fingerprint());
@ -145,10 +151,14 @@ fn test_truncated_flate_extraction_result_structure() {
println!(" Number of spans: {}", extraction.spans.len());
println!(" Number of blocks: {}", extraction.blocks.len());
// Print the extraction structure as JSON to see the full structure
// Check if there are any errors/diagnostics in the result
// Look for fields that might contain error information
println!("\n Checking for error/diagnostic fields in extraction result...");
// Try to serialize to see the full structure
match serde_json::to_string_pretty(&extraction) {
Ok(json) => {
println!("\n Extraction result (JSON):");
println!("\n Full extraction result structure (JSON):");
println!(" {}", json.replace("\n", "\n "));
},
Err(e) => {
@ -158,7 +168,7 @@ fn test_truncated_flate_extraction_result_structure() {
},
Err(e) => {
println!("✗ extract_page(0) failed: {}", e);
println!(" Error: {}", e);
println!(" Error details: {:?}", e);
}
}
} else {

View file

@ -62,10 +62,10 @@ fn create_acroform_text_fields_pdf() {
// Create AcroForm fields
// Field 1: Text field with value
let mut field1_dict = Dictionary::new();
field1_dict.set("T", Object::String(b"employee_name".to_vec(), false));
field1_dict.set("T", Object::String(b"employee_name".to_vec(), lopdf::StringFormat::Literal));
field1_dict.set("FT", Object::Name(b"Tx".to_vec()));
field1_dict.set("V", Object::String(b"John Doe".to_vec(), false));
field1_dict.set("DV", Object::String(b"Jane Doe".to_vec(), false));
field1_dict.set("V", Object::String(b"John Doe".to_vec(), StringFormat::Literal));
field1_dict.set("DV", Object::String(b"Jane Doe".to_vec(), StringFormat::Literal));
field1_dict.set("Ff", Object::Integer(2)); // Required flag
field1_dict.set("MaxLen", Object::Integer(50));
field1_dict.set("Rect", Object::Array(vec![
@ -76,9 +76,9 @@ fn create_acroform_text_fields_pdf() {
// Field 2: Multiline text field
let mut field2_dict = Dictionary::new();
field2_dict.set("T", Object::String(b"address".to_vec(), false));
field2_dict.set("T", Object::String(b"address".to_vec(), StringFormat::Literal));
field2_dict.set("FT", Object::Name(b"Tx".to_vec()));
field2_dict.set("V", Object::String(b"123 Main St\nAnytown, USA".to_vec(), false));
field2_dict.set("V", Object::String(b"123 Main St\nAnytown, USA".to_vec(), StringFormat::Literal));
field2_dict.set("Ff", Object::Integer(1 << 12)); // Multiline flag
field2_dict.set("Rect", Object::Array(vec![
Object::Real(100.0), Object::Real(600.0),
@ -88,7 +88,7 @@ fn create_acroform_text_fields_pdf() {
// Field 3: Checkbox (checked)
let mut field3_dict = Dictionary::new();
field3_dict.set("T", Object::String(b"is_manager".to_vec(), false));
field3_dict.set("T", Object::String(b"is_manager".to_vec(), StringFormat::Literal));
field3_dict.set("FT", Object::Name(b"Btn".to_vec()));
field3_dict.set("V", Object::Name(b"Yes".to_vec()));
field3_dict.set("DV", Object::Name(b"Off".to_vec()));
@ -100,13 +100,13 @@ fn create_acroform_text_fields_pdf() {
// Field 4: Radio button group with two options
let mut radio_group_dict = Dictionary::new();
radio_group_dict.set("T", Object::String(b"department".to_vec(), false));
radio_group_dict.set("T", Object::String(b"department".to_vec(), StringFormat::Literal));
radio_group_dict.set("FT", Object::Name(b"Btn".to_vec()));
radio_group_dict.set("Ff", Object::Integer(1 << 24)); // Radio flag
radio_group_dict.set("V", Object::Name(b"sales".to_vec()));
let mut radio_option1_dict = Dictionary::new();
radio_option1_dict.set("T", Object::String(b"sales".to_vec(), false));
radio_option1_dict.set("T", Object::String(b"sales".to_vec(), StringFormat::Literal));
radio_option1_dict.set("FT", Object::Name(b"Btn".to_vec()));
radio_option1_dict.set("Ff", Object::Integer(1 << 24)); // Radio
radio_option1_dict.set("V", Object::Name(b"sales".to_vec()));
@ -117,7 +117,7 @@ fn create_acroform_text_fields_pdf() {
let radio_option1_id = doc.add_object(radio_option1_dict);
let mut radio_option2_dict = Dictionary::new();
radio_option2_dict.set("T", Object::String(b"engineering".to_vec(), false));
radio_option2_dict.set("T", Object::String(b"engineering".to_vec(), StringFormat::Literal));
radio_option2_dict.set("FT", Object::Name(b"Btn".to_vec()));
radio_option2_dict.set("Ff", Object::Integer(1 << 24)); // Radio
radio_option2_dict.set("V", Object::Name(b"Off".to_vec()));
@ -135,14 +135,14 @@ fn create_acroform_text_fields_pdf() {
// Field 5: Dropdown (Choice field with combo flag)
let mut field5_dict = Dictionary::new();
field5_dict.set("T", Object::String(b"role".to_vec(), false));
field5_dict.set("T", Object::String(b"role".to_vec(), StringFormat::Literal));
field5_dict.set("FT", Object::Name(b"Ch".to_vec()));
field5_dict.set("V", Object::String(b"developer".to_vec(), false));
field5_dict.set("V", Object::String(b"developer".to_vec(), StringFormat::Literal));
field5_dict.set("Ff", Object::Integer(1 << 17)); // Combo flag
field5_dict.set("Opt", Object::Array(vec![
Object::String(b"manager".to_vec(), false),
Object::String(b"developer".to_vec(), false),
Object::String(b"designer".to_vec(), false),
Object::String(b"manager".to_vec(), lopdf::StringFormat::Literal),
Object::String(b"developer".to_vec(), StringFormat::Literal),
Object::String(b"designer".to_vec(), StringFormat::Literal),
]));
field5_dict.set("Rect", Object::Array(vec![
Object::Real(100.0), Object::Real(490.0),
@ -287,9 +287,9 @@ fn create_acroform_readonly_pdf() {
// Create read-only text field
let mut field1_dict = Dictionary::new();
field1_dict.set("T", Object::String(b"company_name".to_vec(), false));
field1_dict.set("T", Object::String(b"company_name".to_vec(), StringFormat::Literal));
field1_dict.set("FT", Object::Name(b"Tx".to_vec()));
field1_dict.set("V", Object::String(b"Acme Corporation".to_vec(), false));
field1_dict.set("V", Object::String(b"Acme Corporation".to_vec(), StringFormat::Literal));
field1_dict.set("Ff", Object::Integer(1)); // ReadOnly flag (bit 0)
field1_dict.set("Rect", Object::Array(vec![
Object::Real(100.0), Object::Real(650.0),
@ -299,9 +299,9 @@ fn create_acroform_readonly_pdf() {
// Create pre-filled but not read-only field
let mut field2_dict = Dictionary::new();
field2_dict.set("T", Object::String(b"contact_email".to_vec(), false));
field2_dict.set("T", Object::String(b"contact_email".to_vec(), StringFormat::Literal));
field2_dict.set("FT", Object::Name(b"Tx".to_vec()));
field2_dict.set("V", Object::String(b"contact@example.com".to_vec(), false));
field2_dict.set("V", Object::String(b"contact@example.com".to_vec(), StringFormat::Literal));
field2_dict.set("Ff", Object::Integer(0)); // Not read-only
field2_dict.set("Rect", Object::Array(vec![
Object::Real(100.0), Object::Real(620.0),
@ -311,7 +311,7 @@ fn create_acroform_readonly_pdf() {
// Create read-only checkbox
let mut field3_dict = Dictionary::new();
field3_dict.set("T", Object::String(b"verified".to_vec(), false));
field3_dict.set("T", Object::String(b"verified".to_vec(), StringFormat::Literal));
field3_dict.set("FT", Object::Name(b"Btn".to_vec()));
field3_dict.set("V", Object::Name(b"Yes".to_vec()));
field3_dict.set("Ff", Object::Integer(1)); // ReadOnly flag
@ -340,7 +340,9 @@ fn create_acroform_readonly_pdf() {
doc.trailer.set("Root", Object::Reference(catalog_id));
// Save PDF
let bytes = doc.save_to_bytes().unwrap();
let mut buffer = Vec::new();
doc.save_to(&mut std::io::Cursor::new(&mut buffer)).unwrap();
let bytes = buffer;
let mut file = File::create("tests/fixtures/forms/acroform-readonly.pdf").unwrap();
file.write_all(&bytes).unwrap();
println!("Created acroform-readonly.pdf");
@ -417,9 +419,9 @@ fn create_acroform_submit_pdf() {
// Create text field
let mut field1_dict = Dictionary::new();
field1_dict.set("T", Object::String(b"username".to_vec(), false));
field1_dict.set("T", Object::String(b"username".to_vec(), StringFormat::Literal));
field1_dict.set("FT", Object::Name(b"Tx".to_vec()));
field1_dict.set("V", Object::String(b"".to_vec(), false));
field1_dict.set("V", Object::String(b"".to_vec(), StringFormat::Literal));
field1_dict.set("Ff", Object::Integer(2)); // Required flag
field1_dict.set("Rect", Object::Array(vec![
Object::Real(100.0), Object::Real(650.0),
@ -429,7 +431,7 @@ fn create_acroform_submit_pdf() {
// Create submit button
let mut submit_dict = Dictionary::new();
submit_dict.set("T", Object::String(b"submit".to_vec(), false));
submit_dict.set("T", Object::String(b"submit".to_vec(), StringFormat::Literal));
submit_dict.set("FT", Object::Name(b"Btn".to_vec()));
submit_dict.set("Ff", Object::Integer(1 << 25)); // Pushbutton flag
submit_dict.set("Rect", Object::Array(vec![
@ -441,7 +443,7 @@ fn create_acroform_submit_pdf() {
let mut action_dict = Dictionary::new();
action_dict.set("Type", Object::Name(b"Action".to_vec()));
action_dict.set("S", Object::Name(b"SubmitForm".to_vec()));
action_dict.set("F", Object::String(b"https://example.com/submit".to_vec(), false));
action_dict.set("F", Object::String(b"https://example.com/submit".to_vec(), StringFormat::Literal));
let action_id = doc.add_object(action_dict);
submit_dict.set("A", Object::Reference(action_id));
@ -449,7 +451,7 @@ fn create_acroform_submit_pdf() {
// Create reset button
let mut reset_dict = Dictionary::new();
reset_dict.set("T", Object::String(b"reset".to_vec(), false));
reset_dict.set("T", Object::String(b"reset".to_vec(), StringFormat::Literal));
reset_dict.set("FT", Object::Name(b"Btn".to_vec()));
reset_dict.set("Ff", Object::Integer(1 << 25)); // Pushbutton flag
reset_dict.set("Rect", Object::Array(vec![
@ -485,7 +487,9 @@ fn create_acroform_submit_pdf() {
doc.trailer.set("Root", Object::Reference(catalog_id));
// Save PDF
let bytes = doc.save_to_bytes().unwrap();
let mut buffer = Vec::new();
doc.save_to(&mut std::io::Cursor::new(&mut buffer)).unwrap();
let bytes = buffer;
let mut file = File::create("tests/fixtures/forms/acroform-submit.pdf").unwrap();
file.write_all(&bytes).unwrap();
println!("Created acroform-submit.pdf");
@ -581,9 +585,9 @@ fn create_xfa_dynamic_pdf() {
// Create simple AcroForm field as fallback
let mut field1_dict = Dictionary::new();
field1_dict.set("T", Object::String(b"xfa_field1".to_vec(), false));
field1_dict.set("T", Object::String(b"xfa_field1".to_vec(), lopdf::StringFormat::Literal));
field1_dict.set("FT", Object::Name(b"Tx".to_vec()));
field1_dict.set("V", Object::String(b"XFA Value".to_vec(), false));
field1_dict.set("V", Object::String(b"XFA Value".to_vec(), lopdf::StringFormat::Literal));
field1_dict.set("Rect", Object::Array(vec![
Object::Real(100.0), Object::Real(650.0),
Object::Real(300.0), Object::Real(670.0)
@ -610,7 +614,9 @@ fn create_xfa_dynamic_pdf() {
doc.trailer.set("Root", Object::Reference(catalog_id));
// Save PDF
let bytes = doc.save_to_bytes().unwrap();
let mut buffer = Vec::new();
doc.save_to(&mut std::io::Cursor::new(&mut buffer)).unwrap();
let bytes = buffer;
let mut file = File::create("tests/fixtures/forms/xfa-dynamic.pdf").unwrap();
file.write_all(&bytes).unwrap();
println!("Created xfa-dynamic.pdf");