feat: add federation bell/clear proxy endpoint

This commit is contained in:
Brian Krabach
2026-04-04 06:52:22 -07:00
parent 5f9d6a593e
commit 6ee6112744
2 changed files with 135 additions and 0 deletions
+48
View File
@@ -1036,6 +1036,54 @@ async def federation_connect(
)
@app.post("/api/federation/{remote_id}/sessions/{session_name}/bell/clear")
async def federation_bell_clear(
remote_id: int, session_name: str, request: Request
) -> dict:
"""Proxy a bell-clear POST to a remote instance.
Looks up the remote by integer index into ``remote_instances`` in settings,
sends ``POST {remote_url}/api/sessions/{session_name}/bell/clear`` with a
Bearer auth header, and returns the remote's JSON response.
Raises HTTP 404 if ``remote_id`` is not a valid integer index.
"""
settings = load_settings()
remotes = settings.get("remote_instances", [])
if remote_id < 0 or remote_id >= len(remotes):
raise HTTPException(
status_code=404,
detail=f"Remote instance '{remote_id}' not found",
)
remote = remotes[remote_id]
remote_url: str = remote.get("url", "").rstrip("/")
remote_key: str = remote.get("key", "")
url = f"{remote_url}/api/sessions/{session_name}/bell/clear"
http_client: httpx.AsyncClient = request.app.state.federation_client
try:
resp = await http_client.post(
url,
headers={"Authorization": f"Bearer {remote_key}"},
)
resp.raise_for_status()
return resp.json()
except httpx.HTTPStatusError as exc:
raise HTTPException(
status_code=502,
detail=f"Remote returned {exc.response.status_code}",
)
except Exception as exc:
_log.warning(
"federation_bell_clear: remote %s unreachable: %s", remote_url, exc
)
raise HTTPException(
status_code=503,
detail=f"Remote unreachable: {remote_url}",
)
# ---------------------------------------------------------------------------
# Static file serving — MUST come after all API routes (first-match-wins)
# ---------------------------------------------------------------------------
+87
View File
@@ -2330,3 +2330,90 @@ def test_cli_uvicorn_log_level_is_info():
"serve() must call uvicorn.run(..., log_level='info') so application "
"logs appear in journalctl; currently set to 'warning' which suppresses them"
)
# ---------------------------------------------------------------------------
# Federation bell/clear proxy (task-3-federation-bell-clear-proxy)
# ---------------------------------------------------------------------------
def test_federation_bell_clear_proxies_to_remote(client, monkeypatch, tmp_path):
"""POST /api/federation/{remote_id}/sessions/{name}/bell/clear proxies POST to remote's bell/clear endpoint.
Looks up remote by integer index, sends POST {remote_url}/api/sessions/{session_name}/bell/clear
with Bearer auth header, and returns the remote's JSON response.
"""
import json
from unittest.mock import MagicMock
import muxplex.settings as settings_mod
settings_path = tmp_path / "settings.json"
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_path)
settings_path.write_text(
json.dumps(
{
"remote_instances": [
{
"url": "http://remote-host:8088",
"key": "secret-key-123",
"name": "remote-host",
"id": "remote-0",
}
],
}
)
)
# Track what POST was called with
post_calls = []
async def mock_post(url, **kwargs):
post_calls.append({"url": url, "kwargs": kwargs})
mock_resp = MagicMock()
mock_resp.status_code = 200
mock_resp.json.return_value = {"cleared": True}
return mock_resp
mock_fed_client = MagicMock()
mock_fed_client.post = mock_post
monkeypatch.setattr(client.app.state, "federation_client", mock_fed_client)
response = client.post("/api/federation/0/sessions/my-session/bell/clear")
assert response.status_code == 200
# Verify the POST was made to the correct URL with session name
assert len(post_calls) == 1, f"Expected exactly 1 POST call, got {len(post_calls)}"
call = post_calls[0]
assert (
call["url"] == "http://remote-host:8088/api/sessions/my-session/bell/clear"
), f"Expected POST to remote bell/clear URL, got: {call['url']}"
# Verify Bearer auth was included
headers = call["kwargs"].get("headers", {})
assert headers.get("Authorization") == "Bearer secret-key-123", (
f"Expected Bearer auth header, got: {headers}"
)
# Verify the response is the remote's JSON
data = response.json()
assert data["cleared"] is True
def test_federation_bell_clear_returns_404_for_invalid_remote(
client, monkeypatch, tmp_path
):
"""POST /api/federation/{remote_id}/sessions/{name}/bell/clear returns 404 when remote_id is out of range."""
import json
import muxplex.settings as settings_mod
settings_path = tmp_path / "settings.json"
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_path)
# No remote instances configured
settings_path.write_text(json.dumps({"remote_instances": []}))
response = client.post("/api/federation/0/sessions/my-session/bell/clear")
assert response.status_code == 404