import pytest from unittest.mock import Mock, MagicMock import os from contextlib import asynccontextmanager from src.naliiabot.bot.agent.agent import Agent, AgentConfig from src.naliiabot.bot.factories.llm_factory import LLMFactory from httpx import ASGITransport, AsyncClient from fastapi import FastAPI from langgraph.checkpoint.memory import MemorySaver from langchain_core.messages import AIMessage 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 @asynccontextmanager 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.") 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 @pytest.fixture(scope="session") def test_app(): """Create a test FastAPI app with in-memory dependencies.""" app = FastAPI( title="NaliiaBot API - Test", description="Test API for NaliiaBot", version="1.0.0", 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 @pytest.fixture(scope="function") async def async_client(test_app): """Fixture para un cliente HTTP asíncrono.""" async with AsyncClient( transport=ASGITransport(app=test_app), base_url="http://localhost:8010" ) as client: yield client @pytest.fixture def mock_tool(): """Mock de herramienta.""" tool = Mock() tool.name = "calculator" tool.invoke = Mock(return_value="42") return tool @pytest.fixture def mock_model(): """Mock de modelo LLM con interfaz completa.""" model = Mock() model.bind_tools = Mock(return_value=model) return model @pytest.fixture def default_config(): """Configuración de test predecible.""" return AgentConfig( system_prompt="Test prompt", max_iterations=5, timeout_seconds=10.0 ) @pytest.fixture def agent(mock_model, default_config): """Agente básico sin herramientas.""" return Agent(model=mock_model, config=default_config) @pytest.fixture 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] ) @pytest.fixture def anthropic_model(): llmFactory = LLMFactory("anthropic") anthropic_model = llmFactory.get_model() return anthropic_model @pytest.fixture def send_message_payload(): """Payload de ejemplo para enviar mensajes al webhook.""" return { "event": "messages.upsert", "instance": "ALEJANDRO AYALA", "data": { "key": { "remoteJid": "5512992544490@s.whatsapp.net", "remoteJidAlt": "5512992544490@s.whatsapp.net", "fromMe": False, "id": "AC4367597BDC50537133B9C8848B2E62", "participant": "", "addressingMode": "lid" }, "pushName": "Alejandro", "status": "DELIVERY_ACK", "message": { "conversation": "Hola", "messageContextInfo": { "threadId": [], "deviceListMetadata": { "senderKeyIndexes": [], "recipientKeyIndexes": [], "senderKeyHash": { "0": 154, "1": 106, "2": 69, "3": 189, "4": 90, "5": 105, "6": 172, "7": 243, "8": 27, "9": 98 }, "senderTimestamp": { "low": 1772156772, "high": 0, "unsigned": "true" }, "recipientKeyHash": { "0": 16, "1": 98, "2": 75, "3": 100, "4": 131, "5": 43, "6": 24, "7": 225, "8": 244, "9": 126 }, "recipientTimestamp": { "low": 1772155597, "high": 0, "unsigned": True } }, "deviceListMetadataVersion": 2, "messageSecret": { "0": 100, "1": 47, "2": 112, "3": 133, "4": 255, "5": 156, "6": 129, "7": 57, "8": 239, "9": 219, "10": 74, "11": 171, "12": 212, "13": 150, "14": 112, "15": 83, "16": 123, "17": 78, "18": 189, "19": 13, "20": 51, "21": 133, "22": 129, "23": 6, "24": 197, "25": 119, "26": 197, "27": 56, "28": 87, "29": 176, "30": 135, "31": 62 }}}, "messageType": "conversation", "messageTimestamp": 1772168493, "instanceId": "75a877f1-d772-422b-a6f1-6ac470af5663", "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" }