1
0
This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/tests/test_openfang_client.py
2026-02-28 19:27:48 -05:00

227 lines
7.5 KiB
Python

"""Chunk 2: OpenFang HTTP client — test first, implement second.
Tests cover:
- Health check returns False when unreachable
- Health check TTL caching
- execute_hand() rejects unknown hands
- execute_hand() success with mocked HTTP
- execute_hand() graceful degradation on error
- Convenience wrappers call the correct hand
"""
import asyncio
import json
from unittest.mock import MagicMock, patch
import pytest
# ---------------------------------------------------------------------------
# Health checks
# ---------------------------------------------------------------------------
def test_health_check_false_when_unreachable():
"""Client should report unhealthy when OpenFang is not running."""
from infrastructure.openfang.client import OpenFangClient
client = OpenFangClient(base_url="http://localhost:19999")
assert client._check_health() is False
def test_health_check_caching():
"""Repeated .healthy calls within TTL should not re-check."""
from infrastructure.openfang.client import OpenFangClient
client = OpenFangClient(base_url="http://localhost:19999")
client._health_cache_ttl = 9999 # very long TTL
# Force a first check (will be False)
_ = client.healthy
assert client._healthy is False
# Manually flip the cached value — next access should use cache
client._healthy = True
assert client.healthy is True # still cached, no re-check
# ---------------------------------------------------------------------------
# execute_hand — unknown hand
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_execute_hand_unknown_hand():
"""Requesting an unknown hand returns success=False immediately."""
from infrastructure.openfang.client import OpenFangClient
client = OpenFangClient(base_url="http://localhost:19999")
result = await client.execute_hand("nonexistent_hand", {})
assert result.success is False
assert "Unknown hand" in result.error
# ---------------------------------------------------------------------------
# execute_hand — success path (mocked HTTP)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_execute_hand_success_mocked():
"""When OpenFang returns 200 with output, HandResult.success is True."""
from infrastructure.openfang.client import OpenFangClient
response_body = json.dumps({
"success": True,
"output": "Page loaded successfully",
"metadata": {"url": "https://example.com"},
}).encode()
mock_resp = MagicMock()
mock_resp.status = 200
mock_resp.read.return_value = response_body
mock_resp.__enter__ = lambda s: s
mock_resp.__exit__ = MagicMock(return_value=False)
with patch("urllib.request.urlopen", return_value=mock_resp):
client = OpenFangClient(base_url="http://localhost:8080")
result = await client.execute_hand("browser", {"url": "https://example.com"})
assert result.success is True
assert result.output == "Page loaded successfully"
assert result.hand == "browser"
assert result.latency_ms > 0
# ---------------------------------------------------------------------------
# execute_hand — graceful degradation on connection error
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_execute_hand_connection_error():
"""When OpenFang is unreachable, HandResult.success is False (no crash)."""
from infrastructure.openfang.client import OpenFangClient
client = OpenFangClient(base_url="http://localhost:19999")
result = await client.execute_hand("browser", {"url": "https://example.com"})
assert result.success is False
assert result.error # non-empty error message
assert result.hand == "browser"
# ---------------------------------------------------------------------------
# Convenience wrappers
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_browse_calls_browser_hand():
"""browse() should delegate to execute_hand('browser', ...)."""
from infrastructure.openfang.client import OpenFangClient
client = OpenFangClient(base_url="http://localhost:19999")
calls = []
original = client.execute_hand
async def spy(hand, params, **kw):
calls.append((hand, params))
return await original(hand, params, **kw)
client.execute_hand = spy
await client.browse("https://example.com", "click button")
assert len(calls) == 1
assert calls[0][0] == "browser"
assert calls[0][1]["url"] == "https://example.com"
@pytest.mark.asyncio
async def test_collect_calls_collector_hand():
"""collect() should delegate to execute_hand('collector', ...)."""
from infrastructure.openfang.client import OpenFangClient
client = OpenFangClient(base_url="http://localhost:19999")
calls = []
original = client.execute_hand
async def spy(hand, params, **kw):
calls.append((hand, params))
return await original(hand, params, **kw)
client.execute_hand = spy
await client.collect("example.com", depth="deep")
assert len(calls) == 1
assert calls[0][0] == "collector"
assert calls[0][1]["target"] == "example.com"
@pytest.mark.asyncio
async def test_predict_calls_predictor_hand():
"""predict() should delegate to execute_hand('predictor', ...)."""
from infrastructure.openfang.client import OpenFangClient
client = OpenFangClient(base_url="http://localhost:19999")
calls = []
original = client.execute_hand
async def spy(hand, params, **kw):
calls.append((hand, params))
return await original(hand, params, **kw)
client.execute_hand = spy
await client.predict("Will BTC hit 100k?", horizon="1m")
assert len(calls) == 1
assert calls[0][0] == "predictor"
assert calls[0][1]["question"] == "Will BTC hit 100k?"
# ---------------------------------------------------------------------------
# HandResult dataclass
# ---------------------------------------------------------------------------
def test_hand_result_defaults():
"""HandResult should have sensible defaults."""
from infrastructure.openfang.client import HandResult
r = HandResult(hand="browser", success=True)
assert r.output == ""
assert r.error == ""
assert r.latency_ms == 0.0
assert r.metadata == {}
# ---------------------------------------------------------------------------
# OPENFANG_HANDS constant
# ---------------------------------------------------------------------------
def test_openfang_hands_tuple():
"""The OPENFANG_HANDS constant should list all 7 hands."""
from infrastructure.openfang.client import OPENFANG_HANDS
assert len(OPENFANG_HANDS) == 7
assert "browser" in OPENFANG_HANDS
assert "collector" in OPENFANG_HANDS
assert "predictor" in OPENFANG_HANDS
assert "lead" in OPENFANG_HANDS
assert "twitter" in OPENFANG_HANDS
assert "researcher" in OPENFANG_HANDS
assert "clip" in OPENFANG_HANDS
# ---------------------------------------------------------------------------
# status() summary
# ---------------------------------------------------------------------------
def test_status_returns_summary():
"""status() should return a dict with url, healthy flag, and hands list."""
from infrastructure.openfang.client import OpenFangClient
client = OpenFangClient(base_url="http://localhost:19999")
s = client.status()
assert "url" in s
assert "healthy" in s
assert "available_hands" in s
assert len(s["available_hands"]) == 7