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"