diff options
| author | Adam Malczewski <[email protected]> | 2026-03-31 23:10:45 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-03-31 23:10:45 +0900 |
| commit | 57c56daf5906442dacc15951c9b3405f89309839 (patch) | |
| tree | e76890119c0e47acb48f8585222b7a2f5e22df56 /spec/dispatch/tool/files/edit_file_spec.rb | |
| parent | 25488d32336e05b69a41391cc7b5153478d3cc8a (diff) | |
| download | dispatch-tool-files-dev.tar.gz dispatch-tool-files-dev.zip | |
impdev
Diffstat (limited to 'spec/dispatch/tool/files/edit_file_spec.rb')
| -rw-r--r-- | spec/dispatch/tool/files/edit_file_spec.rb | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/spec/dispatch/tool/files/edit_file_spec.rb b/spec/dispatch/tool/files/edit_file_spec.rb new file mode 100644 index 0000000..b0366dd --- /dev/null +++ b/spec/dispatch/tool/files/edit_file_spec.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +RSpec.describe "edit_file tool" do + let(:worktree_path) { Dir.mktmpdir("edit-file-test") } + let(:context) { { worktree_path: } } + let(:registry) { Dispatch::Tools::Registry.new } + + before { Dispatch::Tool::Files.register(registry) } + + after { FileUtils.remove_entry(worktree_path) } + + subject(:tool) { registry.get("edit_file") } + + describe "single edit" do + it "replaces old_text with new_text in the file" do + file_path = File.join(worktree_path, "code.rb") + File.write(file_path, "def hello\n puts 'hello'\nend\n") + + result = tool.call({ + "path" => "code.rb", + "edits" => [{ "old_text" => "puts 'hello'", "new_text" => "puts 'goodbye'" }] + }, context:) + + expect(result.success?).to be true + expect(File.read(file_path)).to include("puts 'goodbye'") + expect(File.read(file_path)).not_to include("puts 'hello'") + end + + it "returns a confirmation with the number of edits applied" do + file_path = File.join(worktree_path, "file.txt") + File.write(file_path, "foo bar baz") + + result = tool.call({ + "path" => "file.txt", + "edits" => [{ "old_text" => "bar", "new_text" => "qux" }] + }, context:) + + expect(result.success?).to be true + expect(result.output).to match(/1/i) + end + end + + describe "multiple edits" do + it "applies multiple edits sequentially" do + file_path = File.join(worktree_path, "multi.txt") + File.write(file_path, "aaa bbb ccc") + + result = tool.call({ + "path" => "multi.txt", + "edits" => [ + { "old_text" => "aaa", "new_text" => "xxx" }, + { "old_text" => "ccc", "new_text" => "zzz" } + ] + }, context:) + + expect(result.success?).to be true + expect(File.read(file_path)).to eq("xxx bbb zzz") + end + + it "applies edits sequentially so later edits see results of earlier ones" do + file_path = File.join(worktree_path, "sequential.txt") + File.write(file_path, "hello world") + + result = tool.call({ + "path" => "sequential.txt", + "edits" => [ + { "old_text" => "hello", "new_text" => "hi" }, + { "old_text" => "hi world", "new_text" => "hi there" } + ] + }, context:) + + expect(result.success?).to be true + expect(File.read(file_path)).to eq("hi there") + end + end + + describe "error cases" do + it "returns failure when old_text is not found in the file" do + file_path = File.join(worktree_path, "missing.txt") + File.write(file_path, "actual content") + + result = tool.call({ + "path" => "missing.txt", + "edits" => [{ "old_text" => "nonexistent text", "new_text" => "replacement" }] + }, context:) + + expect(result.failure?).to be true + expect(result.error).to match(/not found/i) + end + + it "returns failure when the file does not exist" do + result = tool.call({ + "path" => "ghost.txt", + "edits" => [{ "old_text" => "a", "new_text" => "b" }] + }, context:) + + expect(result.failure?).to be true + expect(result.error).to match(/not found|does not exist/i) + end + + it "returns failure when the path escapes the sandbox" do + result = tool.call({ + "path" => "../../../etc/passwd", + "edits" => [{ "old_text" => "root", "new_text" => "hacked" }] + }, context:) + + expect(result.failure?).to be true + expect(result.error).to match(/sandbox|outside/i) + end + + it "returns failure when old_text matches ambiguously (multiple occurrences)" do + file_path = File.join(worktree_path, "ambiguous.txt") + File.write(file_path, "foo bar foo baz foo") + + result = tool.call({ + "path" => "ambiguous.txt", + "edits" => [{ "old_text" => "foo", "new_text" => "qux" }] + }, context:) + + expect(result.failure?).to be true + expect(result.error).to match(/ambiguous|multiple/i) + end + end + + describe "parameter validation" do + it "requires the path parameter" do + result = tool.call({ + "edits" => [{ "old_text" => "a", "new_text" => "b" }] + }, context:) + + expect(result.failure?).to be true + end + + it "requires the edits parameter" do + File.write(File.join(worktree_path, "file.txt"), "content") + + result = tool.call({ "path" => "file.txt" }, context:) + + expect(result.failure?).to be true + end + end +end |
