Task 4.3 closes the loop on Chunk 4: the FastAPI lifespan now selects fake vs real clients via IX_TEST_MODE (new AppConfig field), wires /healthz probes to the live selfcheck() on OllamaClient / SuryaOCRClient, and spawns the worker with a production Pipeline factory that builds SetupStep -> OCRStep -> GenAIStep -> ReliabilityStep -> ResponseHandler over the injected clients. Factories: - make_genai_client(cfg) -> FakeGenAIClient | OllamaClient - make_ocr_client(cfg) -> FakeOCRClient | SuryaOCRClient (spec §6.2) Probes run the async selfcheck on a fresh event loop in a short-lived thread so they're safe to call from either sync callers or a live FastAPI handler without stalling the request loop. Drops the worker-loop spawn_worker_task stub — the app module owns the production spawn directly. Tests: +11 unit tests (5 factories + 6 app-wiring / probe adapter / pipeline build). Full suite: 236 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
60 lines
2.1 KiB
Python
60 lines
2.1 KiB
Python
"""Tests for the GenAI + OCR factories (Task 4.3).
|
|
|
|
The factories pick between fake and real clients based on
|
|
``IX_TEST_MODE``. CI runs with ``IX_TEST_MODE=fake``, production runs
|
|
without — so the selection knob is the one lever between hermetic CI and
|
|
real clients.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from ix.config import AppConfig
|
|
from ix.genai import make_genai_client
|
|
from ix.genai.fake import FakeGenAIClient
|
|
from ix.genai.ollama_client import OllamaClient
|
|
from ix.ocr import make_ocr_client
|
|
from ix.ocr.fake import FakeOCRClient
|
|
from ix.ocr.surya_client import SuryaOCRClient
|
|
|
|
|
|
def _cfg(**overrides: object) -> AppConfig:
|
|
"""Build an AppConfig without loading the repo's .env.example."""
|
|
return AppConfig(_env_file=None, **overrides) # type: ignore[call-arg]
|
|
|
|
|
|
class TestGenAIFactory:
|
|
def test_fake_mode_returns_fake(self) -> None:
|
|
cfg = _cfg(test_mode="fake")
|
|
client = make_genai_client(cfg)
|
|
assert isinstance(client, FakeGenAIClient)
|
|
|
|
def test_production_returns_ollama_with_configured_url(self) -> None:
|
|
cfg = _cfg(
|
|
test_mode=None,
|
|
ollama_url="http://ollama.host:11434",
|
|
genai_call_timeout_seconds=42,
|
|
)
|
|
client = make_genai_client(cfg)
|
|
assert isinstance(client, OllamaClient)
|
|
# Inspect the private attrs for binding correctness.
|
|
assert client._base_url == "http://ollama.host:11434"
|
|
assert client._per_call_timeout_s == 42
|
|
|
|
|
|
class TestOCRFactory:
|
|
def test_fake_mode_returns_fake(self) -> None:
|
|
cfg = _cfg(test_mode="fake")
|
|
client = make_ocr_client(cfg)
|
|
assert isinstance(client, FakeOCRClient)
|
|
|
|
def test_production_surya_returns_surya(self) -> None:
|
|
cfg = _cfg(test_mode=None, ocr_engine="surya")
|
|
client = make_ocr_client(cfg)
|
|
assert isinstance(client, SuryaOCRClient)
|
|
|
|
def test_unknown_engine_raises(self) -> None:
|
|
cfg = _cfg(test_mode=None, ocr_engine="tesseract")
|
|
import pytest
|
|
|
|
with pytest.raises(ValueError, match="ocr_engine"):
|
|
make_ocr_client(cfg)
|