import os
import tempfile
import shutil
from pathlib import Path
from unittest.mock import patch, MagicMock, AsyncMock
from typer.testing import CliRunner
from skillhub.commands.list import app as list_app
from skillhub.commands.skill import app as skill_app
from skillhub.commands.source import app as source_app
from skillhub.commands.auth import app as auth_app
from skillhub.commands.cache import app as cache_app
from skillhub.commands.config import app as config_app
from skillhub.commands.doctor import app as doctor_app
from skillhub.commands.install import app as install_app
from skillhub.commands.uninstall import app as uninstall_app
from skillhub.commands.upgrade import app as upgrade_app
from skillhub.commands.info import app as info_app
from skillhub.commands.search import app as search_app
runner = CliRunner()
class TestSearchCommand:
"""Tests for search command."""
def test_search_help(self):
"""Test search command help."""
result = runner.invoke(search_app, ["--help"])
assert result.exit_code == 0
def test_search_function_exists(self):
"""Test that search_skills function exists."""
from skillhub.commands.search import search_skills
assert callable(search_skills)
class TestListCommand:
"""Tests for list command."""
def test_list_help(self):
"""Test list command help."""
result = runner.invoke(list_app, ["--help"])
assert result.exit_code == 0
def test_list_installed_help(self):
"""Test list installed subcommand help."""
result = runner.invoke(list_app, ["installed", "--help"])
assert result.exit_code == 0
class TestSkillCommand:
"""Tests for skill command group."""
def test_skill_help(self):
"""Test skill command help."""
result = runner.invoke(skill_app, ["--help"])
assert result.exit_code == 0
def test_skill_list_help(self):
"""Test skill list subcommand."""
result = runner.invoke(skill_app, ["list", "--help"])
assert result.exit_code == 0
def test_skill_install_help(self):
"""Test skill install subcommand."""
result = runner.invoke(skill_app, ["install", "--help"])
assert result.exit_code == 0
def test_skill_uninstall_help(self):
"""Test skill uninstall subcommand."""
result = runner.invoke(skill_app, ["uninstall", "--help"])
assert result.exit_code == 0
def test_skill_upgrade_help(self):
"""Test skill upgrade subcommand."""
result = runner.invoke(skill_app, ["upgrade", "--help"])
assert result.exit_code == 0
def test_skill_info_help(self):
"""Test skill info subcommand."""
result = runner.invoke(skill_app, ["info", "--help"])
assert result.exit_code == 0
def test_is_local_path_function(self):
"""Test _is_local_path helper."""
from skillhub.commands.skill import _is_local_path
assert _is_local_path("/nonexistent") is False
class TestSourceCommand:
"""Tests for source command."""
def test_source_help(self):
"""Test source command help."""
result = runner.invoke(source_app, ["--help"])
assert result.exit_code == 0
def test_source_add_help(self):
"""Test source add subcommand."""
result = runner.invoke(source_app, ["add", "--help"])
assert result.exit_code == 0
def test_source_list_help(self):
"""Test source list subcommand."""
result = runner.invoke(source_app, ["list", "--help"])
assert result.exit_code == 0
def test_source_remove_help(self):
"""Test source remove subcommand."""
result = runner.invoke(source_app, ["remove", "--help"])
assert result.exit_code == 0
def test_source_test_help(self):
"""Test source test subcommand."""
result = runner.invoke(source_app, ["test", "--help"])
assert result.exit_code == 0
class TestAuthCommand:
"""Tests for auth command."""
def test_auth_help(self):
"""Test auth command help."""
result = runner.invoke(auth_app, ["--help"])
assert result.exit_code == 0
def test_auth_login_help(self):
"""Test auth login subcommand."""
result = runner.invoke(auth_app, ["login", "--help"])
assert result.exit_code == 0
def test_auth_logout_help(self):
"""Test auth logout subcommand."""
result = runner.invoke(auth_app, ["logout", "--help"])
assert result.exit_code == 0
def test_auth_status_help(self):
"""Test auth status subcommand."""
result = runner.invoke(auth_app, ["status", "--help"])
assert result.exit_code == 0
class TestCacheCommand:
"""Tests for cache command."""
def test_cache_help(self):
"""Test cache command help."""
result = runner.invoke(cache_app, ["--help"])
assert result.exit_code == 0
def test_cache_clear_help(self):
"""Test cache clear subcommand."""
result = runner.invoke(cache_app, ["clear", "--help"])
assert result.exit_code == 0
def test_cache_stats_help(self):
"""Test cache stats subcommand."""
result = runner.invoke(cache_app, ["info", "--help"])
assert result.exit_code == 0
class TestConfigCommand:
"""Tests for config command."""
def test_config_help(self):
"""Test config command help."""
result = runner.invoke(config_app, ["--help"])
assert result.exit_code == 0
def test_config_show_help(self):
"""Test config show subcommand."""
result = runner.invoke(config_app, ["list", "--help"])
assert result.exit_code == 0
def test_config_set_help(self):
"""Test config set subcommand."""
result = runner.invoke(config_app, ["set", "--help"])
assert result.exit_code == 0
class TestDoctorCommand:
"""Tests for doctor command."""
def test_doctor_help(self):
"""Test doctor command help."""
result = runner.invoke(doctor_app, ["--help"])
assert result.exit_code == 0
def test_doctor_run_help(self):
"""Test doctor run subcommand."""
result = runner.invoke(doctor_app, ["run", "--help"])
assert result.exit_code == 0
class TestInstallCommand:
"""Tests for install command (alias)."""
def test_install_help(self):
"""Test install command help."""
result = runner.invoke(install_app, ["--help"])
assert result.exit_code == 0
class TestUninstallCommand:
"""Tests for uninstall command (alias)."""
def test_uninstall_help(self):
"""Test uninstall command help."""
result = runner.invoke(uninstall_app, ["--help"])
assert result.exit_code == 0
class TestUpgradeCommand:
"""Tests for upgrade command (alias)."""
def test_upgrade_help(self):
"""Test upgrade command help."""
result = runner.invoke(upgrade_app, ["--help"])
assert result.exit_code == 0
class TestInfoCommand:
"""Tests for info command (alias)."""
def test_info_help(self):
"""Test info command help."""
result = runner.invoke(info_app, ["--help"])
assert result.exit_code == 0
class TestConfigGetCommand:
"""Tests for config get command with real config."""
def test_config_get_existing_key(self):
"""Test config get with existing key."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(config_app, ["get", "cache.enabled"])
assert result.exit_code == 0
assert "true" in result.output.lower() or "false" in result.output.lower()
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def test_config_get_nested_key(self):
"""Test config get with nested key."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(config_app, ["get", "cache.ttl_metadata"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def test_config_get_invalid_key(self):
"""Test config get with invalid key."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(config_app, ["get", "nonexistent_key"])
assert result.exit_code == 1
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestConfigListCommand:
"""Tests for config list command."""
def test_config_list_success(self):
"""Test config list command."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(config_app, ["list"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestConfigResetCommand:
"""Tests for config reset command."""
def test_config_reset_with_force(self):
"""Test config reset with force."""
temp_dir = Path(tempfile.mkdtemp())
try:
config_file = temp_dir / "config.json"
config_file.write_text("{\"test\": \"value\"}")
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(config_app, ["reset", "--force"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSourceListCommand:
"""Tests for source list command with real config."""
def test_source_list_empty(self):
"""Test source list when empty."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(source_app, ["list"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestAuthStatusCommand:
"""Tests for auth status command with real config."""
def test_auth_status(self):
"""Test auth status command."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(auth_app, ["status"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestCacheInfoCommand:
"""Tests for cache info command with real config."""
def test_cache_info(self):
"""Test cache info command."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(cache_app, ["info"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestCacheClearCommand:
"""Tests for cache clear command."""
def test_cache_clear_with_force(self):
"""Test cache clear command with force flag."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(cache_app, ["clear", "--force"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestDoctorCommandWithConfig:
"""Tests for doctor command with real config."""
def test_doctor_basic(self):
"""Test doctor command."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(doctor_app, [])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def test_doctor_verbose(self):
"""Test doctor with verbose flag."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(doctor_app, ["--verbose"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestListInstalledCommand:
"""Tests for list installed command with real config."""
def test_list_installed_empty(self):
"""Test list installed when empty."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(list_app, ["installed"])
assert result.exit_code in (0, 2)
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSearchCommandWithConfig:
"""Tests for search command with real config."""
def test_search_empty_results(self):
"""Test search with empty results."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(search_app, ["test"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSkillListCommand:
"""Tests for skill list command."""
def test_skill_list_empty(self):
"""Test skill list when empty."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(skill_app, ["list"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSkillUninstallCommand:
"""Tests for skill uninstall command."""
def test_skill_uninstall_nonexistent(self):
"""Test skill uninstall nonexistent."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(skill_app, ["uninstall", "nonexistent"])
assert result.exit_code == 1
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestUninstallCommandWithConfig:
"""Tests for uninstall command."""
def test_uninstall_nonexistent(self):
"""Test uninstall nonexistent skill."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(uninstall_app, ["nonexistent"])
assert result.exit_code == 1
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestUpgradeCommandWithConfig:
"""Tests for upgrade command."""
def test_upgrade_nonexistent(self):
"""Test upgrade nonexistent skill."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(upgrade_app, ["nonexistent"])
assert result.exit_code == 2
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestInfoCommandWithConfig:
"""Tests for info command."""
def test_info_nonexistent(self):
"""Test info nonexistent skill."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(info_app, ["nonexistent"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestInstallCommandWithConfig:
"""Tests for install command."""
def test_install_nonexistent(self):
"""Test install nonexistent skill."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(install_app, ["nonexistent"])
assert result.exit_code == 1
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def test_install_local_nonexistent(self):
"""Test install from nonexistent local path."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(install_app, ["./nonexistent_path"])
assert result.exit_code == 1
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestConfigSetCommand:
"""Tests for config set command."""
def test_config_set_invalid_key(self):
"""Test config set with invalid key."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(config_app, ["set", "invalid.key", "value"])
assert result.exit_code == 1
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSourceAddCommand:
"""Tests for source add command."""
def test_source_add_github(self):
"""Test source add github."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(source_app, ["add", "github", "https://github.com/test/skills"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def test_source_add_gitee(self):
"""Test source add gitee."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(source_app, ["add", "gitee", "https://gitee.com/test/skills"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestAuthLoginCommand:
"""Tests for auth login command."""
def test_auth_login_with_token(self):
"""Test auth login with token."""
from skillhub.models.credential import TokenValidation
temp_dir = Path(tempfile.mkdtemp())
try:
mock_validation = TokenValidation(valid=True, scopes=["repo"], message="OK")
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
with patch(
"skillhub.commands.auth.CredentialManagerImpl.validate_token",
new_callable=AsyncMock,
return_value=mock_validation,
):
with patch("skillhub.commands.auth.CredentialManagerImpl.store_token", new_callable=AsyncMock):
result = runner.invoke(auth_app, ["login", "github", "--token", "test_token"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestDoctorFixCommand:
"""Tests for doctor --fix command."""
def test_doctor_fix(self):
"""Test doctor with fix flag."""
temp_dir = Path(tempfile.mkdtemp())
fresh_dir = temp_dir / "fresh"
fresh_dir.mkdir(parents=True, exist_ok=True)
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(fresh_dir)}):
result = runner.invoke(doctor_app, ["--fix"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSearchJsonCommand:
"""Tests for search with JSON output."""
def test_search_json_output(self):
"""Test search with JSON output."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(search_app, ["test", "--json"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestListJsonCommand:
"""Tests for list with JSON output."""
def test_list_json_output(self):
"""Test list with JSON output."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(list_app, ["installed", "--json"])
assert result.exit_code == 2
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSourceRemoveCommand:
"""Tests for source remove command."""
def test_source_remove_nonexistent(self):
"""Test source remove nonexistent."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(source_app, ["remove", "nonexistent"])
assert result.exit_code == 1
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestAuthLogoutCommand:
"""Tests for auth logout command."""
def test_auth_logout(self):
"""Test auth logout."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(auth_app, ["logout", "github"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSkillInfoCommand:
"""Tests for skill info command."""
def test_skill_info_nonexistent(self):
"""Test skill info nonexistent."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(skill_app, ["info", "nonexistent"])
assert result.exit_code == 0
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSkillUpgradeCommand:
"""Tests for skill upgrade command."""
def test_skill_upgrade_nonexistent(self):
"""Test skill upgrade nonexistent."""
temp_dir = Path(tempfile.mkdtemp())
try:
with patch.dict(os.environ, {"SKILLHUB_DATA_DIR": str(temp_dir)}):
result = runner.invoke(skill_app, ["upgrade", "nonexistent"])
assert result.exit_code == 2
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
class TestSourceCommandAdvanced:
"""Advanced tests for source command."""
def test_source_list_with_mock(self):
"""Test source list with mock."""
with patch("skillhub.commands.source.get_config") as mock_config:
mock_config.return_value = MagicMock()
with patch("skillhub.commands.source.SourceManagerImpl") as mock_manager:
mock_instance = MagicMock()
mock_instance.list_sources = AsyncMock(return_value=[])
mock_manager.return_value = mock_instance
result = runner.invoke(source_app, ["list"])
assert result.exit_code == 0
class TestAuthCommandAdvanced:
"""Advanced tests for auth command."""
def test_auth_status_with_mock(self):
"""Test auth status."""
with patch("skillhub.commands.auth.get_config") as mock_config:
mock_config.return_value = MagicMock()
with patch("skillhub.commands.auth.CredentialManagerImpl") as mock_cred:
mock_instance = MagicMock()
mock_instance.list_tokens = AsyncMock(return_value=[])
mock_cred.return_value = mock_instance
result = runner.invoke(auth_app, ["status"])
assert result.exit_code == 0
class TestCacheCommandAdvanced:
"""Advanced tests for cache command."""
def test_cache_stats_with_mock(self):
"""Test cache stats."""
from skillhub.models.cache import CacheStats
mock_stats = CacheStats(
size=100,
hit_rate=0.5,
miss_rate=0.5,
total_size=1024,
oldest_entry=None,
newest_entry=None,
)
with patch("skillhub.commands.cache.get_config") as mock_config:
mock_config.return_value = MagicMock()
with patch("skillhub.commands.cache.CacheManagerImpl") as mock_cache:
mock_instance = MagicMock()
mock_instance.get_stats = AsyncMock(return_value=mock_stats)
mock_cache.return_value = mock_instance
result = runner.invoke(cache_app, ["info"])
assert result.exit_code == 0
class TestConfigCommandAdvanced:
"""Advanced tests for config command."""
def test_config_show_with_mock(self):
"""Test config show."""
with patch("skillhub.commands.config.get_config") as mock_get:
mock_get.return_value = MagicMock()
result = runner.invoke(config_app, ["list"])
assert result.exit_code == 0