auto-coder 0.1.362__py3-none-any.whl → 0.1.364__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/METADATA +2 -2
- {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/RECORD +65 -22
- autocoder/agent/base_agentic/__init__.py +0 -0
- autocoder/agent/base_agentic/agent_hub.py +169 -0
- autocoder/agent/base_agentic/agentic_lang.py +112 -0
- autocoder/agent/base_agentic/agentic_tool_display.py +180 -0
- autocoder/agent/base_agentic/base_agent.py +1582 -0
- autocoder/agent/base_agentic/default_tools.py +683 -0
- autocoder/agent/base_agentic/test_base_agent.py +82 -0
- autocoder/agent/base_agentic/tool_registry.py +425 -0
- autocoder/agent/base_agentic/tools/__init__.py +12 -0
- autocoder/agent/base_agentic/tools/ask_followup_question_tool_resolver.py +72 -0
- autocoder/agent/base_agentic/tools/attempt_completion_tool_resolver.py +37 -0
- autocoder/agent/base_agentic/tools/base_tool_resolver.py +35 -0
- autocoder/agent/base_agentic/tools/example_tool_resolver.py +46 -0
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +72 -0
- autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +110 -0
- autocoder/agent/base_agentic/tools/plan_mode_respond_tool_resolver.py +35 -0
- autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +54 -0
- autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +156 -0
- autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +134 -0
- autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +96 -0
- autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +79 -0
- autocoder/agent/base_agentic/tools/use_mcp_tool_resolver.py +44 -0
- autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +58 -0
- autocoder/agent/base_agentic/types.py +189 -0
- autocoder/agent/base_agentic/utils.py +100 -0
- autocoder/auto_coder_runner.py +6 -4
- autocoder/chat/conf_command.py +11 -10
- autocoder/common/__init__.py +2 -0
- autocoder/common/file_checkpoint/__init__.py +21 -0
- autocoder/common/file_checkpoint/backup.py +264 -0
- autocoder/common/file_checkpoint/examples.py +217 -0
- autocoder/common/file_checkpoint/manager.py +404 -0
- autocoder/common/file_checkpoint/models.py +156 -0
- autocoder/common/file_checkpoint/store.py +383 -0
- autocoder/common/file_checkpoint/test_backup.py +242 -0
- autocoder/common/file_checkpoint/test_manager.py +570 -0
- autocoder/common/file_checkpoint/test_models.py +360 -0
- autocoder/common/file_checkpoint/test_store.py +327 -0
- autocoder/common/file_checkpoint/test_utils.py +297 -0
- autocoder/common/file_checkpoint/utils.py +119 -0
- autocoder/common/rulefiles/autocoderrules_utils.py +138 -55
- autocoder/common/save_formatted_log.py +76 -5
- autocoder/common/v2/agent/agentic_edit.py +339 -216
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +2 -2
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +100 -5
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +160 -10
- autocoder/common/v2/agent/agentic_edit_types.py +1 -2
- autocoder/common/v2/agent/agentic_tool_display.py +2 -3
- autocoder/compilers/normal_compiler.py +64 -0
- autocoder/events/event_manager_singleton.py +133 -4
- autocoder/linters/normal_linter.py +373 -0
- autocoder/linters/python_linter.py +4 -2
- autocoder/rag/long_context_rag.py +424 -397
- autocoder/rag/test_doc_filter.py +393 -0
- autocoder/rag/test_long_context_rag.py +473 -0
- autocoder/rag/test_token_limiter.py +342 -0
- autocoder/shadows/shadow_manager.py +1 -3
- autocoder/version.py +1 -1
- {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest.mock import patch, MagicMock, call
|
|
8
|
+
|
|
9
|
+
from autocoder.common.file_checkpoint.manager import FileChangeManager
|
|
10
|
+
from autocoder.common.file_checkpoint.models import (
|
|
11
|
+
FileChange, ChangeRecord, ApplyResult, UndoResult, DiffResult
|
|
12
|
+
)
|
|
13
|
+
from autocoder.common.file_checkpoint.backup import FileBackupManager
|
|
14
|
+
from autocoder.common.file_checkpoint.store import FileChangeStore
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def temp_test_dir():
|
|
18
|
+
"""提供一个临时的测试目录"""
|
|
19
|
+
temp_dir = tempfile.mkdtemp()
|
|
20
|
+
yield temp_dir
|
|
21
|
+
shutil.rmtree(temp_dir)
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def temp_backup_dir():
|
|
25
|
+
"""提供一个临时的备份目录"""
|
|
26
|
+
temp_dir = tempfile.mkdtemp()
|
|
27
|
+
yield temp_dir
|
|
28
|
+
shutil.rmtree(temp_dir)
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def temp_store_dir():
|
|
32
|
+
"""提供一个临时的存储目录"""
|
|
33
|
+
temp_dir = tempfile.mkdtemp()
|
|
34
|
+
yield temp_dir
|
|
35
|
+
shutil.rmtree(temp_dir)
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def sample_file(temp_test_dir):
|
|
39
|
+
"""创建一个用于测试的样例文件"""
|
|
40
|
+
file_path = os.path.join(temp_test_dir, "sample.txt")
|
|
41
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
42
|
+
f.write("这是一个测试文件的内容")
|
|
43
|
+
return file_path
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
def nested_sample_file(temp_test_dir):
|
|
47
|
+
"""创建一个位于嵌套目录中的样例文件"""
|
|
48
|
+
nested_dir = os.path.join(temp_test_dir, "nested", "dir")
|
|
49
|
+
os.makedirs(nested_dir, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
file_path = os.path.join(nested_dir, "nested_sample.txt")
|
|
52
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
53
|
+
f.write("这是一个嵌套目录中的测试文件")
|
|
54
|
+
|
|
55
|
+
return file_path
|
|
56
|
+
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def sample_change():
|
|
59
|
+
"""创建一个用于测试的文件变更"""
|
|
60
|
+
return FileChange(
|
|
61
|
+
file_path="test.py",
|
|
62
|
+
content="print('hello world')",
|
|
63
|
+
is_new=True,
|
|
64
|
+
is_deletion=False
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
class TestFileChangeManager:
|
|
68
|
+
"""FileChangeManager类的单元测试"""
|
|
69
|
+
|
|
70
|
+
def test_init(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
71
|
+
"""测试初始化"""
|
|
72
|
+
manager = FileChangeManager(
|
|
73
|
+
project_dir=temp_test_dir,
|
|
74
|
+
backup_dir=temp_backup_dir,
|
|
75
|
+
store_dir=temp_store_dir
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert manager.project_dir == os.path.abspath(temp_test_dir)
|
|
79
|
+
assert isinstance(manager.backup_manager, FileBackupManager)
|
|
80
|
+
assert isinstance(manager.change_store, FileChangeStore)
|
|
81
|
+
|
|
82
|
+
def test_apply_changes_new_file(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
83
|
+
"""测试应用新文件变更"""
|
|
84
|
+
manager = FileChangeManager(
|
|
85
|
+
project_dir=temp_test_dir,
|
|
86
|
+
backup_dir=temp_backup_dir,
|
|
87
|
+
store_dir=temp_store_dir
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# 准备变更
|
|
91
|
+
test_file_path = "test_new.py"
|
|
92
|
+
change = FileChange(
|
|
93
|
+
file_path=test_file_path,
|
|
94
|
+
content="print('hello world')",
|
|
95
|
+
is_new=True
|
|
96
|
+
)
|
|
97
|
+
changes = {test_file_path: change}
|
|
98
|
+
|
|
99
|
+
# 应用变更
|
|
100
|
+
result = manager.apply_changes(changes)
|
|
101
|
+
|
|
102
|
+
# 检查结果
|
|
103
|
+
assert result.success is True
|
|
104
|
+
assert len(result.change_ids) == 1
|
|
105
|
+
assert not result.has_errors
|
|
106
|
+
|
|
107
|
+
# 检查文件是否被创建
|
|
108
|
+
expected_file_path = os.path.join(temp_test_dir, test_file_path)
|
|
109
|
+
assert os.path.exists(expected_file_path)
|
|
110
|
+
|
|
111
|
+
# 检查文件内容
|
|
112
|
+
with open(expected_file_path, 'r', encoding='utf-8') as f:
|
|
113
|
+
content = f.read()
|
|
114
|
+
assert content == "print('hello world')"
|
|
115
|
+
|
|
116
|
+
def test_apply_changes_modify_file(self, temp_test_dir, temp_backup_dir, temp_store_dir, sample_file):
|
|
117
|
+
"""测试应用修改文件变更"""
|
|
118
|
+
manager = FileChangeManager(
|
|
119
|
+
project_dir=temp_test_dir,
|
|
120
|
+
backup_dir=temp_backup_dir,
|
|
121
|
+
store_dir=temp_store_dir
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# 准备变更
|
|
125
|
+
rel_path = os.path.basename(sample_file)
|
|
126
|
+
change = FileChange(
|
|
127
|
+
file_path=rel_path,
|
|
128
|
+
content="修改后的内容",
|
|
129
|
+
is_new=False
|
|
130
|
+
)
|
|
131
|
+
changes = {rel_path: change}
|
|
132
|
+
|
|
133
|
+
# 应用变更
|
|
134
|
+
result = manager.apply_changes(changes)
|
|
135
|
+
|
|
136
|
+
# 检查结果
|
|
137
|
+
assert result.success is True
|
|
138
|
+
assert len(result.change_ids) == 1
|
|
139
|
+
assert not result.has_errors
|
|
140
|
+
|
|
141
|
+
# 检查文件内容
|
|
142
|
+
with open(sample_file, 'r', encoding='utf-8') as f:
|
|
143
|
+
content = f.read()
|
|
144
|
+
assert content == "修改后的内容"
|
|
145
|
+
|
|
146
|
+
def test_apply_changes_delete_file(self, temp_test_dir, temp_backup_dir, temp_store_dir, sample_file):
|
|
147
|
+
"""测试应用删除文件变更"""
|
|
148
|
+
manager = FileChangeManager(
|
|
149
|
+
project_dir=temp_test_dir,
|
|
150
|
+
backup_dir=temp_backup_dir,
|
|
151
|
+
store_dir=temp_store_dir
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# 准备变更
|
|
155
|
+
rel_path = os.path.basename(sample_file)
|
|
156
|
+
change = FileChange(
|
|
157
|
+
file_path=rel_path,
|
|
158
|
+
content="",
|
|
159
|
+
is_deletion=True
|
|
160
|
+
)
|
|
161
|
+
changes = {rel_path: change}
|
|
162
|
+
|
|
163
|
+
# 应用变更
|
|
164
|
+
result = manager.apply_changes(changes)
|
|
165
|
+
|
|
166
|
+
# 检查结果
|
|
167
|
+
assert result.success is True
|
|
168
|
+
assert len(result.change_ids) == 1
|
|
169
|
+
assert not result.has_errors
|
|
170
|
+
|
|
171
|
+
# 检查文件是否被删除
|
|
172
|
+
assert not os.path.exists(sample_file)
|
|
173
|
+
|
|
174
|
+
def test_apply_changes_create_nested_dirs(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
175
|
+
"""测试应用变更时创建嵌套目录"""
|
|
176
|
+
manager = FileChangeManager(
|
|
177
|
+
project_dir=temp_test_dir,
|
|
178
|
+
backup_dir=temp_backup_dir,
|
|
179
|
+
store_dir=temp_store_dir
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# 准备变更
|
|
183
|
+
nested_path = os.path.join("nested", "path", "to", "file.txt")
|
|
184
|
+
change = FileChange(
|
|
185
|
+
file_path=nested_path,
|
|
186
|
+
content="嵌套目录中的文件内容",
|
|
187
|
+
is_new=True
|
|
188
|
+
)
|
|
189
|
+
changes = {nested_path: change}
|
|
190
|
+
|
|
191
|
+
# 应用变更
|
|
192
|
+
result = manager.apply_changes(changes)
|
|
193
|
+
|
|
194
|
+
# 检查结果
|
|
195
|
+
assert result.success is True
|
|
196
|
+
assert len(result.change_ids) == 1
|
|
197
|
+
assert not result.has_errors
|
|
198
|
+
|
|
199
|
+
# 检查文件是否被创建
|
|
200
|
+
expected_file_path = os.path.join(temp_test_dir, nested_path)
|
|
201
|
+
assert os.path.exists(expected_file_path)
|
|
202
|
+
|
|
203
|
+
# 检查目录结构是否被创建
|
|
204
|
+
nested_dir = os.path.dirname(expected_file_path)
|
|
205
|
+
assert os.path.isdir(nested_dir)
|
|
206
|
+
|
|
207
|
+
def test_apply_changes_with_error(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
208
|
+
"""测试应用变更出错的情况"""
|
|
209
|
+
manager = FileChangeManager(
|
|
210
|
+
project_dir=temp_test_dir,
|
|
211
|
+
backup_dir=temp_backup_dir,
|
|
212
|
+
store_dir=temp_store_dir
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# 创建一个目标是目录的变更,这应该会导致错误
|
|
216
|
+
os.makedirs(os.path.join(temp_test_dir, "existing_dir"))
|
|
217
|
+
change = FileChange(
|
|
218
|
+
file_path="existing_dir",
|
|
219
|
+
content="这不会成功,因为目标已经是一个目录",
|
|
220
|
+
is_new=False
|
|
221
|
+
)
|
|
222
|
+
changes = {"existing_dir": change}
|
|
223
|
+
|
|
224
|
+
# 应用变更
|
|
225
|
+
result = manager.apply_changes(changes)
|
|
226
|
+
|
|
227
|
+
# 检查结果
|
|
228
|
+
assert result.success is False
|
|
229
|
+
assert result.has_errors
|
|
230
|
+
assert "existing_dir" in result.errors
|
|
231
|
+
|
|
232
|
+
def test_apply_changes_with_group_id(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
233
|
+
"""测试使用组ID应用变更"""
|
|
234
|
+
manager = FileChangeManager(
|
|
235
|
+
project_dir=temp_test_dir,
|
|
236
|
+
backup_dir=temp_backup_dir,
|
|
237
|
+
store_dir=temp_store_dir
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# 准备变更
|
|
241
|
+
change1 = FileChange(
|
|
242
|
+
file_path="file1.txt",
|
|
243
|
+
content="文件1内容"
|
|
244
|
+
)
|
|
245
|
+
change2 = FileChange(
|
|
246
|
+
file_path="file2.txt",
|
|
247
|
+
content="文件2内容"
|
|
248
|
+
)
|
|
249
|
+
changes = {
|
|
250
|
+
"file1.txt": change1,
|
|
251
|
+
"file2.txt": change2
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# 应用变更,使用自定义组ID
|
|
255
|
+
group_id = "test_group_123"
|
|
256
|
+
result = manager.apply_changes(changes, change_group_id=group_id)
|
|
257
|
+
|
|
258
|
+
# 检查结果
|
|
259
|
+
assert result.success is True
|
|
260
|
+
assert len(result.change_ids) == 2
|
|
261
|
+
|
|
262
|
+
# 检查变更记录是否使用了指定的组ID
|
|
263
|
+
for change_id in result.change_ids:
|
|
264
|
+
record = manager.change_store.get_change(change_id)
|
|
265
|
+
assert record.group_id == group_id
|
|
266
|
+
|
|
267
|
+
def test_preview_changes(self, temp_test_dir, temp_backup_dir, temp_store_dir, sample_file):
|
|
268
|
+
"""测试预览变更"""
|
|
269
|
+
manager = FileChangeManager(
|
|
270
|
+
project_dir=temp_test_dir,
|
|
271
|
+
backup_dir=temp_backup_dir,
|
|
272
|
+
store_dir=temp_store_dir
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# 准备变更
|
|
276
|
+
rel_path = os.path.basename(sample_file)
|
|
277
|
+
change1 = FileChange(
|
|
278
|
+
file_path=rel_path,
|
|
279
|
+
content="修改后的内容"
|
|
280
|
+
)
|
|
281
|
+
change2 = FileChange(
|
|
282
|
+
file_path="new_file.txt",
|
|
283
|
+
content="新文件内容",
|
|
284
|
+
is_new=True
|
|
285
|
+
)
|
|
286
|
+
change3 = FileChange(
|
|
287
|
+
file_path="to_be_deleted.txt",
|
|
288
|
+
content="",
|
|
289
|
+
is_deletion=True
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# 创建to_be_deleted.txt文件
|
|
293
|
+
delete_file_path = os.path.join(temp_test_dir, "to_be_deleted.txt")
|
|
294
|
+
with open(delete_file_path, 'w', encoding='utf-8') as f:
|
|
295
|
+
f.write("将被删除的文件")
|
|
296
|
+
|
|
297
|
+
changes = {
|
|
298
|
+
rel_path: change1,
|
|
299
|
+
"new_file.txt": change2,
|
|
300
|
+
"to_be_deleted.txt": change3
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# 预览变更
|
|
304
|
+
diff_results = manager.preview_changes(changes)
|
|
305
|
+
|
|
306
|
+
# 检查结果
|
|
307
|
+
assert len(diff_results) == 3
|
|
308
|
+
|
|
309
|
+
# 检查修改文件的差异
|
|
310
|
+
assert rel_path in diff_results
|
|
311
|
+
modify_diff = diff_results[rel_path]
|
|
312
|
+
assert modify_diff.file_path == rel_path
|
|
313
|
+
assert modify_diff.old_content == "这是一个测试文件的内容"
|
|
314
|
+
assert modify_diff.new_content == "修改后的内容"
|
|
315
|
+
assert not modify_diff.is_new
|
|
316
|
+
assert not modify_diff.is_deletion
|
|
317
|
+
|
|
318
|
+
# 检查新文件的差异
|
|
319
|
+
assert "new_file.txt" in diff_results
|
|
320
|
+
new_diff = diff_results["new_file.txt"]
|
|
321
|
+
assert new_diff.file_path == "new_file.txt"
|
|
322
|
+
assert new_diff.old_content is None
|
|
323
|
+
assert new_diff.new_content == "新文件内容"
|
|
324
|
+
assert new_diff.is_new
|
|
325
|
+
assert not new_diff.is_deletion
|
|
326
|
+
|
|
327
|
+
# 检查删除文件的差异
|
|
328
|
+
assert "to_be_deleted.txt" in diff_results
|
|
329
|
+
delete_diff = diff_results["to_be_deleted.txt"]
|
|
330
|
+
assert delete_diff.file_path == "to_be_deleted.txt"
|
|
331
|
+
assert delete_diff.old_content in ["将被删除的文件", None]
|
|
332
|
+
assert delete_diff.new_content == ""
|
|
333
|
+
assert not delete_diff.is_new
|
|
334
|
+
assert delete_diff.is_deletion
|
|
335
|
+
|
|
336
|
+
def test_get_diff_text(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
337
|
+
"""测试获取差异文本"""
|
|
338
|
+
manager = FileChangeManager(
|
|
339
|
+
project_dir=temp_test_dir,
|
|
340
|
+
backup_dir=temp_backup_dir,
|
|
341
|
+
store_dir=temp_store_dir
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
old_content = "line1\nline2\nline3\n"
|
|
345
|
+
new_content = "line1\nline2 modified\nline3\nline4\n"
|
|
346
|
+
|
|
347
|
+
diff_text = manager.get_diff_text(old_content, new_content)
|
|
348
|
+
|
|
349
|
+
# 检查差异文本
|
|
350
|
+
assert "line2" in diff_text
|
|
351
|
+
assert "line2 modified" in diff_text
|
|
352
|
+
assert "line4" in diff_text
|
|
353
|
+
|
|
354
|
+
def test_undo_last_change(self, temp_test_dir, temp_backup_dir, temp_store_dir, sample_file):
|
|
355
|
+
"""测试撤销最近的变更"""
|
|
356
|
+
manager = FileChangeManager(
|
|
357
|
+
project_dir=temp_test_dir,
|
|
358
|
+
backup_dir=temp_backup_dir,
|
|
359
|
+
store_dir=temp_store_dir
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# 应用变更
|
|
363
|
+
rel_path = os.path.basename(sample_file)
|
|
364
|
+
change = FileChange(
|
|
365
|
+
file_path=rel_path,
|
|
366
|
+
content="修改后的内容"
|
|
367
|
+
)
|
|
368
|
+
changes = {rel_path: change}
|
|
369
|
+
|
|
370
|
+
apply_result = manager.apply_changes(changes)
|
|
371
|
+
assert apply_result.success is True
|
|
372
|
+
|
|
373
|
+
# 检查文件已被修改
|
|
374
|
+
with open(sample_file, 'r', encoding='utf-8') as f:
|
|
375
|
+
content = f.read()
|
|
376
|
+
assert content == "修改后的内容"
|
|
377
|
+
|
|
378
|
+
# 撤销最近的变更
|
|
379
|
+
undo_result = manager.undo_last_change()
|
|
380
|
+
|
|
381
|
+
# 检查撤销结果
|
|
382
|
+
assert undo_result.success is True
|
|
383
|
+
assert len(undo_result.restored_files) == 1
|
|
384
|
+
assert rel_path in undo_result.restored_files
|
|
385
|
+
|
|
386
|
+
# 检查文件内容是否被恢复
|
|
387
|
+
with open(sample_file, 'r', encoding='utf-8') as f:
|
|
388
|
+
content = f.read()
|
|
389
|
+
assert content == "这是一个测试文件的内容"
|
|
390
|
+
|
|
391
|
+
def test_undo_change_group(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
392
|
+
"""测试撤销变更组"""
|
|
393
|
+
manager = FileChangeManager(
|
|
394
|
+
project_dir=temp_test_dir,
|
|
395
|
+
backup_dir=temp_backup_dir,
|
|
396
|
+
store_dir=temp_store_dir
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# 准备变更
|
|
400
|
+
change1 = FileChange(
|
|
401
|
+
file_path="file1.txt",
|
|
402
|
+
content="文件1内容"
|
|
403
|
+
)
|
|
404
|
+
change2 = FileChange(
|
|
405
|
+
file_path="file2.txt",
|
|
406
|
+
content="文件2内容"
|
|
407
|
+
)
|
|
408
|
+
changes = {
|
|
409
|
+
"file1.txt": change1,
|
|
410
|
+
"file2.txt": change2
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
# 应用变更,使用自定义组ID
|
|
414
|
+
group_id = "test_group_456"
|
|
415
|
+
apply_result = manager.apply_changes(changes, change_group_id=group_id)
|
|
416
|
+
assert apply_result.success is True
|
|
417
|
+
|
|
418
|
+
# 检查文件已被创建
|
|
419
|
+
file1_path = os.path.join(temp_test_dir, "file1.txt")
|
|
420
|
+
file2_path = os.path.join(temp_test_dir, "file2.txt")
|
|
421
|
+
assert os.path.exists(file1_path)
|
|
422
|
+
assert os.path.exists(file2_path)
|
|
423
|
+
|
|
424
|
+
# 撤销变更组
|
|
425
|
+
undo_result = manager.undo_change_group(group_id)
|
|
426
|
+
|
|
427
|
+
# 检查撤销结果
|
|
428
|
+
assert undo_result.success is True
|
|
429
|
+
assert len(undo_result.restored_files) == 2
|
|
430
|
+
assert "file1.txt" in undo_result.restored_files
|
|
431
|
+
assert "file2.txt" in undo_result.restored_files
|
|
432
|
+
|
|
433
|
+
# 检查文件是否被删除(因为是新文件)
|
|
434
|
+
assert not os.path.exists(file1_path)
|
|
435
|
+
assert not os.path.exists(file2_path)
|
|
436
|
+
|
|
437
|
+
def test_undo_to_version(self, temp_test_dir, temp_backup_dir, temp_store_dir, sample_file):
|
|
438
|
+
"""测试撤销到指定版本"""
|
|
439
|
+
manager = FileChangeManager(
|
|
440
|
+
project_dir=temp_test_dir,
|
|
441
|
+
backup_dir=temp_backup_dir,
|
|
442
|
+
store_dir=temp_store_dir
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# 应用多个变更
|
|
446
|
+
rel_path = os.path.basename(sample_file)
|
|
447
|
+
changes1 = {rel_path: FileChange(file_path=rel_path, content="第一次修改")}
|
|
448
|
+
changes2 = {rel_path: FileChange(file_path=rel_path, content="第二次修改")}
|
|
449
|
+
changes3 = {rel_path: FileChange(file_path=rel_path, content="第三次修改")}
|
|
450
|
+
|
|
451
|
+
result1 = manager.apply_changes(changes1)
|
|
452
|
+
result2 = manager.apply_changes(changes2)
|
|
453
|
+
result3 = manager.apply_changes(changes3)
|
|
454
|
+
|
|
455
|
+
assert result1.success and result2.success and result3.success
|
|
456
|
+
|
|
457
|
+
# 获取变更历史
|
|
458
|
+
history = manager.get_change_history()
|
|
459
|
+
assert len(history) >= 3
|
|
460
|
+
|
|
461
|
+
# 撤销到第一次变更后的版本
|
|
462
|
+
version_id = result1.change_ids[0]
|
|
463
|
+
undo_result = manager.undo_to_version(version_id)
|
|
464
|
+
|
|
465
|
+
# 检查撤销结果
|
|
466
|
+
assert undo_result.success is True
|
|
467
|
+
assert len(undo_result.restored_files) >= 1
|
|
468
|
+
assert rel_path in undo_result.restored_files
|
|
469
|
+
|
|
470
|
+
# 检查文件内容是否被恢复到第一次修改后的状态
|
|
471
|
+
with open(sample_file, 'r', encoding='utf-8') as f:
|
|
472
|
+
content = f.read()
|
|
473
|
+
assert content == "第一次修改"
|
|
474
|
+
|
|
475
|
+
def test_get_change_history(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
476
|
+
"""测试获取变更历史"""
|
|
477
|
+
manager = FileChangeManager(
|
|
478
|
+
project_dir=temp_test_dir,
|
|
479
|
+
backup_dir=temp_backup_dir,
|
|
480
|
+
store_dir=temp_store_dir
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# 模拟存储器的get_latest_changes方法
|
|
484
|
+
mock_records = [
|
|
485
|
+
ChangeRecord.create(file_path="file1.txt", backup_id="backup1"),
|
|
486
|
+
ChangeRecord.create(file_path="file2.txt", backup_id="backup2")
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
with patch.object(manager.change_store, 'get_latest_changes', return_value=mock_records) as mock_method:
|
|
490
|
+
history = manager.get_change_history(limit=5)
|
|
491
|
+
|
|
492
|
+
# 检查是否调用了正确的方法
|
|
493
|
+
mock_method.assert_called_once_with(5)
|
|
494
|
+
|
|
495
|
+
# 检查结果
|
|
496
|
+
assert len(history) == 2
|
|
497
|
+
assert history[0].file_path == "file1.txt"
|
|
498
|
+
assert history[1].file_path == "file2.txt"
|
|
499
|
+
|
|
500
|
+
def test_get_file_history(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
501
|
+
"""测试获取文件变更历史"""
|
|
502
|
+
manager = FileChangeManager(
|
|
503
|
+
project_dir=temp_test_dir,
|
|
504
|
+
backup_dir=temp_backup_dir,
|
|
505
|
+
store_dir=temp_store_dir
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
# 模拟存储器的get_changes_by_file方法
|
|
509
|
+
mock_records = [
|
|
510
|
+
ChangeRecord.create(file_path="test.py", backup_id="backup1"),
|
|
511
|
+
ChangeRecord.create(file_path="test.py", backup_id="backup2")
|
|
512
|
+
]
|
|
513
|
+
|
|
514
|
+
with patch.object(manager.change_store, 'get_changes_by_file', return_value=mock_records) as mock_method:
|
|
515
|
+
history = manager.get_file_history("test.py", limit=5)
|
|
516
|
+
|
|
517
|
+
# 检查是否调用了正确的方法
|
|
518
|
+
mock_method.assert_called_once_with("test.py", 5)
|
|
519
|
+
|
|
520
|
+
# 检查结果
|
|
521
|
+
assert len(history) == 2
|
|
522
|
+
assert history[0].file_path == "test.py"
|
|
523
|
+
assert history[1].file_path == "test.py"
|
|
524
|
+
|
|
525
|
+
def test_get_change_groups(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
526
|
+
"""测试获取变更组"""
|
|
527
|
+
manager = FileChangeManager(
|
|
528
|
+
project_dir=temp_test_dir,
|
|
529
|
+
backup_dir=temp_backup_dir,
|
|
530
|
+
store_dir=temp_store_dir
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
# 模拟存储器的get_change_groups方法
|
|
534
|
+
mock_groups = [
|
|
535
|
+
("group1", 1000.0, 2),
|
|
536
|
+
("group2", 2000.0, 3)
|
|
537
|
+
]
|
|
538
|
+
|
|
539
|
+
with patch.object(manager.change_store, 'get_change_groups', return_value=mock_groups) as mock_method:
|
|
540
|
+
groups = manager.get_change_groups(limit=5)
|
|
541
|
+
|
|
542
|
+
# 检查是否调用了正确的方法
|
|
543
|
+
mock_method.assert_called_once_with(5)
|
|
544
|
+
|
|
545
|
+
# 检查结果
|
|
546
|
+
assert len(groups) == 2
|
|
547
|
+
assert groups[0][0] == "group1"
|
|
548
|
+
assert groups[1][0] == "group2"
|
|
549
|
+
|
|
550
|
+
def test_get_absolute_path(self, temp_test_dir, temp_backup_dir, temp_store_dir):
|
|
551
|
+
"""测试获取绝对路径"""
|
|
552
|
+
manager = FileChangeManager(
|
|
553
|
+
project_dir=temp_test_dir,
|
|
554
|
+
backup_dir=temp_backup_dir,
|
|
555
|
+
store_dir=temp_store_dir
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# 测试相对路径
|
|
559
|
+
rel_path = "test/path.txt"
|
|
560
|
+
abs_path = manager._get_absolute_path(rel_path)
|
|
561
|
+
|
|
562
|
+
expected_path = os.path.join(temp_test_dir, rel_path)
|
|
563
|
+
assert abs_path == expected_path
|
|
564
|
+
|
|
565
|
+
# 测试绝对路径
|
|
566
|
+
abs_path_input = os.path.abspath("/some/abs/path.txt")
|
|
567
|
+
abs_path_output = manager._get_absolute_path(abs_path_input)
|
|
568
|
+
|
|
569
|
+
# 应该保持原样
|
|
570
|
+
assert abs_path_output == abs_path_input
|