El sistema contesta a quien envió el correo con la plantilla que corresponda. Por defecto (NOTIFICACION_MODO=prueba) la respuesta va solo al equipo con el asunto "[PRUEBA → remitente]"; en modo real va al remitente con copia oculta al equipo. El correo técnico (tablas de SAP, JSON) pasa a ir solo al equipo. - respuesta_proveedor.py: contextos de las plantillas, envío y protecciones (remitente desconocido o sin dominio, propio buzón, no-reply, mensajes automáticos, respuestas a notificaciones, límite de 3 por remitente/24 h). - observaciones.py: motivos de rechazo con título, filas y cómo corregir (borrador de textos); también para correos sin comprobante válido. - Un error de SAP/HANA o un fallo inesperado no se le comunica al proveedor y ya no tumba el ciclo; "guía no encontrada" exige que HANA responda. - outlook_utilities: es_mensaje_automatico y copia oculta en send_email. - seguimiento: notificacion_destinatario (con migración) y contar_respuestas_correo. - config_validator anuncia el modo activo; documentación y pruebas. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
98 lines
4.5 KiB
Python
98 lines
4.5 KiB
Python
import outlook_utilities as o
|
|
|
|
|
|
# ============================================================================
|
|
# es_mensaje_automatico
|
|
# ============================================================================
|
|
def _h(**kv):
|
|
return [{"name": k.replace("_", "-"), "value": v} for k, v in kv.items()]
|
|
|
|
|
|
def test_mensajes_normales_no_son_automaticos():
|
|
assert o.es_mensaje_automatico(None) is False
|
|
assert o.es_mensaje_automatico([]) is False
|
|
assert o.es_mensaje_automatico(_h(Auto_Submitted="no", Content_Type="text/html", Precedence="normal")) is False
|
|
|
|
|
|
def test_mensajes_automaticos():
|
|
assert o.es_mensaje_automatico(_h(Auto_Submitted="auto-replied")) is True
|
|
assert o.es_mensaje_automatico(_h(Auto_Submitted="auto-generated")) is True
|
|
assert o.es_mensaje_automatico(_h(X_Auto_Response_Suppress="All")) is True
|
|
assert o.es_mensaje_automatico(_h(X_Autoreply="yes")) is True
|
|
assert o.es_mensaje_automatico(_h(Precedence="bulk")) is True
|
|
assert o.es_mensaje_automatico(_h(Precedence="auto_reply")) is True
|
|
assert o.es_mensaje_automatico(_h(Content_Type="multipart/report; report-type=delivery-status")) is True
|
|
|
|
|
|
def test_los_nombres_de_encabezado_no_distinguen_mayusculas():
|
|
assert o.es_mensaje_automatico([{"name": "AUTO-SUBMITTED", "value": "Auto-Replied"}]) is True
|
|
|
|
|
|
# ============================================================================
|
|
# send_email (payload hacia Graph)
|
|
# ============================================================================
|
|
class _Respuesta:
|
|
def __init__(self, status=202, text=""):
|
|
self.status_code, self.text = status, text
|
|
|
|
|
|
def test_send_email_incluye_destinatarios_copia_y_copia_oculta(monkeypatch):
|
|
enviado = {}
|
|
monkeypatch.setattr(o.requests, "post", lambda url, **k: enviado.update(url=url, **k) or _Respuesta())
|
|
|
|
ok = o.send_email("tok", "buzon@x.com", ["a@x.com"], "Asunto", "<p>hola</p>", None,
|
|
cc_recipients=["c@x.com"], bcc_recipients=["oculto@x.com"])
|
|
|
|
mensaje = enviado["json"]["message"]
|
|
assert ok is True and enviado["url"].endswith("/users/buzon@x.com/sendMail")
|
|
assert mensaje["toRecipients"] == [{"emailAddress": {"address": "a@x.com"}}]
|
|
assert mensaje["ccRecipients"] == [{"emailAddress": {"address": "c@x.com"}}]
|
|
assert mensaje["bccRecipients"] == [{"emailAddress": {"address": "oculto@x.com"}}]
|
|
assert mensaje["attachments"] == [] and mensaje["body"]["contentType"] == "HTML"
|
|
|
|
|
|
def test_send_email_sin_copia_oculta_envia_la_lista_vacia(monkeypatch):
|
|
enviado = {}
|
|
monkeypatch.setattr(o.requests, "post", lambda url, **k: enviado.update(**k) or _Respuesta())
|
|
o.send_email("tok", "b@x.com", ["a@x.com"], "A", "<p/>")
|
|
assert enviado["json"]["message"]["bccRecipients"] == []
|
|
|
|
|
|
def test_send_email_devuelve_false_si_graph_rechaza_o_falla(monkeypatch):
|
|
monkeypatch.setattr(o.requests, "post", lambda *a, **k: _Respuesta(400, "mal"))
|
|
assert o.send_email("tok", "b@x.com", ["a@x.com"], "A", "<p/>") is False
|
|
|
|
def explota(*a, **k):
|
|
raise ConnectionError("sin red")
|
|
monkeypatch.setattr(o.requests, "post", explota)
|
|
assert o.send_email("tok", "b@x.com", ["a@x.com"], "A", "<p/>") is False
|
|
|
|
|
|
# ============================================================================
|
|
# get_emails
|
|
# ============================================================================
|
|
def test_get_emails_pide_los_encabezados_y_marca_los_automaticos(monkeypatch):
|
|
pedido = {}
|
|
|
|
class Resp:
|
|
status_code = 200
|
|
|
|
def json(self):
|
|
return {"value": [
|
|
{"id": "1", "receivedDateTime": "2026-10-14T15:35:00Z", "hasAttachments": True, "subject": "Factura",
|
|
"from": {"emailAddress": {"address": "a@x.com"}}, "internetMessageHeaders": []},
|
|
{"id": "2", "receivedDateTime": "2026-10-14T16:00:00Z", "hasAttachments": False, "subject": "Auto",
|
|
"from": {"emailAddress": {"address": "b@x.com"}},
|
|
"internetMessageHeaders": [{"name": "Auto-Submitted", "value": "auto-replied"}]},
|
|
{"id": "3", "receivedDateTime": "2026-10-14T16:00:00Z", "subject": "Sin encabezados", "from": {}},
|
|
]}
|
|
|
|
monkeypatch.setattr(o.requests, "get", lambda url, **k: pedido.update(k) or Resp())
|
|
|
|
correos = o.get_emails("buzon@x.com", "tok", "2026-10-14", "2026-10-14")
|
|
|
|
assert "internetMessageHeaders" in pedido["params"]["$select"]
|
|
assert [c["es_automatico"] for c in correos] == [False, True, False]
|
|
assert correos[0]["fecha"] == "2026-10-14 10:35:00" # UTC-5
|
|
assert correos[2]["sender"] == "Desconocido"
|