10bfb846a8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
162 lines
6.7 KiB
Python
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
|