Smartfolio-python / tests /test_pdf_extractor.py
ibrahimgulbutt's picture
deploy: extract only the text a person can see (hidden/white/1pt detection)
be87d41
Raw History Blame Contribute Delete
6.65 kB
import unittest
import sys
import io
from unittest.mock import Mock
# The layout helper tests do not open PDFs, so keep them runnable before the
# optional service dependencies are installed in a fresh clone.
sys.modules.setdefault('pdfplumber', Mock())
from services.pdf_extractor import (
_detect_multiple_columns,
_detect_sections,
_normalize_font_name,
extract_docx_from_stream,
)
class FakePage:
width = 600
def __init__(self, words):
self._words = words
def extract_words(self, **_kwargs):
return self._words
class PdfExtractorTests(unittest.TestCase):
def test_normalizes_embedded_font_variants(self):
self.assertEqual(_normalize_font_name('ABCDEF+Arial-BoldMT'), 'arial')
def test_detects_repeated_left_and_right_regions(self):
words = []
for row in range(8):
top = row * 20
words.extend([
{'top': top, 'x0': 30, 'x1': 140, 'text': 'Left column content'},
{'top': top, 'x0': 390, 'x1': 550, 'text': 'Right column content'},
])
# Ensure the fixture passes the minimum word-count guard.
words.extend({'top': 200 + i, 'x0': 30, 'x1': 60, 'text': 'word'} for i in range(5))
self.assertTrue(_detect_multiple_columns(FakePage(words)))
def test_extracts_named_sections(self):
sections = _detect_sections('Jane Doe\nSUMMARY\nEngineer\nSKILLS\nPython\nEDUCATION\nBS CS')
self.assertEqual(sections['summary'], ['Engineer'])
self.assertEqual(sections['skills'], ['Python'])
self.assertEqual(sections['education'], ['BS CS'])
def test_extracts_docx_text_and_layout_metadata(self):
from docx import Document
from docx.shared import Pt
document = Document()
heading = document.add_paragraph('Jane Doe')
heading.runs[0].font.name = 'Arial'
heading.runs[0].font.size = Pt(16)
document.add_paragraph('SUMMARY')
document.add_paragraph('Software engineer building reliable products.')
document.add_paragraph('SKILLS')
document.add_paragraph('Python, TypeScript')
stream = io.BytesIO()
document.save(stream)
stream.seek(0)
result = extract_docx_from_stream(stream)
self.assertIn('Software engineer', result['text'])
self.assertEqual(result['sections']['skills'], ['Python, TypeScript'])
self.assertIn('fontCount', result['metadata'])
if __name__ == '__main__':
unittest.main()
class FakeCharPage:
"""A page whose characters we control, for the hidden-text splitter."""
def __init__(self, chars):
self.chars = chars
def extract_text(self):
return "".join(str(c.get("text", "")) for c in self.chars)
def filter(self, predicate):
kept = [c for c in self.chars if predicate({**c, "object_type": "char"})]
return FakeCharPage(kept)
def _char(text, size=11.0, color=(0, 0, 0)):
return {"text": text, "size": size, "non_stroking_color": color, "object_type": "char"}
class HiddenTextTests(unittest.TestCase):
"""White-on-white and 1pt text is written for the scorer, not the reader."""
def test_background_colour_detection_across_colourspaces(self):
from services.pdf_extractor import _is_background_colour
self.assertTrue(_is_background_colour(1)) # greyscale white
self.assertTrue(_is_background_colour((1, 1, 1))) # RGB white
self.assertTrue(_is_background_colour((0.99, 1, 0.98))) # near-white, the sneaky one
self.assertTrue(_is_background_colour((0, 0, 0, 0))) # CMYK, no ink
self.assertFalse(_is_background_colour((0, 0, 0))) # black
self.assertFalse(_is_background_colour(0.2)) # dark grey
self.assertFalse(_is_background_colour(None)) # unknown is never evidence
def test_unreadably_small_text_counts_as_hidden(self):
from services.pdf_extractor import _is_hidden_char
self.assertTrue(_is_hidden_char(_char("x", size=1.0)))
self.assertFalse(_is_hidden_char(_char("x", size=9.0)))
def test_white_block_is_split_out_of_the_visible_text(self):
from services.pdf_extractor import _split_hidden
# The hidden block has to be the MINORITY of the page, which is what a
# real attack looks like: an ordinary CV with a small invisible payload.
body = "Frontend Developer at Orchard Labs building the customer dashboard"
visible = [_char(c) for c in body]
hidden = [_char(c, color=(1, 1, 1)) for c in "KubernetesTerraform"]
page = FakeCharPage(visible + hidden)
text, hidden_text = _split_hidden(page)
self.assertEqual(text, body)
self.assertEqual(hidden_text, "KubernetesTerraform")
self.assertNotIn("Kubernetes", text)
def test_a_dark_background_design_is_not_treated_as_hiding(self):
"""Mostly-white type means a dark template, not a hiding place.
Flagging it would accuse a designer of fraud for owning a portfolio CV.
"""
from services.pdf_extractor import _split_hidden
page = FakeCharPage([_char(c, color=(1, 1, 1)) for c in "Creative Director"])
text, hidden_text = _split_hidden(page)
self.assertEqual(text, "Creative Director")
self.assertEqual(hidden_text, "")
def test_an_ordinary_page_reports_nothing_hidden(self):
from services.pdf_extractor import _split_hidden
page = FakeCharPage([_char(c) for c in "Registered Nurse"])
text, hidden_text = _split_hidden(page)
self.assertEqual(text, "Registered Nurse")
self.assertEqual(hidden_text, "")
class HiddenDocxRunTests(unittest.TestCase):
def _run(self, hidden=None, size_pt=None, rgb=None):
run = Mock()
run.font.hidden = hidden
run.font.size = Mock(pt=size_pt) if size_pt is not None else None
run.font.color = Mock(rgb=rgb) if rgb is not None else None
return run
def test_words_own_hidden_attribute_is_respected(self):
from services.pdf_extractor import _is_hidden_run
self.assertTrue(_is_hidden_run(self._run(hidden=True)))
def test_white_font_colour_is_hidden(self):
from services.pdf_extractor import _is_hidden_run
self.assertTrue(_is_hidden_run(self._run(rgb="FFFFFF")))
self.assertFalse(_is_hidden_run(self._run(rgb="222222")))
def test_normal_run_is_visible(self):
from services.pdf_extractor import _is_hidden_run
self.assertFalse(_is_hidden_run(self._run(size_pt=11.0, rgb="000000")))