"""hive — the swarm address book service (see hive/app.py).
The app module reads env config at import time, so each test reimports it with
fresh env into its own key file / database.
"""
import importlib
import sqlite3
import pytest
from fastapi.testclient import TestClient
@pytest.fixture()
def hive(tmp_path, monkeypatch):
monkeypatch.setenv("HIVE_KEY_FILE", str(tmp_path / ".swarm-key"))
monkeypatch.setenv("HIVE_DB", str(tmp_path / "hive.db"))
monkeypatch.setenv("HIVE_TTL_SEC", "300")
(tmp_path / ".swarm-key").write_text("secret-key-123")
import hive.app as hive_app
module = importlib.reload(hive_app)
yield module
module._db.close()
def _headers(key: str = "secret-key-123") -> dict:
return {"X-Swarm-Key": key}
def _announce(client, name="quiet-otter", instance_id="11111111-2222-3333-4444-555555555555", port=8099):
return client.post(
"/announce",
json={"name": name, "instance_id": instance_id, "version": "0.1.0", "port": port,
"meta": {"hostname": "gpu-box", "os": "Linux 6.1", "cpu_cores": 16, "ram_mb": 65536}},
headers=_headers(),
)
def test_health_open_without_key(hive):
with TestClient(hive.app) as client:
resp = client.get("/health")
assert resp.status_code == 200
assert resp.json()["hive"] is True
def test_announce_requires_key(hive):
with TestClient(hive.app) as client:
assert client.post("/announce", json={"name": "x", "instance_id": "12345678", "port": 8099}).status_code == 401
assert client.post(
"/announce",
json={"name": "x", "instance_id": "12345678", "port": 8099},
headers={"X-Swarm-Key": "wrong"},
).status_code == 403
def test_peers_requires_key(hive):
with TestClient(hive.app) as client:
assert client.get("/peers").status_code == 401
assert client.get("/peers", headers={"X-Swarm-Key": "wrong"}).status_code == 403
def test_announce_upsert_and_peers_listing(hive):
with TestClient(hive.app) as client:
assert _announce(client).status_code == 200
# same instance_id again = upsert (renamed, different port), not a second row
assert _announce(client, name="renamed-otter", port=8123).status_code == 200
# a second machine
assert _announce(client, name="amber-falcon", instance_id="99999999-2222-3333-4444-555555555555").status_code == 200
resp = client.get("/peers", headers=_headers())
assert resp.status_code == 200
body = resp.json()
peers = body["peers"]
assert len(peers) == 2
assert [p["name"] for p in peers] == ["amber-falcon", "renamed-otter"] # sorted by name
otter = peers[1]
assert otter["port"] == 8123
assert otter["address"] == f"{otter['host']}:8123"
assert otter["online"] is True
assert otter["meta"]["hostname"] == "gpu-box"
assert otter["meta"]["ram_mb"] == 65536
assert otter["version"] == "0.1.0"
assert body["ttl_sec"] == 300
def test_offline_after_ttl(hive, tmp_path):
with TestClient(hive.app) as client:
_announce(client)
# backdate the last announcement beyond the TTL — machine went quiet
conn = sqlite3.connect(tmp_path / "hive.db")
conn.execute("UPDATE machines SET last_seen = last_seen - 400")
conn.commit()
conn.close()
peers = client.get("/peers", headers=_headers()).json()["peers"]
assert peers[0]["online"] is False
def test_previous_key_still_accepted(hive, tmp_path):
# rotation window: .swarm-key.previous keeps working
(tmp_path / ".swarm-key.previous").write_text("old-key-42")
import hive.app as module
importlib.reload(module)
with TestClient(module.app) as client:
resp = client.post(
"/announce",
json={"name": "x", "instance_id": "12345678", "port": 8099},
headers={"X-Swarm-Key": "old-key-42"},
)
assert resp.status_code == 200
module._db.close()
def test_hive_without_key_file_refuses(hive, tmp_path, monkeypatch):
monkeypatch.setenv("HIVE_KEY_FILE", str(tmp_path / ".no-key"))
monkeypatch.setenv("HIVE_DB", str(tmp_path / "hive2.db"))
import hive.app as module
importlib.reload(module)
with TestClient(module.app) as client:
resp = client.post(
"/announce",
json={"name": "x", "instance_id": "12345678", "port": 8099},
headers={"X-Swarm-Key": "whatever"},
)
assert resp.status_code == 503
module._db.close()