94 lines
3.9 KiB
Python
94 lines
3.9 KiB
Python
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
from PIL import Image, PdfParser
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
|
from scan_document import create_document
|
|
|
|
|
|
class ScanDocumentTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
|
|
def pages(self, count=4):
|
|
paths = []
|
|
for index in range(1, count + 1):
|
|
target = self.root / f"page-{index:06}.png"
|
|
# Distinct geometry identifies every front/back page and its order.
|
|
with Image.new("RGB", (100 + index, 200 + index), "white") as image:
|
|
image.save(target)
|
|
paths.append(target)
|
|
return paths
|
|
|
|
def assert_pdf(self, output, count, resolution=300):
|
|
with PdfParser.PdfParser(filename=str(output)) as pdf:
|
|
self.assertEqual(len(pdf.pages), count)
|
|
for index, reference in enumerate(pdf.pages, 1):
|
|
page = pdf.read_indirect(reference)
|
|
box = page[b"MediaBox"]
|
|
self.assertAlmostEqual(box[2], (100 + index) * 72 / resolution, places=4)
|
|
self.assertAlmostEqual(box[3], (200 + index) * 72 / resolution, places=4)
|
|
|
|
def test_twelve_pages_keep_front_back_order_blank_pages_and_scan_resolution(self):
|
|
output = self.root / "scan.pdf"
|
|
result = create_document(self.pages(12), output, 150)
|
|
self.assertEqual(result, {"pageCount": 12, "warnings": []})
|
|
self.assert_pdf(output, 12, 150)
|
|
|
|
def test_missing_opencv_still_creates_complete_pdf(self):
|
|
output = self.root / "scan.pdf"
|
|
with patch.dict(sys.modules, {"opencv_postprocess": None}):
|
|
result = create_document(self.pages(), output, 300, use_postprocess=True)
|
|
self.assertEqual(len(result["warnings"]), 1)
|
|
self.assert_pdf(output, 4)
|
|
|
|
def test_failed_correction_falls_back_per_page_without_losing_sides(self):
|
|
def correct(source, target, profile):
|
|
if source.name.endswith("000002.png"):
|
|
raise RuntimeError("correction failed")
|
|
target.write_bytes(source.read_bytes())
|
|
|
|
output = self.root / "scan.pdf"
|
|
with patch.dict(sys.modules, {"opencv_postprocess": SimpleNamespace(postprocess=correct)}):
|
|
result = create_document(self.pages(), output, 300, use_postprocess=True)
|
|
self.assertEqual(len(result["warnings"]), 1)
|
|
self.assertIn("Seite 2", result["warnings"][0])
|
|
self.assert_pdf(output, 4)
|
|
|
|
def test_strict_mode_does_not_silently_skip_missing_correction(self):
|
|
output = self.root / "scan.pdf"
|
|
with patch.dict(sys.modules, {"opencv_postprocess": None}):
|
|
with self.assertRaises(ImportError):
|
|
create_document(self.pages(), output, 300, use_postprocess=True, strict=True)
|
|
self.assertFalse(output.exists())
|
|
|
|
def test_corrupt_page_never_publishes_partial_pdf(self):
|
|
pages = self.pages()
|
|
pages[2].write_bytes(b"corrupt")
|
|
output = self.root / "scan.pdf"
|
|
with self.assertRaises(OSError):
|
|
create_document(pages, output, 300)
|
|
self.assertFalse(output.exists())
|
|
self.assertFalse((self.root / "scan.pdf.partial").exists())
|
|
self.assertTrue(all(page.exists() for page in pages))
|
|
|
|
def test_single_page_png_and_tiff_remain_supported(self):
|
|
pages = self.pages(1)
|
|
for extension in ["png", "tiff"]:
|
|
output = self.root / f"scan.{extension}"
|
|
create_document(pages, output, 150)
|
|
with Image.open(output) as image:
|
|
self.assertEqual(image.size, (101, 201))
|
|
self.assertAlmostEqual(image.info["dpi"][0], 150, delta=0.1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|