fix: webhook test modernization and production bug fix
- Fix production bug in send_whatsapp_message: access 'instance_url' instead of 'server_url' to match SendMessageScheme TypedDict definition - Update webhook test to use test_app with in-memory dependencies - Mock send_whatsapp_message to avoid real HTTP calls in tests - Add assertions to verify correct parameters passed to send_whatsapp_message Bug: send_whatsapp_message accessed request_data['server_url'] but SendMessageScheme defines field as 'instance_url', causing KeyError when webhook tries to send WhatsApp messages.
This commit is contained in:
@@ -15,7 +15,7 @@ async def send_whatsapp_message(request_data: SendMessageScheme):
|
|||||||
Envía un mensaje de texto a través de la API de WhatsApp.
|
Envía un mensaje de texto a través de la API de WhatsApp.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
instance_url = request_data["server_url"]
|
instance_url = request_data["instance_url"]
|
||||||
instance_name = request_data["instance_name"]
|
instance_name = request_data["instance_name"]
|
||||||
apikey = request_data["apikey"]
|
apikey = request_data["apikey"]
|
||||||
jid = request_data["jid"]
|
jid = request_data["jid"]
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ from naliiabot.bot.prompts.load_prompt import get_prompt_template
|
|||||||
from naliiabot.bot.tools.naliia_tools import NaliiaTools
|
from naliiabot.bot.tools.naliia_tools import NaliiaTools
|
||||||
import naliiabotapi.api.dependencies as deps
|
import naliiabotapi.api.dependencies as deps
|
||||||
from naliiabotapi.api.v1.endpoints.chat import router as chat_router
|
from naliiabotapi.api.v1.endpoints.chat import router as chat_router
|
||||||
from naliiabotapi.api.v1.webhooks.chat_hook import router as chat_hook_router
|
from naliiabotapi.api.v1.webhooks.chat_hook import (
|
||||||
|
router as chat_hook_router,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@@ -30,7 +32,9 @@ async def test_lifespan(app: FastAPI):
|
|||||||
|
|
||||||
def mock_invoke(*args, **kwargs):
|
def mock_invoke(*args, **kwargs):
|
||||||
"""Mock invoke that returns a proper AIMessage."""
|
"""Mock invoke that returns a proper AIMessage."""
|
||||||
return AIMessage(content="This is a test response from the mock model.")
|
return AIMessage(
|
||||||
|
content="This is a test response from the mock model."
|
||||||
|
)
|
||||||
|
|
||||||
mock_model.invoke = mock_invoke
|
mock_model.invoke = mock_invoke
|
||||||
mock_model.bind_tools = Mock(return_value=mock_model)
|
mock_model.bind_tools = Mock(return_value=mock_model)
|
||||||
@@ -83,7 +87,7 @@ async def async_client(test_app):
|
|||||||
|
|
||||||
async with AsyncClient(
|
async with AsyncClient(
|
||||||
transport=ASGITransport(app=test_app),
|
transport=ASGITransport(app=test_app),
|
||||||
base_url="http://localhost:8010"
|
base_url="http://localhost:8010",
|
||||||
) as client:
|
) as client:
|
||||||
|
|
||||||
yield client
|
yield client
|
||||||
@@ -110,9 +114,7 @@ def mock_model():
|
|||||||
def default_config():
|
def default_config():
|
||||||
"""Configuración de test predecible."""
|
"""Configuración de test predecible."""
|
||||||
return AgentConfig(
|
return AgentConfig(
|
||||||
system_prompt="Test prompt",
|
system_prompt="Test prompt", max_iterations=5, timeout_seconds=10.0
|
||||||
max_iterations=5,
|
|
||||||
timeout_seconds=10.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -126,9 +128,7 @@ def agent(mock_model, default_config):
|
|||||||
def agent_with_tool(mock_model, default_config, mock_tool):
|
def agent_with_tool(mock_model, default_config, mock_tool):
|
||||||
"""Agente con una herramienta mock."""
|
"""Agente con una herramienta mock."""
|
||||||
return Agent(
|
return Agent(
|
||||||
model=mock_model,
|
model=mock_model, config=default_config, tools=[mock_tool]
|
||||||
config=default_config,
|
|
||||||
tools=[mock_tool]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -150,8 +150,10 @@ def send_message_payload():
|
|||||||
"key": {
|
"key": {
|
||||||
"remoteJid": "5512992544490@s.whatsapp.net",
|
"remoteJid": "5512992544490@s.whatsapp.net",
|
||||||
"remoteJidAlt": "5512992544490@s.whatsapp.net",
|
"remoteJidAlt": "5512992544490@s.whatsapp.net",
|
||||||
"fromMe": False, "id": "AC4367597BDC50537133B9C8848B2E62",
|
"fromMe": False,
|
||||||
"participant": "", "addressingMode": "lid"
|
"id": "AC4367597BDC50537133B9C8848B2E62",
|
||||||
|
"participant": "",
|
||||||
|
"addressingMode": "lid",
|
||||||
},
|
},
|
||||||
"pushName": "Alejandro",
|
"pushName": "Alejandro",
|
||||||
"status": "DELIVERY_ACK",
|
"status": "DELIVERY_ACK",
|
||||||
@@ -172,12 +174,12 @@ def send_message_payload():
|
|||||||
"6": 172,
|
"6": 172,
|
||||||
"7": 243,
|
"7": 243,
|
||||||
"8": 27,
|
"8": 27,
|
||||||
"9": 98
|
"9": 98,
|
||||||
},
|
},
|
||||||
"senderTimestamp": {
|
"senderTimestamp": {
|
||||||
"low": 1772156772,
|
"low": 1772156772,
|
||||||
"high": 0,
|
"high": 0,
|
||||||
"unsigned": "true"
|
"unsigned": "true",
|
||||||
},
|
},
|
||||||
"recipientKeyHash": {
|
"recipientKeyHash": {
|
||||||
"0": 16,
|
"0": 16,
|
||||||
@@ -189,13 +191,13 @@ def send_message_payload():
|
|||||||
"6": 24,
|
"6": 24,
|
||||||
"7": 225,
|
"7": 225,
|
||||||
"8": 244,
|
"8": 244,
|
||||||
"9": 126
|
"9": 126,
|
||||||
},
|
},
|
||||||
"recipientTimestamp": {
|
"recipientTimestamp": {
|
||||||
"low": 1772155597,
|
"low": 1772155597,
|
||||||
"high": 0,
|
"high": 0,
|
||||||
"unsigned": True
|
"unsigned": True,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"deviceListMetadataVersion": 2,
|
"deviceListMetadataVersion": 2,
|
||||||
"messageSecret": {
|
"messageSecret": {
|
||||||
@@ -230,18 +232,20 @@ def send_message_payload():
|
|||||||
"28": 87,
|
"28": 87,
|
||||||
"29": 176,
|
"29": 176,
|
||||||
"30": 135,
|
"30": 135,
|
||||||
"31": 62
|
"31": 62,
|
||||||
}}},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"messageType": "conversation",
|
"messageType": "conversation",
|
||||||
"messageTimestamp": 1772168493,
|
"messageTimestamp": 1772168493,
|
||||||
"instanceId": "75a877f1-d772-422b-a6f1-6ac470af5663",
|
"instanceId": "75a877f1-d772-422b-a6f1-6ac470af5663",
|
||||||
"source": "android"
|
"source": "android",
|
||||||
},
|
},
|
||||||
"destination": "http://192.168.58.110:8010/webhook",
|
"destination": "http://192.168.58.110:8010/webhook",
|
||||||
"date_time": "2026-02-27T02:01:34.178Z",
|
"date_time": "2026-02-27T02:01:34.178Z",
|
||||||
"sender": "573016859278@s.whatsapp.net",
|
"sender": "573016859278@s.whatsapp.net",
|
||||||
"server_url": "http://localhost:8080",
|
"server_url": "http://localhost:8080",
|
||||||
"apikey": "98B6B820AFC3-4544-9A4C-011B0719C2D1"
|
"apikey": "98B6B820AFC3-4544-9A4C-011B0719C2D1",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock, Mock
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from httpx import ASGITransport, AsyncClient
|
from httpx import ASGITransport, AsyncClient
|
||||||
from naliiabotapi.main import app
|
|
||||||
from naliiabotapi.api.dependencies import get_agent
|
from naliiabotapi.api.dependencies import get_agent
|
||||||
|
|
||||||
|
|
||||||
@@ -10,25 +9,51 @@ class TestWebhookChatBot:
|
|||||||
"""Tests for WebhookChatBot."""
|
"""Tests for WebhookChatBot."""
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def tests_webhook_success(self, send_message_payload):
|
async def tests_webhook_success(
|
||||||
|
self, send_message_payload, test_app, monkeypatch
|
||||||
|
):
|
||||||
"""Test that the webhook endpoint returns a successful response.
|
"""Test that the webhook endpoint returns a successful response.
|
||||||
|
|
||||||
Se crea un `mock_agent` con `ainvoke` mockeado y se inyecta
|
Usa test_app con in-memory dependencies y mockea send_whatsapp_message
|
||||||
mediante `app.dependency_overrides` antes de realizar la petición.
|
para evitar llamadas HTTP reales durante el test.
|
||||||
"""
|
"""
|
||||||
|
# Mock del agente
|
||||||
mock_agent = SimpleNamespace()
|
mock_agent = SimpleNamespace()
|
||||||
mock_agent.ainvoke = AsyncMock(
|
mock_agent.ainvoke = AsyncMock(
|
||||||
return_value={"messages": [SimpleNamespace(content="Respuesta mock")]}
|
return_value={"messages": [SimpleNamespace(content="Respuesta mock")]}
|
||||||
)
|
)
|
||||||
|
|
||||||
app.dependency_overrides[get_agent] = lambda: mock_agent
|
# Mock de send_whatsapp_message para evitar llamadas HTTP reales
|
||||||
|
mock_send_whatsapp = AsyncMock(return_value={"status": "sent"})
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"naliiabotapi.api.v1.webhooks.chat_hook.send_whatsapp_message",
|
||||||
|
mock_send_whatsapp,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Override de dependencia del agente
|
||||||
|
test_app.dependency_overrides[get_agent] = lambda: mock_agent
|
||||||
|
|
||||||
async with AsyncClient(
|
async with AsyncClient(
|
||||||
transport=ASGITransport(app=app), base_url="http://localhost:8010"
|
transport=ASGITransport(app=test_app),
|
||||||
|
base_url="http://localhost:8010",
|
||||||
) as client:
|
) as client:
|
||||||
response = await client.post("/webhook", json=send_message_payload)
|
response = await client.post("/webhook", json=send_message_payload)
|
||||||
|
|
||||||
app.dependency_overrides.pop(get_agent, None)
|
# Cleanup
|
||||||
|
test_app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
# Verificaciones
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json().get("reply") == "Respuesta mock"
|
response_data = response.json()
|
||||||
|
assert response_data.get("reply") == "Respuesta mock"
|
||||||
|
assert response_data.get("status") == "sent"
|
||||||
|
|
||||||
|
# Verificar que se llamó al mock del agente
|
||||||
|
mock_agent.ainvoke.assert_called_once()
|
||||||
|
|
||||||
|
# Verificar que se llamó a send_whatsapp_message con los parámetros correctos
|
||||||
|
mock_send_whatsapp.assert_called_once()
|
||||||
|
whatsapp_call = mock_send_whatsapp.call_args[0][0]
|
||||||
|
assert whatsapp_call["instance_url"] == "http://localhost:8080"
|
||||||
|
assert whatsapp_call["instance_name"] == "ALEJANDRO AYALA"
|
||||||
|
assert whatsapp_call["text"] == "Respuesta mock"
|
||||||
|
|||||||
Reference in New Issue
Block a user