feat(PHASE-08): 最小测试基座(config + tools 核心测试)
This commit is contained in:
parent
04ebf6bb2f
commit
c9b545538e
@ -31,3 +31,6 @@ dev = [
|
||||
"pytest>=9.0.3",
|
||||
"ruff>=0.15.10",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
|
||||
67
tests/test_config.py
Normal file
67
tests/test_config.py
Normal file
@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from cc_slim.engine import resolve_config
|
||||
|
||||
|
||||
def test_resolve_config_uses_defaults(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "env-openai-key")
|
||||
|
||||
config = resolve_config(tmp_path, {})
|
||||
|
||||
assert config.provider == "openai"
|
||||
assert config.model == "gpt-4.1-mini"
|
||||
assert config.api_key == "env-openai-key"
|
||||
assert config.base_url is None
|
||||
assert config.max_turns == 12
|
||||
|
||||
|
||||
def test_resolve_config_priority_cli_over_env_over_file(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
(tmp_path / ".cc-slim.toml").write_text(
|
||||
"""
|
||||
[cc_slim]
|
||||
provider = "anthropic"
|
||||
model = "file-model"
|
||||
api_key = "file-key"
|
||||
base_url = "https://file.example"
|
||||
max_turns = 4
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("CC_SLIM_PROVIDER", "openai")
|
||||
monkeypatch.setenv("CC_SLIM_MODEL", "env-model")
|
||||
monkeypatch.setenv("CC_SLIM_API_KEY", "env-key")
|
||||
monkeypatch.setenv("CC_SLIM_BASE_URL", "https://env.example")
|
||||
monkeypatch.setenv("CC_SLIM_MAX_TURNS", "9")
|
||||
|
||||
config = resolve_config(
|
||||
tmp_path,
|
||||
{
|
||||
"provider": "anthropic",
|
||||
"model": "cli-model",
|
||||
"api_key": "cli-key",
|
||||
"base_url": "https://cli.example",
|
||||
"max_turns": 15,
|
||||
},
|
||||
)
|
||||
|
||||
assert config.provider == "anthropic"
|
||||
assert config.model == "cli-model"
|
||||
assert config.api_key == "cli-key"
|
||||
assert config.base_url == "https://cli.example"
|
||||
assert config.max_turns == 15
|
||||
|
||||
|
||||
def test_resolve_config_requires_api_key(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
monkeypatch.delenv("CC_SLIM_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
|
||||
|
||||
with pytest.raises(ValueError, match="缺少 API key"):
|
||||
resolve_config(tmp_path, {})
|
||||
57
tests/test_tools.py
Normal file
57
tests/test_tools.py
Normal file
@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from cc_slim.tools import (
|
||||
_safe_path,
|
||||
bash_tool,
|
||||
edit_tool,
|
||||
glob_tool,
|
||||
grep_tool,
|
||||
read_tool,
|
||||
write_tool,
|
||||
)
|
||||
|
||||
|
||||
def test_safe_path_allows_workspace_children(tmp_path: Path) -> None:
|
||||
path = _safe_path(tmp_path, "a/b.txt")
|
||||
|
||||
assert path == (tmp_path / "a/b.txt").resolve()
|
||||
|
||||
|
||||
def test_safe_path_blocks_escape(tmp_path: Path) -> None:
|
||||
with pytest.raises(ValueError, match="路径越过工作区边界"):
|
||||
_safe_path(tmp_path, "../outside.txt")
|
||||
|
||||
|
||||
def test_write_edit_read_glob_grep_flow(tmp_path: Path) -> None:
|
||||
created = write_tool(tmp_path, {"path": "hello.py", "content": "print('hello')\n"})
|
||||
edited = edit_tool(tmp_path, {"path": "hello.py", "content": "print('world')\n"})
|
||||
read_back = read_tool(tmp_path, {"path": "hello.py"})
|
||||
globbed = glob_tool(tmp_path, {"pattern": "*.py"})
|
||||
grepped = grep_tool(tmp_path, {"pattern": "world", "path": "."})
|
||||
|
||||
assert created == "已创建文件: hello.py"
|
||||
assert edited == "已修改文件: hello.py"
|
||||
assert "1: print('world')" in read_back
|
||||
assert globbed == "hello.py"
|
||||
assert "hello.py:1: print('world')" in grepped
|
||||
|
||||
|
||||
def test_bash_tool_success(tmp_path: Path) -> None:
|
||||
result = bash_tool(tmp_path, {"command": "cmd /c exit 0"})
|
||||
payload = json.loads(result)
|
||||
|
||||
assert payload["returncode"] == 0
|
||||
|
||||
|
||||
def test_edit_requires_existing_file(tmp_path: Path) -> None:
|
||||
result = edit_tool(tmp_path, {"path": "missing.txt", "content": "x"})
|
||||
|
||||
assert result == "文件不存在: missing.txt"
|
||||
Loading…
Reference in New Issue
Block a user