from __future__ import annotations from typing import Any, Dict, List, Optional, Tuple from application.service import InvoiceProcessingService from application.strategies import InvoiceExtractorStrategy import application.service as service_module class DummyExtractor(InvoiceExtractorStrategy): """Extractor mínimo para pruebas que devuelve un JSON fijo.""" def __init__(self, fail_first: bool = False) -> None: self.fail_first = fail_first self.calls = 0 def extract(self, images_b64: List[str], instruction: str) -> Dict[str, Any]: self.calls += 1 if self.fail_first and self.calls == 1: raise RuntimeError("forced failure") return { "raw": '{"ok": true}', "usage": {}, } def test_extract_full_from_pdf_propagates_tiff_page_range(monkeypatch, tmp_path) -> None: dummy_pdf = tmp_path / "doc.tif" dummy_pdf.write_bytes(b"fake") captured: List[Dict[str, Any]] = [] def fake_document_to_images_bs64(path: str, **kwargs: Any) -> List[str]: captured.append({"path": path, **kwargs}) return ["img"] # Parchear el símbolo importado en el módulo de servicio monkeypatch.setattr(service_module, "document_to_images_bs64", fake_document_to_images_bs64) service = InvoiceProcessingService(DummyExtractor()) data, usage = service.extract_full_from_pdf(str(dummy_pdf), instruction="x", tiff_page_range=(2, 3)) assert data == {"ok": True} assert isinstance(usage, dict) assert captured, "Se esperaba al menos una llamada a document_to_images_bs64" # Primera llamada (flujo normal) debe respetar el rango solicitado assert captured[0].get("tiff_page_range") == (2, 3) def test_extract_full_from_pdf_fallback_ignores_tiff_page_range(monkeypatch, tmp_path) -> None: dummy_pdf = tmp_path / "doc2.tif" dummy_pdf.write_bytes(b"fake") captured: List[Dict[str, Any]] = [] def fake_document_to_images_bs64(path: str, **kwargs: Any) -> List[str]: captured.append({"path": path, **kwargs}) return ["img"] monkeypatch.setattr(service_module, "document_to_images_bs64", fake_document_to_images_bs64) service = InvoiceProcessingService(DummyExtractor(fail_first=True)) data, usage = service.extract_full_from_pdf(str(dummy_pdf), instruction="x", tiff_page_range=(2, 3)) assert data == {"ok": True} assert isinstance(usage, dict) # Deben existir dos llamadas: una normal (que falla el extractor) y otra fallback assert len(captured) == 2 assert captured[0].get("tiff_page_range") == (2, 3) # En fallback NO debe propagarse el rango explícito assert captured[1].get("tiff_page_range") is None