Files
fn_registry/python/functions/browser/whatsapp_send_message_test.py
T
egutierrez 10bfb846a8 ahora si funciona
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 16:23:52 +02:00

162 lines
6.7 KiB
Python

"""Tests para whatsapp_send_message.
whatsapp_send_message compone whatsapp_open_chat con tres primitivas CDP
(cdp_eval, cdp_type_chars, cdp_press_key) y requiere un Chrome vivo. Aqui se
mockean las cuatro con monkeypatch sobre el modulo `browser.whatsapp_send_message`
(donde quedan ligados los nombres por el `from browser.X import Y`), de modo que
NO hace falta Chrome.
Las llamadas a cdp_eval que importan distinguen por el contenido de la expresion:
- 'aria-label' -> verificacion del destinatario (open_first=False).
- 'innerText' (composer) -> contenido tecleado, comparado contra el texto.
- 'role="row"' (#main) -> ultima fila renderizada tras enviar (last_row).
- cualquier otra (focus del composer) -> value inocuo.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import browser.whatsapp_send_message as wsm
from browser.whatsapp_send_message import whatsapp_send_message
# --- Fakes -----------------------------------------------------------------
def _fake_cdp_eval_factory(*, label_value=None, composer_value="", last_row_value=None):
"""Devuelve un fake de cdp_eval que distingue cada expresion por su contenido."""
def _fake(expr, *, port=9222, target_url_substr=""):
if "aria-label" in expr:
return {"ok": True, "value": label_value, "error": "", "target_url": ""}
if "innerText.replace(/\\n/g" in expr or "innerText.replace(/\n/g" in expr:
return {"ok": True, "value": composer_value, "error": "", "target_url": ""}
if 'role=\\"row\\"' in expr or 'role="row"' in expr:
return {"ok": True, "value": last_row_value, "error": "", "target_url": ""}
return {"ok": True, "value": None, "error": "", "target_url": ""}
return _fake
class _Spy:
"""Registra los argumentos de cada llamada y devuelve un valor fijo."""
def __init__(self, ret=None):
self.calls = []
self.ret = ret if ret is not None else {"ok": True}
def __call__(self, *args, **kwargs):
self.calls.append((args, kwargs))
return self.ret
# --- Tests -----------------------------------------------------------------
def test_golden_envia_mensaje_y_devuelve_last_row(monkeypatch):
text = "hola desde el registry"
last = "hola desde el registry 11:40"
# open_first=True (default): whatsapp_open_chat abre con exito.
monkeypatch.setattr(wsm, "whatsapp_open_chat",
lambda *a, **k: {"opened": True, "name": a[0]})
# composer devuelve exactamente el texto; #main devuelve la ultima fila.
monkeypatch.setattr(wsm, "cdp_eval",
_fake_cdp_eval_factory(composer_value=text, last_row_value=last))
type_spy = _Spy(ret={"ok": True})
press_spy = _Spy(ret={"ok": True})
monkeypatch.setattr(wsm, "cdp_type_chars", type_spy)
monkeypatch.setattr(wsm, "cdp_press_key", press_spy)
monkeypatch.setattr(wsm.time, "sleep", lambda *a, **k: None)
res = whatsapp_send_message("NOTAS WASAP", text,
port=9222, target_url_substr="whatsapp")
assert res["sent"] is True
assert res["name"] == "NOTAS WASAP"
assert res["last_row"] == last
# Se tecleo el texto y se pulso Enter una vez.
assert len(type_spy.calls) == 1
assert type_spy.calls[0][0][0] == text
assert len(press_spy.calls) == 1
assert press_spy.calls[0][0][0] == "Enter"
def test_edge_open_fallido_sent_false_reason(monkeypatch):
# whatsapp_open_chat no abre: aborta sin tocar el composer.
monkeypatch.setattr(
wsm, "whatsapp_open_chat",
lambda *a, **k: {"opened": False, "name": a[0],
"reason": "chat no encontrado en la lista (no cargado o nombre inexacto)"},
)
type_spy = _Spy(ret={"ok": True})
press_spy = _Spy(ret={"ok": True})
monkeypatch.setattr(wsm, "cdp_eval", _fake_cdp_eval_factory())
monkeypatch.setattr(wsm, "cdp_type_chars", type_spy)
monkeypatch.setattr(wsm, "cdp_press_key", press_spy)
monkeypatch.setattr(wsm.time, "sleep", lambda *a, **k: None)
res = whatsapp_send_message("Contacto Inexistente", "hola",
port=9222, target_url_substr="whatsapp")
assert res["sent"] is False
assert res["name"] == "Contacto Inexistente"
assert "no encontrado" in res["reason"]
# No se intento escribir ni enviar cuando el chat no abrio.
assert len(type_spy.calls) == 0
assert len(press_spy.calls) == 0
assert "last_row" not in res
def test_seguridad_open_first_false_label_no_coincide_aborta_sin_escribir(monkeypatch):
# open_first=False y el aria-label del composer NO contiene el name -> abort.
monkeypatch.setattr(wsm, "whatsapp_open_chat",
lambda *a, **k: {"opened": True, "name": a[0]})
monkeypatch.setattr(
wsm, "cdp_eval",
_fake_cdp_eval_factory(label_value="Escribir un mensaje para el grupo OTRO CHAT"),
)
type_spy = _Spy(ret={"ok": True})
press_spy = _Spy(ret={"ok": True})
monkeypatch.setattr(wsm, "cdp_type_chars", type_spy)
monkeypatch.setattr(wsm, "cdp_press_key", press_spy)
monkeypatch.setattr(wsm.time, "sleep", lambda *a, **k: None)
res = whatsapp_send_message("NOTAS WASAP", "hola",
port=9222, target_url_substr="whatsapp",
open_first=False)
assert res["sent"] is False
assert res["name"] == "NOTAS WASAP"
assert "abortado por seguridad" in res["reason"]
# SEGURIDAD: no se llamo a cdp_type_chars ni a cdp_press_key.
assert len(type_spy.calls) == 0
assert len(press_spy.calls) == 0
def test_mismatch_composer_sent_false_sin_press_enter(monkeypatch):
# El composer no contiene el texto esperado tras teclear -> no se envia.
text = "hola desde el registry"
composer_real = "holaa desde ell registryy" # texto distinto (Lexical duplicando)
monkeypatch.setattr(wsm, "whatsapp_open_chat",
lambda *a, **k: {"opened": True, "name": a[0]})
monkeypatch.setattr(wsm, "cdp_eval",
_fake_cdp_eval_factory(composer_value=composer_real))
type_spy = _Spy(ret={"ok": True})
press_spy = _Spy(ret={"ok": True})
monkeypatch.setattr(wsm, "cdp_type_chars", type_spy)
monkeypatch.setattr(wsm, "cdp_press_key", press_spy)
monkeypatch.setattr(wsm.time, "sleep", lambda *a, **k: None)
res = whatsapp_send_message("NOTAS WASAP", text,
port=9222, target_url_substr="whatsapp")
assert res["sent"] is False
assert res["name"] == "NOTAS WASAP"
assert "no contiene el texto esperado" in res["reason"]
assert res["composer"] == composer_real
# Se tecleo pero NO se pulso Enter por el mismatch.
assert len(type_spy.calls) == 1
assert len(press_spy.calls) == 0