Files
hermes-webui/tests/test_issue4087_skill_bundles.py
T
t cec093eef6 test: fix chronic full-suite agent/hermes_cli __path__ isolation poison (2 flakes)
Convert in-place pkg.__path__=[] mutations on the REAL agent/hermes_cli
packages to monkeypatch.setattr (auto-restored), and make
test_issue1574's _activate_spawn_fake_agent snapshot+restore the
HERMES_WEBUI_AGENT_DIR/PYTHONPATH/sys.path it mutates.

Fixes:
- test_v050259_sessiondb_fd_leak::test_session_db_close_is_idempotent
- test_tls_support::test_tls_startup_failure_fallback_to_http
2026-07-06 21:08:52 +00:00

144 lines
5.4 KiB
Python

"""Regression coverage for issue #4087, WebUI skill bundle slash parity."""
from contextlib import contextmanager, nullcontext
from pathlib import Path
from types import ModuleType
import pytest
import api.commands as commands
REPO_ROOT = Path(__file__).resolve().parents[1]
COMMANDS_JS = (REPO_ROOT / "static" / "commands.js").read_text(encoding="utf-8")
MESSAGES_JS = (REPO_ROOT / "static" / "messages.js").read_text(encoding="utf-8")
ROUTES_PY = (REPO_ROOT / "api" / "routes.py").read_text(encoding="utf-8")
def _install_fake_skill_bundles(monkeypatch, *, bundles=None, resolver=None, builder=None):
import sys
agent_pkg = sys.modules.get("agent") or ModuleType("agent")
# `sys.modules.get(...)` returns the REAL agent package; emptying its
# __path__ in place strands it for the rest of the suite (later
# `from agent.<sub> import ...` — e.g. hermes_state's `agent.memory_manager`
# — then fails). monkeypatch.setattr snapshots and restores it on teardown.
monkeypatch.setattr(agent_pkg, "__path__", [], raising=False)
skill_bundles = ModuleType("agent.skill_bundles")
skill_bundles.list_bundles = lambda: list(bundles or [])
skill_bundles.resolve_bundle_command_key = resolver or (lambda name: None)
skill_bundles.build_bundle_invocation_message = builder or (lambda key, args: None)
monkeypatch.setitem(sys.modules, "agent", agent_pkg)
monkeypatch.setitem(sys.modules, "agent.skill_bundles", skill_bundles)
def test_bundle_routes_are_wired_through_dedicated_endpoints():
assert 'if parsed.path == "/api/commands/bundles":' in ROUTES_PY
assert 'if parsed.path == "/api/commands/bundles/resolve":' in ROUTES_PY
assert 'return j(handler, {"bundles": list_command_bundles()})' in ROUTES_PY
assert 'return j(handler, resolve_bundle_command(command))' in ROUTES_PY
def test_frontend_bundle_dispatch_uses_dedicated_metadata_and_resolve_calls():
assert "api('/api/commands/bundles')" in COMMANDS_JS
assert "api('/api/commands/bundles/resolve'" in COMMANDS_JS
assert "await loadAgentCommandMetadata();" in COMMANDS_JS
assert "const _bundleCmd=!_agentCmd&&typeof getBundleCommandMetadata==='function'" in MESSAGES_JS
assert "await resolveBundleCommand(text,_bundleCmd)" in MESSAGES_JS
def test_frontend_checks_agent_ownership_before_bundle_resolution():
agent_idx = MESSAGES_JS.find("await getAgentCommandMetadata(_parsedCmd.name)")
bundle_idx = MESSAGES_JS.find("await getBundleCommandMetadata(_parsedCmd.name)")
assert agent_idx != -1
assert bundle_idx != -1
assert agent_idx < bundle_idx
def test_list_command_bundles_returns_bundle_metadata(monkeypatch):
seen = {}
@contextmanager
def _profile_scope(purpose):
seen["purpose"] = purpose
yield
_install_fake_skill_bundles(
monkeypatch,
bundles=[
{
"slug": "incident-review",
"description": "Investigate incidents with the bundled workflow",
"skills": ["triage", "report"],
},
{
"slug": "",
"description": "ignored",
"skills": ["missing-slug"],
},
],
)
monkeypatch.setattr(commands, "_bundle_profile_context", _profile_scope)
assert commands.list_command_bundles() == [
{
"name": "incident-review",
"description": "Investigate incidents with the bundled workflow",
"skill_count": 2,
"source": "bundle",
}
]
assert seen == {"purpose": "/api/commands/bundles"}
def test_resolve_bundle_command_uses_bundle_runtime(monkeypatch):
seen = {}
@contextmanager
def _profile_scope(purpose):
seen["purpose"] = purpose
yield
def _resolve(name):
seen["resolve_name"] = name
return "/incident-review" if name == "incident-review" else None
def _build(key, args):
seen["build"] = (key, args)
return ("$incident review the primary alerts", ["triage", "report"], [])
_install_fake_skill_bundles(monkeypatch, resolver=_resolve, builder=_build)
monkeypatch.setattr(commands, "_bundle_profile_context", _profile_scope)
assert commands.resolve_bundle_command("/incident-review the primary alerts") == {
"name": "incident-review",
"source": "bundle",
"message": "$incident review the primary alerts",
"loaded_skills": ["triage", "report"],
"missing_skills": [],
}
assert seen == {
"purpose": "/api/commands/bundles/resolve",
"resolve_name": "incident-review",
"build": ("/incident-review", "the primary alerts"),
}
def test_resolve_bundle_command_raises_for_unknown_bundle(monkeypatch):
_install_fake_skill_bundles(monkeypatch)
monkeypatch.setattr(commands, "_bundle_profile_context", lambda purpose: nullcontext())
with pytest.raises(KeyError):
commands.resolve_bundle_command("/does-not-exist investigate this")
def test_resolve_bundle_command_wraps_unexpected_runtime_errors(monkeypatch):
def _explode(_name):
raise AttributeError("bundle runtime broke")
_install_fake_skill_bundles(monkeypatch, resolver=_explode)
monkeypatch.setattr(commands, "_bundle_profile_context", lambda purpose: nullcontext())
with pytest.raises(RuntimeError, match="Skill bundle command unavailable"):
commands.resolve_bundle_command("/incident-review investigate this")