refactor(ai): OCR sidecar canonical naming cleanup — typhoon→np-dms, remove hardcoded keys, asyncio.to_thread, ADR-040/041
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# File: tests/unit/ocr-sidecar/test_residency_wiring.py
|
||||
# Change Log:
|
||||
# - 2026-06-20: Added ADR-040 residency wiring tests for process_ocr.
|
||||
# - 2026-06-20: Updated for async process_ocr (Phase 6 — async I/O refactor).
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from test_path_traversal import load_app
|
||||
|
||||
|
||||
class FakeAsyncResponse:
|
||||
"""จำลอง httpx.AsyncClient response สำหรับ async process_ocr"""
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
def json(self) -> dict:
|
||||
return {"choices": [{"message": {"content": "{\"natural_text\": \"ok\"}"}}]}
|
||||
|
||||
|
||||
class FakeAsyncClient:
|
||||
"""จำลอง httpx.AsyncClient สำหรับ async process_ocr"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
self.payload = None
|
||||
FakeAsyncClient.last_payload = None
|
||||
|
||||
async def post(self, url: str, json: dict, headers: dict) -> FakeAsyncResponse:
|
||||
self.payload = json
|
||||
FakeAsyncClient.last_payload = json
|
||||
return FakeAsyncResponse()
|
||||
|
||||
async def aclose(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
FakeAsyncClient.last_payload = None
|
||||
|
||||
|
||||
def test_process_ocr_uses_calculated_residency_keep_alive(tmp_path: Path) -> None:
|
||||
"""T019: process_ocr ต้องเรียก calculate_ocr_residency และใช้ค่า keep_alive ที่คำนวณได้"""
|
||||
app_module = load_app(tmp_path)
|
||||
decision = SimpleNamespace(keep_alive_seconds=120, reason="headroom-sufficient", vram_headroom_mb=9000.0)
|
||||
fake_client = FakeAsyncClient()
|
||||
with patch.object(app_module, "calculate_ocr_residency", return_value=decision) as calculate, \
|
||||
patch.object(app_module, "prepare_ocr_messages", return_value=[{"content": []}]), \
|
||||
patch.object(app_module, "ollama_client", fake_client):
|
||||
result = asyncio.run(app_module.process_ocr("/tmp/test.pdf", page_num=1))
|
||||
assert result == "ok"
|
||||
calculate.assert_called_once_with(app_module.OCR_ACTIVE_PROFILE)
|
||||
assert FakeAsyncClient.last_payload["keep_alive"] == 120
|
||||
|
||||
|
||||
def test_process_ocr_rejects_backend_keep_alive_override(tmp_path: Path) -> None:
|
||||
"""T021: process_ocr ต้องปฏิเสธ keep_alive จาก backend"""
|
||||
app_module = load_app(tmp_path)
|
||||
|
||||
async def run_test():
|
||||
with pytest.raises(ValueError, match="keep_alive must be calculated"):
|
||||
await app_module.process_ocr("/tmp/test.pdf", options_override={"keep_alive": 0})
|
||||
|
||||
asyncio.run(run_test())
|
||||
|
||||
|
||||
def test_ocr_endpoint_rejects_keep_alive_override(tmp_path: Path) -> None:
|
||||
"""T021: /ocr endpoint ต้องปฏิเสธ keep_alive ใน request body"""
|
||||
app_module = load_app(tmp_path)
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
client = TestClient(app_module.app)
|
||||
response = client.post(
|
||||
"/ocr",
|
||||
json={"pdfPath": str(tmp_path / "document.pdf"), "keep_alive": 0},
|
||||
headers={"X-API-Key": "test-key"},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
Reference in New Issue
Block a user