| from io import StringIO |
|
|
| import pytest |
| from rich.console import Console |
|
|
| from hermes_cli.skills_hub import do_check, do_list, do_update, handle_skills_slash |
|
|
|
|
| class _DummyLockFile: |
| def __init__(self, installed): |
| self._installed = installed |
|
|
| def list_installed(self): |
| return self._installed |
|
|
|
|
| @pytest.fixture() |
| def hub_env(monkeypatch, tmp_path): |
| """Set up isolated hub directory paths and return (monkeypatch, tmp_path).""" |
| import tools.skills_hub as hub |
|
|
| hub_dir = tmp_path / "skills" / ".hub" |
| monkeypatch.setattr(hub, "SKILLS_DIR", tmp_path / "skills") |
| monkeypatch.setattr(hub, "HUB_DIR", hub_dir) |
| monkeypatch.setattr(hub, "LOCK_FILE", hub_dir / "lock.json") |
| monkeypatch.setattr(hub, "QUARANTINE_DIR", hub_dir / "quarantine") |
| monkeypatch.setattr(hub, "AUDIT_LOG", hub_dir / "audit.log") |
| monkeypatch.setattr(hub, "TAPS_FILE", hub_dir / "taps.json") |
| monkeypatch.setattr(hub, "INDEX_CACHE_DIR", hub_dir / "index-cache") |
|
|
| return hub_dir |
|
|
|
|
| |
| |
| |
|
|
| _HUB_ENTRY = {"name": "hub-skill", "source": "github", "trust_level": "community"} |
|
|
| _ALL_THREE_SKILLS = [ |
| {"name": "hub-skill", "category": "x", "description": "hub"}, |
| {"name": "builtin-skill", "category": "x", "description": "builtin"}, |
| {"name": "local-skill", "category": "x", "description": "local"}, |
| ] |
|
|
| _BUILTIN_MANIFEST = {"builtin-skill": "abc123"} |
|
|
|
|
| @pytest.fixture() |
| def three_source_env(monkeypatch, hub_env): |
| """Populate hub/builtin/local skills for source-classification tests.""" |
| import tools.skills_hub as hub |
| import tools.skills_sync as skills_sync |
| import tools.skills_tool as skills_tool |
|
|
| monkeypatch.setattr(hub, "HubLockFile", lambda: _DummyLockFile([_HUB_ENTRY])) |
| monkeypatch.setattr(skills_tool, "_find_all_skills", lambda: list(_ALL_THREE_SKILLS)) |
| monkeypatch.setattr(skills_sync, "_read_manifest", lambda: dict(_BUILTIN_MANIFEST)) |
|
|
| return hub_env |
|
|
|
|
| def _capture(source_filter: str = "all") -> str: |
| """Run do_list into a string buffer and return the output.""" |
| sink = StringIO() |
| console = Console(file=sink, force_terminal=False, color_system=None) |
| do_list(source_filter=source_filter, console=console) |
| return sink.getvalue() |
|
|
|
|
| def _capture_check(monkeypatch, results, name=None) -> str: |
| import tools.skills_hub as hub |
|
|
| sink = StringIO() |
| console = Console(file=sink, force_terminal=False, color_system=None) |
| monkeypatch.setattr(hub, "check_for_skill_updates", lambda **_kwargs: results) |
| do_check(name=name, console=console) |
| return sink.getvalue() |
|
|
|
|
| def _capture_update(monkeypatch, results) -> tuple[str, list[tuple[str, str, bool]]]: |
| import tools.skills_hub as hub |
| import hermes_cli.skills_hub as cli_hub |
|
|
| sink = StringIO() |
| console = Console(file=sink, force_terminal=False, color_system=None) |
| installs = [] |
|
|
| monkeypatch.setattr(hub, "check_for_skill_updates", lambda **_kwargs: results) |
| monkeypatch.setattr(hub, "HubLockFile", lambda: type("L", (), { |
| "get_installed": lambda self, name: {"install_path": "category/" + name} |
| })()) |
| monkeypatch.setattr(cli_hub, "do_install", lambda identifier, category="", force=False, console=None: installs.append((identifier, category, force))) |
|
|
| do_update(console=console) |
| return sink.getvalue(), installs |
|
|
|
|
| |
| |
| |
|
|
|
|
| def test_do_list_initializes_hub_dir(monkeypatch, hub_env): |
| import tools.skills_sync as skills_sync |
| import tools.skills_tool as skills_tool |
|
|
| monkeypatch.setattr(skills_tool, "_find_all_skills", lambda: []) |
| monkeypatch.setattr(skills_sync, "_read_manifest", lambda: {}) |
|
|
| hub_dir = hub_env |
| assert not hub_dir.exists() |
|
|
| _capture() |
|
|
| assert hub_dir.exists() |
| assert (hub_dir / "lock.json").exists() |
| assert (hub_dir / "quarantine").is_dir() |
| assert (hub_dir / "index-cache").is_dir() |
|
|
|
|
| def test_do_list_distinguishes_hub_builtin_and_local(three_source_env): |
| output = _capture() |
|
|
| assert "hub-skill" in output |
| assert "builtin-skill" in output |
| assert "local-skill" in output |
| assert "1 hub-installed, 1 builtin, 1 local" in output |
|
|
|
|
| def test_do_list_filter_local(three_source_env): |
| output = _capture(source_filter="local") |
|
|
| assert "local-skill" in output |
| assert "builtin-skill" not in output |
| assert "hub-skill" not in output |
|
|
|
|
| def test_do_list_filter_hub(three_source_env): |
| output = _capture(source_filter="hub") |
|
|
| assert "hub-skill" in output |
| assert "builtin-skill" not in output |
| assert "local-skill" not in output |
|
|
|
|
| def test_do_list_filter_builtin(three_source_env): |
| output = _capture(source_filter="builtin") |
|
|
| assert "builtin-skill" in output |
| assert "hub-skill" not in output |
| assert "local-skill" not in output |
|
|
|
|
| def test_do_check_reports_available_updates(monkeypatch): |
| output = _capture_check(monkeypatch, [ |
| {"name": "hub-skill", "source": "skills.sh", "status": "update_available"}, |
| {"name": "other-skill", "source": "github", "status": "up_to_date"}, |
| ]) |
|
|
| assert "hub-skill" in output |
| assert "update_available" in output |
| assert "up_to_date" in output |
|
|
|
|
| def test_do_check_handles_no_installed_updates(monkeypatch): |
| output = _capture_check(monkeypatch, []) |
|
|
| assert "No hub-installed skills to check" in output |
|
|
|
|
| def test_do_update_reinstalls_outdated_skills(monkeypatch): |
| output, installs = _capture_update(monkeypatch, [ |
| {"name": "hub-skill", "identifier": "skills-sh/example/repo/hub-skill", "status": "update_available"}, |
| {"name": "other-skill", "identifier": "github/example/other-skill", "status": "up_to_date"}, |
| ]) |
|
|
| assert installs == [("skills-sh/example/repo/hub-skill", "category", True)] |
| assert "Updated 1 skill" in output |
|
|