From 234122fcdbd0bed0b60393428a70fa8656eab5a2 Mon Sep 17 00:00:00 2001 From: wangdl Date: Thu, 18 Jun 2026 13:32:34 +0800 Subject: [PATCH] test: add extract_pdf_text unit tests (DOC-FULL-023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pdf_with_text_stream helper: build minimal PDFs with BT/ET text - single/multiple text blocks, no BT/ET→empty, empty PDF→empty - MAX_SCAN_BYTES limit respected, empty text filtered - page_count from pages dict, default to 1 Co-Authored-By: Claude Opus 4.7 --- crates/zx_document_core/src/pdf.rs | 98 ++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/crates/zx_document_core/src/pdf.rs b/crates/zx_document_core/src/pdf.rs index cf7b4b9..b825605 100644 --- a/crates/zx_document_core/src/pdf.rs +++ b/crates/zx_document_core/src/pdf.rs @@ -344,6 +344,104 @@ mod tests { } } + fn pdf_with_text_stream(text_content: &str) -> Vec { + // Build a minimal PDF with a stream object containing BT/ET text + let stream_text = format!("BT\n({})\nET", text_content); + let obj = format!( + "1 0 obj\n<< /Length {} >>\nstream\n{}\nendstream\nendobj\n", + stream_text.len(), + stream_text + ); + let obj_bytes = obj.as_bytes(); + let header = b"%PDF-1.4\n%\x80\x80\x80\x80\n"; + let header_len = header.len() as u64; + let obj_len = obj_bytes.len() as u64; + let trailer = format!( + "xref\n0 2\n0000000000 65535 f \n{:010} 00000 n \ntrailer\n<< /Size 2 /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n", + header_len, + header_len + obj_len + ); + + let mut pdf = Vec::new(); + pdf.extend_from_slice(header); + pdf.extend_from_slice(obj_bytes); + pdf.extend_from_slice(trailer.as_bytes()); + pdf + } + + #[test] + fn test_extract_pdf_text_single_bt_et_block() { + let pdf = pdf_with_text_stream("Hello World"); + let path = write_temp(&pdf, "text_single.pdf"); + let pages = extract_pdf_text(&path).unwrap(); + assert_eq!(pages.len(), 1); + assert_eq!(pages[0].text, "Hello World"); + } + + #[test] + fn test_extract_pdf_text_multiple_blocks() { + let stream = "BT\n(First line)\nET\nBT\n(Second line)\nET"; + let obj = format!("1 0 obj\n<< /Length {} >>\nstream\n{}\nendstream\nendobj\n", stream.len(), stream); + let header = b"%PDF-1.4\n"; + let pdf: Vec = [header.as_slice(), obj.as_bytes()].concat(); + // This is a slightly malformed PDF but we're testing the BT/ET scanner + let path = write_temp(&pdf, "text_multi.pdf"); + let pages = extract_pdf_text(&path).unwrap(); + // The scanner finds bt\n...\net blocks in sequence + assert!(pages.len() >= 1, "Should find at least one text block"); + } + + #[test] + fn test_extract_pdf_text_no_bt_et_returns_empty() { + let pdf = b"%PDF-1.4\nNo text blocks here\n%%EOF".to_vec(); + let path = write_temp(&pdf, "no_text.pdf"); + let pages = extract_pdf_text(&path).unwrap(); + assert!(pages.is_empty()); + } + + #[test] + fn test_extract_pdf_text_empty_pdf_returns_empty() { + let pdf = b"%PDF-1.4\n".to_vec(); + let path = write_temp(&pdf, "empty.pdf"); + let pages = extract_pdf_text(&path).unwrap(); + assert!(pages.is_empty()); + } + + #[test] + fn test_extract_pdf_text_max_scan_limit_respected() { + // Create a PDF larger than MAX_SCAN_BYTES (8MB) by padding zeros + let mut pdf = pdf_with_text_stream("Hello"); + pdf.resize(MAX_SCAN_BYTES + 1024, 0); + let path = write_temp(&pdf, "large.pdf"); + let result = extract_pdf_text(&path); + assert!(result.is_ok()); + } + + #[test] + fn test_extract_pdf_text_empty_text_filtered() { + let pdf = pdf_with_text_stream(""); + let path = write_temp(&pdf, "empty_text.pdf"); + let pages = extract_pdf_text(&path).unwrap(); + // Empty text blocks are filtered out + assert!(pages.is_empty()); + } + + #[test] + fn test_extract_pdf_text_page_count_from_pages_dict() { + let data = minimal_pdf(3); + let path = write_temp(&data, "count_pages.pdf"); + let meta = read_pdf_metadata(&path).unwrap(); + assert_eq!(meta.page_count, 3); + } + + #[test] + fn test_extract_pdf_text_default_to_one_page() { + let pdf = b"%PDF-1.4\n".to_vec(); + let path = write_temp(&pdf, "default_page.pdf"); + let meta = read_pdf_metadata(&path).unwrap(); + assert_eq!(meta.page_count, 1); + } + #[test] fn test_fixture_scanned_pdf() { let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))