"""Peer endpoints: hello is open, status/ask need the PSK, loop guard works."""
from types import SimpleNamespace
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from navi import swarm
from navi.api.routes import peer as peer_routes
from navi.identity import ensure_identity
KEY = "secret-key-123"
@pytest.fixture()
def peer_env(tmp_path, monkeypatch):
"""Real instance.json + .swarm-key in tmp; settings point there."""
import navi.api.deps # noqa: F401 - resolve the container BEFORE settings are swapped
ensure_identity(tmp_path / "instance.json")
(tmp_path / ".swarm-key").write_text(KEY)
fake = SimpleNamespace(
instance_file=str(tmp_path / "instance.json"),
swarm_key_file=str(tmp_path / ".swarm-key"),
peer_ask_profile="server_admin",
peer_ask_timeout_sec=5,
navi_port=8099,
)
monkeypatch.setattr(peer_routes, "settings", fake)
monkeypatch.setattr("navi.config.settings", fake)
from navi.identity import load_identity
return load_identity(tmp_path / "instance.json")
@pytest.fixture()
def client(peer_env):
app = FastAPI()
app.include_router(peer_routes.router)
return TestClient(app)
def _headers(key: str = KEY) -> dict:
return {"X-Swarm-Key": key}
def test_hello_is_open(client, peer_env):
resp = client.get("/peer/hello") # no key
assert resp.status_code == 200
body = resp.json()
assert body["navi"] is True
assert body["name"] == peer_env.name
assert body["instance_id"] == peer_env.instance_id[:8] # prefix only, not full uuid
assert len(body["instance_id"]) == 8
assert body["port"] == 8099
def test_status_requires_key(client):
assert client.get("/peer/status").status_code == 401
assert client.get("/peer/status", headers=_headers("wrong")).status_code == 403
resp = client.get("/peer/status", headers=_headers())
assert resp.status_code == 200
body = resp.json()
assert body["name"]
assert "machine" in body
assert body["hive"] == {"configured": False} # no announcer in tests
def test_ask_requires_key(client, peer_env):
payload = {"from_name": "amber-falcon", "from_instance_id": "x" * 32, "question": "hi"}
assert client.post("/peer/ask", json=payload).status_code == 401
assert client.post("/peer/ask", json=payload, headers=_headers("bad")).status_code == 403
def test_ask_loop_from_own_uuid_refused(client, peer_env, monkeypatch):
payload = {"from_name": peer_env.name, "from_instance_id": peer_env.instance_id, "question": "echo?"}
resp = client.post("/peer/ask", json=payload, headers=_headers())
assert resp.status_code == 409
def test_ask_runs_agent_without_peer_tool(client, peer_env, monkeypatch):
captured = {}
class FakeAgent:
async def run_ephemeral(self, user_message, profile_id, exclude_tools=None,
briefing=None, timeout_seconds=None, **kw):
captured.update(
message=user_message, profile=profile_id, exclude=exclude_tools,
briefing=briefing, timeout=timeout_seconds,
)
return "the answer from the other side", True
monkeypatch.setattr("navi.api.deps.get_agent", lambda: FakeAgent())
payload = {"from_name": "amber-falcon", "from_instance_id": "y" * 32, "question": "what is your load?"}
resp = client.post("/peer/ask", json=payload, headers=_headers())
assert resp.status_code == 200
body = resp.json()
assert body["answer"] == "the answer from the other side"
assert body["completed"] is True
assert body["request_id"]
# loop guard: answering agent has no peer tool, and the right profile
assert captured["exclude"] == ["peer"]
assert captured["profile"] == "server_admin"
assert captured["message"] == "what is your load?"
assert "amber-falcon" in captured["briefing"]
def test_ask_question_length_capped(client, peer_env):
payload = {"from_name": "amber-falcon", "from_instance_id": "y" * 32, "question": "q" * 5000}
assert client.post("/peer/ask", json=payload, headers=_headers()).status_code == 422
def test_previous_swarm_key_accepted(client, peer_env, tmp_path, monkeypatch):
# rotation window: .swarm-key.previous still authenticates
(tmp_path / ".swarm-key.previous").write_text("old-key")
resp = client.get("/peer/status", headers={"X-Swarm-Key": "old-key"})
assert resp.status_code == 200
def test_verify_swarm_key_rejects_garbage(tmp_path, monkeypatch):
(tmp_path / ".swarm-key").write_text("real")
fake = SimpleNamespace(swarm_key_file=str(tmp_path / ".swarm-key"))
monkeypatch.setattr("navi.config.settings", fake)
assert swarm.verify_swarm_key("real") is True
assert swarm.verify_swarm_key("wrong") is False
assert swarm.verify_swarm_key("") is False
assert swarm.verify_swarm_key(None) is False