genie_cli 0.1.1
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +107 -0
- data/Rakefile +8 -0
- data/bin/genie +8 -0
- data/lib/genie/cli.rb +20 -0
- data/lib/genie/sandboxed_file_tool.rb +22 -0
- data/lib/genie/session.rb +83 -0
- data/lib/genie/session_config.rb +110 -0
- data/lib/genie/version.rb +3 -0
- data/lib/genie.rb +69 -0
- data/lib/tools/append_to_file.rb +33 -0
- data/lib/tools/ask_for_help.rb +14 -0
- data/lib/tools/insert_into_file.rb +58 -0
- data/lib/tools/list_files.rb +61 -0
- data/lib/tools/read_file.rb +34 -0
- data/lib/tools/rename_file.rb +45 -0
- data/lib/tools/replace_lines_in_file.rb +58 -0
- data/lib/tools/run_tests.rb +38 -0
- data/lib/tools/take_a_note.rb +14 -0
- data/lib/tools/write_file.rb +38 -0
- data/test/data/sample_config.yml +5 -0
- data/test/data/sample_files/a_dir/two.txt +0 -0
- data/test/data/sample_files/one.txt +0 -0
- data/test/data/sample_files/read_file_test.txt +3 -0
- data/test/test_cli.rb +11 -0
- data/test/test_helper.rb +5 -0
- data/test/test_session.rb +13 -0
- data/test/test_session_config.rb +55 -0
- data/test/tools/test_append_to_file.rb +29 -0
- data/test/tools/test_insert_into_file.rb +97 -0
- data/test/tools/test_list_files.rb +31 -0
- data/test/tools/test_read_file.rb +26 -0
- data/test/tools/test_rename_file.rb +76 -0
- data/test/tools/test_replace_lines_in_file.rb +141 -0
- data/test/tools/test_run_tests.rb +9 -0
- data/test/tools/test_take_a_note.rb +11 -0
- data/test/tools/test_write_file.rb +17 -0
- metadata +179 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "ruby_llm"
|
3
|
+
|
4
|
+
module Genie
|
5
|
+
class RenameFile < RubyLLM::Tool
|
6
|
+
include SandboxedFileTool
|
7
|
+
|
8
|
+
description "Rename a file within the base path to a new location within the base path."
|
9
|
+
param :filepath, desc: "The path to the source file to rename."
|
10
|
+
param :new_path, desc: "The new path for the file."
|
11
|
+
|
12
|
+
def execute(filepath:, new_path:)
|
13
|
+
# Expand to absolute paths
|
14
|
+
src = File.expand_path(filepath)
|
15
|
+
dst = File.expand_path(new_path)
|
16
|
+
|
17
|
+
Genie.output "Renaming file from: #{src} to #{dst}", color: :blue
|
18
|
+
|
19
|
+
enforce_sandbox!(src)
|
20
|
+
enforce_sandbox!(dst)
|
21
|
+
|
22
|
+
# Check source exists
|
23
|
+
unless File.exist?(src)
|
24
|
+
raise "File not found. Cannot rename a non-existent file."
|
25
|
+
end
|
26
|
+
|
27
|
+
# Check destination does not exist
|
28
|
+
if File.exist?(dst)
|
29
|
+
raise "Destination already exists: #{dst}."
|
30
|
+
end
|
31
|
+
|
32
|
+
# Ensure destination directory exists
|
33
|
+
dest_dir = File.dirname(dst)
|
34
|
+
FileUtils.mkdir_p(dest_dir) unless Dir.exist?(dest_dir)
|
35
|
+
|
36
|
+
# Perform rename
|
37
|
+
FileUtils.mv(src, dst)
|
38
|
+
|
39
|
+
{ success: true }
|
40
|
+
rescue => e
|
41
|
+
Genie.output "Error: #{e.message}", color: :red
|
42
|
+
{ error: e.message }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "ruby_llm"
|
2
|
+
|
3
|
+
module Genie
|
4
|
+
class ReplaceLinesInFile < RubyLLM::Tool
|
5
|
+
include SandboxedFileTool
|
6
|
+
|
7
|
+
description "Replace lines in a file between start and end indices (inclusive) with new content."
|
8
|
+
param :filepath, desc: "The path to the file to modify (must exist within base path)."
|
9
|
+
param :start_line, desc: "Zero-based starting line index to replace."
|
10
|
+
param :end_line, desc: "Zero-based ending line index to replace."
|
11
|
+
param :content, desc: "The new content to insert in place of the removed lines."
|
12
|
+
|
13
|
+
def execute(filepath:, start_line:, end_line:, content:)
|
14
|
+
start_line = start_line.to_i
|
15
|
+
end_line = end_line.to_i
|
16
|
+
|
17
|
+
# Expand to absolute path
|
18
|
+
filepath = File.expand_path(filepath)
|
19
|
+
Genie.output "Replacing lines in file: #{filepath}", color: :blue
|
20
|
+
|
21
|
+
enforce_sandbox!(filepath)
|
22
|
+
|
23
|
+
# Check file exists
|
24
|
+
unless File.exist?(filepath)
|
25
|
+
raise "File not found. Cannot replace lines in a non-existent file."
|
26
|
+
end
|
27
|
+
|
28
|
+
# Read lines
|
29
|
+
lines = File.readlines(filepath)
|
30
|
+
total = lines.size
|
31
|
+
|
32
|
+
# Validate indices
|
33
|
+
if !start_line.is_a?(Integer) || !end_line.is_a?(Integer) || start_line < 0 || end_line < start_line || end_line > total
|
34
|
+
raise "Invalid line numbers: start=#{start_line}, end=#{end_line}, file has #{total} lines."
|
35
|
+
end
|
36
|
+
|
37
|
+
# Split head and tail
|
38
|
+
head = lines[0...start_line]
|
39
|
+
tail = lines[(end_line + 1)..-1] || []
|
40
|
+
|
41
|
+
# Prepare new content lines
|
42
|
+
new_lines = content.to_s.each_line.to_a
|
43
|
+
|
44
|
+
# Combine
|
45
|
+
updated = head + new_lines + tail
|
46
|
+
|
47
|
+
# Write back
|
48
|
+
File.open(filepath, "w") do |f|
|
49
|
+
f.write(updated.join)
|
50
|
+
end
|
51
|
+
|
52
|
+
{ success: true }
|
53
|
+
rescue => e
|
54
|
+
Genie.output "Error: #{e.message}", color: :red
|
55
|
+
{ error: e.message }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Genie
|
2
|
+
class RunTests < RubyLLM::Tool
|
3
|
+
description "Runs the test suite and returns the results"
|
4
|
+
|
5
|
+
def initialize(base_path:, cmd:)
|
6
|
+
@base_path = base_path
|
7
|
+
@base_path.freeze
|
8
|
+
|
9
|
+
@cmd = cmd || "rake test"
|
10
|
+
@cmd.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
# Stubbed execute method; to be implemented in a future iteration
|
14
|
+
def execute
|
15
|
+
Genie.output "Running tests...", color: :blue
|
16
|
+
|
17
|
+
# Run CMD within the base path
|
18
|
+
Dir.chdir(@base_path) do
|
19
|
+
begin
|
20
|
+
cmd = TTY::Command.new(printer: :quiet)
|
21
|
+
result = cmd.run!(@cmd)
|
22
|
+
|
23
|
+
if result.failure?
|
24
|
+
Genie.output "Tests failed!", color: :red
|
25
|
+
{ result: "Tests failed", output: result.out, errors: result.err }
|
26
|
+
else
|
27
|
+
Genie.output "Tests passed successfully!", color: :green
|
28
|
+
{ result: "Tests passed", output: result.out }
|
29
|
+
end
|
30
|
+
rescue => e
|
31
|
+
Genie.output "Error running tests: #{e.message}", color: :red
|
32
|
+
{ error: e.message }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Genie
|
2
|
+
class TakeANote < RubyLLM::Tool
|
3
|
+
description "Take a note. The user will see this note. It can be useful to remember something, or explain to the user what you're thinking."
|
4
|
+
param :note, desc: "The text of the note you want stored."
|
5
|
+
|
6
|
+
def execute(note:)
|
7
|
+
Genie.output "Note: #{note}", color: :green
|
8
|
+
|
9
|
+
{
|
10
|
+
note: note,
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
module Genie
|
4
|
+
class WriteFile < RubyLLM::Tool
|
5
|
+
include SandboxedFileTool
|
6
|
+
|
7
|
+
description "Write a string to a file"
|
8
|
+
param :filepath, desc: "The path to the file to read (e.g., '/home/user/documents/file.txt')"
|
9
|
+
param :content, desc: "The content to write to the file"
|
10
|
+
|
11
|
+
def execute(filepath:, content:)
|
12
|
+
filepath = File.expand_path(filepath)
|
13
|
+
|
14
|
+
Genie.output "Writing file: #{filepath}", color: :blue
|
15
|
+
|
16
|
+
enforce_sandbox!(filepath)
|
17
|
+
|
18
|
+
indented_content = content.each_line.map { |line| " #{line}" }.join
|
19
|
+
|
20
|
+
Genie.output "#{indented_content}", color: :green
|
21
|
+
|
22
|
+
# Ensure the directory exists
|
23
|
+
FileUtils.mkdir_p(File.dirname(filepath))
|
24
|
+
|
25
|
+
File.open(filepath, "w") do |file|
|
26
|
+
file.write(content)
|
27
|
+
end
|
28
|
+
|
29
|
+
{
|
30
|
+
success: true,
|
31
|
+
}
|
32
|
+
rescue => e
|
33
|
+
Genie.output "Error: #{e.message}", color: :red
|
34
|
+
|
35
|
+
{ error: e.message }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
File without changes
|
File without changes
|
data/test/test_cli.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
class SessionConfigTest < TLDR
|
5
|
+
def test_basic_session_config
|
6
|
+
config = Genie::SessionConfig.new(
|
7
|
+
base_path: "/my/cool/path",
|
8
|
+
run_tests_cmd: "bundle exec testit",
|
9
|
+
model: "gpt-4",
|
10
|
+
first_question: "What is the meaning of life?",
|
11
|
+
instructions: "Default instructions"
|
12
|
+
)
|
13
|
+
|
14
|
+
assert_equal "/my/cool/path", config.base_path
|
15
|
+
assert_equal "bundle exec testit", config.run_tests_cmd
|
16
|
+
assert_equal "gpt-4", config.model
|
17
|
+
assert_equal "What is the meaning of life?", config.first_question
|
18
|
+
assert config.instructions.include?("Default instructions")
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_session_from_argv
|
22
|
+
argv = ["-c", "asdf.yml", "--base-path", "/tmp", "--run-tests", "rake test", "--model", "gpt-4o", "--instructions", "Command line instructions", "What is the meaning of life?"]
|
23
|
+
config = Genie::SessionConfig.from_argv(argv)
|
24
|
+
|
25
|
+
assert_equal "/tmp", config.base_path
|
26
|
+
assert_equal "rake test", config.run_tests_cmd
|
27
|
+
assert_equal "gpt-4o", config.model
|
28
|
+
assert_equal "What is the meaning of life?", config.first_question
|
29
|
+
assert config.instructions.include?("Command line instructions")
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_session_from_config_file
|
33
|
+
argv = ["-c", "./test/data/sample_config.yml"]
|
34
|
+
config = Genie::SessionConfig.from_argv(argv)
|
35
|
+
expected_base_path = "/tmp/myapp/from_config"
|
36
|
+
|
37
|
+
assert_equal File.realpath(expected_base_path), File.realpath(config.base_path)
|
38
|
+
assert_equal "bundle exec tests_from_config_ex", config.run_tests_cmd
|
39
|
+
assert_equal "test_model_from_config", config.model
|
40
|
+
assert_equal nil, config.first_question
|
41
|
+
assert config.instructions.include?("Instructions from config file")
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_default_instructions
|
45
|
+
config = Genie::SessionConfig.default
|
46
|
+
default_instructions = config.instructions
|
47
|
+
assert_includes default_instructions, "Genie coding assistant"
|
48
|
+
assert_includes default_instructions, "Test Driven Development"
|
49
|
+
assert_includes default_instructions, "tools available"
|
50
|
+
assert_includes default_instructions, "write tests first"
|
51
|
+
assert_includes default_instructions, "do not have access to any files outside"
|
52
|
+
assert_includes default_instructions, "do not have access to the internet"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "tmpdir"
|
2
|
+
|
3
|
+
require "tools/append_to_file"
|
4
|
+
|
5
|
+
class TestAppendToFile < TLDR
|
6
|
+
def test_append_to_existing_file
|
7
|
+
Dir.mktmpdir do |dir|
|
8
|
+
file = "#{dir}/test_file.txt"
|
9
|
+
# Create initial content
|
10
|
+
File.write(file, "Hello\n")
|
11
|
+
|
12
|
+
result = Genie::AppendToFile.new(base_path: dir).execute(filepath: file, content: "World\n")
|
13
|
+
assert_equal true, result[:success]
|
14
|
+
|
15
|
+
actual = File.read(file)
|
16
|
+
expected = "Hello\nWorld\n"
|
17
|
+
assert_equal expected, actual
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_error_for_file_outside_base_path
|
22
|
+
Dir.mktmpdir do |dir|
|
23
|
+
outside = "#{dir}/../outside.txt"
|
24
|
+
|
25
|
+
result = Genie::AppendToFile.new(base_path: dir).execute(filepath: outside, content: "data")
|
26
|
+
assert result[:error]&.include?("File not allowed"), "Expected error for file outside base path"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require "tmpdir"
|
2
|
+
|
3
|
+
class TestInsertIntoFile < TLDR
|
4
|
+
def test_insert_at_middle
|
5
|
+
Dir.mktmpdir do |dir|
|
6
|
+
file = "#{dir}/test.txt"
|
7
|
+
# Create initial content
|
8
|
+
File.write(file, "A\nB\nC\n")
|
9
|
+
|
10
|
+
result = Genie::InsertIntoFile.new(base_path: dir).execute(
|
11
|
+
filepath: file,
|
12
|
+
content: "X\n",
|
13
|
+
line_number: 2
|
14
|
+
)
|
15
|
+
assert_equal true, result[:success]
|
16
|
+
|
17
|
+
actual = File.read(file)
|
18
|
+
expected = "A\nX\nB\nC\n"
|
19
|
+
assert_equal expected, actual
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_insert_at_beginning
|
24
|
+
Dir.mktmpdir do |dir|
|
25
|
+
file = "#{dir}/test.txt"
|
26
|
+
File.write(file, "Line1\nLine2\n")
|
27
|
+
|
28
|
+
result = Genie::InsertIntoFile.new(base_path: dir).execute(
|
29
|
+
filepath: file,
|
30
|
+
content: "New\n",
|
31
|
+
line_number: 1
|
32
|
+
)
|
33
|
+
assert_equal true, result[:success]
|
34
|
+
actual = File.read(file)
|
35
|
+
expected = "New\nLine1\nLine2\n"
|
36
|
+
assert_equal expected, actual
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_insert_at_end
|
41
|
+
Dir.mktmpdir do |dir|
|
42
|
+
file = "#{dir}/test.txt"
|
43
|
+
File.write(file, "1\n2\n3\n")
|
44
|
+
|
45
|
+
# Append by specifying line_number one past last line
|
46
|
+
result = Genie::InsertIntoFile.new(base_path: dir).execute(
|
47
|
+
filepath: file,
|
48
|
+
content: "4\n",
|
49
|
+
line_number: 4
|
50
|
+
)
|
51
|
+
assert_equal true, result[:success]
|
52
|
+
actual = File.read(file)
|
53
|
+
expected = "1\n2\n3\n4\n"
|
54
|
+
assert_equal expected, actual
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_error_for_invalid_line_number
|
59
|
+
Dir.mktmpdir do |dir|
|
60
|
+
file = "#{dir}/test.txt"
|
61
|
+
File.write(file, "Only one line\n")
|
62
|
+
|
63
|
+
result = Genie::InsertIntoFile.new(base_path: dir).execute(
|
64
|
+
filepath: file,
|
65
|
+
content: "Oops\n",
|
66
|
+
line_number: 0
|
67
|
+
)
|
68
|
+
assert result[:error]&.include?("Invalid line number"), "Expected error for invalid line number"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_error_for_file_outside_base_path
|
73
|
+
Dir.mktmpdir do |dir|
|
74
|
+
outside = "#{dir}/../outside.txt"
|
75
|
+
|
76
|
+
result = Genie::InsertIntoFile.new(base_path: dir).execute(
|
77
|
+
filepath: outside,
|
78
|
+
content: "data\n",
|
79
|
+
line_number: 1
|
80
|
+
)
|
81
|
+
assert result[:error]&.include?("File not allowed"), "Expected error for file outside base path"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_error_for_nonexistent_file
|
86
|
+
Dir.mktmpdir do |dir|
|
87
|
+
file = "#{dir}/no_file.txt"
|
88
|
+
|
89
|
+
result = Genie::InsertIntoFile.new(base_path: dir).execute(
|
90
|
+
filepath: file,
|
91
|
+
content: "data\n",
|
92
|
+
line_number: 1
|
93
|
+
)
|
94
|
+
assert result[:error]&.include?("File not found"), "Expected error for nonexistent file"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "tools/list_files"
|
2
|
+
|
3
|
+
class TestListFiles < TLDR
|
4
|
+
def test_list_files
|
5
|
+
actual = Genie::ListFiles.new(base_path: File.expand_path("../../", __dir__)).execute(directory: "./test/data/sample_files")
|
6
|
+
|
7
|
+
expected = [{ name: "read_file_test.txt", type: "file" },
|
8
|
+
{ name: "one.txt", type: "file" },
|
9
|
+
{ name: "a_dir", type: "directory" }]
|
10
|
+
|
11
|
+
assert_equal expected, actual
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_list_files_in_invalid_directory
|
15
|
+
actual = Genie::ListFiles.new(base_path: "/tmp").execute(directory: "/blah/nothing/data/invalid_dir")
|
16
|
+
|
17
|
+
expected = { error: "Directory not allowed: /blah/nothing/data/invalid_dir. Must be within base path: /tmp" }
|
18
|
+
|
19
|
+
assert_equal expected, actual
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_list_files_with_filter
|
23
|
+
base_path = File.expand_path("../../", __dir__)
|
24
|
+
t = Genie::ListFiles.new(base_path: base_path)
|
25
|
+
actual = t.execute(directory: "./test/data/sample_files", filter: "one")
|
26
|
+
|
27
|
+
expected = [{ name: "one.txt", type: "file" }]
|
28
|
+
|
29
|
+
assert_equal expected, actual
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "tools/read_file"
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
class TestReadFile < TLDR
|
5
|
+
def setup
|
6
|
+
@tmp_dir = Dir.mktmpdir
|
7
|
+
@file_path = File.join(@tmp_dir, "read_file_test.txt")
|
8
|
+
File.write(@file_path, "Line1\nLine2\nLine3\n")
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
FileUtils.remove_entry(@tmp_dir)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_read_file_without_line_numbers
|
16
|
+
actual = Genie::ReadFile.new(base_path: @tmp_dir).execute(filepath: @file_path)
|
17
|
+
expected_contents = "Line1\nLine2\nLine3\n"
|
18
|
+
assert_equal({ contents: expected_contents }, actual)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_read_file_with_line_numbers
|
22
|
+
actual = Genie::ReadFile.new(base_path: @tmp_dir).execute(filepath: @file_path, include_line_numbers: true)
|
23
|
+
expected_contents = "1: Line1\n2: Line2\n3: Line3\n"
|
24
|
+
assert_equal({ contents: expected_contents }, actual)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "tmpdir"
|
2
|
+
|
3
|
+
class TestRenameFile < TLDR
|
4
|
+
def test_rename_success
|
5
|
+
Dir.mktmpdir do |dir|
|
6
|
+
src = "#{dir}/old.txt"
|
7
|
+
dst = "#{dir}/new.txt"
|
8
|
+
content = "Hello"
|
9
|
+
File.write(src, content)
|
10
|
+
|
11
|
+
result = Genie::RenameFile.new(base_path: dir).execute(
|
12
|
+
filepath: src,
|
13
|
+
new_path: dst
|
14
|
+
)
|
15
|
+
assert_equal true, result[:success]
|
16
|
+
|
17
|
+
# Source should no longer exist, target should exist with content preserved
|
18
|
+
assert !File.exist?(src), "Expected source file to be removed"
|
19
|
+
assert File.exist?(dst), "Expected target file to exist"
|
20
|
+
assert_equal content, File.read(dst)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_error_for_source_outside_base_path
|
25
|
+
Dir.mktmpdir do |dir|
|
26
|
+
outside = "#{dir}/../outside.txt"
|
27
|
+
result = Genie::RenameFile.new(base_path: dir).execute(
|
28
|
+
filepath: outside,
|
29
|
+
new_path: "#{dir}/new.txt"
|
30
|
+
)
|
31
|
+
assert result[:error]&.include?("File not allowed"), "Expected error for source outside base path"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_error_for_destination_outside_base_path
|
36
|
+
Dir.mktmpdir do |dir|
|
37
|
+
src = "#{dir}/file.txt"
|
38
|
+
File.write(src, "data")
|
39
|
+
outside_dst = "/blah/new.txt"
|
40
|
+
result = Genie::RenameFile.new(base_path: dir).execute(
|
41
|
+
filepath: src,
|
42
|
+
new_path: outside_dst
|
43
|
+
)
|
44
|
+
|
45
|
+
assert result[:error]&.include?("File not allowed"), "Expected error for outside sandbox source file"
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_error_for_nonexistent_source_file
|
51
|
+
Dir.mktmpdir do |dir|
|
52
|
+
src = "#{dir}/no_file.txt"
|
53
|
+
dst = "#{dir}/new.txt"
|
54
|
+
result = Genie::RenameFile.new(base_path: dir).execute(
|
55
|
+
filepath: src,
|
56
|
+
new_path: dst
|
57
|
+
)
|
58
|
+
assert result[:error]&.include?("File not found"), "Expected error for nonexistent source file"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_error_for_existing_destination_file
|
63
|
+
Dir.mktmpdir do |dir|
|
64
|
+
src = "#{dir}/a.txt"
|
65
|
+
dst = "#{dir}/b.txt"
|
66
|
+
File.write(src, "A")
|
67
|
+
File.write(dst, "B")
|
68
|
+
|
69
|
+
result = Genie::RenameFile.new(base_path: dir).execute(
|
70
|
+
filepath: src,
|
71
|
+
new_path: dst
|
72
|
+
)
|
73
|
+
assert result[:error]&.include?("Destination already exists"), "Expected error for existing destination file"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|