From e461e86502c0e9cc056c0f5ecd14e7a6abe1d44f Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Wed, 29 Jul 2026 17:58:06 -0700 Subject: [PATCH] test(cli): remove importlib.reload seam from web_server session-token tests (#38034) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract _resolve_session_token() in hermes_cli/web_server.py so tests can exercise token resolution directly instead of importlib.reload(ws), which re-executed the whole module mid-suite (fresh FastAPI app + token) and split module identity between test and app state. Salvaged from PR #39038 by @rodboev (maintainer-endorsed direction); rebased onto the rewritten test_web_server.py — dropped the PR's hunks for test_falls_back_to_random_token's old body (test deleted in the prune, re-added here in the PR's new form). Co-authored-by: Rod Boev --- hermes_cli/web_server.py | 8 +++++- tests/hermes_cli/test_web_server.py | 43 ++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 7ee26c138f8..82fd6c74f85 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -295,7 +295,13 @@ app.include_router(_memory_oauth_router) # on every server start. Either way it dies when the process exits and is # injected into the SPA HTML so only the legitimate web UI can use it. # --------------------------------------------------------------------------- -_SESSION_TOKEN = os.environ.get("HERMES_DASHBOARD_SESSION_TOKEN") or secrets.token_urlsafe(32) + + +def _resolve_session_token() -> str: + return os.environ.get("HERMES_DASHBOARD_SESSION_TOKEN") or secrets.token_urlsafe(32) + + +_SESSION_TOKEN = _resolve_session_token() _SESSION_HEADER_NAME = "X-Hermes-Session-Token" _SSH_OWNER_NONCE: Optional[str] = None diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index dddb286d107..dc4bfe34503 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -191,16 +191,45 @@ class TestSessionTokenInjection: """ def test_honors_injected_token(self, monkeypatch): - import importlib import hermes_cli.web_server as ws + original_app = ws.app + original_token = ws._SESSION_TOKEN monkeypatch.setenv("HERMES_DASHBOARD_SESSION_TOKEN", "desktop-seeded-token") - try: - importlib.reload(ws) - assert ws._SESSION_TOKEN == "desktop-seeded-token" - finally: - monkeypatch.delenv("HERMES_DASHBOARD_SESSION_TOKEN", raising=False) - importlib.reload(ws) + assert ws._resolve_session_token() == "desktop-seeded-token" + # No module reload: the loaded app and its adopted token are untouched. + assert ws.app is original_app + assert ws._SESSION_TOKEN == original_token + + def test_falls_back_to_random_token(self, monkeypatch): + import hermes_cli.web_server as ws + + monkeypatch.delenv("HERMES_DASHBOARD_SESSION_TOKEN", raising=False) + with patch.object( + ws.secrets, "token_urlsafe", return_value="generated-token" + ) as token_urlsafe: + assert ws._resolve_session_token() == "generated-token" + token_urlsafe.assert_called_once_with(32) + + def test_session_token_resolution_preserves_loaded_app_auth(self, monkeypatch): + import hermes_cli.web_server as ws + from starlette.testclient import TestClient + + original_app = ws.app + original_header_name = ws._SESSION_HEADER_NAME + original_token = ws._SESSION_TOKEN + monkeypatch.setenv("HERMES_DASHBOARD_SESSION_TOKEN", "desktop-seeded-token") + assert ws._resolve_session_token() == "desktop-seeded-token" + monkeypatch.delenv("HERMES_DASHBOARD_SESSION_TOKEN", raising=False) + with patch.object(ws.secrets, "token_urlsafe", return_value="generated-token"): + assert ws._resolve_session_token() == "generated-token" + + client = TestClient(original_app) + client.headers[original_header_name] = original_token + assert client.get("/api/__session_token_probe").status_code == 404 + assert ws.app is original_app + assert ws._SESSION_HEADER_NAME == original_header_name + assert ws._SESSION_TOKEN == original_token # ---------------------------------------------------------------------------