- HORARIOS cubre las 24 horas: se agregan 21:30-24:00 (cada 60 min) y 05:00-06:30 (cada 30 min); antes el demonio dormía hasta las 06:30 en esos tramos. Se corrigen los comentarios, que no coincidían con los valores. tests/test_horarios.py garantiza que no haya huecos ni solapes. - La carpeta de resultados se resuelve junto al código, no relativa al cwd (un servicio de Windows puede arrancar desde cualquier carpeta). - copiar_a_red no crea carpetas locales cuando RUTA_RED_FACTURACION no es una ruta absoluta en el sistema operativo (una UNC fuera de Windows); antes el draft podía referenciar archivos que no estaban en el recurso compartido. - El log muestra la versión del proyecto (pyproject.toml) al arrancar. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
181 lines
7.9 KiB
Python
181 lines
7.9 KiB
Python
import logging
|
|
import os
|
|
|
|
import adjuntos
|
|
import documentos
|
|
|
|
|
|
# ============================================================================
|
|
# completar_guias
|
|
# ============================================================================
|
|
def test_completar_guias_desde_pdf_normaliza_strings():
|
|
xml_doc = {
|
|
"ruc_emisor": "20537929520", "tipo": "01", "serie": "F002", "numero": "486",
|
|
"lineas": [{"guias_remision": []}, {"guias_remision": [{"tipo": "09", "serie": "T001", "numero": "5"}]}],
|
|
}
|
|
pdf_cache = {"20537929520-01-F002-486.pdf": {"items": [
|
|
{"guias_remision": ["T001-00123"], "guia_transportista": "V002-0011"},
|
|
{"guias_remision": ["T001-999"]},
|
|
]}}
|
|
|
|
copiadas = documentos.completar_guias([xml_doc], pdf_cache)
|
|
|
|
assert copiadas == 1
|
|
assert xml_doc["lineas"][0]["guias_remision"] == [{"tipo": "09", "serie": "T001", "numero": "123"}]
|
|
assert xml_doc["lineas"][0]["guia_transportista"] == {"tipo": "31", "serie": "V002", "numero": "11"}
|
|
assert xml_doc["lineas"][1]["guias_remision"][0]["numero"] == "5" # la que ya tenía no se toca
|
|
|
|
|
|
def test_completar_guias_sin_pdf_no_hace_nada():
|
|
doc = {"ruc_emisor": "1", "tipo": "01", "serie": "F", "numero": "1", "lineas": [{"guias_remision": []}]}
|
|
assert documentos.completar_guias([doc], {}) == 0
|
|
assert doc["lineas"][0]["guias_remision"] == []
|
|
|
|
|
|
# ============================================================================
|
|
# validar_guias
|
|
# ============================================================================
|
|
def test_validar_guias_advierte_solo_las_invalidas(caplog):
|
|
doc = {"serie": "F002", "numero": "1", "lineas": [{
|
|
"guias_remision": [{"tipo": "09", "serie": "T001", "numero": "1"}, {"tipo": "09", "serie": "T001"}],
|
|
"guia_transportista": {"tipo": "31", "serie": "", "numero": "2"},
|
|
}]}
|
|
|
|
with caplog.at_level(logging.WARNING, logger="pipeline"):
|
|
documentos.validar_guias([doc])
|
|
|
|
mensajes = [r.getMessage() for r in caplog.records]
|
|
assert len(mensajes) == 2
|
|
assert any("guias_remision[1] inválida" in m for m in mensajes)
|
|
assert any("guia_transportista inválida" in m for m in mensajes)
|
|
assert all(r.levelno == logging.WARNING for r in caplog.records)
|
|
|
|
|
|
def test_validar_guias_sin_problemas_no_registra(caplog):
|
|
doc = {"serie": "F", "numero": "1", "lineas": [{"guias_remision": [{"tipo": "09", "serie": "T001", "numero": "1"}]}]}
|
|
with caplog.at_level(logging.WARNING, logger="pipeline"):
|
|
documentos.validar_guias([doc])
|
|
assert caplog.records == []
|
|
|
|
|
|
# ============================================================================
|
|
# copiar_a_red
|
|
# ============================================================================
|
|
def _comprobante(tmp_path, fecha="2026-03-05"):
|
|
xml = tmp_path / "a.xml"
|
|
pdf = tmp_path / "a.pdf"
|
|
xml.write_text("<x/>")
|
|
pdf.write_bytes(b"%PDF")
|
|
return {"ruc_emisor": "20111", "tipo": "01", "serie": "F002", "numero": "7",
|
|
"fecha_emision": fecha, "ruta_xml": str(xml), "ruta_pdf": str(pdf)}
|
|
|
|
|
|
def test_copiar_a_red_arma_la_estructura(tmp_path, monkeypatch):
|
|
red = tmp_path / "red"
|
|
monkeypatch.setenv("RUTA_RED_FACTURACION", str(red))
|
|
monkeypatch.setenv("RUC_EMPRESA", "20999")
|
|
data = _comprobante(tmp_path)
|
|
|
|
documentos.copiar_a_red(data)
|
|
|
|
destino = red / "2026" / "3" / "5" / "01" / "20999" / "20111" # mes y día sin ceros
|
|
assert (destino / "20999-01-F002-7.xml").exists()
|
|
assert (destino / "20999-01-F002-7_FormatoImpreso.pdf").exists()
|
|
assert data["ruta_red_xml"].endswith("\\20999-01-F002-7.xml") # estilo UNC con backslash
|
|
assert data["ruta_red_pdf"].endswith("\\20999-01-F002-7_FormatoImpreso.pdf")
|
|
|
|
|
|
def test_copiar_a_red_deshabilitada_si_no_hay_ruta(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("RUTA_RED_FACTURACION", "")
|
|
data = _comprobante(tmp_path)
|
|
documentos.copiar_a_red(data)
|
|
assert "ruta_red_xml" not in data and "ruta_red_pdf" not in data
|
|
|
|
|
|
def test_copiar_a_red_con_una_ruta_que_no_es_absoluta_no_crea_carpetas_ni_apunta_el_draft_a_la_nada(tmp_path, monkeypatch, caplog):
|
|
"""Fuera de Windows una ruta UNC no es absoluta: antes se creaba como una carpeta local con ese nombre."""
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.setenv("RUTA_RED_FACTURACION", "red_relativa")
|
|
monkeypatch.setenv("RUC_EMPRESA", "20999")
|
|
data = _comprobante(tmp_path)
|
|
|
|
with caplog.at_level(logging.WARNING, logger="pipeline"):
|
|
documentos.copiar_a_red(data)
|
|
|
|
assert not (tmp_path / "red_relativa").exists() # no se creó nada
|
|
assert "ruta_red_xml" not in data and "ruta_red_pdf" not in data # el draft no referencia archivos inexistentes
|
|
assert any("no es una ruta absoluta" in r.getMessage() for r in caplog.records)
|
|
|
|
|
|
def test_copiar_a_red_fecha_invalida_no_falla(tmp_path, monkeypatch):
|
|
red = tmp_path / "red"
|
|
monkeypatch.setenv("RUTA_RED_FACTURACION", str(red))
|
|
monkeypatch.setenv("RUC_EMPRESA", "20999")
|
|
data = _comprobante(tmp_path, fecha="no-es-fecha")
|
|
documentos.copiar_a_red(data)
|
|
assert not red.exists() and "ruta_red_xml" not in data
|
|
|
|
|
|
# ============================================================================
|
|
# adjuntos.filtrar_y_limpiar
|
|
# ============================================================================
|
|
def test_filtrar_conserva_pdf_xml_y_borra_el_resto(tmp_path):
|
|
for nombre in ("a.pdf", "B.XML", "c.png"):
|
|
(tmp_path / nombre).write_text("x")
|
|
email = {"adjuntos": ["a.pdf", "B.XML", "c.png"]}
|
|
|
|
assert adjuntos.filtrar_y_limpiar(email, str(tmp_path)) is True
|
|
assert email["adjuntos"] == ["a.pdf", "B.XML"]
|
|
assert sorted(os.listdir(tmp_path)) == ["B.XML", "a.pdf"]
|
|
|
|
|
|
def test_filtrar_sin_pdf_ni_xml_borra_todo(tmp_path):
|
|
for nombre in ("c.png", "d.docx"):
|
|
(tmp_path / nombre).write_text("x")
|
|
email = {"adjuntos": ["c.png", "d.docx"]}
|
|
|
|
assert adjuntos.filtrar_y_limpiar(email, str(tmp_path)) is False
|
|
assert os.listdir(tmp_path) == []
|
|
|
|
|
|
# ============================================================================
|
|
# Rutas: no dependen del directorio de trabajo
|
|
# ============================================================================
|
|
def test_la_base_de_datos_va_junto_al_codigo_aunque_se_lance_desde_otra_carpeta(tmp_path, monkeypatch):
|
|
import importlib.util
|
|
import database_utilities
|
|
|
|
monkeypatch.chdir(tmp_path) # un servicio puede arrancar desde cualquier carpeta
|
|
spec = importlib.util.spec_from_file_location("database_utilities_nueva", database_utilities.__file__)
|
|
nueva = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(nueva) # DB_NAME se calcula al importar
|
|
|
|
carpeta_del_codigo = os.path.dirname(os.path.abspath(database_utilities.__file__))
|
|
assert nueva.DB_NAME == os.path.join(carpeta_del_codigo, "emails.db")
|
|
|
|
|
|
def test_ruta_carpeta_no_depende_del_directorio_actual(tmp_path, monkeypatch):
|
|
import pipeline_utils
|
|
monkeypatch.chdir(tmp_path)
|
|
esperado = os.path.join(os.path.dirname(os.path.abspath(pipeline_utils.__file__)), "resultado")
|
|
assert pipeline_utils.ruta_carpeta("resultado") == esperado
|
|
|
|
|
|
def test_los_resultados_relativos_se_guardan_con_ruta_carpeta_no_en_el_cwd(tmp_path, monkeypatch):
|
|
import persistencia
|
|
proyecto, otro = tmp_path / "proyecto", tmp_path / "otro"
|
|
proyecto.mkdir(), otro.mkdir()
|
|
monkeypatch.chdir(otro)
|
|
monkeypatch.setattr(persistencia, "ruta_carpeta", lambda r: os.path.join(proyecto, r))
|
|
|
|
persistencia.guardar_draft_json({"a": 1}, "F002", "1", "resultado")
|
|
|
|
assert any(n.startswith("draft_sapb1_F002-1") for n in os.listdir(proyecto / "resultado"))
|
|
assert os.listdir(otro) == [] # nada en el directorio desde el que se lanzó
|
|
|
|
|
|
def test_una_carpeta_de_resultados_absoluta_se_respeta(tmp_path):
|
|
import persistencia
|
|
persistencia.guardar_draft_json({"a": 1}, "F002", "2", str(tmp_path / "salida"))
|
|
assert any(n.startswith("draft_sapb1_F002-2") for n in os.listdir(tmp_path / "salida"))
|