Files
opensquilla--opensquilla/tests/test_skills_loader_namespaces.py
2026-07-13 13:12:33 +08:00

219 lines
5.9 KiB
Python

"""Loader namespace fallback + ClawHub field-alias contract tests."""
from __future__ import annotations
from pathlib import Path
from opensquilla.skills.loader import SkillLoader
ROOT = Path(__file__).resolve().parents[1]
BUNDLED = ROOT / "src" / "opensquilla" / "skills" / "bundled"
def _write_skill(dir_path: Path, name: str, body: str) -> None:
skill_dir = dir_path / name
skill_dir.mkdir(parents=True, exist_ok=True)
(skill_dir / "SKILL.md").write_text(body, encoding="utf-8")
def test_clawdbot_namespace_resolves(tmp_path: Path) -> None:
"""`metadata.clawdbot.requires.bins` must populate SkillSpec.metadata.requires.bins."""
_write_skill(
tmp_path,
"clawdbot-skill",
"""---
name: clawdbot-skill
description: Synthetic skill exercising the clawdbot namespace fallback.
metadata:
clawdbot:
requires:
bins: [foo]
---
# body
""",
)
loader = SkillLoader(bundled_dir=tmp_path)
spec = loader.get_by_name("clawdbot-skill")
assert spec is not None
assert spec.metadata is not None
assert spec.metadata.requires is not None
assert spec.metadata.requires.bins == ["foo"]
def test_commands_alias_for_bins(tmp_path: Path) -> None:
"""`requires.commands` should map onto `requires.bins` when bins is absent."""
_write_skill(
tmp_path,
"commands-alias",
"""---
name: commands-alias
description: Synthetic skill exercising the requires.commands alias.
metadata:
platform:
requires:
commands: [bar]
---
# body
""",
)
loader = SkillLoader(bundled_dir=tmp_path)
spec = loader.get_by_name("commands-alias")
assert spec is not None
assert spec.metadata is not None
assert spec.metadata.requires is not None
assert spec.metadata.requires.bins == ["bar"]
def test_explicit_bins_wins_over_commands(tmp_path: Path) -> None:
"""When both `bins` and `commands` are present, `bins` is authoritative."""
_write_skill(
tmp_path,
"bins-wins",
"""---
name: bins-wins
description: bins wins over commands when both present.
metadata:
platform:
requires:
bins: [keep]
commands: [drop]
---
# body
""",
)
loader = SkillLoader(bundled_dir=tmp_path)
spec = loader.get_by_name("bins-wins")
assert spec is not None
assert spec.metadata is not None
assert spec.metadata.requires is not None
assert spec.metadata.requires.bins == ["keep"]
def test_opensquilla_capabilities_and_risk_resolve(tmp_path: Path) -> None:
"""Auto-enable risk evaluation reads manifest capabilities from metadata."""
_write_skill(
tmp_path,
"capability-risk",
"""---
name: capability-risk
description: Synthetic skill declaring auto-enable risk metadata.
metadata:
opensquilla:
capabilities: [filesystem-write, network]
risk: medium
---
# body
""",
)
loader = SkillLoader(bundled_dir=tmp_path, snapshot_path=tmp_path / "snap.json")
spec = loader.get_by_name("capability-risk")
assert spec is not None
assert spec.metadata is not None
assert spec.metadata.capabilities == ["filesystem-write", "network"]
assert spec.metadata.risk_level == "medium"
def test_opensquilla_risk_metadata_preserves_platform_requires(
tmp_path: Path,
) -> None:
_write_skill(
tmp_path,
"capability-risk-with-requires",
"""---
name: capability-risk-with-requires
description: Synthetic skill declaring platform deps and risk metadata.
metadata:
requires:
anyBins: [python]
opensquilla:
risk: low
capabilities: []
---
# body
""",
)
loader = SkillLoader(bundled_dir=tmp_path, snapshot_path=tmp_path / "snap.json")
spec = loader.get_by_name("capability-risk-with-requires")
assert spec is not None
assert spec.metadata is not None
assert spec.metadata.requires is not None
assert spec.metadata.requires.any_bins == ["python"]
assert spec.metadata.risk_level == "low"
assert spec.metadata.capabilities == []
def test_env_any_requires_parse(tmp_path: Path) -> None:
_write_skill(
tmp_path,
"env-any-parse",
"""---
name: env-any-parse
description: Synthetic skill exercising requires.envAny parsing.
metadata:
opensquilla:
requires:
envAny: [OPENROUTER_API_KEY, ARK_API_KEY]
---
# body
""",
)
loader = SkillLoader(bundled_dir=tmp_path)
spec = loader.get_by_name("env-any-parse")
assert spec is not None
assert spec.metadata is not None
assert spec.metadata.requires is not None
assert spec.metadata.requires.env_any == ["OPENROUTER_API_KEY", "ARK_API_KEY"]
def test_env_any_snapshot_roundtrip(tmp_path: Path) -> None:
_write_skill(
tmp_path,
"env-any-snapshot",
"""---
name: env-any-snapshot
description: Synthetic skill exercising requires.envAny snapshot restore.
metadata:
opensquilla:
requires:
envAny: [BRAVE_SEARCH_API_KEY, BRAVE_API_KEY]
---
# body
""",
)
loader = SkillLoader(bundled_dir=tmp_path, snapshot_path=tmp_path / "snapshot.json")
loader.load_all()
loader.save_snapshot()
restored = loader.load_snapshot()
assert restored is not None
spec = next(item for item in restored if item.name == "env-any-snapshot")
assert spec.metadata is not None
assert spec.metadata.requires is not None
assert spec.metadata.requires.env_any == [
"BRAVE_SEARCH_API_KEY",
"BRAVE_API_KEY",
]
def test_existing_bundled_skills_still_parse() -> None:
"""Regression guard: every bundled SKILL.md must still parse after the patch."""
loader = SkillLoader(bundled_dir=BUNDLED)
skills = loader.load_all()
parsed_names = {spec.name for spec in skills}
on_disk = {
path.name
for path in BUNDLED.iterdir()
if path.is_dir() and (path / "SKILL.md").is_file()
}
assert on_disk.issubset(parsed_names), (
f"loader dropped bundled skill(s): {on_disk - parsed_names}"
)