mojentic 0.7.1__py3-none-any.whl → 0.7.2__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.
@@ -0,0 +1,723 @@
1
+ import os
2
+ import re
3
+ import glob
4
+ import tempfile
5
+ import shutil
6
+ from unittest.mock import Mock, patch
7
+
8
+ import pytest
9
+
10
+ from mojentic.llm.tools.file_manager import (
11
+ FilesystemGateway, FileManager, ListFilesTool, ReadFileTool, WriteFileTool,
12
+ ListAllFilesTool, FindFilesByGlobTool, FindFilesContainingTool, FindLinesMatchingTool,
13
+ EditFileWithDiffTool, CreateDirectoryTool
14
+ )
15
+
16
+
17
+ @pytest.fixture
18
+ def temp_dir():
19
+ """Create a temporary directory for testing."""
20
+ temp_dir = tempfile.mkdtemp()
21
+ yield temp_dir
22
+ shutil.rmtree(temp_dir)
23
+
24
+
25
+ @pytest.fixture
26
+ def fs_gateway(temp_dir):
27
+ """Create a FilesystemGateway with a temporary directory."""
28
+ return FilesystemGateway(base_path=temp_dir)
29
+
30
+
31
+ @pytest.fixture
32
+ def file_manager(fs_gateway):
33
+ """Create a FileManager with a filesystem gateway."""
34
+ return FileManager(fs=fs_gateway)
35
+
36
+
37
+ @pytest.fixture
38
+ def setup_test_files(temp_dir):
39
+ """Set up test files in the temporary directory."""
40
+ # Create a directory structure
41
+ os.makedirs(os.path.join(temp_dir, "subdir"), exist_ok=True)
42
+
43
+ # Create test files
44
+ with open(os.path.join(temp_dir, "test1.txt"), "w") as f:
45
+ f.write("This is test file 1\nIt has multiple lines\nFor testing purposes")
46
+
47
+ with open(os.path.join(temp_dir, "test2.py"), "w") as f:
48
+ f.write("def test_function():\n return 'Hello, World!'")
49
+
50
+ with open(os.path.join(temp_dir, "subdir", "test3.txt"), "w") as f:
51
+ f.write("This is a test file in a subdirectory")
52
+
53
+ return temp_dir
54
+
55
+
56
+ class DescribeFilesystemGateway:
57
+
58
+ def should_resolve_path_correctly(self, fs_gateway, temp_dir):
59
+ """
60
+ Given a FilesystemGateway
61
+ When resolving a path
62
+ Then it should correctly join with the base path
63
+ """
64
+ resolved_path = fs_gateway._resolve_path("test.txt")
65
+ expected_path = os.path.normpath(os.path.join(temp_dir, "test.txt"))
66
+ assert resolved_path == expected_path
67
+
68
+ def should_prevent_path_traversal(self, fs_gateway):
69
+ """
70
+ Given a FilesystemGateway
71
+ When resolving a path that attempts to escape the sandbox
72
+ Then it should raise a ValueError
73
+ """
74
+ with pytest.raises(ValueError):
75
+ fs_gateway._resolve_path("../../../etc/passwd")
76
+
77
+ def should_list_files(self, fs_gateway, setup_test_files):
78
+ """
79
+ Given a FilesystemGateway with test files
80
+ When listing files in the root directory
81
+ Then it should return the correct list of files
82
+ """
83
+ files = fs_gateway.ls("")
84
+ assert sorted(files) == sorted(["subdir", "test1.txt", "test2.py"])
85
+
86
+ def should_list_all_files_recursively(self, fs_gateway, setup_test_files):
87
+ """
88
+ Given a FilesystemGateway with test files
89
+ When listing all files recursively
90
+ Then it should return all files including those in subdirectories
91
+ """
92
+ all_files = fs_gateway.list_all_files("")
93
+ assert sorted(all_files) == sorted(["test1.txt", "test2.py", os.path.join("subdir", "test3.txt")])
94
+
95
+ def should_find_files_by_glob(self, fs_gateway, setup_test_files):
96
+ """
97
+ Given a FilesystemGateway with test files
98
+ When finding files by glob pattern
99
+ Then it should return files matching the pattern
100
+ """
101
+ txt_files = fs_gateway.find_files_by_glob("", "*.txt")
102
+ assert sorted(txt_files) == sorted(["test1.txt"])
103
+
104
+ all_txt_files = fs_gateway.find_files_by_glob("", "**/*.txt")
105
+ assert sorted(all_txt_files) == sorted(["test1.txt", os.path.join("subdir", "test3.txt")])
106
+
107
+ def should_find_files_containing(self, fs_gateway, setup_test_files):
108
+ """
109
+ Given a FilesystemGateway with test files
110
+ When finding files containing a pattern
111
+ Then it should return files with content matching the pattern
112
+ """
113
+ files_with_hello = fs_gateway.find_files_containing("", "Hello")
114
+ assert files_with_hello == ["test2.py"]
115
+
116
+ files_with_test = fs_gateway.find_files_containing("", "test file")
117
+ assert sorted(files_with_test) == sorted(["test1.txt", os.path.join("subdir", "test3.txt")])
118
+
119
+ def should_find_lines_matching(self, fs_gateway, setup_test_files):
120
+ """
121
+ Given a FilesystemGateway with test files
122
+ When finding lines matching a pattern in a file
123
+ Then it should return the matching lines with line numbers
124
+ """
125
+ matching_lines = fs_gateway.find_lines_matching("", "test1.txt", "multiple")
126
+ assert matching_lines == [{"line_number": 2, "content": "It has multiple lines"}]
127
+
128
+ def should_edit_file_with_diff(self, fs_gateway, setup_test_files):
129
+ """
130
+ Given a FilesystemGateway with test files
131
+ When editing a file with a diff
132
+ Then it should apply the changes correctly
133
+ """
134
+ # Create a diff that changes "test file 1" to "modified file 1"
135
+ diff = """--- test1.txt
136
+ +++ test1.txt
137
+ @@ -1,3 +1,3 @@
138
+ -This is test file 1
139
+ +This is modified file 1
140
+ It has multiple lines
141
+ For testing purposes"""
142
+
143
+ result = fs_gateway.edit_file_with_diff("", "test1.txt", diff)
144
+ assert "Successfully" in result
145
+
146
+ # Verify the file was modified
147
+ with open(os.path.join(setup_test_files, "test1.txt"), "r") as f:
148
+ content = f.read()
149
+
150
+ assert "This is modified file 1" in content
151
+ assert "This is test file 1" not in content
152
+ assert "It has multiple lines" in content
153
+ assert "For testing purposes" in content
154
+
155
+ def should_edit_file_with_diff_adding_lines(self, fs_gateway, setup_test_files):
156
+ """
157
+ Given a FilesystemGateway with test files
158
+ When editing a file with a diff that adds lines
159
+ Then it should add the lines while preserving the rest of the content
160
+ """
161
+ # Create a test file with known content
162
+ test_file_path = os.path.join(setup_test_files, "add_lines_test.txt")
163
+ with open(test_file_path, 'w') as f:
164
+ f.write("Line 1\nLine 2\nLine 3\n")
165
+
166
+ # Create a diff that adds a new line after Line 2
167
+ diff = """--- add_lines_test.txt
168
+ +++ add_lines_test.txt
169
+ @@ -1,3 +1,4 @@
170
+ Line 1
171
+ Line 2
172
+ +New Line 2.5
173
+ Line 3
174
+ """
175
+
176
+ result = fs_gateway.edit_file_with_diff("", "add_lines_test.txt", diff)
177
+ assert "Successfully" in result
178
+
179
+ # Verify the file was modified correctly
180
+ with open(test_file_path, "r") as f:
181
+ content = f.read()
182
+
183
+ assert "Line 1\nLine 2\nNew Line 2.5\nLine 3\n" == content
184
+
185
+ def should_edit_file_with_diff_removing_lines(self, fs_gateway, setup_test_files):
186
+ """
187
+ Given a FilesystemGateway with test files
188
+ When editing a file with a diff that removes lines
189
+ Then it should remove the lines while preserving the rest of the content
190
+ """
191
+ # Create a test file with known content
192
+ test_file_path = os.path.join(setup_test_files, "remove_lines_test.txt")
193
+ with open(test_file_path, 'w') as f:
194
+ f.write("Line 1\nLine 2\nLine 3\nLine 4\n")
195
+
196
+ # Create a diff that removes Line 3
197
+ diff = """--- remove_lines_test.txt
198
+ +++ remove_lines_test.txt
199
+ @@ -1,4 +1,3 @@
200
+ Line 1
201
+ Line 2
202
+ -Line 3
203
+ Line 4
204
+ """
205
+
206
+ result = fs_gateway.edit_file_with_diff("", "remove_lines_test.txt", diff)
207
+ assert "Successfully" in result
208
+
209
+ # Verify the file was modified correctly
210
+ with open(test_file_path, "r") as f:
211
+ content = f.read()
212
+
213
+ assert "Line 1\nLine 2\nLine 4\n" == content
214
+
215
+ def should_edit_file_with_diff_multiple_hunks(self, fs_gateway, setup_test_files):
216
+ """
217
+ Given a FilesystemGateway with test files
218
+ When editing a file with a diff that contains multiple hunks
219
+ Then it should apply all changes correctly
220
+ """
221
+ # Create a test file with known content
222
+ test_file_path = os.path.join(setup_test_files, "multiple_hunks_test.txt")
223
+ with open(test_file_path, 'w') as f:
224
+ f.write("Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\n")
225
+
226
+ # Create a diff with multiple hunks
227
+ diff = """--- multiple_hunks_test.txt
228
+ +++ multiple_hunks_test.txt
229
+ @@ -1,3 +1,3 @@
230
+ Line 1
231
+ -Line 2
232
+ +Modified Line 2
233
+ Line 3
234
+ @@ -4,3 +4,4 @@
235
+ Line 4
236
+ Line 5
237
+ +New Line 5.5
238
+ Line 6
239
+ """
240
+
241
+ result = fs_gateway.edit_file_with_diff("", "multiple_hunks_test.txt", diff)
242
+ assert "Successfully" in result
243
+
244
+ # Verify the file was modified correctly
245
+ with open(test_file_path, "r") as f:
246
+ content = f.read()
247
+
248
+ expected = "Line 1\nModified Line 2\nLine 3\nLine 4\nLine 5\nNew Line 5.5\nLine 6\n"
249
+ assert expected == content
250
+
251
+ def should_read_file(self, fs_gateway, setup_test_files):
252
+ """
253
+ Given a FilesystemGateway with test files
254
+ When reading a file
255
+ Then it should return the file content
256
+ """
257
+ content = fs_gateway.read("", "test1.txt")
258
+ assert "This is test file 1" in content
259
+
260
+ def should_write_file(self, fs_gateway, setup_test_files):
261
+ """
262
+ Given a FilesystemGateway with test files
263
+ When writing to a file
264
+ Then it should update the file content
265
+ """
266
+ fs_gateway.write("", "new_file.txt", "This is a new file")
267
+
268
+ # Verify the file was created
269
+ with open(os.path.join(setup_test_files, "new_file.txt"), "r") as f:
270
+ content = f.read()
271
+
272
+ assert content == "This is a new file"
273
+
274
+
275
+ class DescribeFileManager:
276
+
277
+ def should_list_files_with_extension(self, file_manager, setup_test_files):
278
+ """
279
+ Given a FileManager with test files
280
+ When listing files with a specific extension
281
+ Then it should return only files with that extension
282
+ """
283
+ txt_files = file_manager.ls("", ".txt")
284
+ assert sorted(txt_files) == sorted(["test1.txt"])
285
+
286
+ def should_list_all_files(self, file_manager, setup_test_files):
287
+ """
288
+ Given a FileManager with test files
289
+ When listing all files
290
+ Then it should return all files recursively
291
+ """
292
+ all_files = file_manager.list_all_files("")
293
+ assert sorted(all_files) == sorted(["test1.txt", "test2.py", os.path.join("subdir", "test3.txt")])
294
+
295
+ def should_find_files_by_glob(self, file_manager, setup_test_files):
296
+ """
297
+ Given a FileManager with test files
298
+ When finding files by glob pattern
299
+ Then it should return files matching the pattern
300
+ """
301
+ py_files = file_manager.find_files_by_glob("", "*.py")
302
+ assert py_files == ["test2.py"]
303
+
304
+ def should_find_files_containing(self, file_manager, setup_test_files):
305
+ """
306
+ Given a FileManager with test files
307
+ When finding files containing a pattern
308
+ Then it should return files with content matching the pattern
309
+ """
310
+ files_with_multiple = file_manager.find_files_containing("", "multiple")
311
+ assert files_with_multiple == ["test1.txt"]
312
+
313
+ def should_find_lines_matching(self, file_manager, setup_test_files):
314
+ """
315
+ Given a FileManager with test files
316
+ When finding lines matching a pattern in a file
317
+ Then it should return the matching lines with line numbers
318
+ """
319
+ matching_lines = file_manager.find_lines_matching("", "test1.txt", "testing")
320
+ assert matching_lines == [{"line_number": 3, "content": "For testing purposes"}]
321
+
322
+ def should_edit_file_with_diff(self, file_manager, setup_test_files):
323
+ """
324
+ Given a FileManager with test files
325
+ When editing a file with a diff
326
+ Then it should apply the changes correctly
327
+ """
328
+ # Create a diff that changes "test file 1" to "edited file 1"
329
+ diff = """--- test1.txt
330
+ +++ test1.txt
331
+ @@ -1,3 +1,3 @@
332
+ -This is test file 1
333
+ +This is edited file 1
334
+ It has multiple lines
335
+ For testing purposes"""
336
+
337
+ result = file_manager.edit_file_with_diff("", "test1.txt", diff)
338
+ assert "Successfully" in result
339
+
340
+ # Verify the file was modified
341
+ content = file_manager.read("", "test1.txt")
342
+ assert "This is edited file 1" in content
343
+
344
+ def should_read_file(self, file_manager, setup_test_files):
345
+ """
346
+ Given a FileManager with test files
347
+ When reading a file
348
+ Then it should return the file content
349
+ """
350
+ content = file_manager.read("", "test2.py")
351
+ assert "def test_function()" in content
352
+
353
+ def should_write_file(self, file_manager, setup_test_files):
354
+ """
355
+ Given a FileManager with test files
356
+ When writing to a file
357
+ Then it should update the file content
358
+ """
359
+ file_manager.write("", "new_file2.txt", "This is another new file")
360
+
361
+ # Verify the file was created
362
+ content = file_manager.read("", "new_file2.txt")
363
+ assert content == "This is another new file"
364
+
365
+
366
+ class DescribeListFilesTool:
367
+
368
+ def should_list_files_with_extension(self, setup_test_files):
369
+ """
370
+ Given a ListFilesTool
371
+ When running with an extension
372
+ Then it should return files with that extension
373
+ """
374
+ fs = FilesystemGateway(base_path=setup_test_files)
375
+ tool = ListFilesTool(fs)
376
+ result = tool.run(path="", extension=".txt")
377
+ assert sorted(result) == sorted(["test1.txt"])
378
+
379
+ def should_have_correct_descriptor(self):
380
+ """
381
+ Given a ListFilesTool
382
+ When accessing its descriptor
383
+ Then it should have the correct structure
384
+ """
385
+ fs = FilesystemGateway(base_path="/tmp")
386
+ tool = ListFilesTool(fs)
387
+ descriptor = tool.descriptor
388
+ assert descriptor["type"] == "function"
389
+ assert descriptor["function"]["name"] == "list_files"
390
+ assert "path" in descriptor["function"]["parameters"]["properties"]
391
+ assert "extension" in descriptor["function"]["parameters"]["properties"]
392
+
393
+
394
+ class DescribeReadFileTool:
395
+
396
+ def should_read_file_content(self, setup_test_files):
397
+ """
398
+ Given a ReadFileTool
399
+ When running with a path
400
+ Then it should return the file content
401
+ """
402
+ fs = FilesystemGateway(base_path=setup_test_files)
403
+ tool = ReadFileTool(fs)
404
+ result = tool.run(path="test1.txt")
405
+ assert "This is test file 1" in result
406
+
407
+ def should_have_correct_descriptor(self):
408
+ """
409
+ Given a ReadFileTool
410
+ When accessing its descriptor
411
+ Then it should have the correct structure
412
+ """
413
+ fs = FilesystemGateway(base_path="/tmp")
414
+ tool = ReadFileTool(fs)
415
+ descriptor = tool.descriptor
416
+ assert descriptor["type"] == "function"
417
+ assert descriptor["function"]["name"] == "read_file"
418
+ assert "path" in descriptor["function"]["parameters"]["properties"]
419
+ assert "file_name" not in descriptor["function"]["parameters"]["properties"]
420
+
421
+
422
+ class DescribeWriteFileTool:
423
+
424
+ def should_write_file_content(self, setup_test_files):
425
+ """
426
+ Given a WriteFileTool
427
+ When running with a path and content
428
+ Then it should write the content to the file
429
+ """
430
+ fs = FilesystemGateway(base_path=setup_test_files)
431
+ tool = WriteFileTool(fs)
432
+ result = tool.run(path="write_test.txt", content="Test content")
433
+ assert "Successfully" in result
434
+
435
+ # Verify the file was created
436
+ with open(os.path.join(setup_test_files, "write_test.txt"), "r") as f:
437
+ content = f.read()
438
+ assert content == "Test content"
439
+
440
+ def should_have_correct_descriptor(self):
441
+ """
442
+ Given a WriteFileTool
443
+ When accessing its descriptor
444
+ Then it should have the correct structure
445
+ """
446
+ fs = FilesystemGateway(base_path="/tmp")
447
+ tool = WriteFileTool(fs)
448
+ descriptor = tool.descriptor
449
+ assert descriptor["type"] == "function"
450
+ assert descriptor["function"]["name"] == "write_file"
451
+ assert "path" in descriptor["function"]["parameters"]["properties"]
452
+ assert "content" in descriptor["function"]["parameters"]["properties"]
453
+ assert "file_name" not in descriptor["function"]["parameters"]["properties"]
454
+
455
+
456
+ class DescribeListAllFilesTool:
457
+
458
+ def should_list_all_files(self, setup_test_files):
459
+ """
460
+ Given a ListAllFilesTool
461
+ When running
462
+ Then it should return all files recursively
463
+ """
464
+ fs = FilesystemGateway(base_path=setup_test_files)
465
+ tool = ListAllFilesTool(fs)
466
+ result = tool.run(path="")
467
+ assert sorted(result) == sorted(["test1.txt", "test2.py", os.path.join("subdir", "test3.txt")])
468
+
469
+ def should_have_correct_descriptor(self):
470
+ """
471
+ Given a ListAllFilesTool
472
+ When accessing its descriptor
473
+ Then it should have the correct structure
474
+ """
475
+ fs = FilesystemGateway(base_path="/tmp")
476
+ tool = ListAllFilesTool(fs)
477
+ descriptor = tool.descriptor
478
+ assert descriptor["type"] == "function"
479
+ assert descriptor["function"]["name"] == "list_all_files"
480
+ assert "path" in descriptor["function"]["parameters"]["properties"]
481
+
482
+
483
+ class DescribeFindFilesByGlobTool:
484
+
485
+ def should_find_files_by_glob_pattern(self, setup_test_files):
486
+ """
487
+ Given a FindFilesByGlobTool
488
+ When running with a glob pattern
489
+ Then it should return files matching the pattern
490
+ """
491
+ fs = FilesystemGateway(base_path=setup_test_files)
492
+ tool = FindFilesByGlobTool(fs)
493
+ result = tool.run(path="", pattern="*.py")
494
+ assert result == ["test2.py"]
495
+
496
+ def should_have_correct_descriptor(self):
497
+ """
498
+ Given a FindFilesByGlobTool
499
+ When accessing its descriptor
500
+ Then it should have the correct structure
501
+ """
502
+ fs = FilesystemGateway(base_path="/tmp")
503
+ tool = FindFilesByGlobTool(fs)
504
+ descriptor = tool.descriptor
505
+ assert descriptor["type"] == "function"
506
+ assert descriptor["function"]["name"] == "find_files_by_glob"
507
+ assert "path" in descriptor["function"]["parameters"]["properties"]
508
+ assert "pattern" in descriptor["function"]["parameters"]["properties"]
509
+
510
+
511
+ class DescribeFindFilesContainingTool:
512
+
513
+ def should_find_files_containing_pattern(self, setup_test_files):
514
+ """
515
+ Given a FindFilesContainingTool
516
+ When running with a regex pattern
517
+ Then it should return files containing text matching the pattern
518
+ """
519
+ fs = FilesystemGateway(base_path=setup_test_files)
520
+ tool = FindFilesContainingTool(fs)
521
+ result = tool.run(path="", pattern="Hello")
522
+ assert result == ["test2.py"]
523
+
524
+ def should_have_correct_descriptor(self):
525
+ """
526
+ Given a FindFilesContainingTool
527
+ When accessing its descriptor
528
+ Then it should have the correct structure
529
+ """
530
+ fs = FilesystemGateway(base_path="/tmp")
531
+ tool = FindFilesContainingTool(fs)
532
+ descriptor = tool.descriptor
533
+ assert descriptor["type"] == "function"
534
+ assert descriptor["function"]["name"] == "find_files_containing"
535
+ assert "path" in descriptor["function"]["parameters"]["properties"]
536
+ assert "pattern" in descriptor["function"]["parameters"]["properties"]
537
+
538
+
539
+ class DescribeFindLinesMatchingTool:
540
+
541
+ def should_find_lines_matching_pattern(self, setup_test_files):
542
+ """
543
+ Given a FindLinesMatchingTool
544
+ When running with a path and regex pattern
545
+ Then it should return lines matching the pattern with line numbers
546
+ """
547
+ fs = FilesystemGateway(base_path=setup_test_files)
548
+ tool = FindLinesMatchingTool(fs)
549
+ result = tool.run(path="test1.txt", pattern="multiple")
550
+ assert result == [{"line_number": 2, "content": "It has multiple lines"}]
551
+
552
+ def should_have_correct_descriptor(self):
553
+ """
554
+ Given a FindLinesMatchingTool
555
+ When accessing its descriptor
556
+ Then it should have the correct structure
557
+ """
558
+ fs = FilesystemGateway(base_path="/tmp")
559
+ tool = FindLinesMatchingTool(fs)
560
+ descriptor = tool.descriptor
561
+ assert descriptor["type"] == "function"
562
+ assert descriptor["function"]["name"] == "find_lines_matching"
563
+ assert "path" in descriptor["function"]["parameters"]["properties"]
564
+ assert "file_name" not in descriptor["function"]["parameters"]["properties"]
565
+ assert "pattern" in descriptor["function"]["parameters"]["properties"]
566
+
567
+
568
+ class DescribeEditFileWithDiffTool:
569
+
570
+ def should_edit_file_with_diff(self, setup_test_files):
571
+ """
572
+ Given an EditFileWithDiffTool
573
+ When running with a path and diff
574
+ Then it should apply the diff to the file
575
+ """
576
+ fs = FilesystemGateway(base_path=setup_test_files)
577
+ tool = EditFileWithDiffTool(fs)
578
+
579
+ # Create a diff that changes "test file 1" to "modified by tool"
580
+ diff = """--- test1.txt
581
+ +++ test1.txt
582
+ @@ -1,3 +1,3 @@
583
+ -This is test file 1
584
+ +This is modified by tool
585
+ It has multiple lines
586
+ For testing purposes"""
587
+
588
+ result = tool.run(path="test1.txt", diff=diff)
589
+ assert "Successfully" in result
590
+
591
+ # Verify the file was modified
592
+ with open(os.path.join(setup_test_files, "test1.txt"), "r") as f:
593
+ content = f.read()
594
+ assert "This is modified by tool" in content
595
+ assert "This is test file 1" not in content
596
+ assert "It has multiple lines" in content
597
+ assert "For testing purposes" in content
598
+
599
+ def should_edit_file_with_diff_adding_lines(self, setup_test_files):
600
+ """
601
+ Given an EditFileWithDiffTool
602
+ When running with a path and diff that adds lines
603
+ Then it should add the lines while preserving the rest of the content
604
+ """
605
+ fs = FilesystemGateway(base_path=setup_test_files)
606
+ tool = EditFileWithDiffTool(fs)
607
+
608
+ # Create a test file with known content
609
+ test_file_path = os.path.join(setup_test_files, "tool_add_lines_test.txt")
610
+ with open(test_file_path, 'w') as f:
611
+ f.write("Line 1\nLine 2\nLine 3\n")
612
+
613
+ # Create a diff that adds a new line after Line 2
614
+ diff = """--- tool_add_lines_test.txt
615
+ +++ tool_add_lines_test.txt
616
+ @@ -1,3 +1,4 @@
617
+ Line 1
618
+ Line 2
619
+ +New Line 2.5
620
+ Line 3
621
+ """
622
+
623
+ result = tool.run(path="tool_add_lines_test.txt", diff=diff)
624
+ assert "Successfully" in result
625
+
626
+ # Verify the file was modified correctly
627
+ with open(test_file_path, "r") as f:
628
+ content = f.read()
629
+
630
+ assert "Line 1\nLine 2\nNew Line 2.5\nLine 3\n" == content
631
+
632
+ def should_edit_file_with_diff_multiple_hunks(self, setup_test_files):
633
+ """
634
+ Given an EditFileWithDiffTool
635
+ When running with a path and diff that contains multiple hunks
636
+ Then it should apply all changes correctly
637
+ """
638
+ fs = FilesystemGateway(base_path=setup_test_files)
639
+ tool = EditFileWithDiffTool(fs)
640
+
641
+ # Create a test file with known content
642
+ test_file_path = os.path.join(setup_test_files, "tool_multiple_hunks_test.txt")
643
+ with open(test_file_path, 'w') as f:
644
+ f.write("Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\n")
645
+
646
+ # Create a diff with multiple hunks
647
+ diff = """--- tool_multiple_hunks_test.txt
648
+ +++ tool_multiple_hunks_test.txt
649
+ @@ -1,3 +1,3 @@
650
+ Line 1
651
+ -Line 2
652
+ +Modified Line 2
653
+ Line 3
654
+ @@ -4,3 +4,4 @@
655
+ Line 4
656
+ Line 5
657
+ +New Line 5.5
658
+ Line 6
659
+ """
660
+
661
+ result = tool.run(path="tool_multiple_hunks_test.txt", diff=diff)
662
+ assert "Successfully" in result
663
+
664
+ # Verify the file was modified correctly
665
+ with open(test_file_path, "r") as f:
666
+ content = f.read()
667
+
668
+ expected = "Line 1\nModified Line 2\nLine 3\nLine 4\nLine 5\nNew Line 5.5\nLine 6\n"
669
+ assert expected == content
670
+
671
+ def should_have_correct_descriptor(self):
672
+ """
673
+ Given an EditFileWithDiffTool
674
+ When accessing its descriptor
675
+ Then it should have the correct structure
676
+ """
677
+ fs = FilesystemGateway(base_path="/tmp")
678
+ tool = EditFileWithDiffTool(fs)
679
+ descriptor = tool.descriptor
680
+ assert descriptor["type"] == "function"
681
+ assert descriptor["function"]["name"] == "edit_file_with_diff"
682
+ assert "path" in descriptor["function"]["parameters"]["properties"]
683
+ assert "file_name" not in descriptor["function"]["parameters"]["properties"]
684
+ assert "diff" in descriptor["function"]["parameters"]["properties"]
685
+
686
+
687
+ class DescribeCreateDirectoryTool:
688
+
689
+ def should_create_directory(self, setup_test_files):
690
+ """
691
+ Given a CreateDirectoryTool
692
+ When running with a path
693
+ Then it should create the directory
694
+ """
695
+ fs = FilesystemGateway(base_path=setup_test_files)
696
+ tool = CreateDirectoryTool(fs)
697
+
698
+ # Create a new directory
699
+ result = tool.run(path="new_directory")
700
+ assert "Successfully" in result
701
+
702
+ # Verify the directory was created
703
+ assert os.path.isdir(os.path.join(setup_test_files, "new_directory"))
704
+
705
+ # Test creating nested directories
706
+ result = tool.run(path="nested/directory/structure")
707
+ assert "Successfully" in result
708
+
709
+ # Verify the nested directories were created
710
+ assert os.path.isdir(os.path.join(setup_test_files, "nested/directory/structure"))
711
+
712
+ def should_have_correct_descriptor(self):
713
+ """
714
+ Given a CreateDirectoryTool
715
+ When accessing its descriptor
716
+ Then it should have the correct structure
717
+ """
718
+ fs = FilesystemGateway(base_path="/tmp")
719
+ tool = CreateDirectoryTool(fs)
720
+ descriptor = tool.descriptor
721
+ assert descriptor["type"] == "function"
722
+ assert descriptor["function"]["name"] == "create_directory"
723
+ assert "path" in descriptor["function"]["parameters"]["properties"]