Files
CLI-Anything/skill_generation/tests/test_skill_path.py
yuhao 55d11a5df6 test: add skill path discovery tests for installed packages
51 tests covering:
- ReplSkin auto-detection of skills/SKILL.md from __file__ location
- Absolute path resolution, missing file handling, explicit override
- Banner output includes/omits skill path as expected
- All 11 harnesses have SKILL.md in correct package location
- All 11 setup.py files include package_data for skills/*.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 09:31:08 +00:00

190 lines
6.7 KiB
Python

"""Tests that SKILL.md is discoverable after pip install.
Simulates the installed package layout and verifies:
1. ReplSkin auto-detects the skill file from its __file__ location
2. The banner output includes the absolute skill path
3. Missing skill file results in skill_path=None
"""
import os
import sys
import shutil
import tempfile
import textwrap
from pathlib import Path
from io import StringIO
import pytest
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _build_package_tree(root: Path, software: str = "demo") -> Path:
"""Create a minimal cli_anything/<software>/ layout with repl_skin + SKILL.md.
Returns the path to the utils/ directory (where repl_skin.py lives).
"""
pkg = root / "cli_anything" / software
utils = pkg / "utils"
skills = pkg / "skills"
utils.mkdir(parents=True)
skills.mkdir(parents=True)
# Copy the canonical repl_skin.py from the plugin
src = Path(__file__).resolve().parent.parent.parent / "cli-anything-plugin" / "repl_skin.py"
shutil.copy(src, utils / "repl_skin.py")
# Write a minimal SKILL.md
(skills / "SKILL.md").write_text(textwrap.dedent("""\
---
name: "cli-anything-demo"
description: "Demo skill"
---
# cli-anything-demo
"""))
return utils
def _load_repl_skin(utils_dir: Path):
"""Import ReplSkin from the given utils directory (simulating installed path)."""
import importlib.util
spec = importlib.util.spec_from_file_location(
"repl_skin", utils_dir / "repl_skin.py"
)
mod = importlib.util.module_from_spec(spec)
# Set __file__ so auto-detection resolves relative to this location
mod.__file__ = str(utils_dir / "repl_skin.py")
spec.loader.exec_module(mod)
return mod.ReplSkin
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
class TestSkillPathAutoDetect:
"""ReplSkin should auto-detect skills/SKILL.md relative to its own location."""
def test_auto_detects_skill_path(self, tmp_path):
utils = _build_package_tree(tmp_path)
ReplSkin = _load_repl_skin(utils)
skin = ReplSkin("demo", version="1.0.0")
expected = str(tmp_path / "cli_anything" / "demo" / "skills" / "SKILL.md")
assert skin.skill_path == expected
def test_skill_path_is_absolute(self, tmp_path):
utils = _build_package_tree(tmp_path)
ReplSkin = _load_repl_skin(utils)
skin = ReplSkin("demo", version="1.0.0")
assert os.path.isabs(skin.skill_path)
def test_skill_file_exists_at_detected_path(self, tmp_path):
utils = _build_package_tree(tmp_path)
ReplSkin = _load_repl_skin(utils)
skin = ReplSkin("demo", version="1.0.0")
assert Path(skin.skill_path).is_file()
def test_none_when_skill_missing(self, tmp_path):
utils = _build_package_tree(tmp_path)
# Remove the SKILL.md
(tmp_path / "cli_anything" / "demo" / "skills" / "SKILL.md").unlink()
ReplSkin = _load_repl_skin(utils)
skin = ReplSkin("demo", version="1.0.0")
assert skin.skill_path is None
def test_explicit_skill_path_overrides_auto(self, tmp_path):
utils = _build_package_tree(tmp_path)
ReplSkin = _load_repl_skin(utils)
skin = ReplSkin("demo", version="1.0.0", skill_path="/custom/SKILL.md")
assert skin.skill_path == "/custom/SKILL.md"
class TestSkillPathInBanner:
"""The REPL banner should display the skill path when present."""
def test_banner_shows_skill_path(self, tmp_path, capsys):
utils = _build_package_tree(tmp_path)
ReplSkin = _load_repl_skin(utils)
skin = ReplSkin("demo", version="1.0.0")
skin.print_banner()
output = capsys.readouterr().out
assert "Skill:" in output
assert "SKILL.md" in output
def test_banner_omits_skill_when_missing(self, tmp_path, capsys):
utils = _build_package_tree(tmp_path)
(tmp_path / "cli_anything" / "demo" / "skills" / "SKILL.md").unlink()
ReplSkin = _load_repl_skin(utils)
skin = ReplSkin("demo", version="1.0.0")
skin.print_banner()
output = capsys.readouterr().out
assert "Skill:" not in output
class TestInstalledHarnesses:
"""Verify each real harness has SKILL.md in the correct package location."""
HARNESSES = [
("gimp", "gimp"),
("blender", "blender"),
("inkscape", "inkscape"),
("audacity", "audacity"),
("libreoffice", "libreoffice"),
("obs-studio", "obs_studio"),
("kdenlive", "kdenlive"),
("shotcut", "shotcut"),
("zoom", "zoom"),
("drawio", "drawio"),
("anygen", "anygen"),
]
@pytest.mark.parametrize("dir_name,pkg_name", HARNESSES)
def test_skill_md_exists_in_package(self, dir_name, pkg_name):
repo_root = Path(__file__).resolve().parent.parent.parent
skill_path = repo_root / dir_name / "agent-harness" / "cli_anything" / pkg_name / "skills" / "SKILL.md"
assert skill_path.is_file(), f"Missing: {skill_path}"
@pytest.mark.parametrize("dir_name,pkg_name", HARNESSES)
def test_skill_md_has_yaml_frontmatter(self, dir_name, pkg_name):
repo_root = Path(__file__).resolve().parent.parent.parent
skill_path = repo_root / dir_name / "agent-harness" / "cli_anything" / pkg_name / "skills" / "SKILL.md"
content = skill_path.read_text()
assert content.startswith("---"), f"Missing YAML frontmatter in {skill_path}"
# Must have closing ---
assert content.count("---") >= 2
@pytest.mark.parametrize("dir_name,pkg_name", HARNESSES)
def test_skill_md_has_command_groups(self, dir_name, pkg_name):
repo_root = Path(__file__).resolve().parent.parent.parent
skill_path = repo_root / dir_name / "agent-harness" / "cli_anything" / pkg_name / "skills" / "SKILL.md"
content = skill_path.read_text()
assert "## Command Groups" in content
# Must have at least one filled command row
assert "| `" in content, f"Empty command tables in {skill_path}"
@pytest.mark.parametrize("dir_name,pkg_name", HARNESSES)
def test_setup_py_includes_package_data(self, dir_name, pkg_name):
repo_root = Path(__file__).resolve().parent.parent.parent
setup_path = repo_root / dir_name / "agent-harness" / "setup.py"
content = setup_path.read_text()
assert "package_data" in content, f"Missing package_data in {setup_path}"
assert "skills/*.md" in content, f"Missing skills/*.md in package_data: {setup_path}"