infoxtractor/tests/unit/test_genai_fake.py
Dirk Riemann 118a9abd09
All checks were successful
tests / test (push) Successful in 1m0s
tests / test (pull_request) Successful in 1m1s
feat(clients): OCRClient + GenAIClient protocols + fakes (spec §6.2, §6.3)
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>
2026-04-18 11:08:24 +02:00

55 lines
1.9 KiB
Python

"""Tests for GenAIClient Protocol + FakeGenAIClient (spec §6.3)."""
from __future__ import annotations
import pytest
from pydantic import BaseModel
from ix.genai import (
FakeGenAIClient,
GenAIClient,
GenAIInvocationResult,
GenAIUsage,
)
class _Schema(BaseModel):
foo: str
bar: int
class TestProtocolConformance:
def test_fake_is_runtime_checkable_as_protocol(self) -> None:
client = FakeGenAIClient(parsed=_Schema(foo="x", bar=1))
assert isinstance(client, GenAIClient)
class TestReturnsCannedResult:
async def test_defaults_populate_usage_and_model(self) -> None:
parsed = _Schema(foo="bank", bar=2)
client = FakeGenAIClient(parsed=parsed)
result = await client.invoke(request_kwargs={"model": "x"}, response_schema=_Schema)
assert isinstance(result, GenAIInvocationResult)
assert result.parsed is parsed
assert isinstance(result.usage, GenAIUsage)
assert result.usage.prompt_tokens == 0
assert result.usage.completion_tokens == 0
assert result.model_name == "fake"
async def test_explicit_usage_and_model_passed_through(self) -> None:
parsed = _Schema(foo="k", bar=3)
usage = GenAIUsage(prompt_tokens=10, completion_tokens=20)
client = FakeGenAIClient(parsed=parsed, usage=usage, model_name="mock:0.1")
result = await client.invoke(request_kwargs={}, response_schema=_Schema)
assert result.usage is usage
assert result.model_name == "mock:0.1"
class TestRaiseOnCallHook:
async def test_raise_on_call_propagates(self) -> None:
err = RuntimeError("ollama is down")
client = FakeGenAIClient(
parsed=_Schema(foo="x", bar=1), raise_on_call=err
)
with pytest.raises(RuntimeError, match="ollama is down"):
await client.invoke(request_kwargs={}, response_schema=_Schema)