Files
hope-voice-api/tests/test_mcp_tools.py
T

123 lines
3.9 KiB
Python

"""Tests for the MCP tool servers (weather, memory, skills) via docker exec.
Each test spawns the real MCP server script inside the container and drives it
with a JSON-RPC session using the mcp client library from the agent venv.
"""
import json
import re
import uuid
import pytest
def _extract_number(text: str) -> float | None:
"""Pull the first plausible temperature number out of a weather summary."""
m = re.search(r"(\d+)\s*degrees", text)
if m:
return float(m.group(1))
m = re.search(r"\b(\d{2,3})\b", text)
return float(m.group(1)) if m else None
@pytest.mark.slow
def test_weather_get(mcp_client_factory):
text, is_error = mcp_client_factory("weather_mcp.py", "get_weather", {"location": "Raleigh"})
assert not is_error
assert "degrees" in text.lower()
@pytest.mark.slow
def test_weather_fahrenheit(mcp_client_factory):
"""wttr.in reports Celsius; the tool must convert to Fahrenheit."""
text, is_error = mcp_client_factory("weather_mcp.py", "get_weather", {"location": "Raleigh"})
assert not is_error
temp = _extract_number(text)
assert temp is not None, f"no temperature found in: {text}"
# Fahrenheit for any inhabited place is > 50 (Celsius would be ~10-30).
assert temp > 50, f"temperature {temp} looks like Celsius, expected Fahrenheit"
@pytest.mark.slow
def test_time_get(mcp_client_factory):
text, is_error = mcp_client_factory("weather_mcp.py", "get_time", {})
assert not is_error
assert ("AM" in text or "PM" in text)
days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
assert any(d in text for d in days), f"no day name in: {text}"
@pytest.mark.slow
def test_memory_save_recall_and_delete(memory_client):
"""Authenticated memory is durable and scoped to Shane's token."""
marker = f"voice-probe-{uuid.uuid4().hex[:10]}"
saved, err = memory_client(
"memory_save",
{
"title": f"Voice integration probe {marker}",
"body": f"{marker} is the remote memory integration probe.",
"tags": ["test", "voice"],
},
)
assert not err, f"memory_save failed: {saved}"
note = json.loads(saved)
try:
recalled, err = memory_client("memory_recall", {"query": marker, "topK": 3})
assert not err
assert marker in recalled
finally:
deleted, err = memory_client(
"delete_note",
{"folder": note["folder"], "slug": note["slug"]},
)
assert not err, f"delete_note failed: {deleted}"
@pytest.mark.slow
def test_skill_save_and_recall(mcp_client_factory):
name = "Test Suite Probe Skill"
description = "A probe skill used by the voice test suite."
steps = "Step 1: do the thing. Step 2: verify zebra42 was done."
saved, err = mcp_client_factory(
"skills_mcp.py",
"skill_save",
{"name": name, "description": description, "steps": steps},
)
assert not err
assert "Saved" in saved
try:
recalled, err = mcp_client_factory("skills_mcp.py", "skill_recall", {"query": "zebra42"})
assert not err
assert "zebra42" in recalled
assert "Steps" in recalled
finally:
from conftest import docker_exec
docker_exec("rm", "-f", "/skills/test-suite-probe-skill.md")
def test_skill_list(mcp_client_factory):
from conftest import docker_exec
name = "Test Suite List Skill"
try:
saved, err = mcp_client_factory(
"skills_mcp.py",
"skill_save",
{
"name": name,
"description": "listing probe skill",
"steps": "one step",
},
)
assert not err
listed, err = mcp_client_factory("skills_mcp.py", "skill_list", {})
assert not err
# list output uses the slugified filename
assert "test-suite-list-skill" in listed
finally:
docker_exec("rm", "-f", "/skills/test-suite-list-skill.md")