From c03c85c76a425d680a98bf0a81e5184e9c907024 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Wed, 1 Apr 2026 10:43:31 -0700 Subject: [PATCH] feat: create httpx.AsyncClient in lifespan for federation --- muxplex/main.py | 5 +++++ muxplex/tests/test_api.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/muxplex/main.py b/muxplex/main.py index 49f177a..434eab9 100644 --- a/muxplex/main.py +++ b/muxplex/main.py @@ -20,6 +20,7 @@ import sys import time from typing import Literal +import httpx import websockets import websockets.exceptions from websockets.typing import Subprotocol @@ -175,8 +176,12 @@ async def lifespan(app: FastAPI): except Exception: pass # tmux not running at startup is OK; hook will be set on first poll + app.state.federation_client = httpx.AsyncClient(timeout=5.0, follow_redirects=False) + yield + await app.state.federation_client.aclose() + # Shutdown: cancel the poll loop task and wait for it to finish. if _poll_task is not None: _poll_task.cancel() diff --git a/muxplex/tests/test_api.py b/muxplex/tests/test_api.py index 71a9d0b..339142e 100644 --- a/muxplex/tests/test_api.py +++ b/muxplex/tests/test_api.py @@ -1435,3 +1435,32 @@ def test_federation_bearer_auth_accepted(monkeypatch): ) assert response.status_code == 200 + + +# --------------------------------------------------------------------------- +# httpx.AsyncClient federation client on app.state (task-7) +# --------------------------------------------------------------------------- + + +def test_federation_client_exists_on_app_state(monkeypatch): + """app.state.federation_client is set during lifespan and is not None. + + Verifies that the lifespan creates an httpx.AsyncClient and attaches it to + app.state.federation_client before the application begins serving requests. + """ + import httpx + + monkeypatch.setenv("MUXPLEX_PASSWORD", "test-password") + + with TestClient(app) as c: + # Inside the context manager the lifespan has completed startup, + # so app.state.federation_client must be set. + assert hasattr(app.state, "federation_client"), ( + "app.state.federation_client must be set during lifespan startup" + ) + assert app.state.federation_client is not None, ( + "app.state.federation_client must not be None" + ) + assert isinstance(app.state.federation_client, httpx.AsyncClient), ( + "app.state.federation_client must be an httpx.AsyncClient instance" + )