summaryrefslogtreecommitdiffhomepage
path: root/spec/dispatch/tool/files/list_files_spec.rb
blob: 05c4088829758ca971a69720f438c6dcb9619200 (plain)
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
# frozen_string_literal: true

RSpec.describe "list_files tool" do
  let(:worktree_path) { Dir.mktmpdir("list-files-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("list_files") }

  describe "listing all files" do
    before do
      FileUtils.mkdir_p(File.join(worktree_path, "src", "lib"))
      File.write(File.join(worktree_path, "README.md"), "readme")
      File.write(File.join(worktree_path, "src", "main.rb"), "main")
      File.write(File.join(worktree_path, "src", "lib", "helper.rb"), "helper")
    end

    it "lists all files recursively by default" do
      result = tool.call({}, context:)

      expect(result.success?).to be true
      expect(result.output).to include("README.md")
      expect(result.output).to include("src/main.rb")
      expect(result.output).to include("src/lib/helper.rb")
    end

    it "returns paths relative to the worktree root" do
      result = tool.call({}, context:)

      expect(result.success?).to be true
      expect(result.output).not_to include(worktree_path)
    end
  end

  describe "listing with a path" do
    before do
      FileUtils.mkdir_p(File.join(worktree_path, "src"))
      FileUtils.mkdir_p(File.join(worktree_path, "test"))
      File.write(File.join(worktree_path, "src", "app.rb"), "app")
      File.write(File.join(worktree_path, "test", "app_test.rb"), "test")
    end

    it "lists files only in the specified subdirectory" do
      result = tool.call({ "path" => "src" }, context:)

      expect(result.success?).to be true
      expect(result.output).to include("app.rb")
      expect(result.output).not_to include("app_test.rb")
    end
  end

  describe "glob pattern filtering" do
    before do
      FileUtils.mkdir_p(File.join(worktree_path, "src"))
      File.write(File.join(worktree_path, "src", "main.rb"), "main")
      File.write(File.join(worktree_path, "src", "style.css"), "css")
      File.write(File.join(worktree_path, "README.md"), "readme")
    end

    it "filters files using a glob pattern" do
      result = tool.call({ "pattern" => "**/*.rb" }, context:)

      expect(result.success?).to be true
      expect(result.output).to include("main.rb")
      expect(result.output).not_to include("style.css")
      expect(result.output).not_to include("README.md")
    end

    it "supports multiple extension glob patterns" do
      result = tool.call({ "pattern" => "**/*.{rb,md}" }, context:)

      expect(result.success?).to be true
      expect(result.output).to include("main.rb")
      expect(result.output).to include("README.md")
      expect(result.output).not_to include("style.css")
    end
  end

  describe "recursive vs non-recursive" do
    before do
      FileUtils.mkdir_p(File.join(worktree_path, "nested", "deep"))
      File.write(File.join(worktree_path, "top.txt"), "top")
      File.write(File.join(worktree_path, "nested", "mid.txt"), "mid")
      File.write(File.join(worktree_path, "nested", "deep", "bottom.txt"), "bottom")
    end

    it "includes nested files when recursive is true" do
      result = tool.call({ "recursive" => true }, context:)

      expect(result.success?).to be true
      expect(result.output).to include("top.txt")
      expect(result.output).to include("nested/mid.txt")
      expect(result.output).to include("nested/deep/bottom.txt")
    end

    it "lists only top-level files when recursive is false" do
      result = tool.call({ "recursive" => false }, context:)

      expect(result.success?).to be true
      expect(result.output).to include("top.txt")
      expect(result.output).not_to include("mid.txt")
      expect(result.output).not_to include("bottom.txt")
    end
  end

  describe "error cases" do
    it "returns failure when the directory does not exist" do
      result = tool.call({ "path" => "nonexistent_dir" }, 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" }, context:)

      expect(result.failure?).to be true
      expect(result.error).to match(/sandbox|outside/i)
    end
  end
end