Reduces LLM round-trips by allowing multiple files to be read in a single tool call. Uses best-effort error handling so partial failures still return successful reads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
"""Tests for the read_many_files tool."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app.models.config import AppConfig, load_config
|
|
from app.models.tool_call import ToolResultStatus
|
|
from app.tools.filesystem import ReadManyFilesTool
|
|
|
|
|
|
@pytest.fixture
|
|
def config() -> AppConfig:
|
|
return load_config()
|
|
|
|
|
|
@pytest.fixture
|
|
def tmp_workspace(tmp_path: Path, config: AppConfig) -> tuple[Path, AppConfig]:
|
|
"""Create a temporary workspace for read_many_files tests."""
|
|
config.agent.workspace_root = tmp_path
|
|
return tmp_path, config
|
|
|
|
|
|
class TestReadManyFilesTool:
|
|
def test_read_multiple_files(self, tmp_workspace: tuple[Path, AppConfig]) -> None:
|
|
ws, cfg = tmp_workspace
|
|
(ws / "a.txt").write_text("alpha")
|
|
(ws / "b.txt").write_text("bravo")
|
|
tool = ReadManyFilesTool(ws, cfg)
|
|
result = tool.run("tc-1", {"file_paths": ["a.txt", "b.txt"]})
|
|
assert result.status == ToolResultStatus.SUCCESS
|
|
assert "=== a.txt ===" in result.output
|
|
assert "alpha" in result.output
|
|
assert "=== b.txt ===" in result.output
|
|
assert "bravo" in result.output
|
|
|
|
def test_partial_failure(self, tmp_workspace: tuple[Path, AppConfig]) -> None:
|
|
ws, cfg = tmp_workspace
|
|
(ws / "exists.txt").write_text("hello")
|
|
tool = ReadManyFilesTool(ws, cfg)
|
|
result = tool.run("tc-2", {"file_paths": ["exists.txt", "missing.txt"]})
|
|
assert result.status == ToolResultStatus.SUCCESS
|
|
assert "hello" in result.output
|
|
assert "[ERROR]" in result.output
|
|
assert "=== missing.txt ===" in result.output
|
|
|
|
def test_all_files_fail(self, tmp_workspace: tuple[Path, AppConfig]) -> None:
|
|
ws, cfg = tmp_workspace
|
|
tool = ReadManyFilesTool(ws, cfg)
|
|
result = tool.run("tc-3", {"file_paths": ["no1.txt", "no2.txt"]})
|
|
assert result.status == ToolResultStatus.ERROR
|
|
assert "All files failed" in (result.error or "")
|
|
|
|
def test_empty_file_paths(self, tmp_workspace: tuple[Path, AppConfig]) -> None:
|
|
ws, cfg = tmp_workspace
|
|
tool = ReadManyFilesTool(ws, cfg)
|
|
result = tool.run("tc-4", {"file_paths": []})
|
|
assert result.status == ToolResultStatus.ERROR
|
|
assert "empty" in (result.error or "").lower()
|
|
|
|
def test_path_security_inline_error(self, tmp_workspace: tuple[Path, AppConfig]) -> None:
|
|
ws, cfg = tmp_workspace
|
|
(ws / "safe.txt").write_text("ok")
|
|
tool = ReadManyFilesTool(ws, cfg)
|
|
result = tool.run("tc-5", {"file_paths": ["safe.txt", "../../etc/passwd"]})
|
|
assert result.status == ToolResultStatus.SUCCESS
|
|
assert "ok" in result.output
|
|
assert "[ERROR]" in result.output
|
|
assert "outside" in result.output.lower()
|