"""Swarm announce loop: PSK reading, state transitions, context provider."""
import httpx
import pytest
from navi.identity import InstanceIdentity
from navi.swarm import HiveAnnouncer, read_swarm_key
IDENT = InstanceIdentity(name="quiet-otter", instance_id="11111111-2222-3333-4444-555555555555", created_at="")
def _announcer(transport) -> HiveAnnouncer:
client = httpx.AsyncClient(transport=transport, base_url="http://hive.test")
return HiveAnnouncer(
hive_url="http://hive.test",
key="secret",
identity=IDENT,
port=8099,
interval=120,
client=client,
)
def test_read_swarm_key(tmp_path):
missing = read_swarm_key(tmp_path / "absent")
assert missing is None
empty = tmp_path / "empty"
empty.write_text(" \n")
assert read_swarm_key(empty) is None
good = tmp_path / ".swarm-key"
good.write_text(" deadbeef \n")
assert read_swarm_key(good) == "deadbeef"
@pytest.mark.asyncio
async def test_announce_success_marks_reachable():
seen = {}
def handler(request: httpx.Request) -> httpx.Response:
import json
seen["url"] = str(request.url)
seen["key"] = request.headers.get("X-Swarm-Key")
seen["json"] = json.loads(request.content)
return httpx.Response(200, json={"ok": True})
announcer = _announcer(httpx.MockTransport(handler))
ok = await announcer.announce_once()
assert ok is True
status = announcer.get_status()
assert status["reachable"] is True
assert status["down_since"] is None
assert status["last_error"] is None
# request shape
assert seen["url"] == "http://hive.test/announce"
assert seen["key"] == "secret"
assert seen["json"]["name"] == "quiet-otter"
assert seen["json"]["port"] == 8099
assert seen["json"]["instance_id"] == IDENT.instance_id
assert seen["json"]["meta"]["hostname"]
@pytest.mark.asyncio
async def test_announce_failure_sets_down_state_and_keeps_it():
calls = {"n": 0}
def handler(request: httpx.Request) -> httpx.Response:
calls["n"] += 1
raise httpx.ConnectError("connection refused")
announcer = _announcer(httpx.MockTransport(handler))
assert await announcer.announce_once() is False
status = announcer.get_status()
assert status["reachable"] is False
assert status["down_since"] is not None
assert "ConnectError" in status["last_error"]
assert status["consecutive_failures"] == 1
# second failure: down_since does not shift (no re-log of transition)
await announcer.announce_once()
assert announcer.get_status()["down_since"] == status["down_since"]
assert announcer.get_status()["consecutive_failures"] == 2
@pytest.mark.asyncio
async def test_announce_recovers_after_failure():
fail = {"on": True}
def handler(request: httpx.Request) -> httpx.Response:
if fail["on"]:
raise httpx.ConnectError("down")
return httpx.Response(200, json={"ok": True})
announcer = _announcer(httpx.MockTransport(handler))
await announcer.announce_once()
assert announcer.get_status()["reachable"] is False
fail["on"] = False
assert await announcer.announce_once() is True
status = announcer.get_status()
assert status["reachable"] is True
assert status["down_since"] is None
assert status["consecutive_failures"] == 0
@pytest.mark.asyncio
async def test_hive_status_provider_silent_when_healthy_or_unconfigured(monkeypatch):
from types import SimpleNamespace
from navi.context_providers import hive_status as provider
from navi import swarm
# unconfigured: no context at all
monkeypatch.setattr(provider, "settings", SimpleNamespace(hive_url=""))
assert await provider.get_context() is None
# configured + healthy: silent (token economy)
monkeypatch.setattr(provider, "settings", SimpleNamespace(hive_url="http://hive.test"))
class FakeAnnouncer:
def get_status(self):
return {"reachable": True, "url": "http://hive.test", "down_since": None, "last_error": None}
monkeypatch.setattr(swarm, "get_announcer", lambda: FakeAnnouncer())
assert await provider.get_context() is None
@pytest.mark.asyncio
async def test_hive_status_provider_reports_outage(monkeypatch):
from types import SimpleNamespace
from navi.context_providers import hive_status as provider
from navi import swarm
monkeypatch.setattr(provider, "settings", SimpleNamespace(hive_url="http://hive.test"))
class FakeAnnouncer:
def get_status(self):
return {
"reachable": False,
"url": "http://hive.test",
"down_since": "2026-09-10T12:00:00+00:00",
"last_error": "ConnectError: connection refused",
}
monkeypatch.setattr(swarm, "get_announcer", lambda: FakeAnnouncer())
text = await provider.get_context()
assert text is not None
assert "unreachable" in text
assert "http://hive.test" in text
assert "connection refused" in text