Files
hermes-webui/tests/test_issue3623_profile_visibility.py
2026-07-02 13:38:43 +00:00

121 lines
4.0 KiB
Python

from __future__ import annotations
import sys
import types
from pathlib import Path
from types import SimpleNamespace
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
def _profile_row(name: str, path: Path, *, is_default: bool = False):
return SimpleNamespace(
name=name,
path=path,
is_default=is_default,
gateway_running=False,
model=None,
provider=None,
has_env=False,
)
@pytest.fixture(autouse=True)
def _clear_profile_rows_cache():
import api.profiles as profiles
profiles._LIST_PROFILES_CACHE = None
yield
profiles._LIST_PROFILES_CACHE = None
def _install_fake_hermes_profiles(monkeypatch, rows):
hermes_cli = types.ModuleType("hermes_cli")
profiles_mod = types.ModuleType("hermes_cli.profiles")
profiles_mod.list_profiles = lambda: rows
monkeypatch.setitem(sys.modules, "hermes_cli", hermes_cli)
monkeypatch.setitem(sys.modules, "hermes_cli.profiles", profiles_mod)
def test_profile_yaml_visible_false_is_exposed_as_hidden(monkeypatch, tmp_path):
import api.profiles as profiles
hidden = tmp_path / "profiles" / "worker-coder"
visible = tmp_path / "profiles" / "human"
missing = tmp_path / "profiles" / "missing-meta"
malformed = tmp_path / "profiles" / "malformed"
string_false = tmp_path / "profiles" / "string-false"
for path in (hidden, visible, missing, malformed, string_false):
path.mkdir(parents=True)
(hidden / "profile.yaml").write_text("visible: false\n", encoding="utf-8")
(visible / "profile.yaml").write_text("visible: true\n", encoding="utf-8")
(malformed / "profile.yaml").write_text("visible: [\n", encoding="utf-8")
(string_false / "profile.yaml").write_text('visible: "false"\n', encoding="utf-8")
rows = [
_profile_row("worker-coder", hidden),
_profile_row("human", visible),
_profile_row("missing-meta", missing),
_profile_row("malformed", malformed),
_profile_row("string-false", string_false),
]
_install_fake_hermes_profiles(monkeypatch, rows)
monkeypatch.setattr(profiles, "_get_profile_skills_stats", lambda _path: (0, 0))
monkeypatch.setattr(profiles, "get_active_profile_name", lambda: "human")
result = {row["name"]: row["visible"] for row in profiles.list_profiles_api()}
assert result == {
"worker-coder": False,
"human": True,
"missing-meta": True,
"malformed": True,
"string-false": True,
}
def test_default_profile_fallback_stays_visible(monkeypatch):
import api.profiles as profiles
monkeypatch.setattr(profiles, "_get_profile_skills_stats", lambda _path: (0, 0))
assert profiles._default_profile_dict()["visible"] is True
def _panels_js() -> str:
return (REPO_ROOT / "static" / "panels.js").read_text(encoding="utf-8")
def _function_body(src: str, signature: str) -> str:
start = src.find(signature)
assert start != -1, f"{signature} not found"
i = src.find("{", start)
depth = 0
for j in range(i, len(src)):
if src[j] == "{":
depth += 1
elif src[j] == "}":
depth -= 1
if depth == 0:
return src[start : j + 1]
raise AssertionError(f"could not find end of {signature}")
def test_profile_dropdown_filters_hidden_profiles_but_preserves_active():
body = _function_body(_panels_js(), "function renderProfileDropdown(data)")
assert "data = data || {};" in body
assert "const allProfiles = (Array.isArray(data.profiles) ? data.profiles : []).filter(p => p && typeof p.name === 'string');" in body
assert "allProfiles.some(p => p.name === S.activeProfile)" in body
assert "const profiles = allProfiles.filter(p => p && (p.visible !== false || p.name === active));" in body
def test_profiles_management_panel_still_renders_all_profiles():
body = _function_body(_panels_js(), "async function loadProfilesPanel()")
assert "for (const p of data.profiles)" in body
assert "visible !== false" not in body