"""peer tool: hive book with stale cache, ask payload shape, self-ask guard."""
import json
from types import SimpleNamespace
import httpx
import pytest
from navi.identity import ensure_identity
from navi.tools import peer as peer_tool
from navi.tools.peer import PeerTool
KEY = "secret-key-123"
ME = {"name": "quiet-otter", "instance_id": "11111111-2222-3333-4444-555555555555"}
FALCON = {
"name": "amber-falcon",
"instance_id": "99999999-2222-3333-4444-555555555555",
"host": "192.168.1.50",
"port": 8099,
"address": "192.168.1.50:8099",
"online": True,
"meta": {"os": "Linux 6.1", "cpu_cores": 16, "ram_mb": 65536},
"version": "0.1.0",
}
@pytest.fixture()
def tool(tmp_path, monkeypatch):
"""Tool wired to a tmp identity/key and injectable http transport."""
ensure_identity(tmp_path / "instance.json")
(tmp_path / ".swarm-key").write_text(KEY)
fake = SimpleNamespace(
hive_url="http://hive.test",
swarm_key_file=str(tmp_path / ".swarm-key"),
instance_file=str(tmp_path / "instance.json"),
peer_ask_timeout_sec=30,
)
monkeypatch.setattr(peer_tool, "settings", fake)
real_client = httpx.AsyncClient
def make_client(transport):
# The tool builds its own httpx.AsyncClient; swap in the mock transport.
monkeypatch.setattr(
httpx, "AsyncClient",
lambda timeout=None: real_client(transport=transport, timeout=timeout),
)
yield make_client
peer_tool._peers_cache = None
def _hive_transport(state: dict):
"""Transport serving the hive /peers endpoint (and peer endpoints)."""
def handler(request: httpx.Request) -> httpx.Response:
if request.url.path == "/peers":
if state.get("hive_down"):
raise httpx.ConnectError("registry unreachable")
return httpx.Response(200, json={"peers": state.get("peers", []), "ttl_sec": 300})
if request.url.path == "/peer/status" and state.get("status_ok", True):
return httpx.Response(200, json={
"name": FALCON["name"], "instance_id": FALCON["instance_id"],
"version": "0.1.0", "port": 8099, "uptime_sec": 1234,
"machine": {"os": "Linux 6.1", "cpu_cores": 16},
"hive": {"reachable": True},
})
if request.url.path == "/peer/ask" and state.get("ask_ok", True):
body = json.loads(request.content)
state["last_ask"] = {"body": body, "key": request.headers.get("X-Swarm-Key")}
return httpx.Response(200, json={"answer": "load is 0.42", "completed": True, "request_id": "abc"})
raise httpx.ConnectError("no route")
return httpx.MockTransport(handler)
@pytest.mark.asyncio
async def test_list_success_excludes_self(tool):
tool(_hive_transport({"peers": [FALCON]}))
result = await PeerTool().execute({"action": "list"})
assert result.success
assert "amber-falcon" in result.output
assert "192.168.1.50:8099" in result.output
assert "online" in result.output
@pytest.mark.asyncio
async def test_list_falls_back_to_stale_cache(tool):
state = {"peers": [FALCON]}
tool(_hive_transport(state))
assert (await PeerTool().execute({"action": "list"})).success # warm the cache
# hive goes down -> cached copy still answers, marked stale
state["hive_down"] = True
peer_tool._peers_cache["peers"][0]["online"] = False # mutate what we cached
result = await PeerTool().execute({"action": "list"})
assert result.success
assert "STALE" in result.output
assert "amber-falcon" in result.output
assert "offline" in result.output # last-known state
@pytest.mark.asyncio
async def test_list_fails_when_no_cache_and_hive_down(tool):
tool(_hive_transport({"peers": [FALCON], "hive_down": True}))
result = await PeerTool().execute({"action": "list"})
assert not result.success
assert "Cannot reach the hive" in result.output
@pytest.mark.asyncio
async def test_ask_sends_identity_and_key(tool):
from navi.identity import load_identity
me = load_identity(peer_tool.settings.instance_file)
state = {"peers": [FALCON]}
tool(_hive_transport(state))
result = await PeerTool().execute({"action": "ask", "peer": "amber-falcon", "question": "what is your load?"})
assert result.success
assert "load is 0.42" in result.output
sent = state["last_ask"]
assert sent["key"] == KEY
assert sent["body"]["question"] == "what is your load?"
assert sent["body"]["from_name"] == me.name
assert sent["body"]["from_instance_id"] == me.instance_id # our real generated identity
assert result.output.startswith("amber-falcon answered:")
@pytest.mark.asyncio
async def test_ask_unknown_peer_lists_known(tool):
tool(_hive_transport({"peers": [FALCON]}))
result = await PeerTool().execute({"action": "ask", "peer": "ghost", "question": "hi"})
assert not result.success
assert "Unknown peer 'ghost'" in result.output
assert "amber-falcon" in result.output # helpful: names the known peers
@pytest.mark.asyncio
async def test_ask_refuses_self(tool):
from navi.identity import load_identity
me = load_identity(peer_tool.settings.instance_file)
tool(_hive_transport({"peers": [FALCON]}))
result = await PeerTool().execute({"action": "ask", "peer": me.name, "question": "hi"})
assert not result.success
assert "this very navi" in result.output
@pytest.mark.asyncio
async def test_ask_reports_peer_refusal(tool):
def handler(request: httpx.Request) -> httpx.Response:
if request.url.path == "/peers":
return httpx.Response(200, json={"peers": [FALCON], "ttl_sec": 300})
return httpx.Response(409, json={"detail": "ask loop detected - this question originated here"})
tool(httpx.MockTransport(handler))
result = await PeerTool().execute({"action": "ask", "peer": "amber-falcon", "question": "hi"})
assert not result.success
assert "refused" in result.output
assert "ask loop detected" in result.output
@pytest.mark.asyncio
async def test_status_formats_peer_state(tool):
tool(_hive_transport({"peers": [FALCON]}))
result = await PeerTool().execute({"action": "status", "peer": "amber-falcon"})
assert result.success
assert "uptime: 1234s" in result.output
assert "Linux 6.1" in result.output
@pytest.mark.asyncio
async def test_incomplete_answer_is_flagged(tool):
def handler(request: httpx.Request) -> httpx.Response:
if request.url.path == "/peers":
return httpx.Response(200, json={"peers": [FALCON], "ttl_sec": 300})
return httpx.Response(200, json={"answer": "partial", "completed": False, "request_id": "x"})
tool(httpx.MockTransport(handler))
result = await PeerTool().execute({"action": "ask", "peer": "amber-falcon", "question": "hi"})
assert result.success
assert "turn limit" in result.output