Files
smart-support/backend/tests/unit/test_graph.py
Yaojia Wang 1050df780d feat: complete phase 2 -- multi-agent routing, interrupt TTL, escalation, templates
- Intent classification with LLM structured output (single/multi/ambiguous)
- Discount agent with apply_discount and generate_coupon tools
- Interrupt manager with 30-min TTL auto-expiration and retry prompts
- Webhook escalation module with exponential backoff retry (max 3)
- Three vertical industry templates (e-commerce, SaaS, fintech)
- Template loading in AgentRegistry
- Enhanced supervisor prompt with dynamic agent descriptions
- 153 tests passing, 90.18% coverage
2026-03-30 21:04:39 +02:00

90 lines
3.2 KiB
Python

"""Tests for app.graph module."""
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock, MagicMock
import pytest
from app.graph import build_agent_nodes, build_graph, classify_intent
from app.intent import ClassificationResult, IntentTarget
if TYPE_CHECKING:
from app.registry import AgentRegistry
@pytest.mark.unit
class TestBuildAgentNodes:
def test_creates_correct_number_of_nodes(self, sample_registry: AgentRegistry) -> None:
mock_llm = MagicMock()
nodes = build_agent_nodes(sample_registry, mock_llm)
assert len(nodes) == 3
def test_nodes_are_runnable(self, sample_registry: AgentRegistry) -> None:
mock_llm = MagicMock()
nodes = build_agent_nodes(sample_registry, mock_llm)
for node in nodes:
assert hasattr(node, "invoke") or hasattr(node, "ainvoke")
@pytest.mark.unit
class TestBuildGraph:
def test_graph_compiles_with_mock_checkpointer(self, sample_registry: AgentRegistry) -> None:
mock_llm = MagicMock()
mock_llm.bind_tools = MagicMock(return_value=mock_llm)
mock_llm.with_structured_output = MagicMock(return_value=mock_llm)
mock_checkpointer = AsyncMock()
graph = build_graph(sample_registry, mock_llm, mock_checkpointer)
assert graph is not None
def test_graph_has_classifier_attached(self, sample_registry: AgentRegistry) -> None:
mock_llm = MagicMock()
mock_llm.bind_tools = MagicMock(return_value=mock_llm)
mock_llm.with_structured_output = MagicMock(return_value=mock_llm)
mock_checkpointer = AsyncMock()
mock_classifier = MagicMock()
graph = build_graph(
sample_registry, mock_llm, mock_checkpointer, intent_classifier=mock_classifier
)
assert graph.intent_classifier is mock_classifier
assert graph.agent_registry is sample_registry
def test_graph_without_classifier(self, sample_registry: AgentRegistry) -> None:
mock_llm = MagicMock()
mock_llm.bind_tools = MagicMock(return_value=mock_llm)
mock_llm.with_structured_output = MagicMock(return_value=mock_llm)
mock_checkpointer = AsyncMock()
graph = build_graph(sample_registry, mock_llm, mock_checkpointer)
assert graph.intent_classifier is None
@pytest.mark.unit
class TestClassifyIntent:
@pytest.mark.asyncio
async def test_returns_none_without_classifier(self) -> None:
graph = MagicMock()
graph.intent_classifier = None
result = await classify_intent(graph, "hello")
assert result is None
@pytest.mark.asyncio
async def test_calls_classifier(self) -> None:
expected = ClassificationResult(
intents=(IntentTarget(agent_name="order_lookup", confidence=0.9, reasoning="test"),),
)
mock_classifier = AsyncMock()
mock_classifier.classify = AsyncMock(return_value=expected)
graph = MagicMock()
graph.intent_classifier = mock_classifier
graph.agent_registry = MagicMock()
graph.agent_registry.list_agents = MagicMock(return_value=())
result = await classify_intent(graph, "check order")
assert result is not None
assert result.intents[0].agent_name == "order_lookup"