File size: 6,270 Bytes
5ef3875 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | import pytest
from ycode.tools import ToolRegistry, default_tools
from ycode.tools.filesystem import EditFileTool, ListDirectoryTool, ReadFileTool, WriteFileTool
from ycode.tools.search import FindFilesTool, SearchFilesTool
@pytest.fixture
def registry():
return ToolRegistry(default_tools())
def test_read_file_numbers_lines(workspace, make_ctx, registry):
(workspace / "a.txt").write_text("one\ntwo\nthree\n")
result = registry.dispatch("read_file", {"path": "a.txt"}, make_ctx(workspace))
assert not result.is_error
assert "1\tone" in result.content and "3\tthree" in result.content
def test_read_file_window(workspace, make_ctx):
(workspace / "big.txt").write_text("\n".join(f"line{i}" for i in range(1, 101)))
result = ReadFileTool().run({"path": "big.txt", "offset": 10, "limit": 5}, make_ctx(workspace))
assert "10\tline10" in result.content and "14\tline14" in result.content
assert "line15" not in result.content
assert "Showing lines 10-14 of 100" in result.content
def test_read_missing_file_is_reported(workspace, make_ctx, registry):
result = registry.dispatch("read_file", {"path": "nope.py"}, make_ctx(workspace))
assert result.is_error and "not found" in result.content.lower()
def test_read_binary_file_rejected(workspace, make_ctx, registry):
(workspace / "img.bin").write_bytes(b"\x89PNG\x00\x00binary")
result = registry.dispatch("read_file", {"path": "img.bin"}, make_ctx(workspace))
assert result.is_error and "binary" in result.content
def test_read_sensitive_file_requires_approval(workspace, make_ctx, registry, approver):
(workspace / ".env").write_text("SECRET=1\n")
result = registry.dispatch("read_file", {"path": ".env"}, make_ctx(workspace))
assert result.is_error and "Permission denied" in result.content
assert approver.requests[0].kind == "read"
approver.answer = "yes"
result = registry.dispatch("read_file", {"path": ".env"}, make_ctx(workspace))
assert "SECRET=1" in result.content
def test_write_file_creates_dirs_and_tracks_change(workspace, make_ctx):
ctx = make_ctx(workspace)
result = WriteFileTool().run({"path": "src/new/mod.py", "content": "x = 1\n"}, ctx)
assert (workspace / "src/new/mod.py").read_text() == "x = 1\n"
assert "Created" in result.summary
assert "src/new/mod.py" in ctx.changed_files
assert result.display["diff"].startswith("--- a/src/new/mod.py")
def test_write_into_git_dir_blocked(workspace, make_ctx, registry):
result = registry.dispatch("write_file", {"path": ".git/config", "content": "x"}, make_ctx(workspace))
assert result.is_error and ".git" in result.content
def test_edit_file_replaces_unique_string(workspace, make_ctx):
path = workspace / "m.py"
path.write_text("def f():\n return 1\n")
ctx = make_ctx(workspace)
result = EditFileTool().run({"path": "m.py", "old_string": "return 1", "new_string": "return 2"}, ctx)
assert path.read_text() == "def f():\n return 2\n"
assert "+1 -1" in result.summary
assert "m.py" in ctx.changed_files
def test_edit_file_ambiguous_match(workspace, make_ctx, registry):
(workspace / "m.py").write_text("a = 1\na = 1\n")
ctx = make_ctx(workspace)
result = registry.dispatch("edit_file", {"path": "m.py", "old_string": "a = 1", "new_string": "a = 2"}, ctx)
assert result.is_error and "2 times" in result.content
result = registry.dispatch("edit_file", {"path": "m.py", "old_string": "a = 1", "new_string": "a = 2",
"replace_all": True}, ctx)
assert not result.is_error
assert (workspace / "m.py").read_text() == "a = 2\na = 2\n"
def test_edit_file_missing_string(workspace, make_ctx, registry):
(workspace / "m.py").write_text(" value = 1\n")
result = registry.dispatch("edit_file", {"path": "m.py", "old_string": "value = 2", "new_string": "x"},
make_ctx(workspace))
assert result.is_error and "not found" in result.content
def test_edit_file_identical_strings(workspace, make_ctx, registry):
(workspace / "m.py").write_text("a\n")
result = registry.dispatch("edit_file", {"path": "m.py", "old_string": "a", "new_string": "a"},
make_ctx(workspace))
assert result.is_error
def test_strict_mode_asks_before_writing(workspace, make_ctx, registry, approver):
result = registry.dispatch("write_file", {"path": "x.txt", "content": "hi"}, make_ctx(workspace, mode="strict"))
assert result.is_error and not (workspace / "x.txt").exists()
assert approver.requests[0].kind == "write"
def test_list_directory_ignores_configured_dirs(workspace, make_ctx):
(workspace / "src").mkdir()
(workspace / "src" / "main.py").write_text("")
(workspace / "node_modules" / "pkg").mkdir(parents=True)
result = ListDirectoryTool().run({}, make_ctx(workspace))
assert "src/" in result.content and "main.py" in result.content
assert "node_modules" not in result.content
def test_search_files(workspace, make_ctx):
(workspace / "src").mkdir()
(workspace / "src" / "auth.ts").write_text("export function validateToken() {}\n")
(workspace / "src" / "other.py").write_text("validateToken = None\n")
(workspace / "node_modules").mkdir()
(workspace / "node_modules" / "x.ts").write_text("validateToken\n")
ctx = make_ctx(workspace)
result = SearchFilesTool().run({"pattern": "validatetoken"}, ctx)
assert "src/auth.ts:1:" in result.content and "src/other.py:1:" in result.content
assert "node_modules" not in result.content
result = SearchFilesTool().run({"pattern": "validateToken", "glob": "*.ts"}, ctx)
assert "other.py" not in result.content
def test_search_invalid_regex(workspace, make_ctx, registry):
result = registry.dispatch("search_files", {"pattern": "("}, make_ctx(workspace))
assert result.is_error and "regular expression" in result.content
def test_find_files(workspace, make_ctx):
(workspace / "a").mkdir()
(workspace / "a" / "x.test.ts").write_text("")
(workspace / "a" / "x.ts").write_text("")
result = FindFilesTool().run({"pattern": "*.test.ts"}, make_ctx(workspace))
assert result.content.strip() == "a/x.test.ts"
|