Files
hermes-webui/tests/test_issue2023_hermes_home_skill_modules.py
48b40d930a Release: isolate Hermes home per streaming turn (#5877, @starship-s) (#6379)
* fix(profiles): isolate Hermes home per streaming turn

Assisted-by: OpenCode:gpt-5.3-codex-spark
Assisted-by: Hermes Agent:gpt-5.6-sol
Assisted-by: Codex:gpt-5.3-codex-spark

* fix(profiles): gate skill isolation by capability

Assisted-by: OpenCode:gpt-5.3-codex-spark
Assisted-by: Hermes Agent:gpt-5.6-sol
Assisted-by: Codex:gpt-5.3-codex-spark

* fix(profiles): harden fallback lock boundaries

Assisted-by: OpenCode:gpt-5.3-codex-spark
Assisted-by: Hermes Agent:gpt-5.6-sol

* test(profiles): adapt streaming isolation harness

Assisted-by: Codex:gpt-5.3-codex-spark
Assisted-by: Hermes Agent:gpt-5.6-sol

* Release: isolate Hermes home per streaming turn (#5877, @starship-s)

---------

Co-authored-by: starship-s <45587122+starship-s@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-21 03:48:00 +00:00

151 lines
5.9 KiB
Python

"""Regression coverage for issue #2023.
Process-wide profile switches must keep both skill tool modules pointed at the
active profile home. The modules live in hermes-agent and may not be importable
in this test environment, so the test injects lightweight stand-ins into
``sys.modules``.
"""
import sys
import types
def _skill_module(name, home):
module = types.ModuleType(name)
module.HERMES_HOME = home
module.SKILLS_DIR = home / "skills"
return module
def test_set_hermes_home_patches_both_skill_tool_module_caches(monkeypatch, tmp_path):
from api.profiles import _set_hermes_home
old_home = tmp_path / "old-home"
new_home = tmp_path / "new-home"
skills_tool = _skill_module("tools.skills_tool", old_home)
skill_manager_tool = _skill_module("tools.skill_manager_tool", old_home)
monkeypatch.setitem(sys.modules, "tools.skills_tool", skills_tool)
monkeypatch.setitem(sys.modules, "tools.skill_manager_tool", skill_manager_tool)
_set_hermes_home(new_home)
assert skills_tool.HERMES_HOME == new_home
assert skills_tool.SKILLS_DIR == new_home / "skills"
assert skill_manager_tool.HERMES_HOME == new_home
assert skill_manager_tool.SKILLS_DIR == new_home / "skills"
def test_skill_modules_support_profile_home_returns_true_for_dynamic_modules(monkeypatch, tmp_path):
from api.profiles import _skill_modules_support_profile_home
profile_home = tmp_path / "profile"
expected = profile_home / "skills"
baseline = tmp_path / "base" / "skills"
skills_tool = types.ModuleType("tools.skills_tool")
skills_tool.HERMES_HOME = profile_home.parent
skills_tool.SKILLS_DIR = baseline
skills_tool._SKILLS_DIR_AT_IMPORT = baseline
skills_tool._skills_dir = lambda: expected
skill_manager_tool = types.ModuleType("tools.skill_manager_tool")
skill_manager_tool.HERMES_HOME = profile_home.parent
skill_manager_tool.SKILLS_DIR = baseline
skill_manager_tool._SKILLS_DIR_AT_IMPORT = baseline
skill_manager_tool._skills_dir = lambda: expected
monkeypatch.setitem(sys.modules, "tools.skills_tool", skills_tool)
monkeypatch.setitem(sys.modules, "tools.skill_manager_tool", skill_manager_tool)
assert _skill_modules_support_profile_home(profile_home) is True
def test_skill_modules_support_profile_home_returns_false_when_already_globally_patched(monkeypatch, tmp_path):
from api.profiles import _skill_modules_support_profile_home
profile_home = tmp_path / "profile"
expected = profile_home / "skills"
baseline = tmp_path / "base" / "skills"
patched = tmp_path / "alpha" / "skills"
skills_tool = types.ModuleType("tools.skills_tool")
skills_tool.HERMES_HOME = profile_home.parent
skills_tool.SKILLS_DIR = patched
skills_tool._SKILLS_DIR_AT_IMPORT = baseline
skills_tool._skills_dir = lambda: expected
skill_manager_tool = types.ModuleType("tools.skill_manager_tool")
skill_manager_tool.HERMES_HOME = profile_home.parent
skill_manager_tool.SKILLS_DIR = baseline
skill_manager_tool._SKILLS_DIR_AT_IMPORT = baseline
skill_manager_tool._skills_dir = lambda: expected
monkeypatch.setitem(sys.modules, "tools.skills_tool", skills_tool)
monkeypatch.setitem(sys.modules, "tools.skill_manager_tool", skill_manager_tool)
assert _skill_modules_support_profile_home(profile_home) is False
def test_skill_modules_support_profile_home_returns_false_when_module_is_static(monkeypatch, tmp_path):
from api.profiles import _skill_modules_support_profile_home
profile_home = tmp_path / "profile"
expected = profile_home / "skills"
baseline = tmp_path / "base" / "skills"
skills_tool = types.ModuleType("tools.skills_tool")
skills_tool.SKILLS_DIR = baseline
skills_tool._SKILLS_DIR_AT_IMPORT = baseline
skills_tool._skills_dir = expected
skill_manager_tool = types.ModuleType("tools.skill_manager_tool")
skill_manager_tool.SKILLS_DIR = baseline
skill_manager_tool._SKILLS_DIR_AT_IMPORT = baseline
skill_manager_tool._skills_dir = lambda: expected
monkeypatch.setitem(sys.modules, "tools.skills_tool", skills_tool)
monkeypatch.setitem(sys.modules, "tools.skill_manager_tool", skill_manager_tool)
assert _skill_modules_support_profile_home(profile_home) is False
def test_skill_modules_support_profile_home_returns_false_when_resolver_raises(monkeypatch, tmp_path):
from api.profiles import _skill_modules_support_profile_home
profile_home = tmp_path / "profile"
expected = profile_home / "skills"
baseline = tmp_path / "base" / "skills"
def _raise():
raise RuntimeError("not callable")
skills_tool = types.ModuleType("tools.skills_tool")
skills_tool.SKILLS_DIR = baseline
skills_tool._SKILLS_DIR_AT_IMPORT = baseline
skills_tool._skills_dir = _raise
skill_manager_tool = types.ModuleType("tools.skill_manager_tool")
skill_manager_tool.SKILLS_DIR = baseline
skill_manager_tool._SKILLS_DIR_AT_IMPORT = baseline
skill_manager_tool._skills_dir = lambda: expected
monkeypatch.setitem(sys.modules, "tools.skills_tool", skills_tool)
monkeypatch.setitem(sys.modules, "tools.skill_manager_tool", skill_manager_tool)
assert _skill_modules_support_profile_home(profile_home) is False
def test_skill_modules_support_profile_home_returns_false_when_module_missing(monkeypatch, tmp_path):
from api.profiles import _skill_modules_support_profile_home
profile_home = tmp_path / "profile"
expected = profile_home / "skills"
baseline = tmp_path / "base" / "skills"
skill_manager_tool = types.ModuleType("tools.skill_manager_tool")
skill_manager_tool.SKILLS_DIR = baseline
skill_manager_tool._SKILLS_DIR_AT_IMPORT = baseline
skill_manager_tool._skills_dir = lambda: expected
monkeypatch.delitem(sys.modules, "tools.skills_tool", raising=False)
monkeypatch.setitem(sys.modules, "tools.skill_manager_tool", skill_manager_tool)
assert _skill_modules_support_profile_home(profile_home) is False