ahora si funciona
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user