diff --git a/src/naliiabotapi/api/v1/webhooks/chat_hook.py b/src/naliiabotapi/api/v1/webhooks/chat_hook.py index 6109c63..8698a03 100644 --- a/src/naliiabotapi/api/v1/webhooks/chat_hook.py +++ b/src/naliiabotapi/api/v1/webhooks/chat_hook.py @@ -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. """ - instance_url = request_data["server_url"] + instance_url = request_data["instance_url"] instance_name = request_data["instance_name"] apikey = request_data["apikey"] jid = request_data["jid"] diff --git a/tests/conftest.py b/tests/conftest.py index e9b51e2..2882d69 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,7 +15,9 @@ from naliiabot.bot.prompts.load_prompt import get_prompt_template from naliiabot.bot.tools.naliia_tools import NaliiaTools import naliiabotapi.api.dependencies as deps 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 @@ -23,31 +25,33 @@ async def test_lifespan(app: FastAPI): """Test lifespan that uses in-memory checkpointer instead of PostgreSQL.""" # Use in-memory checkpointer for tests checkpointer = MemorySaver() - + # Use a mock model for tests to avoid needing API keys # The mock needs to return proper AIMessage objects mock_model = MagicMock() - + def mock_invoke(*args, **kwargs): """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.bind_tools = Mock(return_value=mock_model) - + naliia_prompt = get_prompt_template("NALIIA_PROMPT") naliia_tools = NaliiaTools().get_tools() config = AgentConfig(system_prompt=naliia_prompt) - + deps.agent_instance = Agent( model=mock_model, config=config, checkpointer=checkpointer, tools=naliia_tools, ) - + yield - + # Cleanup deps.agent_instance = None @@ -62,18 +66,18 @@ def test_app(): root_path="/agent/api", lifespan=test_lifespan, ) - + app.include_router(chat_router) app.include_router(chat_hook_router) - + @app.get("/") def read_root(): return {"Hello": "World"} - + @app.get("/health") def health_check(): return {"status": "ok"} - + return app @@ -83,7 +87,7 @@ async def async_client(test_app): async with AsyncClient( transport=ASGITransport(app=test_app), - base_url="http://localhost:8010" + base_url="http://localhost:8010", ) as client: yield client @@ -110,9 +114,7 @@ def mock_model(): def default_config(): """Configuración de test predecible.""" return AgentConfig( - system_prompt="Test prompt", - max_iterations=5, - timeout_seconds=10.0 + system_prompt="Test prompt", 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): """Agente con una herramienta mock.""" return Agent( - model=mock_model, - config=default_config, - tools=[mock_tool] + model=mock_model, config=default_config, tools=[mock_tool] ) @@ -150,8 +150,10 @@ def send_message_payload(): "key": { "remoteJid": "5512992544490@s.whatsapp.net", "remoteJidAlt": "5512992544490@s.whatsapp.net", - "fromMe": False, "id": "AC4367597BDC50537133B9C8848B2E62", - "participant": "", "addressingMode": "lid" + "fromMe": False, + "id": "AC4367597BDC50537133B9C8848B2E62", + "participant": "", + "addressingMode": "lid", }, "pushName": "Alejandro", "status": "DELIVERY_ACK", @@ -172,12 +174,12 @@ def send_message_payload(): "6": 172, "7": 243, "8": 27, - "9": 98 + "9": 98, }, "senderTimestamp": { "low": 1772156772, "high": 0, - "unsigned": "true" + "unsigned": "true", }, "recipientKeyHash": { "0": 16, @@ -189,13 +191,13 @@ def send_message_payload(): "6": 24, "7": 225, "8": 244, - "9": 126 + "9": 126, }, "recipientTimestamp": { "low": 1772155597, "high": 0, - "unsigned": True - } + "unsigned": True, + }, }, "deviceListMetadataVersion": 2, "messageSecret": { @@ -230,18 +232,20 @@ def send_message_payload(): "28": 87, "29": 176, "30": 135, - "31": 62 - }}}, + "31": 62, + }, + }, + }, "messageType": "conversation", "messageTimestamp": 1772168493, "instanceId": "75a877f1-d772-422b-a6f1-6ac470af5663", - "source": "android" + "source": "android", }, "destination": "http://192.168.58.110:8010/webhook", "date_time": "2026-02-27T02:01:34.178Z", "sender": "573016859278@s.whatsapp.net", "server_url": "http://localhost:8080", - "apikey": "98B6B820AFC3-4544-9A4C-011B0719C2D1" + "apikey": "98B6B820AFC3-4544-9A4C-011B0719C2D1", } diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 7a9eb4a..96bff4f 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -1,8 +1,7 @@ import pytest -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, Mock from types import SimpleNamespace from httpx import ASGITransport, AsyncClient -from naliiabotapi.main import app from naliiabotapi.api.dependencies import get_agent @@ -10,25 +9,51 @@ class TestWebhookChatBot: """Tests for WebhookChatBot.""" @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. - Se crea un `mock_agent` con `ainvoke` mockeado y se inyecta - mediante `app.dependency_overrides` antes de realizar la petición. + Usa test_app con in-memory dependencies y mockea send_whatsapp_message + para evitar llamadas HTTP reales durante el test. """ + # Mock del agente mock_agent = SimpleNamespace() mock_agent.ainvoke = AsyncMock( 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( - transport=ASGITransport(app=app), base_url="http://localhost:8010" + transport=ASGITransport(app=test_app), + base_url="http://localhost:8010", ) as client: 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.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"