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>
55 lines
1.9 KiB
Python
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)
|