Skip to content

Commit 2c36516

Browse files
GeneAIclaude
authored andcommitted
test: Add CLI command tests to increase coverage to 86%
New test files: - tests/unit/test_doctor_cmd.py: Health check command tests - tests/unit/test_setup_hooks_cmd.py: Git hooks setup tests - tests/unit/test_update_config_cmd.py: Config update tests Coverage improvements: - doctor_cmd.py: 18% → 100% - setup_hooks_cmd.py: 19% → 100% - update_config_cmd.py: 17% → 96% - Overall: 78% → 86% (exceeds 85% target) All 435 tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent c1e5fcd commit 2c36516

File tree

3 files changed

+727
-0
lines changed

3 files changed

+727
-0
lines changed

tests/unit/test_doctor_cmd.py

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
"""Tests for doctor command module."""
2+
3+
import json
4+
import os
5+
from pathlib import Path
6+
from unittest.mock import MagicMock, patch
7+
8+
import pytest
9+
from click.testing import CliRunner
10+
11+
from memdocs.cli_modules.commands.doctor_cmd import (
12+
_check_api_key,
13+
_check_docs_exist,
14+
_check_item,
15+
_check_memdocs_command,
16+
_check_memdocs_initialized,
17+
_check_memory_exists,
18+
_check_mcp_server_responsive,
19+
_check_vscode_settings,
20+
_check_vscode_tasks,
21+
_get_stats,
22+
doctor,
23+
)
24+
25+
26+
class TestCheckItem:
27+
"""Tests for _check_item helper function."""
28+
29+
def test_check_item_passes(self, capsys):
30+
"""Test _check_item when check passes."""
31+
result = _check_item("Test check", lambda: True)
32+
assert result is True
33+
34+
def test_check_item_fails(self, capsys):
35+
"""Test _check_item when check fails."""
36+
result = _check_item("Test check", lambda: False, "Try this fix")
37+
assert result is False
38+
39+
def test_check_item_exception(self, capsys):
40+
"""Test _check_item when check raises exception."""
41+
42+
def raise_error():
43+
raise ValueError("Test error")
44+
45+
result = _check_item("Test check", raise_error, "Fix hint")
46+
assert result is False
47+
48+
49+
class TestCheckMemdocsInitialized:
50+
"""Tests for _check_memdocs_initialized."""
51+
52+
def test_initialized_true(self, tmp_path, monkeypatch):
53+
"""Test when .memdocs.yml exists."""
54+
monkeypatch.chdir(tmp_path)
55+
(tmp_path / ".memdocs.yml").write_text("version: 1")
56+
assert _check_memdocs_initialized() is True
57+
58+
def test_initialized_false(self, tmp_path, monkeypatch):
59+
"""Test when .memdocs.yml doesn't exist."""
60+
monkeypatch.chdir(tmp_path)
61+
assert _check_memdocs_initialized() is False
62+
63+
64+
class TestCheckDocsExist:
65+
"""Tests for _check_docs_exist."""
66+
67+
def test_docs_exist_with_json(self, tmp_path, monkeypatch):
68+
"""Test when docs directory has JSON files."""
69+
monkeypatch.chdir(tmp_path)
70+
docs_dir = tmp_path / ".memdocs" / "docs"
71+
docs_dir.mkdir(parents=True)
72+
(docs_dir / "index.json").write_text("{}")
73+
assert _check_docs_exist() is True
74+
75+
def test_docs_exist_empty_dir(self, tmp_path, monkeypatch):
76+
"""Test when docs directory exists but is empty."""
77+
monkeypatch.chdir(tmp_path)
78+
docs_dir = tmp_path / ".memdocs" / "docs"
79+
docs_dir.mkdir(parents=True)
80+
assert _check_docs_exist() is False
81+
82+
def test_docs_not_exist(self, tmp_path, monkeypatch):
83+
"""Test when docs directory doesn't exist."""
84+
monkeypatch.chdir(tmp_path)
85+
assert _check_docs_exist() is False
86+
87+
88+
class TestCheckMemoryExists:
89+
"""Tests for _check_memory_exists."""
90+
91+
def test_memory_exists_with_faiss(self, tmp_path, monkeypatch):
92+
"""Test when memory directory has FAISS index."""
93+
monkeypatch.chdir(tmp_path)
94+
memory_dir = tmp_path / ".memdocs" / "memory"
95+
memory_dir.mkdir(parents=True)
96+
(memory_dir / "faiss.index").write_bytes(b"fake index")
97+
assert _check_memory_exists() is True
98+
99+
def test_memory_exists_no_faiss(self, tmp_path, monkeypatch):
100+
"""Test when memory directory exists but no FAISS index."""
101+
monkeypatch.chdir(tmp_path)
102+
memory_dir = tmp_path / ".memdocs" / "memory"
103+
memory_dir.mkdir(parents=True)
104+
assert _check_memory_exists() is False
105+
106+
def test_memory_not_exist(self, tmp_path, monkeypatch):
107+
"""Test when memory directory doesn't exist."""
108+
monkeypatch.chdir(tmp_path)
109+
assert _check_memory_exists() is False
110+
111+
112+
class TestCheckApiKey:
113+
"""Tests for _check_api_key."""
114+
115+
def test_api_key_set(self, monkeypatch):
116+
"""Test when ANTHROPIC_API_KEY is set."""
117+
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test123")
118+
assert _check_api_key() is True
119+
120+
def test_api_key_not_set(self, monkeypatch):
121+
"""Test when ANTHROPIC_API_KEY is not set."""
122+
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
123+
assert _check_api_key() is False
124+
125+
def test_api_key_empty(self, monkeypatch):
126+
"""Test when ANTHROPIC_API_KEY is empty string."""
127+
monkeypatch.setenv("ANTHROPIC_API_KEY", "")
128+
assert _check_api_key() is False
129+
130+
131+
class TestCheckVscodeIntegration:
132+
"""Tests for VS Code integration checks."""
133+
134+
def test_vscode_tasks_exists(self, tmp_path, monkeypatch):
135+
"""Test when tasks.json exists."""
136+
monkeypatch.chdir(tmp_path)
137+
vscode_dir = tmp_path / ".vscode"
138+
vscode_dir.mkdir()
139+
(vscode_dir / "tasks.json").write_text("{}")
140+
assert _check_vscode_tasks() is True
141+
142+
def test_vscode_tasks_not_exists(self, tmp_path, monkeypatch):
143+
"""Test when tasks.json doesn't exist."""
144+
monkeypatch.chdir(tmp_path)
145+
assert _check_vscode_tasks() is False
146+
147+
def test_vscode_settings_auto_tasks_on(self, tmp_path, monkeypatch):
148+
"""Test when auto-tasks is enabled."""
149+
monkeypatch.chdir(tmp_path)
150+
vscode_dir = tmp_path / ".vscode"
151+
vscode_dir.mkdir()
152+
settings = {"task.allowAutomaticTasks": "on"}
153+
(vscode_dir / "settings.json").write_text(json.dumps(settings))
154+
assert _check_vscode_settings() is True
155+
156+
def test_vscode_settings_auto_tasks_off(self, tmp_path, monkeypatch):
157+
"""Test when auto-tasks is not enabled."""
158+
monkeypatch.chdir(tmp_path)
159+
vscode_dir = tmp_path / ".vscode"
160+
vscode_dir.mkdir()
161+
settings = {"task.allowAutomaticTasks": "off"}
162+
(vscode_dir / "settings.json").write_text(json.dumps(settings))
163+
assert _check_vscode_settings() is False
164+
165+
def test_vscode_settings_not_exists(self, tmp_path, monkeypatch):
166+
"""Test when settings.json doesn't exist."""
167+
monkeypatch.chdir(tmp_path)
168+
assert _check_vscode_settings() is False
169+
170+
def test_vscode_settings_invalid_json(self, tmp_path, monkeypatch):
171+
"""Test when settings.json is invalid JSON."""
172+
monkeypatch.chdir(tmp_path)
173+
vscode_dir = tmp_path / ".vscode"
174+
vscode_dir.mkdir()
175+
(vscode_dir / "settings.json").write_text("not valid json")
176+
assert _check_vscode_settings() is False
177+
178+
179+
class TestCheckMcpServer:
180+
"""Tests for MCP server check."""
181+
182+
def test_mcp_server_responsive(self):
183+
"""Test when MCP server responds correctly."""
184+
with patch("memdocs.cli_modules.commands.doctor_cmd.requests.get") as mock_get:
185+
mock_response = MagicMock()
186+
mock_response.status_code = 200
187+
mock_response.json.return_value = {"status": "ok"}
188+
mock_get.return_value = mock_response
189+
assert _check_mcp_server_responsive() is True
190+
191+
def test_mcp_server_wrong_status(self):
192+
"""Test when MCP server returns wrong status."""
193+
with patch("memdocs.cli_modules.commands.doctor_cmd.requests.get") as mock_get:
194+
mock_response = MagicMock()
195+
mock_response.status_code = 200
196+
mock_response.json.return_value = {"status": "error"}
197+
mock_get.return_value = mock_response
198+
assert _check_mcp_server_responsive() is False
199+
200+
def test_mcp_server_connection_error(self):
201+
"""Test when MCP server is not running."""
202+
with patch("memdocs.cli_modules.commands.doctor_cmd.requests.get") as mock_get:
203+
mock_get.side_effect = ConnectionError("Connection refused")
204+
assert _check_mcp_server_responsive() is False
205+
206+
207+
class TestCheckMemdocsCommand:
208+
"""Tests for memdocs command check."""
209+
210+
def test_memdocs_command_available(self):
211+
"""Test when memdocs command is available."""
212+
with patch("subprocess.run") as mock_run:
213+
mock_run.return_value = MagicMock(returncode=0)
214+
assert _check_memdocs_command() is True
215+
216+
def test_memdocs_command_not_found(self):
217+
"""Test when memdocs command is not found."""
218+
with patch("subprocess.run") as mock_run:
219+
mock_run.side_effect = FileNotFoundError()
220+
assert _check_memdocs_command() is False
221+
222+
223+
class TestGetStats:
224+
"""Tests for _get_stats."""
225+
226+
def test_get_stats_with_data(self, tmp_path, monkeypatch):
227+
"""Test stats with documentation and embeddings."""
228+
monkeypatch.chdir(tmp_path)
229+
230+
# Create docs
231+
docs_dir = tmp_path / ".memdocs" / "docs"
232+
docs_dir.mkdir(parents=True)
233+
(docs_dir / "index.json").write_text("{}")
234+
(docs_dir / "other.json").write_text("{}")
235+
236+
# Create memory
237+
memory_dir = tmp_path / ".memdocs" / "memory"
238+
memory_dir.mkdir(parents=True)
239+
(memory_dir / "faiss.index").write_bytes(b"fake")
240+
241+
stats = _get_stats()
242+
assert stats["documented_files"] == 2
243+
assert stats["embeddings_available"] is True
244+
245+
def test_get_stats_empty(self, tmp_path, monkeypatch):
246+
"""Test stats with no data."""
247+
monkeypatch.chdir(tmp_path)
248+
stats = _get_stats()
249+
assert stats["documented_files"] == 0
250+
assert stats["embeddings_available"] is False
251+
252+
253+
class TestDoctorCommand:
254+
"""Tests for doctor CLI command."""
255+
256+
def test_doctor_all_checks_pass(self, tmp_path, monkeypatch):
257+
"""Test doctor command when all checks pass."""
258+
monkeypatch.chdir(tmp_path)
259+
260+
# Setup passing environment
261+
(tmp_path / ".memdocs.yml").write_text("version: 1")
262+
docs_dir = tmp_path / ".memdocs" / "docs"
263+
docs_dir.mkdir(parents=True)
264+
(docs_dir / "index.json").write_text("{}")
265+
memory_dir = tmp_path / ".memdocs" / "memory"
266+
memory_dir.mkdir(parents=True)
267+
(memory_dir / "faiss.index").write_bytes(b"fake")
268+
vscode_dir = tmp_path / ".vscode"
269+
vscode_dir.mkdir()
270+
(vscode_dir / "tasks.json").write_text("{}")
271+
(vscode_dir / "settings.json").write_text('{"task.allowAutomaticTasks": "on"}')
272+
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test")
273+
274+
runner = CliRunner()
275+
with patch(
276+
"memdocs.cli_modules.commands.doctor_cmd._check_mcp_server_responsive",
277+
return_value=True,
278+
):
279+
result = runner.invoke(doctor)
280+
# Note: exits with 0 when all pass, but CliRunner catches SystemExit
281+
assert "All checks passed" in result.output or "passed" in result.output.lower()
282+
283+
def test_doctor_some_checks_fail(self, tmp_path, monkeypatch):
284+
"""Test doctor command when some checks fail."""
285+
monkeypatch.chdir(tmp_path)
286+
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
287+
288+
runner = CliRunner()
289+
result = runner.invoke(doctor)
290+
assert "failed" in result.output.lower() or "Fix" in result.output
291+
292+
def test_doctor_with_fix_flag(self, tmp_path, monkeypatch):
293+
"""Test doctor command with --fix flag."""
294+
monkeypatch.chdir(tmp_path)
295+
296+
runner = CliRunner()
297+
result = runner.invoke(doctor, ["--fix"])
298+
assert "not yet implemented" in result.output.lower() or "fix" in result.output.lower()

0 commit comments

Comments
 (0)