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
83 lines
2.8 KiB
Python
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"])
|