"""End-to-end CLI pipeline tests using the fake VLM client.""" from __future__ import annotations import json from pathlib import Path from typing import Any import pytest from lxml import etree from PIL import Image from odoo_ocr import cli from odoo_ocr.config import Settings from tests.conftest import FakeVLMClient EXTRACTION: dict[str, Any] = { "vendor_name": "Acme", "vendor_address": None, "vendor_vat": None, "invoice_number": "INV-9", "invoice_date": "2024-01-01", "due_date": None, "currency": "EUR", "payment_terms": None, "line_items": [ { "description": "Widget", "quantity": 1, "unit_price": 100.0, "total_price": 100.0, "tax_rate": 0, } ], "subtotal": 100.0, "tax_total": 0.0, "total": 100.0, "iban": None, "raw_ocr_text": "", } def _responses(confidence: float) -> dict[str, Any]: return { "document classifier": { "category": "scanned_print", "confidence": 0.9, "reasoning": "printed invoice", }, "OCR engine": {"text": "Invoice INV-9 from Acme, total 100.00 EUR"}, "invoice data extraction": EXTRACTION, "invoice review assistant": { "valid": True, "confidence": confidence, "issues": [], "corrected_invoice": None, }, } def _write_invoice(tmp_path: Path) -> Path: src = tmp_path / "invoice.jpg" Image.new("RGB", (100, 100), "white").save(src) return src def _install_fake(monkeypatch: pytest.MonkeyPatch, responses: dict[str, Any]) -> FakeVLMClient: fake = FakeVLMClient(responses=responses) monkeypatch.setattr(cli, "OllamaClient", lambda _settings: fake) return fake @pytest.mark.asyncio @pytest.mark.parametrize( ("confidence", "expect_xml", "expect_review_comment"), [(0.95, True, False), (0.80, True, True), (0.70, False, False)], ) async def test_confidence_bands( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, confidence: float, expect_xml: bool, expect_review_comment: bool, ) -> None: src = _write_invoice(tmp_path) out_dir = tmp_path / "out" _install_fake(monkeypatch, _responses(confidence)) sidecar = await cli.process_single(src, out_dir, Settings()) assert sidecar["review"]["confidence"] == confidence assert (out_dir / "invoice_sidecar.json").exists() data = json.loads((out_dir / "invoice_sidecar.json").read_text(encoding="utf-8")) assert data["extracted_invoice"]["vendor_name"] == "Acme" xml_file = out_dir / "invoice.xml" assert xml_file.exists() is expect_xml if expect_xml: xml = xml_file.read_text(encoding="utf-8") assert ("HUMAN REVIEW ADVISED" in xml) is expect_review_comment root = etree.fromstring(xml.encode("utf-8")) models = {record.get("model") for record in root.iter("record")} assert {"res.partner", "account.move", "account.move.line", "account.tax"} <= models @pytest.mark.asyncio async def test_uses_corrected_invoice_from_review( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: src = _write_invoice(tmp_path) responses = _responses(0.95) responses["invoice review assistant"]["corrected_invoice"] = { **EXTRACTION, "invoice_number": "INV-999", } _install_fake(monkeypatch, responses) await cli.process_single(src, tmp_path / "out", Settings()) xml = (tmp_path / "out" / "invoice.xml").read_text(encoding="utf-8") assert "INV-999" in xml @pytest.mark.asyncio async def test_process_batch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: _install_fake(monkeypatch, _responses(0.95)) (tmp_path / "a.jpg").touch() Image.new("RGB", (10, 10), "white").save(tmp_path / "b.jpg") results = await cli.process_batch(tmp_path, tmp_path / "out", Settings()) assert len(results) == 1 assert (tmp_path / "out" / "b.xml").exists()