feat: add skill-seekers doctor health check command (#316)
8 diagnostic checks: Python version (3.10+), package install, git, 14 core deps, 10 optional deps, API keys, MCP server, output dir. Each check reports pass/warn/fail with --verbose for extra detail. Exit code 0 if no critical failures, 1 otherwise. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
164
tests/test_doctor.py
Normal file
164
tests/test_doctor.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""Tests for skill-seekers doctor command (#316)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from skill_seekers.cli.doctor import (
|
||||
CheckResult,
|
||||
check_api_keys,
|
||||
check_core_deps,
|
||||
check_git,
|
||||
check_mcp_server,
|
||||
check_optional_deps,
|
||||
check_output_directory,
|
||||
check_package_installed,
|
||||
check_python_version,
|
||||
print_report,
|
||||
run_all_checks,
|
||||
)
|
||||
|
||||
|
||||
class TestCheckPythonVersion:
|
||||
def test_passes_on_current_python(self):
|
||||
result = check_python_version()
|
||||
assert result.status == "pass"
|
||||
assert result.critical is True
|
||||
|
||||
def test_detail_contains_version(self):
|
||||
result = check_python_version()
|
||||
assert "." in result.detail # e.g. "3.14.3"
|
||||
|
||||
|
||||
class TestCheckPackageInstalled:
|
||||
def test_passes_when_installed(self):
|
||||
result = check_package_installed()
|
||||
assert result.status == "pass"
|
||||
assert result.detail.startswith("v")
|
||||
|
||||
def test_fails_when_import_broken(self):
|
||||
with (
|
||||
patch.dict("sys.modules", {"skill_seekers._version": None}),
|
||||
patch("builtins.__import__", side_effect=ImportError("mocked")),
|
||||
):
|
||||
result = check_package_installed()
|
||||
assert result.status == "fail"
|
||||
|
||||
|
||||
class TestCheckGit:
|
||||
def test_passes_when_git_available(self):
|
||||
result = check_git()
|
||||
# Most CI/dev environments have git
|
||||
assert result.status in ("pass", "warn")
|
||||
|
||||
def test_warns_when_git_missing(self):
|
||||
with patch("skill_seekers.cli.doctor.shutil.which", return_value=None):
|
||||
result = check_git()
|
||||
assert result.status == "warn"
|
||||
|
||||
|
||||
class TestCheckCoreDeps:
|
||||
def test_passes_in_normal_environment(self):
|
||||
result = check_core_deps()
|
||||
assert result.status == "pass"
|
||||
assert result.critical is True
|
||||
|
||||
def test_detail_shows_count(self):
|
||||
result = check_core_deps()
|
||||
assert "found" in result.detail.lower() or "missing" in result.detail.lower()
|
||||
|
||||
|
||||
class TestCheckOptionalDeps:
|
||||
def test_returns_result(self):
|
||||
result = check_optional_deps()
|
||||
assert result.status in ("pass", "warn")
|
||||
assert "/" in result.detail # e.g. "7/10 installed"
|
||||
|
||||
|
||||
class TestCheckApiKeys:
|
||||
def test_warns_when_no_keys(self):
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
result = check_api_keys()
|
||||
assert result.status == "warn"
|
||||
|
||||
def test_passes_when_all_set(self):
|
||||
env = {
|
||||
"ANTHROPIC_API_KEY": "sk-ant-test123456789",
|
||||
"GITHUB_TOKEN": "ghp_test123456789",
|
||||
"GOOGLE_API_KEY": "AIza_test123456789",
|
||||
"OPENAI_API_KEY": "sk-test123456789",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
result = check_api_keys()
|
||||
assert result.status == "pass"
|
||||
|
||||
def test_partial_keys_warns(self):
|
||||
env = {"ANTHROPIC_API_KEY": "sk-ant-test123456789"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
result = check_api_keys()
|
||||
assert result.status == "warn"
|
||||
assert "1 set" in result.detail
|
||||
|
||||
|
||||
class TestCheckMcpServer:
|
||||
def test_returns_result(self):
|
||||
result = check_mcp_server()
|
||||
assert result.status in ("pass", "warn")
|
||||
|
||||
|
||||
class TestCheckOutputDirectory:
|
||||
def test_passes_in_writable_dir(self):
|
||||
result = check_output_directory()
|
||||
assert result.status == "pass"
|
||||
assert result.critical is True
|
||||
|
||||
|
||||
class TestRunAllChecks:
|
||||
def test_returns_8_results(self):
|
||||
results = run_all_checks()
|
||||
assert len(results) == 8
|
||||
|
||||
def test_all_have_name_and_status(self):
|
||||
results = run_all_checks()
|
||||
for r in results:
|
||||
assert isinstance(r, CheckResult)
|
||||
assert r.name
|
||||
assert r.status in ("pass", "warn", "fail")
|
||||
|
||||
|
||||
class TestPrintReport:
|
||||
def test_returns_0_when_no_failures(self, capsys):
|
||||
results = [
|
||||
CheckResult("Test1", "pass", "ok", critical=True),
|
||||
CheckResult("Test2", "warn", "meh"),
|
||||
]
|
||||
code = print_report(results)
|
||||
assert code == 0
|
||||
captured = capsys.readouterr()
|
||||
assert "1 passed" in captured.out
|
||||
assert "1 warnings" in captured.out
|
||||
|
||||
def test_returns_1_when_critical_failure(self, capsys):
|
||||
results = [
|
||||
CheckResult("Test1", "pass", "ok"),
|
||||
CheckResult("Test2", "fail", "broken", critical=True),
|
||||
]
|
||||
code = print_report(results)
|
||||
assert code == 1
|
||||
|
||||
def test_verbose_shows_detail(self, capsys):
|
||||
results = [
|
||||
CheckResult("Test1", "pass", "ok", verbose_detail=" extra: info"),
|
||||
]
|
||||
print_report(results, verbose=True)
|
||||
captured = capsys.readouterr()
|
||||
assert "extra: info" in captured.out
|
||||
|
||||
def test_no_verbose_hides_detail(self, capsys):
|
||||
results = [
|
||||
CheckResult("Test1", "pass", "ok", verbose_detail=" secret: hidden"),
|
||||
]
|
||||
print_report(results, verbose=False)
|
||||
captured = capsys.readouterr()
|
||||
assert "secret: hidden" not in captured.out
|
||||
Reference in New Issue
Block a user