Files
skill-seekers-reference/tests/test_install_agent.py
yusyus 9d43956b1d style: Run black formatter on 16 files
Applied black formatting to files modified in linting fixes:

Source files (8):
- config_extractor.py
- doc_scraper.py
- how_to_guide_builder.py
- llms_txt_downloader.py
- llms_txt_parser.py
- pattern_recognizer.py
- test_example_extractor.py
- unified_codebase_analyzer.py

Test files (8):
- test_architecture_scenarios.py
- test_async_scraping.py
- test_github_scraper.py
- test_guide_enhancer.py
- test_install_agent.py
- test_issue_219_e2e.py
- test_llms_txt_downloader.py
- test_skip_llms_txt.py

All formatting issues resolved.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 23:56:24 +03:00

559 lines
20 KiB
Python

"""
Tests for install_agent CLI tool.
Tests cover:
- Agent path mapping and resolution
- Agent name validation with fuzzy matching
- Skill directory validation
- Installation to single agent
- Installation to all agents
- CLI interface
"""
import shutil
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
# Add src to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from skill_seekers.cli.install_agent import (
get_agent_path,
get_available_agents,
install_to_agent,
install_to_all_agents,
main,
validate_agent_name,
validate_skill_directory,
)
class TestAgentPathMapping:
"""Test agent path resolution and mapping."""
def test_get_agent_path_home_expansion(self):
"""Test that ~ expands to home directory for global agents."""
# Test claude (global agent with ~)
path = get_agent_path("claude")
assert path.is_absolute()
assert ".claude" in str(path)
assert str(path).startswith(str(Path.home()))
def test_get_agent_path_project_relative(self):
"""Test that project-relative paths use current directory."""
# Test cursor (project-relative agent)
path = get_agent_path("cursor")
assert path.is_absolute()
assert ".cursor" in str(path)
# Should be relative to current directory
assert str(Path.cwd()) in str(path)
def test_get_agent_path_project_relative_with_custom_root(self):
"""Test project-relative paths with custom project root."""
custom_root = Path("/tmp/test-project")
path = get_agent_path("cursor", project_root=custom_root)
assert path.is_absolute()
assert str(custom_root) in str(path)
assert ".cursor" in str(path)
def test_get_agent_path_invalid_agent(self):
"""Test that invalid agent raises ValueError."""
with pytest.raises(ValueError, match="Unknown agent"):
get_agent_path("invalid_agent")
def test_get_available_agents(self):
"""Test that all 11 agents are listed."""
agents = get_available_agents()
assert len(agents) == 11
assert "claude" in agents
assert "cursor" in agents
assert "vscode" in agents
assert "amp" in agents
assert "goose" in agents
assert "neovate" in agents
assert sorted(agents) == agents # Should be sorted
def test_agent_path_case_insensitive(self):
"""Test that agent names are case-insensitive."""
path_lower = get_agent_path("claude")
path_upper = get_agent_path("CLAUDE")
path_mixed = get_agent_path("Claude")
assert path_lower == path_upper == path_mixed
class TestAgentNameValidation:
"""Test agent name validation and fuzzy matching."""
def test_validate_valid_agent(self):
"""Test that valid agent names pass validation."""
is_valid, error = validate_agent_name("claude")
assert is_valid is True
assert error is None
def test_validate_invalid_agent_suggests_similar(self):
"""Test that similar agent names are suggested for typos."""
is_valid, error = validate_agent_name("courser")
assert is_valid is False
assert "cursor" in error.lower() # Should suggest 'cursor'
def test_validate_special_all(self):
"""Test that 'all' is a valid special agent name."""
is_valid, error = validate_agent_name("all")
assert is_valid is True
assert error is None
def test_validate_case_insensitive(self):
"""Test that validation is case-insensitive."""
for name in ["Claude", "CLAUDE", "claude", "cLaUdE"]:
is_valid, error = validate_agent_name(name)
assert is_valid is True
assert error is None
def test_validate_shows_available_agents(self):
"""Test that error message shows available agents."""
is_valid, error = validate_agent_name("invalid")
assert is_valid is False
assert "available agents" in error.lower()
assert "claude" in error.lower()
assert "cursor" in error.lower()
class TestSkillDirectoryValidation:
"""Test skill directory validation."""
def test_validate_valid_skill_directory(self):
"""Test that valid skill directory passes validation."""
with tempfile.TemporaryDirectory() as tmpdir:
skill_dir = Path(tmpdir) / "test-skill"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text("# Test Skill")
is_valid, error = validate_skill_directory(skill_dir)
assert is_valid is True
assert error is None
def test_validate_missing_directory(self):
"""Test that missing directory fails validation."""
skill_dir = Path("/nonexistent/directory")
is_valid, error = validate_skill_directory(skill_dir)
assert is_valid is False
assert "does not exist" in error
def test_validate_not_a_directory(self):
"""Test that file (not directory) fails validation."""
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
try:
is_valid, error = validate_skill_directory(Path(tmpfile.name))
assert is_valid is False
assert "not a directory" in error
finally:
Path(tmpfile.name).unlink()
def test_validate_missing_skill_md(self):
"""Test that directory without SKILL.md fails validation."""
with tempfile.TemporaryDirectory() as tmpdir:
skill_dir = Path(tmpdir) / "test-skill"
skill_dir.mkdir()
is_valid, error = validate_skill_directory(skill_dir)
assert is_valid is False
assert "SKILL.md not found" in error
class TestInstallToAgent:
"""Test installation to single agent."""
def setup_method(self):
"""Create test skill directory before each test."""
self.tmpdir = tempfile.mkdtemp()
self.skill_dir = Path(self.tmpdir) / "test-skill"
self.skill_dir.mkdir()
# Create SKILL.md
(self.skill_dir / "SKILL.md").write_text(
"# Test Skill\n\nThis is a test skill."
)
# Create references directory with files
refs_dir = self.skill_dir / "references"
refs_dir.mkdir()
(refs_dir / "index.md").write_text("# Index")
(refs_dir / "getting_started.md").write_text("# Getting Started")
# Create empty directories
(self.skill_dir / "scripts").mkdir()
(self.skill_dir / "assets").mkdir()
def teardown_method(self):
"""Clean up after each test."""
shutil.rmtree(self.tmpdir, ignore_errors=True)
def test_install_creates_skill_subdirectory(self):
"""Test that installation creates {agent_path}/{skill_name}/ directory."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
agent_path = Path(agent_tmpdir) / ".claude" / "skills"
with patch(
"skill_seekers.cli.install_agent.get_agent_path",
return_value=agent_path,
):
success, message = install_to_agent(
self.skill_dir, "claude", force=True
)
assert success is True
target_path = agent_path / "test-skill"
assert target_path.exists()
assert target_path.is_dir()
def test_install_preserves_structure(self):
"""Test that installation preserves SKILL.md, references/, scripts/, assets/."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
agent_path = Path(agent_tmpdir) / ".claude" / "skills"
with patch(
"skill_seekers.cli.install_agent.get_agent_path",
return_value=agent_path,
):
success, message = install_to_agent(
self.skill_dir, "claude", force=True
)
assert success is True
target_path = agent_path / "test-skill"
# Check structure
assert (target_path / "SKILL.md").exists()
assert (target_path / "references").exists()
assert (target_path / "references" / "index.md").exists()
assert (target_path / "references" / "getting_started.md").exists()
assert (target_path / "scripts").exists()
assert (target_path / "assets").exists()
def test_install_excludes_backups(self):
"""Test that .backup files are excluded from installation."""
# Create backup file
(self.skill_dir / "SKILL.md.backup").write_text("# Backup")
with tempfile.TemporaryDirectory() as agent_tmpdir:
agent_path = Path(agent_tmpdir) / ".claude" / "skills"
with patch(
"skill_seekers.cli.install_agent.get_agent_path",
return_value=agent_path,
):
success, message = install_to_agent(
self.skill_dir, "claude", force=True
)
assert success is True
target_path = agent_path / "test-skill"
# Backup should NOT be copied
assert not (target_path / "SKILL.md.backup").exists()
# Main file should be copied
assert (target_path / "SKILL.md").exists()
def test_install_existing_directory_no_force(self):
"""Test that existing directory without --force fails with clear message."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
agent_path = Path(agent_tmpdir) / ".claude" / "skills"
target_path = agent_path / "test-skill"
target_path.mkdir(parents=True)
with patch(
"skill_seekers.cli.install_agent.get_agent_path",
return_value=agent_path,
):
success, message = install_to_agent(
self.skill_dir, "claude", force=False
)
assert success is False
assert "already installed" in message.lower()
assert "--force" in message
def test_install_existing_directory_with_force(self):
"""Test that existing directory with --force overwrites successfully."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
agent_path = Path(agent_tmpdir) / ".claude" / "skills"
target_path = agent_path / "test-skill"
target_path.mkdir(parents=True)
(target_path / "old_file.txt").write_text("old content")
with patch(
"skill_seekers.cli.install_agent.get_agent_path",
return_value=agent_path,
):
success, message = install_to_agent(
self.skill_dir, "claude", force=True
)
assert success is True
# Old file should be gone
assert not (target_path / "old_file.txt").exists()
# New structure should exist
assert (target_path / "SKILL.md").exists()
def test_install_invalid_skill_directory(self):
"""Test that installation fails for invalid skill directory."""
invalid_dir = Path("/nonexistent/directory")
success, message = install_to_agent(invalid_dir, "claude")
assert success is False
assert "does not exist" in message
def test_install_missing_skill_md(self):
"""Test that installation fails if SKILL.md is missing."""
with tempfile.TemporaryDirectory() as tmpdir:
bad_skill_dir = Path(tmpdir) / "bad-skill"
bad_skill_dir.mkdir()
success, message = install_to_agent(bad_skill_dir, "claude")
assert success is False
assert "SKILL.md not found" in message
def test_install_dry_run(self):
"""Test that dry-run mode previews without making changes."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
agent_path = Path(agent_tmpdir) / ".claude" / "skills"
with patch(
"skill_seekers.cli.install_agent.get_agent_path",
return_value=agent_path,
):
success, message = install_to_agent(
self.skill_dir, "claude", dry_run=True
)
assert success is True
assert "DRY RUN" in message
# Directory should NOT be created
assert not (agent_path / "test-skill").exists()
class TestInstallToAllAgents:
"""Test installation to all agents."""
def setup_method(self):
"""Create test skill directory before each test."""
self.tmpdir = tempfile.mkdtemp()
self.skill_dir = Path(self.tmpdir) / "test-skill"
self.skill_dir.mkdir()
(self.skill_dir / "SKILL.md").write_text("# Test Skill")
(self.skill_dir / "references").mkdir()
def teardown_method(self):
"""Clean up after each test."""
shutil.rmtree(self.tmpdir, ignore_errors=True)
def test_install_to_all_success(self):
"""Test that install_to_all_agents attempts all 11 agents."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
def mock_get_agent_path(agent_name, _project_root=None):
return Path(agent_tmpdir) / f".{agent_name}" / "skills"
with patch(
"skill_seekers.cli.install_agent.get_agent_path",
side_effect=mock_get_agent_path,
):
results = install_to_all_agents(self.skill_dir, force=True)
assert len(results) == 11
assert "claude" in results
assert "cursor" in results
def test_install_to_all_partial_success(self):
"""Test that install_to_all collects both successes and failures."""
# This is hard to test without complex mocking, so we'll do dry-run
results = install_to_all_agents(self.skill_dir, dry_run=True)
# All should succeed in dry-run mode
assert len(results) == 11
for _agent_name, (success, message) in results.items():
assert success is True
assert "DRY RUN" in message
def test_install_to_all_with_force(self):
"""Test that install_to_all respects force flag."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
# Create existing directories for all agents
for agent in get_available_agents():
agent_dir = Path(agent_tmpdir) / f".{agent}" / "skills" / "test-skill"
agent_dir.mkdir(parents=True)
def mock_get_agent_path(agent_name, _project_root=None):
return Path(agent_tmpdir) / f".{agent_name}" / "skills"
with patch(
"skill_seekers.cli.install_agent.get_agent_path",
side_effect=mock_get_agent_path,
):
# Without force - should fail
results_no_force = install_to_all_agents(self.skill_dir, force=False)
# All should fail because directories exist
for _agent_name, (success, message) in results_no_force.items():
assert success is False
assert "already installed" in message.lower()
# With force - should succeed
results_with_force = install_to_all_agents(self.skill_dir, force=True)
for _agent_name, (success, _message) in results_with_force.items():
assert success is True
def test_install_to_all_returns_results(self):
"""Test that install_to_all returns dict with all results."""
results = install_to_all_agents(self.skill_dir, dry_run=True)
assert isinstance(results, dict)
assert len(results) == 11
for agent_name, (success, message) in results.items():
assert isinstance(success, bool)
assert isinstance(message, str)
assert agent_name in get_available_agents()
class TestInstallAgentCLI:
"""Test CLI interface."""
def setup_method(self):
"""Create test skill directory before each test."""
self.tmpdir = tempfile.mkdtemp()
self.skill_dir = Path(self.tmpdir) / "test-skill"
self.skill_dir.mkdir()
(self.skill_dir / "SKILL.md").write_text("# Test Skill")
(self.skill_dir / "references").mkdir()
def teardown_method(self):
"""Clean up after each test."""
shutil.rmtree(self.tmpdir, ignore_errors=True)
def test_cli_help_output(self):
"""Test that --help shows usage information."""
with (
pytest.raises(SystemExit) as exc_info,
patch("sys.argv", ["install_agent.py", "--help"]),
):
main()
# --help exits with code 0
assert exc_info.value.code == 0
def test_cli_requires_agent_flag(self):
"""Test that CLI fails without --agent flag."""
with (
pytest.raises(SystemExit) as exc_info,
patch("sys.argv", ["install_agent.py", str(self.skill_dir)]),
):
main()
# Missing required argument exits with code 2
assert exc_info.value.code == 2
def test_cli_dry_run(self):
"""Test that --dry-run flag works correctly."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
def mock_get_agent_path(agent_name, _project_root=None):
return Path(agent_tmpdir) / f".{agent_name}" / "skills"
with (
patch(
"skill_seekers.cli.install_agent.get_agent_path",
side_effect=mock_get_agent_path,
),
patch(
"sys.argv",
[
"install_agent.py",
str(self.skill_dir),
"--agent",
"claude",
"--dry-run",
],
),
):
exit_code = main()
assert exit_code == 0
# Directory should NOT be created
assert not (
Path(agent_tmpdir) / ".claude" / "skills" / "test-skill"
).exists()
def test_cli_integration(self):
"""Test end-to-end CLI execution."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
def mock_get_agent_path(agent_name, _project_root=None):
return Path(agent_tmpdir) / f".{agent_name}" / "skills"
with (
patch(
"skill_seekers.cli.install_agent.get_agent_path",
side_effect=mock_get_agent_path,
),
patch(
"sys.argv",
[
"install_agent.py",
str(self.skill_dir),
"--agent",
"claude",
"--force",
],
),
):
exit_code = main()
assert exit_code == 0
# Directory should be created
target = Path(agent_tmpdir) / ".claude" / "skills" / "test-skill"
assert target.exists()
assert (target / "SKILL.md").exists()
def test_cli_install_to_all(self):
"""Test CLI with --agent all."""
with tempfile.TemporaryDirectory() as agent_tmpdir:
def mock_get_agent_path(agent_name, _project_root=None):
return Path(agent_tmpdir) / f".{agent_name}" / "skills"
with (
patch(
"skill_seekers.cli.install_agent.get_agent_path",
side_effect=mock_get_agent_path,
),
patch(
"sys.argv",
[
"install_agent.py",
str(self.skill_dir),
"--agent",
"all",
"--force",
],
),
):
exit_code = main()
assert exit_code == 0
# All agent directories should be created
for agent in get_available_agents():
target = Path(agent_tmpdir) / f".{agent}" / "skills" / "test-skill"
assert target.exists(), f"Directory not created for {agent}"
if __name__ == "__main__":
# Run tests with pytest
pytest.main([__file__, "-v"])