Files
ss-tools/agent/tests/test_agent/test_intent_keyword_edges.py
busya b95df37cd5 refactor(agent): extract agent+shared into standalone packages with full GRACE semantic markup
- Move agent code from backend/src/agent/ to agent/src/ss_tools/agent/
- Extract shared stdlib-only utilities to shared/src/ss_tools/shared/
- Add #region/#endregion contracts to all ~140 functions (INV_1 compliance)
- Update docker files, entrypoint, build scripts for new package layout
- Backend now imports ss_tools.shared._llm_health (no gradio/langchain deps)
- Add specs for 036-039 feature plans
2026-07-07 15:18:24 +03:00

135 lines
6.5 KiB
Python

# #region Test.IntentKeyword.Edges [C:2] [TYPE Module] [SEMANTICS test,metadata,invariants]
# @BRIEF Metadata invariant tests for agent tools — tool catalog consistency, docstring coverage.
# Keyword matching tests removed (LLM handles intent detection via LangGraph tool-calling).
# @RELATION BINDS_TO -> [AgentChat.Tools]
# @TEST_EDGE: all_tools_registered -> get_all_tools() returns consistent list
# @TEST_EDGE: docstrings_present -> every tool has a non-empty docstring
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
import pytest
from unittest.mock import MagicMock, patch
# ═══════════════════════════════════════════════════════════════════
# A — Tool catalog consistency
# ═══════════════════════════════════════════════════════════════════
class TestToolCatalog:
"""Verify tool catalog is internally consistent."""
def test_get_all_tools_returns_all_24(self):
"""get_all_tools() returns at least 24 tools."""
with patch("ss_tools.agent.tools.logger", MagicMock()):
from ss_tools.agent.tools import get_all_tools
tools = get_all_tools()
tool_names = {t.name for t in tools}
assert len(tools) >= 24, f"Expected ≥24 tools, got {len(tools)}: {tool_names}"
# Core tools must be present
assert "show_capabilities" in tool_names
assert "search_dashboards" in tool_names
assert "get_health_summary" in tool_names
assert "start_maintenance" in tool_names
def test_tool_names_are_unique(self):
"""No duplicate tool names in get_all_tools()."""
with patch("ss_tools.agent.tools.logger", MagicMock()):
from ss_tools.agent.tools import get_all_tools
tools = get_all_tools()
names = [t.name for t in tools]
assert len(names) == len(set(names)), f"Duplicate tool names: {names}"
def test_every_tool_has_docstring(self):
"""Every tool MUST have a docstring — enforced by LangChain but verified here."""
with patch("ss_tools.agent.tools.logger", MagicMock()):
from ss_tools.agent.tools import get_all_tools
for t in get_all_tools():
desc = (t.description or "").strip()
assert desc, f"Tool '{t.name}' has empty description. LangChain @tool requires a docstring."
# ═══════════════════════════════════════════════════════════════════
# B — Embedding router metadata
# ═══════════════════════════════════════════════════════════════════
class TestEmbeddingMetadata:
"""Verify embedding router is consistent with tool catalog."""
def test_embedding_top_k_returns_empty_for_empty_query(self):
"""Empty query → empty result."""
try:
from ss_tools.agent._embedding_router import embedding_top_k
except ImportError:
pytest.skip("_embedding_router module not importable")
assert embedding_top_k("") == []
def test_embedding_is_available_returns_bool(self):
"""embedding_is_available returns bool, does not raise."""
try:
from ss_tools.agent._embedding_router import embedding_is_available
except ImportError:
pytest.skip("_embedding_router module not importable")
result = embedding_is_available()
assert isinstance(result, bool)
def test_get_descriptions_matches_all_tools(self):
"""_get_descriptions() covers every tool in get_all_tools() 1:1."""
with patch("ss_tools.agent.tools.logger", MagicMock()):
from ss_tools.agent.tools import get_all_tools
try:
from ss_tools.agent._embedding_router import _get_descriptions
except ImportError:
pytest.skip("_embedding_router module not importable")
descs, names = _get_descriptions()
tool_names = {t.name for t in get_all_tools()}
assert set(names) == tool_names, (
f"Mismatch: descriptions={set(names) - tool_names}, tools={tool_names - set(names)}"
)
assert len(descs) == len(tool_names), f"Expected {len(tool_names)} descriptions, got {len(descs)}"
# ═══════════════════════════════════════════════════════════════════
# C — Tool resolver utilities (kept functions)
# ═══════════════════════════════════════════════════════════════════
class TestToolResolverUtils:
"""Utility functions in _tool_resolver still work."""
def test_normalize_tool_args_handles_dict(self):
from ss_tools.agent._tool_resolver import normalize_tool_args
assert normalize_tool_args({"key": "value"}) == {"key": "value"}
def test_normalize_tool_args_handles_none(self):
from ss_tools.agent._tool_resolver import normalize_tool_args
assert normalize_tool_args(None) == {}
def test_coerce_tool_call_from_dict(self):
from ss_tools.agent._tool_resolver import coerce_tool_call
name, args = coerce_tool_call({"name": "test_tool", "args": {"x": 1}})
assert name == "test_tool"
assert args == {"x": 1}
def test_extract_tool_call_from_state_empty(self):
from ss_tools.agent._tool_resolver import extract_tool_call_from_state
from unittest.mock import MagicMock
state = MagicMock()
state.values = {"messages": []}
state.next = None
name, args = extract_tool_call_from_state(state)
assert name is None
assert args == {}
def test_known_agent_tool_names(self):
from ss_tools.agent._tool_resolver import known_agent_tool_names
names = known_agent_tool_names()
assert "show_capabilities" in names
assert "search_dashboards" in names
assert len(names) >= 24
# #endregion Test.IntentKeyword.Edges