Files
IPSExtractor/services/extraction/tests/test_desktop_ingest.py
T

229 lines
7.0 KiB
Python

"""Regresiones de persistencia para ingestas desktop síncronas."""
from __future__ import annotations
import asyncio
from contextlib import contextmanager
from types import SimpleNamespace
import pytest
from api import desktop_ingest
from api import main as extraction_api
class FakeSession:
def __init__(self) -> None:
self.added: list[object] = []
self.batch = SimpleNamespace(status="running", updated_at=None)
def add(self, value: object) -> None:
self.added.append(value)
def flush(self) -> None:
pass
def get(self, _model: object, _identifier: str) -> object:
return self.batch
def refresh(self, _value: object) -> None:
pass
def test_multiple_document_types_complete_once_per_radicado(monkeypatch) -> None:
monkeypatch.setattr(
desktop_ingest,
"flatten_extraction_result",
lambda fused, _fields, _aliases: [fused],
)
session = FakeSession()
request = {
"structure_mode": "radicados",
"fields": ["factura/numero", "soporte/codigo"],
"doc_types": [{"name": "factura"}, {"name": "soporte"}],
"documents": [
{"radicado": "RAD-1", "filename": "factura.pdf", "doc_type": "factura"},
{"radicado": "RAD-1", "filename": "soporte.pdf", "doc_type": "soporte"},
],
}
task = SimpleNamespace(
result={"processed": 0, "total": 1, "row_count": 0, "partials": {}},
status="running",
finished_at=None,
updated_at=None,
)
first = desktop_ingest.persist_desktop_ingest_result(
session,
task=task,
batch_id="batch-1",
radicado="RAD-1",
filename="factura.pdf",
doc_type="factura",
request=request,
normalized_obj={"numero": "F-1"},
)
desktop_ingest.mark_task_completed_if_done(session, task, "batch-1")
assert first["finalized"] is False
assert task.result["processed"] == 0
assert task.status == "running"
assert task.result["partials"] == {"RAD-1": {"factura": {"numero": "F-1"}}}
assert session.added == []
second = desktop_ingest.persist_desktop_ingest_result(
session,
task=task,
batch_id="batch-1",
radicado="RAD-1",
filename="soporte.pdf",
doc_type="soporte",
request=request,
normalized_obj={"codigo": "S-1"},
)
desktop_ingest.mark_task_completed_if_done(session, task, "batch-1")
assert second["finalized"] is True
assert task.result["processed"] == 1
assert task.result["partials"] == {}
assert task.status == "completed"
assert session.batch.status == "completed"
assert len(session.added) == 1
row = session.added[0]
assert row.radicado == "RAD-1"
assert row.raw_json["factura"] == {"numero": "F-1"}
assert row.raw_json["soporte"] == {"codigo": "S-1"}
def test_document_failure_completes_radicado_and_discards_partials(monkeypatch) -> None:
monkeypatch.setattr(
desktop_ingest,
"flatten_extraction_result",
lambda fused, _fields, _aliases: [fused],
)
session = FakeSession()
request = {
"structure_mode": "radicados",
"fields": ["factura/numero", "soporte/codigo"],
"doc_types": [{"name": "factura"}, {"name": "soporte"}],
"documents": [
{"radicado": "RAD-1", "filename": "factura.pdf", "doc_type": "factura"},
{"radicado": "RAD-1", "filename": "soporte.pdf", "doc_type": "soporte"},
],
}
task = SimpleNamespace(
result={"processed": 0, "total": 1, "row_count": 0, "partials": {}},
status="running",
finished_at=None,
updated_at=None,
)
desktop_ingest.persist_desktop_ingest_result(
session,
task=task,
batch_id="batch-1",
radicado="RAD-1",
filename="factura.pdf",
doc_type="factura",
request=request,
normalized_obj={"numero": "F-1"},
)
failure = desktop_ingest.record_desktop_ingest_error(
session,
task=task,
radicado="RAD-1",
filename="soporte.pdf",
doc_type="soporte",
error=RuntimeError("backend no disponible"),
)
desktop_ingest.mark_task_completed_if_done(session, task, "batch-1")
assert failure["first_failure"] is True
assert task.result["processed"] == 1
assert task.result["partials"] == {}
assert task.result["failed_radicados"] == ["RAD-1"]
assert task.result["errors"] == [{
"radicado": "RAD-1",
"filename": "soporte.pdf",
"doc_type": "soporte",
"error": "backend no disponible",
}]
assert task.status == "completed"
assert session.added == []
late_success = desktop_ingest.persist_desktop_ingest_result(
session,
task=task,
batch_id="batch-1",
radicado="RAD-1",
filename="soporte.pdf",
doc_type="soporte",
request=request,
normalized_obj={"codigo": "S-1"},
)
duplicate_failure = desktop_ingest.record_desktop_ingest_error(
session,
task=task,
radicado="RAD-1",
filename="factura.pdf",
doc_type="factura",
error=RuntimeError("otro fallo tardío"),
)
assert late_success["skipped"] is True
assert duplicate_failure["first_failure"] is False
assert task.result["processed"] == 1
assert task.result["partials"] == {}
assert session.added == []
def test_process_sync_persists_failure_before_reraising(monkeypatch) -> None:
task = SimpleNamespace(
result={"processed": 0, "total": 1, "row_count": 0, "partials": {}},
status="running",
finished_at=None,
updated_at=None,
)
session = FakeSession()
def get(model: object, _identifier: str, **_kwargs: object) -> object:
if model is extraction_api.Task:
return task
return session.batch
session.get = get # type: ignore[method-assign]
@contextmanager
def fake_session_scope():
yield session
class FailingService:
extractor = SimpleNamespace(model="test-model")
def extract_full_from_pdf(self, *_args: object, **_kwargs: object) -> object:
raise RuntimeError("fallo simulado")
monkeypatch.setattr(extraction_api, "session_scope", fake_session_scope)
monkeypatch.setattr(extraction_api, "processing_service", FailingService())
with pytest.raises(RuntimeError, match="fallo simulado"):
asyncio.run(extraction_api._process_sync(
"task-1",
"batch-1",
b"pdf",
"factura.pdf",
"RAD-1",
"factura",
{
"fields": ["factura/numero"],
"doc_types": [{"name": "factura"}],
"documents": [
{"radicado": "RAD-1", "filename": "factura.pdf", "doc_type": "factura"},
],
},
))
assert task.result["processed"] == 1
assert task.result["failed_radicados"] == ["RAD-1"]
assert task.result["errors"][0]["error"] == "fallo simulado"
assert task.status == "completed"