Files
hermes-webui/tests/test_issue4662_sidebar_redaction_read_once.py
T
nesquena-hermes 4f071895cc perf(#4662): read redaction setting once per sidebar response, not per row
Phase 3 of #4662. _session_list_payload_to_response read settings.json once
per row via _redact_text's per-call load_settings() fallback (the multi-second
response_write stage in /api/sessions diagnostics on large lists). The
_sidebar_session_response_item -> _redact_text(_enabled=...) threading already
existed; this wires the caller to read the api_redact_enabled flag once and
thread it through every row, matching the read-once optimization
redact_session_data() already uses. Behavior-preserving; redaction still
applies (or not) exactly as before.
2026-06-23 05:42:32 +00:00

68 lines
3.1 KiB
Python

"""Phase 3 (#4662): sidebar session-list serialization must read the redaction
setting ONCE per response, not once per row. Regression guard for the per-row
settings.json reload that dominated /api/sessions response_write on large lists.
"""
import api.routes as routes
def test_sidebar_payload_reads_redaction_setting_once(monkeypatch):
calls = {"n": 0}
def _counting_load_settings():
calls["n"] += 1
return {"api_redact_enabled": True}
# _redact_text() falls back to load_settings() only when _enabled is None,
# so a per-row read shows up as a load_settings() call per row. After the
# fix the caller reads it once and threads redact_enabled to every row.
monkeypatch.setattr("api.config.load_settings", _counting_load_settings)
monkeypatch.setattr("api.helpers.load_settings", _counting_load_settings, raising=False)
payload = {
"sessions": [
{"session_id": f"s{i}", "title": f"title {i}", "preview": f"preview {i}"}
for i in range(12)
],
"cli_count": 0,
}
# Pass rows straight through — avoid runtime-overlay noise from the cache layer.
monkeypatch.setattr(routes, "_session_list_cache_overlay_runtime_rows", lambda rows: rows)
routes._session_list_payload_to_response(payload)
# Before the fix: ~1 read per row (>=12). After: exactly 1 for the whole response.
assert calls["n"] <= 1, f"settings read {calls['n']}x; expected <=1 (read-once per response)"
def test_sidebar_payload_still_redacts_titles(monkeypatch):
"""The read-once optimization must not disable redaction: a title that looks
like a credential is still redacted when api_redact_enabled is True."""
monkeypatch.setattr("api.config.load_settings", lambda: {"api_redact_enabled": True})
monkeypatch.setattr("api.helpers.load_settings", lambda: {"api_redact_enabled": True}, raising=False)
monkeypatch.setattr(routes, "_session_list_cache_overlay_runtime_rows", lambda rows: rows)
secret = "sk-ant-api03-" + ("A" * 40)
payload = {"sessions": [{"session_id": "s1", "title": f"key {secret}"}], "cli_count": 0}
resp = routes._session_list_payload_to_response(payload)
title = resp["sessions"][0]["title"]
assert secret not in title, f"credential leaked into sidebar title: {title!r}"
def test_sidebar_payload_no_redaction_when_disabled(monkeypatch):
"""When api_redact_enabled is False, titles pass through unchanged (and we
still only read the setting once)."""
calls = {"n": 0}
def _load():
calls["n"] += 1
return {"api_redact_enabled": False}
monkeypatch.setattr("api.config.load_settings", _load)
monkeypatch.setattr("api.helpers.load_settings", _load, raising=False)
monkeypatch.setattr(routes, "_session_list_cache_overlay_runtime_rows", lambda rows: rows)
payload = {"sessions": [{"session_id": "s1", "title": "plain title"}], "cli_count": 0}
resp = routes._session_list_payload_to_response(payload)
assert resp["sessions"][0]["title"] == "plain title"
assert calls["n"] <= 1, f"settings read {calls['n']}x with redaction disabled; expected <=1"