fix(#5588): reset memoized password-hash cache around every test (isolation flake)

tests/test_issue803.py::TestProfileCookieHelpers failed when run after
tests/test_session_static_assets.py (passed in isolation). Root cause:
api.auth.get_password_hash() memoizes the resolved hash process-wide
(_AUTH_HASH_CACHE / _AUTH_HASH_COMPUTED) and is NOT keyed on the
HERMES_WEBUI_PASSWORD env var. test_session_static_auth_exemption sets that env
var + populates the cache; monkeypatch pops the env on teardown but the cache
stays populated, so is_auth_enabled() reads stale True and build_profile_cookie
raises "requires a request handler when auth is enabled".

Fix: an autouse conftest fixture (_reset_password_hash_cache) that invalidates the
cache before AND after every test — matching the existing _isolate_hermes_config_path
/ _restore_profile_home_globals autouse-reset convention. Fixes the whole class
regardless of which test sets the password; no-op when auth is off. Adds a
direct regression test (cache a password, pop env, assert auth reads disabled +
build_profile_cookie doesn't raise). Paired-run repro now passes.
This commit is contained in:
nesquena-hermes committed 2026-07-05 08:35:14 +00:00
1 parent c922f179f7
commit 4c9690bf59
2 files changed
+50

No files matched your search

+26
View File
@@ -190,6 +190,32 @@ def _isolate_hermes_config_path():
os.environ['HERMES_CONFIG_PATH'] = isolated_config_path
@pytest.fixture(autouse=True)
def _reset_password_hash_cache():
"""Reset the memoized password-hash cache around every test (#5588).
api.auth.get_password_hash() caches the resolved hash process-wide
(_AUTH_HASH_CACHE / _AUTH_HASH_COMPUTED) for perf — it is NOT keyed on the
HERMES_WEBUI_PASSWORD env var. A test that sets that env var (e.g.
test_session_static_assets.test_session_static_auth_exemption) populates the
cache with a real hash; monkeypatch pops the env var on teardown but the
cache stays populated, so is_auth_enabled() reads stale True and later tests
(e.g. test_issue803's profile-cookie helpers) fail with a spurious
"requires a request handler when auth is enabled". Invalidate before AND
after each test so neither a pre-existing cached value nor a value this test
populates leaks across the isolation boundary. No-op when auth is off.
"""
try:
from api.auth import _invalidate_password_hash_cache
except Exception:
_invalidate_password_hash_cache = None
if _invalidate_password_hash_cache:
_invalidate_password_hash_cache()
yield
if _invalidate_password_hash_cache:
_invalidate_password_hash_cache()
_MISSING = object() # sentinel: api.profiles module not loaded pre-test
+24
View File
@@ -34,6 +34,30 @@ class TestProfileCookieHelpers:
assert 'SameSite=Lax' in s
assert 'Path=/' in s
def test_build_profile_cookie_survives_stale_password_hash_cache(self, monkeypatch):
"""#5588 regression: a prior test that set HERMES_WEBUI_PASSWORD populates
api.auth's process-wide password-hash cache; if that cache leaks past the
env-var teardown, is_auth_enabled() reads stale True and build_profile_cookie
raises "requires a request handler when auth is enabled". Simulate the leak
in-test: cache a password, pop the env, and confirm the memoized state does
not make a no-handler build_profile_cookie raise. (The autouse
_reset_password_hash_cache fixture is what keeps this from leaking across
real test files.)"""
import api.auth as auth
from api.helpers import build_profile_cookie
# Populate the cache as if an auth-enabled test just ran.
monkeypatch.setenv("HERMES_WEBUI_PASSWORD", "leak-check")
auth._invalidate_password_hash_cache()
assert auth.is_auth_enabled() is True # cache now holds a hash
# Env goes away (as monkeypatch would on teardown) but reset the cache the
# way the autouse fixture does — auth must read as disabled again.
monkeypatch.delenv("HERMES_WEBUI_PASSWORD", raising=False)
auth._invalidate_password_hash_cache()
assert auth.is_auth_enabled() is False
# And the no-handler cookie build must not raise.
s = build_profile_cookie('alice')
assert 'hermes_profile=alice' in s
def test_build_profile_cookie_default_persists(self):
from api.helpers import build_profile_cookie
s = build_profile_cookie('default')