1use crate::error::Result;
7use crate::verification::parser::{parse_pdf, ParsedPdf};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
12pub struct PdfDifference {
13 pub location: String,
14 pub expected: String,
15 pub actual: String,
16 pub severity: DifferenceSeverity,
17}
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum DifferenceSeverity {
21 Critical,
23 Important,
25 Minor,
27 Cosmetic,
29}
30
31#[derive(Debug, Clone)]
33pub struct ComparisonResult {
34 pub structurally_equivalent: bool,
35 pub content_equivalent: bool,
36 pub differences: Vec<PdfDifference>,
37 pub similarity_score: f64, }
39
40pub fn compare_pdfs(generated: &[u8], reference: &[u8]) -> Result<ComparisonResult> {
42 let parsed_generated = parse_pdf(generated)?;
43 let parsed_reference = parse_pdf(reference)?;
44
45 let differences = find_differences(&parsed_generated, &parsed_reference);
46 let similarity_score = calculate_similarity_score(&differences);
47
48 let structurally_equivalent = differences.iter().all(|diff| {
49 diff.severity == DifferenceSeverity::Cosmetic || diff.severity == DifferenceSeverity::Minor
50 });
51
52 let content_equivalent = differences
53 .iter()
54 .all(|diff| diff.severity == DifferenceSeverity::Cosmetic);
55
56 Ok(ComparisonResult {
57 structurally_equivalent,
58 content_equivalent,
59 differences,
60 similarity_score,
61 })
62}
63
64fn find_differences(generated: &ParsedPdf, reference: &ParsedPdf) -> Vec<PdfDifference> {
66 let mut differences = Vec::new();
67
68 if generated.version != reference.version {
70 let severity = if generated.version.chars().next() != reference.version.chars().next() {
71 DifferenceSeverity::Important
72 } else {
73 DifferenceSeverity::Minor
74 };
75
76 differences.push(PdfDifference {
77 location: "PDF Version".to_string(),
78 expected: reference.version.clone(),
79 actual: generated.version.clone(),
80 severity,
81 });
82 }
83
84 differences.extend(compare_catalogs(&generated.catalog, &reference.catalog));
86
87 differences.extend(compare_page_trees(
89 &generated.page_tree,
90 &reference.page_tree,
91 ));
92
93 differences.extend(compare_fonts(&generated.fonts, &reference.fonts));
95
96 differences.extend(compare_color_spaces(generated, reference));
98
99 differences.extend(compare_graphics_states(
101 &generated.graphics_states,
102 &reference.graphics_states,
103 ));
104
105 differences.extend(compare_text_objects(
107 &generated.text_objects,
108 &reference.text_objects,
109 ));
110
111 differences.extend(compare_annotations(
113 &generated.annotations,
114 &reference.annotations,
115 ));
116
117 if generated.xref_valid != reference.xref_valid {
119 differences.push(PdfDifference {
120 location: "Cross-reference table".to_string(),
121 expected: reference.xref_valid.to_string(),
122 actual: generated.xref_valid.to_string(),
123 severity: DifferenceSeverity::Critical,
124 });
125 }
126
127 differences
128}
129
130fn compare_catalogs(
132 generated: &Option<HashMap<String, String>>,
133 reference: &Option<HashMap<String, String>>,
134) -> Vec<PdfDifference> {
135 let mut differences = Vec::new();
136
137 match (generated, reference) {
138 (Some(gen_catalog), Some(ref_catalog)) => {
139 for key in ["Type", "Pages"] {
141 match (gen_catalog.get(key), ref_catalog.get(key)) {
142 (Some(gen_val), Some(ref_val)) => {
143 if gen_val != ref_val {
144 differences.push(PdfDifference {
145 location: format!("Catalog/{}", key),
146 expected: ref_val.clone(),
147 actual: gen_val.clone(),
148 severity: DifferenceSeverity::Critical,
149 });
150 }
151 }
152 (None, Some(ref_val)) => {
153 differences.push(PdfDifference {
154 location: format!("Catalog/{}", key),
155 expected: ref_val.clone(),
156 actual: "missing".to_string(),
157 severity: DifferenceSeverity::Critical,
158 });
159 }
160 (Some(gen_val), None) => {
161 differences.push(PdfDifference {
162 location: format!("Catalog/{}", key),
163 expected: "missing".to_string(),
164 actual: gen_val.clone(),
165 severity: DifferenceSeverity::Minor,
166 });
167 }
168 (None, None) => {} }
170 }
171 }
172 (None, Some(_)) => {
173 differences.push(PdfDifference {
174 location: "Document Catalog".to_string(),
175 expected: "present".to_string(),
176 actual: "missing".to_string(),
177 severity: DifferenceSeverity::Critical,
178 });
179 }
180 (Some(_), None) => {
181 differences.push(PdfDifference {
182 location: "Document Catalog".to_string(),
183 expected: "missing".to_string(),
184 actual: "present".to_string(),
185 severity: DifferenceSeverity::Minor,
186 });
187 }
188 (None, None) => {
189 differences.push(PdfDifference {
190 location: "Document Catalog".to_string(),
191 expected: "present".to_string(),
192 actual: "missing".to_string(),
193 severity: DifferenceSeverity::Critical,
194 });
195 }
196 }
197
198 differences
199}
200
201fn compare_page_trees(
203 generated: &Option<crate::verification::parser::PageTree>,
204 reference: &Option<crate::verification::parser::PageTree>,
205) -> Vec<PdfDifference> {
206 let mut differences = Vec::new();
207
208 match (generated, reference) {
209 (Some(gen_tree), Some(ref_tree)) => {
210 if gen_tree.page_count != ref_tree.page_count {
211 differences.push(PdfDifference {
212 location: "Page Tree/Count".to_string(),
213 expected: ref_tree.page_count.to_string(),
214 actual: gen_tree.page_count.to_string(),
215 severity: DifferenceSeverity::Critical,
216 });
217 }
218
219 if gen_tree.root_type != ref_tree.root_type {
220 differences.push(PdfDifference {
221 location: "Page Tree/Type".to_string(),
222 expected: ref_tree.root_type.clone(),
223 actual: gen_tree.root_type.clone(),
224 severity: DifferenceSeverity::Critical,
225 });
226 }
227 }
228 (None, Some(_)) => {
229 differences.push(PdfDifference {
230 location: "Page Tree".to_string(),
231 expected: "present".to_string(),
232 actual: "missing".to_string(),
233 severity: DifferenceSeverity::Critical,
234 });
235 }
236 (Some(_), None) => {
237 differences.push(PdfDifference {
238 location: "Page Tree".to_string(),
239 expected: "missing".to_string(),
240 actual: "present".to_string(),
241 severity: DifferenceSeverity::Minor,
242 });
243 }
244 (None, None) => {} }
246
247 differences
248}
249
250fn compare_fonts(generated: &[String], reference: &[String]) -> Vec<PdfDifference> {
252 let mut differences = Vec::new();
253
254 for ref_font in reference {
256 if !generated.contains(ref_font) {
257 differences.push(PdfDifference {
258 location: format!("Fonts/{}", ref_font),
259 expected: "present".to_string(),
260 actual: "missing".to_string(),
261 severity: DifferenceSeverity::Important,
262 });
263 }
264 }
265
266 for gen_font in generated {
268 if !reference.contains(gen_font) {
269 differences.push(PdfDifference {
270 location: format!("Fonts/{}", gen_font),
271 expected: "missing".to_string(),
272 actual: "present".to_string(),
273 severity: DifferenceSeverity::Minor,
274 });
275 }
276 }
277
278 differences
279}
280
281fn compare_color_spaces(generated: &ParsedPdf, reference: &ParsedPdf) -> Vec<PdfDifference> {
283 let mut differences = Vec::new();
284
285 if generated.uses_device_rgb != reference.uses_device_rgb {
286 differences.push(PdfDifference {
287 location: "Color Spaces/DeviceRGB".to_string(),
288 expected: reference.uses_device_rgb.to_string(),
289 actual: generated.uses_device_rgb.to_string(),
290 severity: DifferenceSeverity::Important,
291 });
292 }
293
294 if generated.uses_device_cmyk != reference.uses_device_cmyk {
295 differences.push(PdfDifference {
296 location: "Color Spaces/DeviceCMYK".to_string(),
297 expected: reference.uses_device_cmyk.to_string(),
298 actual: generated.uses_device_cmyk.to_string(),
299 severity: DifferenceSeverity::Important,
300 });
301 }
302
303 if generated.uses_device_gray != reference.uses_device_gray {
304 differences.push(PdfDifference {
305 location: "Color Spaces/DeviceGray".to_string(),
306 expected: reference.uses_device_gray.to_string(),
307 actual: generated.uses_device_gray.to_string(),
308 severity: DifferenceSeverity::Important,
309 });
310 }
311
312 differences
313}
314
315fn compare_graphics_states(
317 generated: &[crate::verification::parser::GraphicsState],
318 reference: &[crate::verification::parser::GraphicsState],
319) -> Vec<PdfDifference> {
320 let mut differences = Vec::new();
321
322 if generated.len() != reference.len() {
323 differences.push(PdfDifference {
324 location: "Graphics States/Count".to_string(),
325 expected: reference.len().to_string(),
326 actual: generated.len().to_string(),
327 severity: DifferenceSeverity::Important,
328 });
329 }
330
331 let min_len = generated.len().min(reference.len());
333 for i in 0..min_len.min(3) {
334 let gen_state = &generated[i];
336 let ref_state = &reference[i];
337
338 if gen_state.line_width != ref_state.line_width {
339 differences.push(PdfDifference {
340 location: format!("Graphics State {}/LineWidth", i),
341 expected: format!("{:?}", ref_state.line_width),
342 actual: format!("{:?}", gen_state.line_width),
343 severity: DifferenceSeverity::Minor,
344 });
345 }
346 }
347
348 differences
349}
350
351fn compare_text_objects(
353 generated: &[crate::verification::parser::TextObject],
354 reference: &[crate::verification::parser::TextObject],
355) -> Vec<PdfDifference> {
356 let mut differences = Vec::new();
357
358 if generated.len() != reference.len() {
359 differences.push(PdfDifference {
360 location: "Text Objects/Count".to_string(),
361 expected: reference.len().to_string(),
362 actual: generated.len().to_string(),
363 severity: DifferenceSeverity::Important,
364 });
365 }
366
367 let min_len = generated.len().min(reference.len());
369 for i in 0..min_len {
370 let gen_text = &generated[i];
371 let ref_text = &reference[i];
372
373 if gen_text.text_content != ref_text.text_content {
374 differences.push(PdfDifference {
375 location: format!("Text Object {}/Content", i),
376 expected: ref_text.text_content.clone(),
377 actual: gen_text.text_content.clone(),
378 severity: DifferenceSeverity::Important,
379 });
380 }
381 }
382
383 differences
384}
385
386fn compare_annotations(
388 generated: &[crate::verification::parser::Annotation],
389 reference: &[crate::verification::parser::Annotation],
390) -> Vec<PdfDifference> {
391 let mut differences = Vec::new();
392
393 if generated.len() != reference.len() {
394 differences.push(PdfDifference {
395 location: "Annotations/Count".to_string(),
396 expected: reference.len().to_string(),
397 actual: generated.len().to_string(),
398 severity: DifferenceSeverity::Important,
399 });
400 }
401
402 differences
403}
404
405fn calculate_similarity_score(differences: &[PdfDifference]) -> f64 {
407 if differences.is_empty() {
408 return 1.0;
409 }
410
411 let mut penalty = 0.0;
412 for diff in differences {
413 penalty += match diff.severity {
414 DifferenceSeverity::Critical => 0.3,
415 DifferenceSeverity::Important => 0.1,
416 DifferenceSeverity::Minor => 0.05,
417 DifferenceSeverity::Cosmetic => 0.01,
418 };
419 }
420
421 (1.0f64 - penalty).max(0.0)
422}
423
424pub fn pdfs_structurally_equivalent(generated: &[u8], reference: &[u8]) -> bool {
426 match compare_pdfs(generated, reference) {
427 Ok(result) => result.structurally_equivalent,
428 Err(_) => false,
429 }
430}
431
432pub fn extract_pdf_differences(generated: &[u8], reference: &[u8]) -> Result<Vec<PdfDifference>> {
434 let result = compare_pdfs(generated, reference)?;
435 Ok(result.differences)
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use crate::verification::parser::{Annotation, GraphicsState, PageTree, TextObject};
442
443 fn create_test_pdf(version: &str, catalog_type: &str) -> Vec<u8> {
444 format!(
445 "%PDF-{}\n1 0 obj\n<<\n/Type /{}\n>>\nendobj\n%%EOF",
446 version, catalog_type
447 )
448 .into_bytes()
449 }
450
451 #[test]
452 fn test_identical_pdfs() {
453 let pdf1 = create_test_pdf("1.4", "Catalog");
454 let pdf2 = create_test_pdf("1.4", "Catalog");
455
456 let result = compare_pdfs(&pdf1, &pdf2).unwrap();
457 assert!(result.content_equivalent);
458 assert_eq!(result.similarity_score, 1.0);
459 }
460
461 #[test]
462 fn test_version_difference() {
463 let pdf1 = create_test_pdf("1.4", "Catalog");
464 let pdf2 = create_test_pdf("1.7", "Catalog");
465
466 let result = compare_pdfs(&pdf1, &pdf2).unwrap();
467 assert!(!result.content_equivalent);
468 assert!(result.similarity_score < 1.0);
469 assert!(result
470 .differences
471 .iter()
472 .any(|d| d.location == "PDF Version"));
473 }
474
475 #[test]
476 fn test_structural_difference() {
477 let pdf1 = create_test_pdf("1.4", "Catalog");
478 let pdf2 = create_test_pdf("1.7", "Catalog"); let result = compare_pdfs(&pdf1, &pdf2).unwrap();
481
482 assert!(result.structurally_equivalent);
484 assert!(!result.differences.is_empty()); assert!(result
488 .differences
489 .iter()
490 .any(|d| d.location == "PDF Version"));
491 }
492
493 #[test]
494 fn test_calculate_similarity_score() {
495 let differences = vec![PdfDifference {
496 location: "test".to_string(),
497 expected: "a".to_string(),
498 actual: "b".to_string(),
499 severity: DifferenceSeverity::Critical,
500 }];
501
502 let score = calculate_similarity_score(&differences);
503 assert_eq!(score, 0.7); }
505
506 #[test]
509 fn test_calculate_similarity_score_empty() {
510 let differences: Vec<PdfDifference> = vec![];
511 let score = calculate_similarity_score(&differences);
512 assert_eq!(score, 1.0);
513 }
514
515 #[test]
516 fn test_calculate_similarity_score_important() {
517 let differences = vec![PdfDifference {
518 location: "test".to_string(),
519 expected: "a".to_string(),
520 actual: "b".to_string(),
521 severity: DifferenceSeverity::Important,
522 }];
523
524 let score = calculate_similarity_score(&differences);
525 assert!((score - 0.9).abs() < 0.001); }
527
528 #[test]
529 fn test_calculate_similarity_score_minor() {
530 let differences = vec![PdfDifference {
531 location: "test".to_string(),
532 expected: "a".to_string(),
533 actual: "b".to_string(),
534 severity: DifferenceSeverity::Minor,
535 }];
536
537 let score = calculate_similarity_score(&differences);
538 assert!((score - 0.95).abs() < 0.001); }
540
541 #[test]
542 fn test_calculate_similarity_score_cosmetic() {
543 let differences = vec![PdfDifference {
544 location: "test".to_string(),
545 expected: "a".to_string(),
546 actual: "b".to_string(),
547 severity: DifferenceSeverity::Cosmetic,
548 }];
549
550 let score = calculate_similarity_score(&differences);
551 assert!((score - 0.99).abs() < 0.001); }
553
554 #[test]
555 fn test_calculate_similarity_score_multiple() {
556 let differences = vec![
557 PdfDifference {
558 location: "test1".to_string(),
559 expected: "a".to_string(),
560 actual: "b".to_string(),
561 severity: DifferenceSeverity::Critical, },
563 PdfDifference {
564 location: "test2".to_string(),
565 expected: "c".to_string(),
566 actual: "d".to_string(),
567 severity: DifferenceSeverity::Important, },
569 ];
570
571 let score = calculate_similarity_score(&differences);
572 assert!((score - 0.6).abs() < 0.001); }
574
575 #[test]
576 fn test_calculate_similarity_score_max_penalty() {
577 let differences = vec![
579 PdfDifference {
580 location: "test1".to_string(),
581 expected: "a".to_string(),
582 actual: "b".to_string(),
583 severity: DifferenceSeverity::Critical,
584 },
585 PdfDifference {
586 location: "test2".to_string(),
587 expected: "a".to_string(),
588 actual: "b".to_string(),
589 severity: DifferenceSeverity::Critical,
590 },
591 PdfDifference {
592 location: "test3".to_string(),
593 expected: "a".to_string(),
594 actual: "b".to_string(),
595 severity: DifferenceSeverity::Critical,
596 },
597 PdfDifference {
598 location: "test4".to_string(),
599 expected: "a".to_string(),
600 actual: "b".to_string(),
601 severity: DifferenceSeverity::Critical,
602 },
603 ];
604
605 let score = calculate_similarity_score(&differences);
606 assert_eq!(score, 0.0); }
608
609 #[test]
610 fn test_difference_severity_equality() {
611 assert_eq!(DifferenceSeverity::Critical, DifferenceSeverity::Critical);
612 assert_eq!(DifferenceSeverity::Important, DifferenceSeverity::Important);
613 assert_eq!(DifferenceSeverity::Minor, DifferenceSeverity::Minor);
614 assert_eq!(DifferenceSeverity::Cosmetic, DifferenceSeverity::Cosmetic);
615 assert_ne!(DifferenceSeverity::Critical, DifferenceSeverity::Minor);
616 }
617
618 #[test]
619 fn test_pdf_difference_clone() {
620 let diff = PdfDifference {
621 location: "test".to_string(),
622 expected: "a".to_string(),
623 actual: "b".to_string(),
624 severity: DifferenceSeverity::Critical,
625 };
626 let cloned = diff.clone();
627 assert_eq!(diff.location, cloned.location);
628 assert_eq!(diff.expected, cloned.expected);
629 assert_eq!(diff.actual, cloned.actual);
630 }
631
632 #[test]
633 fn test_comparison_result_clone() {
634 let result = ComparisonResult {
635 structurally_equivalent: true,
636 content_equivalent: false,
637 differences: vec![],
638 similarity_score: 0.95,
639 };
640 let cloned = result.clone();
641 assert_eq!(
642 result.structurally_equivalent,
643 cloned.structurally_equivalent
644 );
645 assert_eq!(result.content_equivalent, cloned.content_equivalent);
646 assert_eq!(result.similarity_score, cloned.similarity_score);
647 }
648
649 #[test]
650 fn test_compare_fonts_missing_reference() {
651 let generated = vec!["Font1".to_string(), "Font2".to_string()];
652 let reference = vec!["Font1".to_string(), "Font3".to_string()];
653
654 let differences = compare_fonts(&generated, &reference);
655
656 assert!(differences
658 .iter()
659 .any(|d| { d.location.contains("Font3") && d.actual == "missing" }));
660
661 assert!(differences
663 .iter()
664 .any(|d| { d.location.contains("Font2") && d.expected == "missing" }));
665 }
666
667 #[test]
668 fn test_compare_fonts_empty() {
669 let generated: Vec<String> = vec![];
670 let reference: Vec<String> = vec![];
671
672 let differences = compare_fonts(&generated, &reference);
673 assert!(differences.is_empty());
674 }
675
676 #[test]
677 fn test_compare_fonts_identical() {
678 let generated = vec!["Font1".to_string(), "Font2".to_string()];
679 let reference = vec!["Font1".to_string(), "Font2".to_string()];
680
681 let differences = compare_fonts(&generated, &reference);
682 assert!(differences.is_empty());
683 }
684
685 #[test]
686 fn test_compare_annotations_different_count() {
687 let generated: Vec<Annotation> = vec![];
688 let reference = vec![Annotation {
689 subtype: "Link".to_string(),
690 rect: None,
691 contents: None,
692 }];
693
694 let differences = compare_annotations(&generated, &reference);
695
696 assert!(differences
697 .iter()
698 .any(|d| { d.location.contains("Annotations/Count") }));
699 }
700
701 #[test]
702 fn test_compare_annotations_same_count() {
703 let generated = vec![Annotation {
704 subtype: "Link".to_string(),
705 rect: None,
706 contents: None,
707 }];
708 let reference = vec![Annotation {
709 subtype: "Text".to_string(),
710 rect: None,
711 contents: None,
712 }];
713
714 let differences = compare_annotations(&generated, &reference);
715 assert!(differences.is_empty()); }
717
718 #[test]
719 fn test_compare_text_objects_different_content() {
720 let generated = vec![TextObject {
721 text_content: "Hello".to_string(),
722 font: Some("Helvetica".to_string()),
723 font_size: Some(12.0),
724 }];
725 let reference = vec![TextObject {
726 text_content: "World".to_string(),
727 font: Some("Helvetica".to_string()),
728 font_size: Some(12.0),
729 }];
730
731 let differences = compare_text_objects(&generated, &reference);
732
733 assert!(differences
734 .iter()
735 .any(|d| { d.location.contains("Text Object") && d.location.contains("Content") }));
736 }
737
738 #[test]
739 fn test_compare_text_objects_different_count() {
740 let generated: Vec<TextObject> = vec![];
741 let reference = vec![TextObject {
742 text_content: "Test".to_string(),
743 font: Some("Helvetica".to_string()),
744 font_size: Some(12.0),
745 }];
746
747 let differences = compare_text_objects(&generated, &reference);
748
749 assert!(differences
750 .iter()
751 .any(|d| { d.location.contains("Text Objects/Count") }));
752 }
753
754 #[test]
755 fn test_compare_graphics_states_different_count() {
756 let generated: Vec<GraphicsState> = vec![];
757 let reference = vec![GraphicsState {
758 line_width: Some(1.0),
759 line_cap: None,
760 line_join: None,
761 fill_color: None,
762 stroke_color: None,
763 }];
764
765 let differences = compare_graphics_states(&generated, &reference);
766
767 assert!(differences
768 .iter()
769 .any(|d| { d.location.contains("Graphics States/Count") }));
770 }
771
772 #[test]
773 fn test_compare_graphics_states_different_line_width() {
774 let generated = vec![GraphicsState {
775 line_width: Some(2.0),
776 line_cap: None,
777 line_join: None,
778 fill_color: None,
779 stroke_color: None,
780 }];
781 let reference = vec![GraphicsState {
782 line_width: Some(1.0),
783 line_cap: None,
784 line_join: None,
785 fill_color: None,
786 stroke_color: None,
787 }];
788
789 let differences = compare_graphics_states(&generated, &reference);
790
791 assert!(differences
792 .iter()
793 .any(|d| { d.location.contains("LineWidth") }));
794 }
795
796 #[test]
797 fn test_compare_catalogs_both_present_with_diff() {
798 let mut gen_catalog = HashMap::new();
799 gen_catalog.insert("Type".to_string(), "Catalog".to_string());
800 gen_catalog.insert("Pages".to_string(), "1 0 R".to_string());
801
802 let mut ref_catalog = HashMap::new();
803 ref_catalog.insert("Type".to_string(), "Catalog".to_string());
804 ref_catalog.insert("Pages".to_string(), "2 0 R".to_string()); let differences = compare_catalogs(&Some(gen_catalog), &Some(ref_catalog));
807
808 assert!(differences
809 .iter()
810 .any(|d| { d.location.contains("Catalog/Pages") }));
811 }
812
813 #[test]
814 fn test_compare_catalogs_generated_missing_key() {
815 let mut gen_catalog = HashMap::new();
816 gen_catalog.insert("Type".to_string(), "Catalog".to_string());
817 let mut ref_catalog = HashMap::new();
820 ref_catalog.insert("Type".to_string(), "Catalog".to_string());
821 ref_catalog.insert("Pages".to_string(), "1 0 R".to_string());
822
823 let differences = compare_catalogs(&Some(gen_catalog), &Some(ref_catalog));
824
825 assert!(differences
826 .iter()
827 .any(|d| { d.location.contains("Catalog/Pages") && d.actual == "missing" }));
828 }
829
830 #[test]
831 fn test_compare_catalogs_reference_missing_key() {
832 let mut gen_catalog = HashMap::new();
833 gen_catalog.insert("Type".to_string(), "Catalog".to_string());
834 gen_catalog.insert("Pages".to_string(), "1 0 R".to_string());
835
836 let mut ref_catalog = HashMap::new();
837 ref_catalog.insert("Type".to_string(), "Catalog".to_string());
838 let differences = compare_catalogs(&Some(gen_catalog), &Some(ref_catalog));
841
842 assert!(differences
843 .iter()
844 .any(|d| { d.location.contains("Catalog/Pages") && d.expected == "missing" }));
845 }
846
847 #[test]
848 fn test_compare_catalogs_generated_none() {
849 let ref_catalog = HashMap::new();
850 let differences = compare_catalogs(&None, &Some(ref_catalog));
851
852 assert!(differences
853 .iter()
854 .any(|d| { d.location.contains("Document Catalog") && d.actual == "missing" }));
855 }
856
857 #[test]
858 fn test_compare_catalogs_reference_none() {
859 let gen_catalog = HashMap::new();
860 let differences = compare_catalogs(&Some(gen_catalog), &None);
861
862 assert!(differences
863 .iter()
864 .any(|d| { d.location.contains("Document Catalog") && d.expected == "missing" }));
865 }
866
867 #[test]
868 fn test_compare_catalogs_both_none() {
869 let differences = compare_catalogs(&None, &None);
870
871 assert!(differences.iter().any(|d| {
872 d.location.contains("Document Catalog") && d.severity == DifferenceSeverity::Critical
873 }));
874 }
875
876 #[test]
877 fn test_compare_page_trees_different_count() {
878 let gen_tree = PageTree {
879 page_count: 5,
880 root_type: "Pages".to_string(),
881 kids_arrays: vec![],
882 };
883 let ref_tree = PageTree {
884 page_count: 3,
885 root_type: "Pages".to_string(),
886 kids_arrays: vec![],
887 };
888
889 let differences = compare_page_trees(&Some(gen_tree), &Some(ref_tree));
890
891 assert!(differences
892 .iter()
893 .any(|d| { d.location.contains("Page Tree/Count") }));
894 }
895
896 #[test]
897 fn test_compare_page_trees_different_type() {
898 let gen_tree = PageTree {
899 page_count: 1,
900 root_type: "Page".to_string(),
901 kids_arrays: vec![],
902 };
903 let ref_tree = PageTree {
904 page_count: 1,
905 root_type: "Pages".to_string(),
906 kids_arrays: vec![],
907 };
908
909 let differences = compare_page_trees(&Some(gen_tree), &Some(ref_tree));
910
911 assert!(differences
912 .iter()
913 .any(|d| { d.location.contains("Page Tree/Type") }));
914 }
915
916 #[test]
917 fn test_compare_page_trees_generated_none() {
918 let ref_tree = PageTree {
919 page_count: 1,
920 root_type: "Pages".to_string(),
921 kids_arrays: vec![],
922 };
923
924 let differences = compare_page_trees(&None, &Some(ref_tree));
925
926 assert!(differences
927 .iter()
928 .any(|d| { d.location.contains("Page Tree") && d.actual == "missing" }));
929 }
930
931 #[test]
932 fn test_compare_page_trees_reference_none() {
933 let gen_tree = PageTree {
934 page_count: 1,
935 root_type: "Pages".to_string(),
936 kids_arrays: vec![],
937 };
938
939 let differences = compare_page_trees(&Some(gen_tree), &None);
940
941 assert!(differences
942 .iter()
943 .any(|d| { d.location.contains("Page Tree") && d.expected == "missing" }));
944 }
945
946 #[test]
947 fn test_compare_page_trees_both_none() {
948 let differences = compare_page_trees(&None, &None);
949 assert!(differences.is_empty()); }
951
952 #[test]
953 fn test_pdfs_structurally_equivalent_true() {
954 let pdf1 = create_test_pdf("1.4", "Catalog");
955 let pdf2 = create_test_pdf("1.4", "Catalog");
956
957 assert!(pdfs_structurally_equivalent(&pdf1, &pdf2));
958 }
959
960 #[test]
961 fn test_pdfs_structurally_equivalent_invalid_pdf() {
962 let pdf1 = b"not a pdf".to_vec();
963 let pdf2 = b"also not a pdf".to_vec();
964
965 assert!(!pdfs_structurally_equivalent(&pdf1, &pdf2));
967 }
968
969 #[test]
970 fn test_extract_pdf_differences() {
971 let pdf1 = create_test_pdf("1.4", "Catalog");
972 let pdf2 = create_test_pdf("1.7", "Catalog");
973
974 let differences = extract_pdf_differences(&pdf1, &pdf2).unwrap();
975 assert!(!differences.is_empty());
976 }
977
978 #[test]
979 fn test_extract_pdf_differences_identical() {
980 let pdf1 = create_test_pdf("1.4", "Catalog");
981 let pdf2 = create_test_pdf("1.4", "Catalog");
982
983 let differences = extract_pdf_differences(&pdf1, &pdf2).unwrap();
984 assert!(differences.is_empty());
985 }
986
987 #[test]
988 fn test_major_version_difference() {
989 let pdf1 = create_test_pdf("1.4", "Catalog");
990 let pdf2 = create_test_pdf("2.0", "Catalog"); let result = compare_pdfs(&pdf1, &pdf2).unwrap();
993
994 assert!(result.differences.iter().any(|d| {
996 d.location == "PDF Version" && d.severity == DifferenceSeverity::Important
997 }));
998 }
999}