fix: use in-memory checkpointer for API tests to avoid database dependency

- Replace PostgreSQL AsyncPostgresSaver with MemorySaver in test fixtures
- Create test_app fixture with test_lifespan using in-memory dependencies
- Mock LLM model to avoid requiring API keys during tests
- Update test_api.py to use test_app fixture instead of production app
- Fix test_root endpoint URL to match root_path configuration
- All API tests now pass without requiring external services (0.06s vs 30s+ timeout)

This change allows tests to run in isolation without PostgreSQL or API keys,
making them faster and more reliable for CI/CD and local development.
This commit is contained in:
2026-06-10 16:06:10 -05:00
parent 7fa4d12e30
commit 34053b3fc1
3 changed files with 86 additions and 13 deletions

View File

@@ -5,7 +5,9 @@ from naliiabot.bot.factories.llm_factory import LLMFactory
from naliiabot.bot.prompts.load_prompt import get_prompt_template
from naliiabot.bot.tools.naliia_tools import NaliiaTools
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,
)
import naliiabotapi.api.dependencies as deps
from contextlib import asynccontextmanager
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
@@ -30,7 +32,7 @@ async def lifespan(app: FastAPI):
model=model,
config=config,
checkpointer=checkpointer,
tools=naliia_tools
tools=naliia_tools,
)
yield
@@ -42,10 +44,11 @@ app = FastAPI(
title="NaliiaBot API",
description=(
"API for NaliiaBot, a chatbot that provides "
"customer service and related topics."),
"customer service and related topics."
),
version="1.0.0",
root_path="/agent/api",
lifespan=lifespan
lifespan=lifespan,
)
app.include_router(chat_router)

View File

@@ -1,18 +1,86 @@
import pytest
from unittest.mock import Mock
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 naliiabotapi.main import app
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():
async def async_client(test_app):
"""Fixture para un cliente HTTP asíncrono."""
async with AsyncClient(
transport=ASGITransport(app=app),
transport=ASGITransport(app=test_app),
base_url="http://localhost:8010"
) as client:

View File

@@ -1,16 +1,16 @@
import pytest
from fastapi.testclient import TestClient
from src.naliiabotapi.main import app
@pytest.fixture
def client():
with TestClient(app) as client:
def client(test_app):
"""Create a test client using the test app with in-memory dependencies."""
with TestClient(test_app) as client:
yield client
def test_root(client):
response = client.get("/")
response = client.get("/agent/api")
assert response.status_code == 200
@@ -22,7 +22,9 @@ def test_health(client):
def test_chat(client):
response = client.post("/chat", json={"messages": "Hello, how are you?"})
response = client.post(
"/chat", json={"messages": "Hello, how are you?"}
)
assert response.status_code == 200
assert response.json() is not None