forked from Rockachopa/Timmy-time-dashboard
feat: dockerize OpenFang as vendored tool runtime sidecar (#96)
This commit is contained in:
committed by
GitHub
parent
d7d7a5a80a
commit
b7c89d1101
223
tests/test_openfang_tools.py
Normal file
223
tests/test_openfang_tools.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""Chunk 3: OpenFang MCP tool registration — test first, implement second.
|
||||
|
||||
Tests cover:
|
||||
- register_openfang_tools() registers all 7 hands
|
||||
- Each tool has correct category, tags, and schema
|
||||
- Twitter hand requires confirmation
|
||||
- Persona-hand mapping is correct
|
||||
- Handler delegates to openfang_client.execute_hand()
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_tool_registry():
|
||||
"""Remove OpenFang tools between tests so registration is idempotent."""
|
||||
yield
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
for name in list(tool_registry._tools.keys()):
|
||||
if name.startswith("openfang_"):
|
||||
tool_registry.unregister(name)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Registration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_register_openfang_tools_count():
|
||||
"""register_openfang_tools() should register exactly 7 tools."""
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
|
||||
count = register_openfang_tools()
|
||||
assert count == 7
|
||||
|
||||
|
||||
def test_all_seven_hands_registered():
|
||||
"""After registration, all 7 openfang_* tools exist in the registry."""
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
register_openfang_tools()
|
||||
|
||||
expected = {
|
||||
"openfang_browser",
|
||||
"openfang_collector",
|
||||
"openfang_predictor",
|
||||
"openfang_lead",
|
||||
"openfang_twitter",
|
||||
"openfang_researcher",
|
||||
"openfang_clip",
|
||||
}
|
||||
registered = set(tool_registry.list_tools(category="openfang"))
|
||||
assert registered == expected
|
||||
|
||||
|
||||
def test_tools_have_correct_category():
|
||||
"""Every OpenFang tool should be in the 'openfang' category."""
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
register_openfang_tools()
|
||||
|
||||
for name in tool_registry.list_tools(category="openfang"):
|
||||
record = tool_registry.get(name)
|
||||
assert record is not None
|
||||
assert record.category == "openfang"
|
||||
|
||||
|
||||
def test_tools_have_vendor_tag():
|
||||
"""Every OpenFang tool should be tagged with 'vendor'."""
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
register_openfang_tools()
|
||||
|
||||
for name in tool_registry.list_tools(category="openfang"):
|
||||
record = tool_registry.get(name)
|
||||
assert "vendor" in record.tags
|
||||
assert "openfang" in record.tags
|
||||
|
||||
|
||||
def test_twitter_requires_confirmation():
|
||||
"""The twitter hand should require user confirmation before execution."""
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
register_openfang_tools()
|
||||
|
||||
twitter = tool_registry.get("openfang_twitter")
|
||||
assert twitter is not None
|
||||
assert twitter.requires_confirmation is True
|
||||
|
||||
|
||||
def test_non_twitter_no_confirmation():
|
||||
"""Non-twitter hands should NOT require confirmation."""
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
register_openfang_tools()
|
||||
|
||||
for name in ["openfang_browser", "openfang_collector", "openfang_predictor"]:
|
||||
record = tool_registry.get(name)
|
||||
assert record is not None
|
||||
assert record.requires_confirmation is False
|
||||
|
||||
|
||||
def test_tools_have_schemas():
|
||||
"""Every OpenFang tool should have a non-empty schema with 'name' and 'parameters'."""
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
register_openfang_tools()
|
||||
|
||||
for name in tool_registry.list_tools(category="openfang"):
|
||||
record = tool_registry.get(name)
|
||||
assert record.schema
|
||||
assert "name" in record.schema
|
||||
assert "parameters" in record.schema
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Persona-hand mapping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_persona_hand_map_mace():
|
||||
"""Mace (Security) should have collector and browser."""
|
||||
from infrastructure.openfang.tools import get_hands_for_persona
|
||||
|
||||
hands = get_hands_for_persona("mace")
|
||||
assert "openfang_collector" in hands
|
||||
assert "openfang_browser" in hands
|
||||
|
||||
|
||||
def test_persona_hand_map_seer():
|
||||
"""Seer (Analytics) should have predictor and researcher."""
|
||||
from infrastructure.openfang.tools import get_hands_for_persona
|
||||
|
||||
hands = get_hands_for_persona("seer")
|
||||
assert "openfang_predictor" in hands
|
||||
assert "openfang_researcher" in hands
|
||||
|
||||
|
||||
def test_persona_hand_map_echo():
|
||||
"""Echo (Research) should have researcher, browser, and collector."""
|
||||
from infrastructure.openfang.tools import get_hands_for_persona
|
||||
|
||||
hands = get_hands_for_persona("echo")
|
||||
assert "openfang_researcher" in hands
|
||||
assert "openfang_browser" in hands
|
||||
assert "openfang_collector" in hands
|
||||
|
||||
|
||||
def test_persona_hand_map_unknown():
|
||||
"""Unknown persona should get empty list."""
|
||||
from infrastructure.openfang.tools import get_hands_for_persona
|
||||
|
||||
hands = get_hands_for_persona("nonexistent")
|
||||
assert hands == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Handler delegation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handler_delegates_to_client():
|
||||
"""Tool handler should call openfang_client.execute_hand()."""
|
||||
from infrastructure.openfang.client import HandResult
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
register_openfang_tools()
|
||||
|
||||
mock_result = HandResult(
|
||||
hand="browser",
|
||||
success=True,
|
||||
output="Page loaded",
|
||||
)
|
||||
|
||||
with patch(
|
||||
"infrastructure.openfang.tools.openfang_client"
|
||||
) as mock_client:
|
||||
mock_client.execute_hand = AsyncMock(return_value=mock_result)
|
||||
|
||||
record = tool_registry.get("openfang_browser")
|
||||
assert record is not None
|
||||
|
||||
output = await record.handler(url="https://example.com")
|
||||
assert output == "Page loaded"
|
||||
mock_client.execute_hand.assert_called_once_with(
|
||||
"browser", {"url": "https://example.com"}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handler_returns_error_on_failure():
|
||||
"""On failure, handler should return the error string (not raise)."""
|
||||
from infrastructure.openfang.client import HandResult
|
||||
from infrastructure.openfang.tools import register_openfang_tools
|
||||
from mcp.registry import tool_registry
|
||||
|
||||
register_openfang_tools()
|
||||
|
||||
mock_result = HandResult(
|
||||
hand="collector",
|
||||
success=False,
|
||||
error="Connection refused",
|
||||
)
|
||||
|
||||
with patch(
|
||||
"infrastructure.openfang.tools.openfang_client"
|
||||
) as mock_client:
|
||||
mock_client.execute_hand = AsyncMock(return_value=mock_result)
|
||||
|
||||
record = tool_registry.get("openfang_collector")
|
||||
output = await record.handler(target="example.com")
|
||||
assert "error" in output.lower()
|
||||
assert "Connection refused" in output
|
||||
Reference in New Issue
Block a user