19297: Adds tests exposing the bug.
[arvados.git] / services / api / test / unit / commit_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6 require 'helpers/git_test_helper'
7
8 # NOTE: calling Commit.find_commit_range(nil, nil, 'rev')
9 # produces an error message "fatal: bad object 'rev'" on stderr if
10 # 'rev' does not exist in a given repository.  Many of these tests
11 # report such errors; their presence does not represent a fatal
12 # condition.
13
14 class CommitTest < ActiveSupport::TestCase
15   # See git_setup.rb for the commit log for test.git.tar
16   include GitTestHelper
17
18   setup do
19     authorize_with :active
20   end
21
22   test 'find_commit_range does not bypass permissions' do
23     authorize_with :inactive
24     assert_raises ArgumentError do
25       CommitsHelper::find_commit_range 'foo', nil, 'main', []
26     end
27   end
28
29   def must_pipe(cmd)
30     begin
31       return IO.read("|#{cmd}")
32     ensure
33       assert $?.success?
34     end
35   end
36
37   [
38    'https://github.com/arvados/arvados.git',
39    'http://github.com/arvados/arvados.git',
40    'git://github.com/arvados/arvados.git',
41   ].each do |url|
42     test "find_commit_range uses fetch_remote_repository to get #{url}" do
43       fake_gitdir = repositories(:foo).server_path
44       CommitsHelper::expects(:cache_dir_for).once.with(url).returns fake_gitdir
45       CommitsHelper::expects(:fetch_remote_repository).once.with(fake_gitdir, url).returns true
46       c = CommitsHelper::find_commit_range url, nil, 'main', []
47       refute_empty c
48     end
49   end
50
51   [
52    'bogus/repo',
53    '/bogus/repo',
54    '/not/allowed/.git',
55    'file:///not/allowed.git',
56    'git.arvados.org/arvados.git',
57    'github.com/arvados/arvados.git',
58   ].each do |url|
59     test "find_commit_range skips fetch_remote_repository for #{url}" do
60       CommitsHelper::expects(:fetch_remote_repository).never
61       assert_raises ArgumentError do
62         CommitsHelper::find_commit_range url, nil, 'main', []
63       end
64     end
65   end
66
67   test 'fetch_remote_repository does not leak commits across repositories' do
68     url = "http://localhost:1/fake/fake.git"
69     fetch_remote_from_local_repo url, :foo
70     c = CommitsHelper::find_commit_range url, nil, 'main', []
71     assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57'], c
72
73     url = "http://localhost:2/fake/fake.git"
74     fetch_remote_from_local_repo url, 'file://' + File.expand_path('../../.git', Rails.root)
75     c = CommitsHelper::find_commit_range url, nil, '077ba2ad3ea24a929091a9e6ce545c93199b8e57', []
76     assert_equal [], c
77   end
78
79   test 'tag_in_internal_repository creates and updates tags in internal.git' do
80     authorize_with :active
81     gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir}"
82     IO.read("|#{gitint} tag -d testtag 2>/dev/null") # "no such tag", fine
83     assert_match(/^fatal: /, IO.read("|#{gitint} show testtag 2>&1"))
84     refute $?.success?
85     CommitsHelper::tag_in_internal_repository 'active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', 'testtag'
86     assert_match(/^commit 31ce37f/, IO.read("|#{gitint} show testtag"))
87     assert $?.success?
88   end
89
90   def with_foo_repository
91     Dir.chdir("#{Rails.configuration.Git.Repositories}/#{repositories(:foo).uuid}") do
92       must_pipe("git checkout main 2>&1")
93       yield
94     end
95   end
96
97   test 'tag_in_internal_repository, new non-tip sha1 in local repo' do
98     tag = "tag#{rand(10**10)}"
99     sha1 = nil
100     with_foo_repository do
101       must_pipe("git checkout -b branch-#{rand(10**10)} 2>&1")
102       must_pipe("echo -n #{tag.shellescape} >bar")
103       must_pipe("git add bar")
104       must_pipe("git -c user.email=x@x -c user.name=X commit -m -")
105       sha1 = must_pipe("git log -n1 --format=%H").strip
106       must_pipe("git rm bar")
107       must_pipe("git -c user.email=x@x -c user.name=X commit -m -")
108     end
109     CommitsHelper::tag_in_internal_repository 'active/foo', sha1, tag
110     gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir.shellescape}"
111     assert_match(/^commit /, IO.read("|#{gitint} show #{tag.shellescape}"))
112     assert $?.success?
113   end
114
115   test 'tag_in_internal_repository, new unreferenced sha1 in local repo' do
116     tag = "tag#{rand(10**10)}"
117     sha1 = nil
118     with_foo_repository do
119       must_pipe("echo -n #{tag.shellescape} >bar")
120       must_pipe("git add bar")
121       must_pipe("git -c user.email=x@x -c user.name=X commit -m -")
122       sha1 = must_pipe("git log -n1 --format=%H").strip
123       must_pipe("git reset --hard HEAD^")
124     end
125     CommitsHelper::tag_in_internal_repository 'active/foo', sha1, tag
126     gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir.shellescape}"
127     assert_match(/^commit /, IO.read("|#{gitint} show #{tag.shellescape}"))
128     assert $?.success?
129   end
130
131   # In active/shabranchnames, "7387838c69a21827834586cc42b467ff6c63293b" is
132   # both a commit hash, and the name of a branch that begins from that same
133   # commit.
134   COMMIT_BRANCH_NAME = "7387838c69a21827834586cc42b467ff6c63293b"
135   # A commit that appears in the branch after 7387838c.
136   COMMIT_BRANCH_COMMIT_2 = "abec49829bf1758413509b7ffcab32a771b71e81"
137   # "738783" is another branch that starts from the above commit.
138   SHORT_COMMIT_BRANCH_NAME = COMMIT_BRANCH_NAME[0, 6]
139   # A commit that appears in branch 738783 after 7387838c.
140   SHORT_BRANCH_COMMIT_2 = "77e1a93093663705a63bb4d505698047e109dedd"
141
142   test "find_commit_range min_version prefers commits over branch names" do
143     assert_equal([COMMIT_BRANCH_NAME],
144                  CommitsHelper::find_commit_range("active/shabranchnames",
145                                           COMMIT_BRANCH_NAME, nil, nil))
146   end
147
148   test "find_commit_range max_version prefers commits over branch names" do
149     assert_equal([COMMIT_BRANCH_NAME],
150                  CommitsHelper::find_commit_range("active/shabranchnames",
151                                           nil, COMMIT_BRANCH_NAME, nil))
152   end
153
154   test "find_commit_range min_version with short branch name" do
155     assert_equal([SHORT_BRANCH_COMMIT_2],
156                  CommitsHelper::find_commit_range("active/shabranchnames",
157                                           SHORT_COMMIT_BRANCH_NAME, nil, nil))
158   end
159
160   test "find_commit_range max_version with short branch name" do
161     assert_equal([SHORT_BRANCH_COMMIT_2],
162                  CommitsHelper::find_commit_range("active/shabranchnames",
163                                           nil, SHORT_COMMIT_BRANCH_NAME, nil))
164   end
165
166   test "find_commit_range min_version with disambiguated branch name" do
167     assert_equal([COMMIT_BRANCH_COMMIT_2],
168                  CommitsHelper::find_commit_range("active/shabranchnames",
169                                           "heads/#{COMMIT_BRANCH_NAME}",
170                                           nil, nil))
171   end
172
173   test "find_commit_range max_version with disambiguated branch name" do
174     assert_equal([COMMIT_BRANCH_COMMIT_2],
175                  CommitsHelper::find_commit_range("active/shabranchnames", nil,
176                                           "heads/#{COMMIT_BRANCH_NAME}", nil))
177   end
178
179   test "find_commit_range min_version with unambiguous short name" do
180     assert_equal([COMMIT_BRANCH_NAME],
181                  CommitsHelper::find_commit_range("active/shabranchnames",
182                                           COMMIT_BRANCH_NAME[0..-2], nil, nil))
183   end
184
185   test "find_commit_range max_version with unambiguous short name" do
186     assert_equal([COMMIT_BRANCH_NAME],
187                  CommitsHelper::find_commit_range("active/shabranchnames", nil,
188                                           COMMIT_BRANCH_NAME[0..-2], nil))
189   end
190
191   test "find_commit_range laundry list" do
192     authorize_with :active
193
194     # single
195     a = CommitsHelper::find_commit_range('active/foo', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
196     assert_equal ['31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
197
198     #test "test_branch1" do
199     a = CommitsHelper::find_commit_range('active/foo', nil, 'main', nil)
200     assert_includes(a, '077ba2ad3ea24a929091a9e6ce545c93199b8e57')
201
202     #test "test_branch2" do
203     a = CommitsHelper::find_commit_range('active/foo', nil, 'b1', nil)
204     assert_equal ['1de84a854e2b440dc53bf42f8548afa4c17da332'], a
205
206     #test "test_branch3" do
207     a = CommitsHelper::find_commit_range('active/foo', nil, 'HEAD', nil)
208     assert_equal ['1de84a854e2b440dc53bf42f8548afa4c17da332'], a
209
210     #test "test_single_revision_repo" do
211     a = CommitsHelper::find_commit_range('active/foo', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
212     assert_equal ['31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
213     a = CommitsHelper::find_commit_range('arvados', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
214     assert_equal [], a
215
216     #test "test_multi_revision" do
217     # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
218     a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', nil)
219     assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '4fe459abe02d9b365932b8f5dc419439ab4e2577', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
220
221     #test "test_tag" do
222     # complains "fatal: ambiguous argument 'tag1': unknown revision or path
223     # not in the working tree."
224     a = CommitsHelper::find_commit_range('active/foo', 'tag1', 'main', nil)
225     assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '4fe459abe02d9b365932b8f5dc419439ab4e2577'], a
226
227     #test "test_multi_revision_exclude" do
228     a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', ['4fe459abe02d9b365932b8f5dc419439ab4e2577'])
229     assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
230
231     #test "test_multi_revision_tagged_exclude" do
232     # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
233     a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', ['tag1'])
234     assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
235
236     Dir.mktmpdir do |touchdir|
237       # invalid input to maximum
238       a = CommitsHelper::find_commit_range('active/foo', nil, "31ce37fe365b3dc204300a3e4c396ad333ed0556 ; touch #{touchdir}/uh_oh", nil)
239       assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'maximum' parameter of find_commit_range is exploitable"
240       assert_equal [], a
241
242       # invalid input to maximum
243       a = CommitsHelper::find_commit_range('active/foo', nil, "$(uname>#{touchdir}/uh_oh)", nil)
244       assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'maximum' parameter of find_commit_range is exploitable"
245       assert_equal [], a
246
247       # invalid input to minimum
248       a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556 ; touch #{touchdir}/uh_oh", "31ce37fe365b3dc204300a3e4c396ad333ed0556", nil)
249       assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'minimum' parameter of find_commit_range is exploitable"
250       assert_equal [], a
251
252       # invalid input to minimum
253       a = CommitsHelper::find_commit_range('active/foo', "$(uname>#{touchdir}/uh_oh)", "31ce37fe365b3dc204300a3e4c396ad333ed0556", nil)
254       assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'minimum' parameter of find_commit_range is exploitable"
255       assert_equal [], a
256
257       # invalid input to 'excludes'
258       # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
259       a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556", "077ba2ad3ea24a929091a9e6ce545c93199b8e57", ["4fe459abe02d9b365932b8f5dc419439ab4e2577 ; touch #{touchdir}/uh_oh"])
260       assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'excludes' parameter of find_commit_range is exploitable"
261       assert_equal [], a
262
263       # invalid input to 'excludes'
264       # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
265       a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556", "077ba2ad3ea24a929091a9e6ce545c93199b8e57", ["$(uname>#{touchdir}/uh_oh)"])
266       assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'excludes' parameter of find_commit_range is exploitable"
267       assert_equal [], a
268     end
269   end
270 end