Files
smart-support/backend/tests/unit/test_agents.py
Yaojia Wang 33488fd634 feat: complete phase 1 -- core framework with chat loop, agents, and React UI
Backend:
- FastAPI WebSocket /ws endpoint with streaming via LangGraph astream
- LangGraph Supervisor connecting 3 mock agents (order_lookup, order_actions, fallback)
- YAML Agent Registry with Pydantic validation and immutable configs
- PostgresSaver checkpoint persistence via langgraph-checkpoint-postgres
- Session TTL with 30-min sliding window and interrupt extension
- LLM provider abstraction (Anthropic/OpenAI/Google)
- Token usage + cost tracking callback handler
- Input validation: message size cap, thread_id format, content length
- Security: no hardcoded defaults, startup API key validation, no input reflection

Frontend:
- React 19 + TypeScript + Vite chat UI
- WebSocket hook with reconnect + exponential backoff
- Streaming token display with agent attribution
- Interrupt approval/reject UI for write operations
- Collapsible tool call viewer

Testing:
- 87 unit tests, 87% coverage (exceeds 80% requirement)
- Ruff lint + format clean

Infrastructure:
- Docker Compose (PostgreSQL 16 + backend)
- pyproject.toml with full dependency management
2026-03-30 00:54:21 +02:00

83 lines
2.8 KiB
Python

"""Tests for agent tools."""
from __future__ import annotations
from unittest.mock import patch
import pytest
from app.agents.fallback import fallback_respond
from app.agents.order_lookup import get_order_status, get_tracking_info
@pytest.mark.unit
class TestOrderLookup:
def test_get_order_status_existing(self) -> None:
result = get_order_status.invoke({"order_id": "1042"})
assert result["order_id"] == "1042"
assert result["status"] == "shipped"
def test_get_order_status_not_found(self) -> None:
result = get_order_status.invoke({"order_id": "9999"})
assert "error" in result
assert "9999" in result["error"]
def test_get_tracking_info_existing(self) -> None:
result = get_tracking_info.invoke({"order_id": "1042"})
assert result["carrier"] == "FedEx"
assert result["tracking_number"] == "FX-9876543210"
def test_get_tracking_info_not_found(self) -> None:
result = get_tracking_info.invoke({"order_id": "1043"})
assert "error" in result
def test_all_mock_orders_have_required_fields(self) -> None:
from app.agents.order_lookup import MOCK_ORDERS
for oid, order in MOCK_ORDERS.items():
assert "order_id" in order
assert "status" in order
assert order["order_id"] == oid
@pytest.mark.unit
class TestFallback:
def test_fallback_respond_returns_help(self) -> None:
result = fallback_respond.invoke({"query": "random question"})
assert "order" in result.lower()
assert "help" in result.lower() or "can do" in result.lower()
@pytest.mark.unit
class TestOrderActions:
def test_cancel_order_approved(self) -> None:
with patch("app.agents.order_actions.interrupt", return_value=True):
from app.agents.order_actions import cancel_order
result = cancel_order.invoke({"order_id": "1042"})
assert result["status"] == "cancelled"
assert "1042" in result["message"]
def test_cancel_order_rejected(self) -> None:
with patch("app.agents.order_actions.interrupt", return_value=False):
from app.agents.order_actions import cancel_order
result = cancel_order.invoke({"order_id": "1042"})
assert result["status"] == "kept"
assert "declined" in result["message"]
@pytest.mark.unit
class TestToolBridge:
def test_get_tools_by_names(self) -> None:
from app.agents import get_tools_by_names
tools = get_tools_by_names(["get_order_status", "cancel_order"])
assert len(tools) == 2
def test_unknown_tool_raises(self) -> None:
from app.agents import get_tools_by_names
with pytest.raises(ValueError, match="Unknown tool"):
get_tools_by_names(["nonexistent_tool"])