1. Fix `test_default_beads_path_auto_detection`
- Changed beads_path to use `Field(default_factory=_default_beads_path)` so the default is evaluated at instance
creation time, not class definition time
- Updated test to mock both `shutil.which` and `os.access`
2. Fix `test_init_creates_beads_directory`
- Fixed test to pass `working_dir=temp_dir` to `BdClient` instead of using `os.chdir()`
- The `_get_working_dir()` method checks `PWD` env var first, which isn't updated by `os.chdir()`
3. Fix minor linting errors reported by `ruff` tool
4. Update `beads` version to `0.9.6` in `uv.lock` file
MCP Server test coverage is now excellent, at 92% overall maintaining our high-standards of production level quality.
```
Name Stmts Miss Cover
------------------------------------------------
src/beads_mcp/__init__.py 1 0 100%
src/beads_mcp/__main__.py 3 3 0%
src/beads_mcp/bd_client.py 214 14 93%
src/beads_mcp/config.py 51 2 96%
src/beads_mcp/models.py 92 1 99%
src/beads_mcp/server.py 58 16 72%
src/beads_mcp/tools.py 59 0 100%
------------------------------------------------
TOTAL 478 36 92%
```
132 lines
5.1 KiB
Python
132 lines
5.1 KiB
Python
"""Tests for beads_mcp.config module."""
|
|
|
|
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from beads_mcp.config import Config, ConfigError, load_config
|
|
|
|
|
|
class TestConfig:
|
|
"""Tests for Config class."""
|
|
|
|
def test_default_beads_path_auto_detection(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that bd is auto-detected from PATH when BEADS_PATH not set."""
|
|
# Clear BEADS_PATH if set
|
|
monkeypatch.delenv("BEADS_PATH", raising=False)
|
|
|
|
# Mock shutil.which to return a test path
|
|
with patch("shutil.which", return_value="/usr/local/bin/bd"):
|
|
# Mock os.access to say the file is executable
|
|
with patch("os.access", return_value=True):
|
|
config = Config()
|
|
assert config.beads_path == "/usr/local/bin/bd"
|
|
|
|
def test_beads_path_from_env(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that BEADS_PATH environment variable is respected."""
|
|
# Create a fake bd executable
|
|
bd_path = tmp_path / "bd"
|
|
bd_path.touch(mode=0o755)
|
|
|
|
monkeypatch.setenv("BEADS_PATH", str(bd_path))
|
|
config = Config()
|
|
assert config.beads_path == str(bd_path)
|
|
|
|
def test_beads_path_command_name_resolution(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that command names like 'bd' are resolved via PATH."""
|
|
# Set BEADS_PATH to just "bd" (command name, not path)
|
|
monkeypatch.setenv("BEADS_PATH", "bd")
|
|
|
|
# Mock shutil.which to simulate finding bd in PATH
|
|
with patch("shutil.which", return_value="/usr/local/bin/bd"):
|
|
# Mock os.access to say the file is executable
|
|
with patch("os.access", return_value=True):
|
|
config = Config()
|
|
assert config.beads_path == "/usr/local/bin/bd"
|
|
|
|
def test_beads_path_not_found(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that invalid BEADS_PATH raises ValueError."""
|
|
monkeypatch.setenv("BEADS_PATH", "/nonexistent/bd")
|
|
|
|
with pytest.raises(ValueError, match="bd executable not found"):
|
|
Config()
|
|
|
|
def test_beads_path_not_executable(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that non-executable bd raises ValueError."""
|
|
# Create a non-executable file
|
|
bd_path = tmp_path / "bd"
|
|
bd_path.touch(mode=0o644) # rw-r--r--
|
|
|
|
monkeypatch.setenv("BEADS_PATH", str(bd_path))
|
|
|
|
with pytest.raises(ValueError, match="not executable"):
|
|
Config()
|
|
|
|
def test_beads_db_validation(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that BEADS_DB must point to existing file."""
|
|
# Create valid bd executable
|
|
bd_path = tmp_path / "bd"
|
|
bd_path.touch(mode=0o755)
|
|
monkeypatch.setenv("BEADS_PATH", str(bd_path))
|
|
|
|
# Set BEADS_DB to non-existent file
|
|
monkeypatch.setenv("BEADS_DB", "/nonexistent/db.sqlite")
|
|
|
|
with pytest.raises(ValueError, match="non-existent file"):
|
|
Config()
|
|
|
|
def test_beads_db_none_allowed(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that BEADS_DB can be unset (None)."""
|
|
# Create valid bd executable
|
|
bd_path = tmp_path / "bd"
|
|
bd_path.touch(mode=0o755)
|
|
monkeypatch.setenv("BEADS_PATH", str(bd_path))
|
|
monkeypatch.delenv("BEADS_DB", raising=False)
|
|
|
|
config = Config()
|
|
assert config.beads_db is None
|
|
|
|
def test_beads_actor_from_env(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that BEADS_ACTOR is read from environment."""
|
|
bd_path = tmp_path / "bd"
|
|
bd_path.touch(mode=0o755)
|
|
monkeypatch.setenv("BEADS_PATH", str(bd_path))
|
|
monkeypatch.setenv("BEADS_ACTOR", "test-user")
|
|
|
|
config = Config()
|
|
assert config.beads_actor == "test-user"
|
|
|
|
def test_auto_flags_default_false(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that auto-flush and auto-import default to False."""
|
|
bd_path = tmp_path / "bd"
|
|
bd_path.touch(mode=0o755)
|
|
monkeypatch.setenv("BEADS_PATH", str(bd_path))
|
|
|
|
config = Config()
|
|
assert config.beads_no_auto_flush is False
|
|
assert config.beads_no_auto_import is False
|
|
|
|
|
|
class TestLoadConfig:
|
|
"""Tests for load_config function."""
|
|
|
|
def test_load_config_success(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that load_config returns valid Config."""
|
|
bd_path = tmp_path / "bd"
|
|
bd_path.touch(mode=0o755)
|
|
monkeypatch.setenv("BEADS_PATH", str(bd_path))
|
|
|
|
config = load_config()
|
|
assert isinstance(config, Config)
|
|
assert config.beads_path == str(bd_path)
|
|
|
|
def test_load_config_error_handling(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Test that load_config raises ConfigError with helpful message."""
|
|
monkeypatch.setenv("BEADS_PATH", "/nonexistent/bd")
|
|
|
|
with pytest.raises(ConfigError, match="Configuration Error"):
|
|
load_config()
|