121 lines
3.9 KiB
Python
121 lines
3.9 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import strix.skills as skills_mod
|
|
from strix.skills import (
|
|
get_all_skill_names,
|
|
get_available_skills,
|
|
load_skills,
|
|
register_skill_dir,
|
|
registered_skill_dirs,
|
|
skill_search_dirs,
|
|
validate_requested_skills,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_extra_dirs() -> None:
|
|
original = list(skills_mod._EXTRA_SKILL_DIRS)
|
|
skills_mod._EXTRA_SKILL_DIRS.clear()
|
|
try:
|
|
yield
|
|
finally:
|
|
skills_mod._EXTRA_SKILL_DIRS[:] = original
|
|
|
|
|
|
def _write_skill(root: Path, category: str, name: str, body: str) -> None:
|
|
category_dir = root / category
|
|
category_dir.mkdir(parents=True, exist_ok=True)
|
|
(category_dir / f"{name}.md").write_text(body, encoding="utf-8")
|
|
|
|
|
|
def _write_root_skill(root: Path, name: str, body: str) -> None:
|
|
root.mkdir(parents=True, exist_ok=True)
|
|
(root / f"{name}.md").write_text(body, encoding="utf-8")
|
|
|
|
|
|
def test_no_registration_leaves_builtin_only() -> None:
|
|
assert registered_skill_dirs() == ()
|
|
builtin = skills_mod.get_strix_resource_path("skills")
|
|
assert skill_search_dirs() == (builtin,)
|
|
assert {"nmap", "subfinder"}.issubset(get_available_skills()["tooling"])
|
|
|
|
|
|
def test_register_is_idempotent_and_ordered(tmp_path: Path) -> None:
|
|
a = tmp_path / "a"
|
|
b = tmp_path / "b"
|
|
a.mkdir()
|
|
b.mkdir()
|
|
|
|
register_skill_dir(a)
|
|
register_skill_dir(b)
|
|
register_skill_dir(a)
|
|
|
|
# Most recently registered wins → highest precedence first.
|
|
assert registered_skill_dirs() == (b, a)
|
|
|
|
|
|
def test_registered_dir_adds_new_skill(tmp_path: Path) -> None:
|
|
_write_skill(tmp_path, "extra", "widget", "widget body")
|
|
register_skill_dir(tmp_path)
|
|
|
|
assert "widget" in get_all_skill_names()
|
|
assert get_available_skills()["extra"] == ["widget"]
|
|
assert load_skills(["widget"]) == {"widget": "widget body"}
|
|
|
|
|
|
def test_registered_root_skill_is_discoverable_and_valid(tmp_path: Path) -> None:
|
|
_write_root_skill(tmp_path, "widget", "widget body")
|
|
register_skill_dir(tmp_path)
|
|
|
|
assert "widget" in get_all_skill_names()
|
|
assert get_available_skills()["root"] == ["widget"]
|
|
assert validate_requested_skills(["widget"]) is None
|
|
assert validate_requested_skills(["root/widget"]) is None
|
|
assert load_skills(["widget"]) == {"widget": "widget body"}
|
|
assert load_skills(["root/widget"]) == {"widget": "widget body"}
|
|
|
|
|
|
def test_ambiguous_bare_skill_requires_qualified_name(tmp_path: Path) -> None:
|
|
_write_skill(tmp_path, "alpha", "widget", "alpha body")
|
|
_write_skill(tmp_path, "beta", "widget", "beta body")
|
|
register_skill_dir(tmp_path)
|
|
|
|
assert "widget" in get_all_skill_names()
|
|
assert get_available_skills()["alpha"] == ["widget"]
|
|
assert get_available_skills()["beta"] == ["widget"]
|
|
assert validate_requested_skills(["alpha/widget"]) is None
|
|
assert validate_requested_skills(["beta/widget"]) is None
|
|
|
|
error = validate_requested_skills(["widget"])
|
|
assert error is not None
|
|
assert "Ambiguous skill name" in error
|
|
assert "alpha/widget" in error
|
|
assert "beta/widget" in error
|
|
|
|
assert load_skills(["widget"]) == {}
|
|
assert load_skills(["alpha/widget"]) == {"widget": "alpha body"}
|
|
assert load_skills(["beta/widget"]) == {"widget": "beta body"}
|
|
|
|
|
|
def test_registered_dir_overrides_builtin_skill(tmp_path: Path) -> None:
|
|
_write_skill(tmp_path, "coordination", "root_agent", "overridden root agent")
|
|
register_skill_dir(tmp_path)
|
|
|
|
loaded = load_skills(["coordination/root_agent"])
|
|
assert loaded["root_agent"] == "overridden root agent"
|
|
|
|
|
|
def test_builtin_skill_still_loads_when_not_overridden(tmp_path: Path) -> None:
|
|
_write_skill(tmp_path, "extra", "widget", "widget body")
|
|
register_skill_dir(tmp_path)
|
|
|
|
# A packaged skill the registered dir does not shadow still resolves.
|
|
assert load_skills(["scan_modes/deep"]).get("deep")
|
|
|
|
|
|
def test_missing_skill_is_skipped(tmp_path: Path) -> None:
|
|
register_skill_dir(tmp_path)
|
|
assert load_skills(["does_not_exist"]) == {}
|