test_pdf_thumbnail.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """Unit tests for server-side PDF thumbnails (#2976).
  2. generate_pdf_thumbnail renders page one with PDFium into the same square,
  3. white-backed PNG the browser preview posts back, and returns None rather than
  4. raising for anything it cannot render.
  5. """
  6. from pathlib import Path
  7. import pytest
  8. from PIL import Image
  9. from reportlab.lib.pagesizes import A4, landscape
  10. from reportlab.pdfgen import canvas
  11. from backend.app.services.pdf_thumbnail import generate_pdf_thumbnail
  12. def _make_pdf(path: Path, pagesize=A4, pages: int = 1) -> Path:
  13. c = canvas.Canvas(str(path), pagesize=pagesize)
  14. for n in range(pages):
  15. # A solid black block in the top-left corner of each page, so the
  16. # render can be told apart from a blank white square.
  17. c.setFillColorRGB(0, 0, 0)
  18. c.rect(0, pagesize[1] - 200, 200, 200, fill=1, stroke=0)
  19. c.drawString(72, 72, f"page {n + 1}")
  20. c.showPage()
  21. c.save()
  22. return path
  23. def test_renders_page_one_as_square_png(tmp_path):
  24. pdf = _make_pdf(tmp_path / "doc.pdf", pages=3)
  25. thumb = generate_pdf_thumbnail(pdf, tmp_path)
  26. assert thumb is not None
  27. with Image.open(thumb) as img:
  28. assert img.format == "PNG"
  29. assert img.size == (256, 256)
  30. assert img.mode == "RGB"
  31. # Something was drawn: not a blank white square.
  32. assert img.getextrema() != ((255, 255), (255, 255), (255, 255))
  33. def test_portrait_page_is_centred_on_white(tmp_path):
  34. pdf = _make_pdf(tmp_path / "portrait.pdf")
  35. thumb = generate_pdf_thumbnail(pdf, tmp_path)
  36. with Image.open(thumb) as img:
  37. # A4 portrait is narrower than tall: the side margins are padding.
  38. assert img.getpixel((2, 128)) == (255, 255, 255)
  39. assert img.getpixel((253, 128)) == (255, 255, 255)
  40. def test_landscape_page_is_centred_on_white(tmp_path):
  41. pdf = _make_pdf(tmp_path / "landscape.pdf", pagesize=landscape(A4))
  42. thumb = generate_pdf_thumbnail(pdf, tmp_path)
  43. with Image.open(thumb) as img:
  44. assert img.size == (256, 256)
  45. assert img.getpixel((128, 2)) == (255, 255, 255)
  46. assert img.getpixel((128, 253)) == (255, 255, 255)
  47. def test_huge_mediabox_still_yields_bounded_thumbnail(tmp_path):
  48. # 5 m x 5 m: the render scale comes from the page size, so this must not
  49. # allocate a page-sized bitmap.
  50. pdf = _make_pdf(tmp_path / "huge.pdf", pagesize=(14400, 14400))
  51. thumb = generate_pdf_thumbnail(pdf, tmp_path)
  52. with Image.open(thumb) as img:
  53. assert img.size == (256, 256)
  54. @pytest.mark.parametrize("content", [b"", b"not a pdf at all", b"%PDF-1.4\n%truncated"])
  55. def test_unreadable_pdf_returns_none(tmp_path, content):
  56. bad = tmp_path / "bad.pdf"
  57. bad.write_bytes(content)
  58. assert generate_pdf_thumbnail(bad, tmp_path) is None
  59. assert list(tmp_path.glob("*.png")) == []
  60. def test_missing_file_returns_none(tmp_path):
  61. assert generate_pdf_thumbnail(tmp_path / "missing.pdf", tmp_path) is None