Adds the two Protocol-based client contracts the pipeline steps depend on, plus test-oriented fakes. Real engines (Surya, Ollama) land in Chunk 4. - ix.ocr.client.OCRClient — runtime_checkable Protocol with async ocr(). - ix.genai.client.GenAIClient — runtime_checkable Protocol with async invoke(); GenAIInvocationResult + GenAIUsage dataclasses carry the parsed model, token usage, and model name. - FakeOCRClient / FakeGenAIClient: return canned results; both expose a raise_on_call hook for error-path tests. 8 unit tests across tests/unit/test_ocr_fake.py + test_genai_fake.py confirm protocol conformance, canned-return behaviour, usage/model-name defaults, and raise_on_call propagation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
"""Tests for OCRClient Protocol + FakeOCRClient (spec §6.2)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from ix.contracts import Line, OCRDetails, OCRResult, Page
|
|
from ix.ocr import FakeOCRClient, OCRClient
|
|
|
|
|
|
def _canned() -> OCRResult:
|
|
return OCRResult(
|
|
result=OCRDetails(
|
|
text="hello world",
|
|
pages=[
|
|
Page(
|
|
page_no=1,
|
|
width=100.0,
|
|
height=200.0,
|
|
lines=[Line(text="hello world", bounding_box=[0, 0, 10, 0, 10, 5, 0, 5])],
|
|
)
|
|
],
|
|
),
|
|
meta_data={"engine": "fake"},
|
|
)
|
|
|
|
|
|
class TestProtocolConformance:
|
|
def test_fake_is_runtime_checkable_as_protocol(self) -> None:
|
|
client = FakeOCRClient(canned=_canned())
|
|
assert isinstance(client, OCRClient)
|
|
|
|
|
|
class TestReturnsCannedResult:
|
|
async def test_returns_exact_canned_result(self) -> None:
|
|
canned = _canned()
|
|
client = FakeOCRClient(canned=canned)
|
|
result = await client.ocr(pages=[])
|
|
assert result is canned
|
|
assert result.result.text == "hello world"
|
|
assert result.meta_data == {"engine": "fake"}
|
|
|
|
async def test_pages_argument_is_accepted_but_ignored(self) -> None:
|
|
canned = _canned()
|
|
client = FakeOCRClient(canned=canned)
|
|
result = await client.ocr(
|
|
pages=[Page(page_no=5, width=1.0, height=1.0, lines=[])]
|
|
)
|
|
assert result is canned
|
|
|
|
|
|
class TestRaiseOnCallHook:
|
|
async def test_raise_on_call_propagates(self) -> None:
|
|
err = RuntimeError("surya is down")
|
|
client = FakeOCRClient(canned=_canned(), raise_on_call=err)
|
|
with pytest.raises(RuntimeError, match="surya is down"):
|
|
await client.ocr(pages=[])
|