"""Tests for :class:`ix.pipeline.response_handler_step.ResponseHandlerStep` (spec ยง8)."""
from __future__ import annotations
from ix.contracts import (
Context,
Line,
OCRDetails,
OCROptions,
OCRResult,
Options,
Page,
RequestIX,
ResponseIX,
)
from ix.contracts.response import _InternalContext
from ix.pipeline.response_handler_step import ResponseHandlerStep
def _make_request(
*,
include_geometries: bool = False,
include_ocr_text: bool = False,
) -> RequestIX:
return RequestIX(
use_case="bank_statement_header",
ix_client_id="test",
request_id="r-1",
context=Context(files=[], texts=[]),
options=Options(
ocr=OCROptions(
include_geometries=include_geometries,
include_ocr_text=include_ocr_text,
)
),
)
def _populated_response() -> ResponseIX:
resp = ResponseIX(
ocr_result=OCRResult(
result=OCRDetails(
text=None,
pages=[
Page(
page_no=1,
width=100.0,
height=200.0,
lines=[
Line(text='', bounding_box=[]),
Line(text="hello", bounding_box=[0, 0, 1, 0, 1, 1, 0, 1]),
Line(text="world", bounding_box=[0, 2, 1, 2, 1, 3, 0, 3]),
Line(text="", bounding_box=[]),
],
),
Page(
page_no=2,
width=100.0,
height=200.0,
lines=[
Line(text="p2 line", bounding_box=[0, 0, 1, 0, 1, 1, 0, 1]),
],
),
],
),
meta_data={"adapter": "fake"},
)
)
resp.context = _InternalContext()
return resp
class TestValidateAlwaysTrue:
async def test_validate_always_true(self) -> None:
step = ResponseHandlerStep()
req = _make_request()
assert await step.validate(req, _populated_response()) is True
class TestAttachOcrText:
async def test_include_ocr_text_concatenates_lines(self) -> None:
step = ResponseHandlerStep()
req = _make_request(include_ocr_text=True, include_geometries=True)
resp = _populated_response()
resp = await step.process(req, resp)
# Page tag lines excluded; real lines joined within page with \n,
# pages with \n\n.
text = resp.ocr_result.result.text
assert text is not None
assert "hello\nworld" in text
assert "p2 line" in text
assert " None:
step = ResponseHandlerStep()
req = _make_request(include_ocr_text=False, include_geometries=True)
resp = _populated_response()
resp.ocr_result.result.text = None
resp = await step.process(req, resp)
assert resp.ocr_result.result.text is None
class TestStripGeometries:
async def test_strips_pages_and_meta_when_off(self) -> None:
step = ResponseHandlerStep()
req = _make_request(include_geometries=False)
resp = _populated_response()
resp = await step.process(req, resp)
assert resp.ocr_result.result.pages == []
assert resp.ocr_result.meta_data == {}
async def test_keeps_pages_when_on(self) -> None:
step = ResponseHandlerStep()
req = _make_request(include_geometries=True)
resp = _populated_response()
pages_before = [p.page_no for p in resp.ocr_result.result.pages]
resp = await step.process(req, resp)
assert [p.page_no for p in resp.ocr_result.result.pages] == pages_before
assert resp.ocr_result.meta_data == {"adapter": "fake"}
class TestContextDeletion:
async def test_context_removed(self) -> None:
step = ResponseHandlerStep()
req = _make_request()
resp = _populated_response()
resp = await step.process(req, resp)
assert resp.context is None
async def test_context_not_in_model_dump(self) -> None:
step = ResponseHandlerStep()
req = _make_request()
resp = _populated_response()
resp = await step.process(req, resp)
dump = resp.model_dump()
assert "context" not in dump