Skip to content

Commit e556135

Browse files
zhujian0805claude
andcommitted
test(cli): add 15 tests for app.py and doctor.py CLI modules
Add unit tests for CLI app configuration commands and doctor checks: - 8 tests for app.py: validate_config, list_config, main app, global options - 7 tests for doctor.py: run_doctor_checks with various configurations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 062bb0b commit e556135

2 files changed

Lines changed: 230 additions & 0 deletions

File tree

tests/unit/test_app_cli.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Tests for CLI app module."""
2+
3+
import json
4+
from pathlib import Path
5+
from unittest.mock import MagicMock, patch
6+
7+
import pytest
8+
from typer.testing import CliRunner
9+
10+
from code_assistant_manager.cli.app import app, config_app, list_config, validate_config
11+
12+
13+
@pytest.fixture
14+
def runner():
15+
"""Create a CLI test runner."""
16+
return CliRunner()
17+
18+
19+
class TestValidateConfig:
20+
"""Tests for config validation command."""
21+
22+
def test_validate_config_success(self, runner, tmp_path):
23+
"""validate_config passes with valid configuration."""
24+
config_file = tmp_path / "providers.json"
25+
config_file.write_text(json.dumps({"providers": []}))
26+
27+
mock_cm = MagicMock()
28+
mock_cm.validate_config.return_value = (True, [])
29+
30+
with patch("code_assistant_manager.config.ConfigManager", return_value=mock_cm):
31+
result = runner.invoke(
32+
config_app, ["validate", "--config", str(config_file)]
33+
)
34+
35+
assert result.exit_code == 0
36+
assert "passed" in result.output.lower()
37+
38+
def test_validate_config_failure(self, runner, tmp_path):
39+
"""validate_config reports errors with invalid configuration."""
40+
config_file = tmp_path / "providers.json"
41+
config_file.write_text(json.dumps({"providers": []}))
42+
43+
mock_cm = MagicMock()
44+
mock_cm.validate_config.return_value = (False, ["Missing required field"])
45+
46+
with patch("code_assistant_manager.config.ConfigManager", return_value=mock_cm):
47+
result = runner.invoke(
48+
config_app, ["validate", "--config", str(config_file)]
49+
)
50+
51+
assert "failed" in result.output.lower()
52+
assert "Missing required field" in result.output
53+
54+
def test_validate_config_file_not_found(self, runner, tmp_path):
55+
"""validate_config handles missing file gracefully."""
56+
config_file = tmp_path / "nonexistent.json"
57+
58+
with patch(
59+
"code_assistant_manager.config.ConfigManager",
60+
side_effect=FileNotFoundError("Config not found"),
61+
):
62+
result = runner.invoke(
63+
config_app, ["validate", "--config", str(config_file)]
64+
)
65+
66+
assert "not found" in result.output.lower()
67+
68+
69+
class TestListConfig:
70+
"""Tests for config list command."""
71+
72+
def test_list_config_shows_locations(self, runner):
73+
"""list_config shows configuration file locations."""
74+
result = runner.invoke(config_app, ["list"])
75+
76+
assert result.exit_code == 0
77+
assert "Configuration Files" in result.output
78+
# Should mention some standard locations
79+
assert "providers.json" in result.output
80+
81+
def test_list_config_shows_editor_configs(self, runner):
82+
"""list_config shows editor configuration locations."""
83+
result = runner.invoke(config_app, ["list"])
84+
85+
assert result.exit_code == 0
86+
# Should show editor configurations section
87+
assert "Editor" in result.output or "claude" in result.output.lower()
88+
89+
90+
class TestMainApp:
91+
"""Tests for main app commands."""
92+
93+
def test_app_help(self, runner):
94+
"""app shows help message."""
95+
result = runner.invoke(app, ["--help"])
96+
97+
assert result.exit_code == 0
98+
assert "Code Assistant Manager" in result.output
99+
100+
def test_app_subcommands_available(self, runner):
101+
"""app has expected subcommands."""
102+
result = runner.invoke(app, ["--help"])
103+
104+
assert result.exit_code == 0
105+
# Check for main subcommands
106+
assert "mcp" in result.output
107+
assert "prompt" in result.output
108+
assert "skill" in result.output
109+
assert "plugin" in result.output
110+
111+
112+
class TestGlobalOptions:
113+
"""Tests for global CLI options."""
114+
115+
def test_debug_option(self, runner):
116+
"""--debug option enables debug logging."""
117+
with patch("code_assistant_manager.cli.app.logging") as mock_logging:
118+
result = runner.invoke(app, ["--debug", "--help"])
119+
120+
# The debug option should be recognized
121+
assert result.exit_code == 0

tests/unit/test_doctor.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Tests for CLI doctor module."""
2+
3+
import json
4+
import sys
5+
from pathlib import Path
6+
from unittest.mock import MagicMock, patch
7+
8+
import pytest
9+
10+
from code_assistant_manager.cli.doctor import run_doctor_checks
11+
12+
13+
@pytest.fixture
14+
def mock_config(tmp_path):
15+
"""Create a mock config object."""
16+
config_file = tmp_path / "providers.json"
17+
config_file.write_text(json.dumps({"providers": []}))
18+
19+
config = MagicMock()
20+
config.config_path = str(config_file)
21+
# Add config_data attribute for JSON serialization in doctor checks
22+
config.config_data = {"providers": []}
23+
return config
24+
25+
26+
class TestRunDoctorChecks:
27+
"""Tests for run_doctor_checks function."""
28+
29+
def test_doctor_checks_basic_pass(self, mock_config, capsys):
30+
"""Doctor checks pass with valid installation."""
31+
# Mock the dependencies that doctor checks need
32+
# Patch at the source module where find_env_file is imported from
33+
with patch(
34+
"code_assistant_manager.env_loader.find_env_file", return_value=None
35+
):
36+
result = run_doctor_checks(mock_config, verbose=False)
37+
38+
captured = capsys.readouterr().out
39+
# Should show installation check passed
40+
assert "installed" in captured.lower()
41+
assert "Python" in captured
42+
43+
def test_doctor_checks_config_exists(self, mock_config, capsys):
44+
"""Doctor shows config file exists when it does."""
45+
with patch(
46+
"code_assistant_manager.env_loader.find_env_file", return_value=None
47+
):
48+
result = run_doctor_checks(mock_config, verbose=False)
49+
50+
captured = capsys.readouterr().out
51+
assert "Configuration" in captured
52+
53+
def test_doctor_checks_config_missing(self, tmp_path, capsys):
54+
"""Doctor reports when config file is missing."""
55+
config = MagicMock()
56+
config.config_path = str(tmp_path / "nonexistent.json")
57+
# Add config_data for JSON serialization even when config file is missing
58+
config.config_data = {"providers": []}
59+
60+
with patch(
61+
"code_assistant_manager.env_loader.find_env_file", return_value=None
62+
):
63+
result = run_doctor_checks(config, verbose=False)
64+
65+
captured = capsys.readouterr().out
66+
assert "Configuration" in captured or "not found" in captured.lower()
67+
68+
def test_doctor_checks_verbose(self, mock_config, capsys):
69+
"""Doctor provides additional info in verbose mode."""
70+
with patch(
71+
"code_assistant_manager.env_loader.find_env_file", return_value=None
72+
):
73+
result = run_doctor_checks(mock_config, verbose=True)
74+
75+
captured = capsys.readouterr().out
76+
# Verbose mode should still complete
77+
assert "installed" in captured.lower()
78+
79+
def test_doctor_shows_python_version(self, mock_config, capsys):
80+
"""Doctor shows Python version information."""
81+
with patch(
82+
"code_assistant_manager.env_loader.find_env_file", return_value=None
83+
):
84+
result = run_doctor_checks(mock_config, verbose=False)
85+
86+
captured = capsys.readouterr().out
87+
assert "Python" in captured
88+
assert sys.version.split()[0] in captured
89+
90+
def test_doctor_shows_environment_check(self, mock_config, capsys):
91+
"""Doctor includes environment variables check section."""
92+
with patch(
93+
"code_assistant_manager.env_loader.find_env_file", return_value=None
94+
):
95+
result = run_doctor_checks(mock_config, verbose=False)
96+
97+
captured = capsys.readouterr().out
98+
assert "Environment" in captured
99+
100+
def test_doctor_returns_zero_on_success(self, mock_config):
101+
"""Doctor returns 0 when all checks pass."""
102+
with patch(
103+
"code_assistant_manager.env_loader.find_env_file", return_value=None
104+
):
105+
result = run_doctor_checks(mock_config, verbose=False)
106+
107+
# If the basic checks pass (installation, python, config exists)
108+
# it should return a low or zero issue count
109+
assert isinstance(result, int)

0 commit comments

Comments
 (0)