XmLLM / tests /unit /test_adapter_word_box.py
Claude
Code quality: fix all ruff warnings, add CI/CD, improve test coverage
bbbfba8 unverified
Raw
History Blame Contribute Delete
7.39 kB
"""Tests for the word_box_json adapter (PaddleOCR format)."""
from __future__ import annotations
import json
from typing import TYPE_CHECKING
import pytest
from src.app.domain.models import (
EvidenceType,
GeometryStatus,
RawProviderPayload,
)
from src.app.domain.models.geometry import GeometryContext
from src.app.providers.adapters.word_box_json import WordBoxJsonAdapter
if TYPE_CHECKING:
from pathlib import Path
@pytest.fixture
def adapter() -> WordBoxJsonAdapter:
return WordBoxJsonAdapter()
@pytest.fixture
def paddle_payload(fixtures_dir: Path) -> list:
with open(fixtures_dir / "paddle_ocr_sample.json") as f:
return json.load(f)
@pytest.fixture
def raw(paddle_payload: list) -> RawProviderPayload:
return RawProviderPayload(
provider_id="paddleocr",
adapter_id="adapter.word_box_json.v1",
runtime_type="local",
payload=paddle_payload,
image_width=2480,
image_height=3508,
)
@pytest.fixture
def geo_ctx() -> GeometryContext:
return GeometryContext(source_width=2480, source_height=3508)
class TestWordBoxJsonAdapter:
def test_family(self, adapter: WordBoxJsonAdapter) -> None:
assert adapter.family == "word_box_json"
def test_version(self, adapter: WordBoxJsonAdapter) -> None:
assert "v1" in adapter.version
def test_normalize_produces_valid_document(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(
raw, geo_ctx, document_id="test_doc", source_filename="test.png"
)
assert doc.document_id == "test_doc"
assert doc.source.filename == "test.png"
assert len(doc.pages) == 1
def test_page_dimensions(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
page = doc.pages[0]
assert page.width == 2480
assert page.height == 3508
def test_correct_word_count(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
page = doc.pages[0]
# 5 items in fixture → 5 lines, each with 1 word
total_words = sum(
len(line.words)
for region in page.text_regions
for line in region.lines
)
assert total_words == 5
def test_word_text_preserved(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
words = [
w.text
for r in doc.pages[0].text_regions
for ln in r.lines
for w in ln.words
]
assert words[0] == "Bonjour"
assert words[1] == "le"
assert words[2] == "monde"
def test_confidence_preserved(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
first_word = doc.pages[0].text_regions[0].lines[0].words[0]
assert first_word.confidence == pytest.approx(0.96)
def test_geometry_is_exact(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
first_word = doc.pages[0].text_regions[0].lines[0].words[0]
assert first_word.geometry.status == GeometryStatus.EXACT
def test_polygon_preserved(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
first_word = doc.pages[0].text_regions[0].lines[0].words[0]
assert first_word.geometry.polygon is not None
assert len(first_word.geometry.polygon) == 4
def test_bbox_correct(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
first_word = doc.pages[0].text_regions[0].lines[0].words[0]
# From fixture: [[100,200],[300,200],[300,240],[100,240]]
# → bbox = (100, 200, 200, 40)
x, y, w, h = first_word.geometry.bbox
assert x == pytest.approx(100)
assert y == pytest.approx(200)
assert w == pytest.approx(200)
assert h == pytest.approx(40)
def test_provenance_native(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
first_word = doc.pages[0].text_regions[0].lines[0].words[0]
assert first_word.provenance.evidence_type == EvidenceType.PROVIDER_NATIVE
assert first_word.provenance.provider == "paddleocr"
assert "$[0]" in first_word.provenance.source_ref
def test_block_is_inferred(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
region = doc.pages[0].text_regions[0]
assert region.geometry.status == GeometryStatus.INFERRED
assert region.provenance.evidence_type == EvidenceType.DERIVED
def test_reading_order(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
assert doc.pages[0].reading_order == ["tb1"]
def test_empty_payload_rejected(
self,
adapter: WordBoxJsonAdapter,
geo_ctx: GeometryContext,
) -> None:
raw = RawProviderPayload(
provider_id="test",
adapter_id="v1",
runtime_type="local",
payload=[],
)
with pytest.raises(ValueError, match="no items"):
adapter.normalize(raw, geo_ctx, document_id="test_doc")
def test_dict_payload_rejected(
self,
adapter: WordBoxJsonAdapter,
geo_ctx: GeometryContext,
) -> None:
raw = RawProviderPayload(
provider_id="test",
adapter_id="v1",
runtime_type="local",
payload={"not": "a list"},
)
with pytest.raises(ValueError, match="list payload"):
adapter.normalize(raw, geo_ctx, document_id="test_doc")
def test_json_roundtrip(
self,
adapter: WordBoxJsonAdapter,
raw: RawProviderPayload,
geo_ctx: GeometryContext,
) -> None:
doc = adapter.normalize(raw, geo_ctx, document_id="test_doc")
data = doc.model_dump(mode="json")
from src.app.domain.models import CanonicalDocument
doc2 = CanonicalDocument.model_validate(data)
assert doc2.document_id == doc.document_id