Merge branch 'master' into 4904-arv-web
[arvados.git] / services / api / test / functional / arvados / v1 / groups_controller_test.rb
1 require 'test_helper'
2
3 class Arvados::V1::GroupsControllerTest < ActionController::TestCase
4
5   test "attempt to delete group without read or write access" do
6     authorize_with :active
7     post :destroy, id: groups(:empty_lonely_group).uuid
8     assert_response 404
9   end
10
11   test "attempt to delete group without write access" do
12     authorize_with :active
13     post :destroy, id: groups(:all_users).uuid
14     assert_response 403
15   end
16
17   test "get list of projects" do
18     authorize_with :active
19     get :index, filters: [['group_class', '=', 'project']], format: :json
20     assert_response :success
21     group_uuids = []
22     json_response['items'].each do |group|
23       assert_equal 'project', group['group_class']
24       group_uuids << group['uuid']
25     end
26     assert_includes group_uuids, groups(:aproject).uuid
27     assert_includes group_uuids, groups(:asubproject).uuid
28     assert_not_includes group_uuids, groups(:system_group).uuid
29     assert_not_includes group_uuids, groups(:private).uuid
30   end
31
32   test "get list of groups that are not projects" do
33     authorize_with :active
34     get :index, filters: [['group_class', '!=', 'project']], format: :json
35     assert_response :success
36     group_uuids = []
37     json_response['items'].each do |group|
38       assert_not_equal 'project', group['group_class']
39       group_uuids << group['uuid']
40     end
41     assert_not_includes group_uuids, groups(:aproject).uuid
42     assert_not_includes group_uuids, groups(:asubproject).uuid
43     assert_includes group_uuids, groups(:private).uuid
44   end
45
46   test "get list of groups with bogus group_class" do
47     authorize_with :active
48     get :index, {
49       filters: [['group_class', '=', 'nogrouphasthislittleclass']],
50       format: :json,
51     }
52     assert_response :success
53     assert_equal [], json_response['items']
54     assert_equal 0, json_response['items_available']
55   end
56
57   def check_project_contents_response
58     assert_response :success
59     assert_operator 2, :<=, json_response['items_available']
60     assert_operator 2, :<=, json_response['items'].count
61     kinds = json_response['items'].collect { |i| i['kind'] }.uniq
62     expect_kinds = %w'arvados#group arvados#specimen arvados#pipelineTemplate arvados#job'
63     assert_equal expect_kinds, (expect_kinds & kinds)
64
65     json_response['items'].each do |i|
66       if i['kind'] == 'arvados#group'
67         assert(i['group_class'] == 'project',
68                "group#contents returned a non-project group")
69       end
70     end
71   end
72
73   test 'get group-owned objects' do
74     authorize_with :active
75     get :contents, {
76       id: groups(:aproject).uuid,
77       format: :json,
78     }
79     check_project_contents_response
80   end
81
82   test "user with project read permission can see project objects" do
83     authorize_with :project_viewer
84     get :contents, {
85       id: groups(:aproject).uuid,
86       format: :json,
87     }
88     check_project_contents_response
89   end
90
91   test "list objects across projects" do
92     authorize_with :project_viewer
93     get :contents, {
94       format: :json,
95       filters: [['uuid', 'is_a', 'arvados#specimen']]
96     }
97     assert_response :success
98     found_uuids = json_response['items'].collect { |i| i['uuid'] }
99     [[:in_aproject, true],
100      [:in_asubproject, true],
101      [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
102       if should_find
103         assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
104       else
105         refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
106       end
107     end
108   end
109
110   test "list objects in home project" do
111     authorize_with :active
112     get :contents, {
113       format: :json,
114       id: users(:active).uuid
115     }
116     assert_response :success
117     found_uuids = json_response['items'].collect { |i| i['uuid'] }
118     assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
119     refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
120   end
121
122   test "user with project read permission can see project collections" do
123     authorize_with :project_viewer
124     get :contents, {
125       id: groups(:asubproject).uuid,
126       format: :json,
127     }
128     ids = json_response['items'].map { |item| item["uuid"] }
129     assert_includes ids, collections(:baz_file_in_asubproject).uuid
130   end
131
132   [['asc', :<=],
133    ['desc', :>=]].each do |order, operator|
134     test "user with project read permission can sort project collections #{order}" do
135       authorize_with :project_viewer
136       get :contents, {
137         id: groups(:asubproject).uuid,
138         format: :json,
139         filters: [['uuid', 'is_a', "arvados#collection"]],
140         order: "collections.name #{order}"
141       }
142       sorted_names = json_response['items'].collect { |item| item["name"] }
143       # Here we avoid assuming too much about the database
144       # collation. Both "alice"<"Bob" and "alice">"Bob" can be
145       # correct. Hopefully it _is_ safe to assume that if "a" comes
146       # before "b" in the ascii alphabet, "aX">"bY" is never true for
147       # any strings X and Y.
148       reliably_sortable_names = sorted_names.select do |name|
149         name[0] >= 'a' and name[0] <= 'z'
150       end.uniq do |name|
151         name[0]
152       end
153       # Preserve order of sorted_names. But do not use &=. If
154       # sorted_names has out-of-order duplicates, we want to preserve
155       # them here, so we can detect them and fail the test below.
156       sorted_names.select! do |name|
157         reliably_sortable_names.include? name
158       end
159       actually_checked_anything = false
160       previous = nil
161       sorted_names.each do |entry|
162         if previous
163           assert_operator(previous, operator, entry,
164                           "Entries sorted incorrectly.")
165           actually_checked_anything = true
166         end
167         previous = entry
168       end
169       assert actually_checked_anything, "Didn't even find two names to compare."
170     end
171   end
172
173   test 'list objects across multiple projects' do
174     authorize_with :project_viewer
175     get :contents, {
176       format: :json,
177       filters: [['uuid', 'is_a', 'arvados#specimen']]
178     }
179     assert_response :success
180     found_uuids = json_response['items'].collect { |i| i['uuid'] }
181     [[:in_aproject, true],
182      [:in_asubproject, true],
183      [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
184       if should_find
185         assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
186       else
187         refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
188       end
189     end
190   end
191
192   # Even though the project_viewer tests go through other controllers,
193   # I'm putting them here so they're easy to find alongside the other
194   # project tests.
195   def check_new_project_link_fails(link_attrs)
196     @controller = Arvados::V1::LinksController.new
197     post :create, link: {
198       link_class: "permission",
199       name: "can_read",
200       head_uuid: groups(:aproject).uuid,
201     }.merge(link_attrs)
202     assert_includes(403..422, response.status)
203   end
204
205   test "user with project read permission can't add users to it" do
206     authorize_with :project_viewer
207     check_new_project_link_fails(tail_uuid: users(:spectator).uuid)
208   end
209
210   test "user with project read permission can't add items to it" do
211     authorize_with :project_viewer
212     check_new_project_link_fails(tail_uuid: collections(:baz_file).uuid)
213   end
214
215   test "user with project read permission can't rename items in it" do
216     authorize_with :project_viewer
217     @controller = Arvados::V1::LinksController.new
218     post :update, {
219       id: jobs(:running).uuid,
220       name: "Denied test name",
221     }
222     assert_includes(403..404, response.status)
223   end
224
225   test "user with project read permission can't remove items from it" do
226     @controller = Arvados::V1::PipelineTemplatesController.new
227     authorize_with :project_viewer
228     post :update, {
229       id: pipeline_templates(:two_part).uuid,
230       pipeline_template: {
231         owner_uuid: users(:project_viewer).uuid,
232       }
233     }
234     assert_response 403
235   end
236
237   test "user with project read permission can't delete it" do
238     authorize_with :project_viewer
239     post :destroy, {id: groups(:aproject).uuid}
240     assert_response 403
241   end
242
243   test 'get group-owned objects with limit' do
244     authorize_with :active
245     get :contents, {
246       id: groups(:aproject).uuid,
247       limit: 1,
248       format: :json,
249     }
250     assert_response :success
251     assert_operator 1, :<, json_response['items_available']
252     assert_equal 1, json_response['items'].count
253   end
254
255   test 'get group-owned objects with limit and offset' do
256     authorize_with :active
257     get :contents, {
258       id: groups(:aproject).uuid,
259       limit: 1,
260       offset: 12345,
261       format: :json,
262     }
263     assert_response :success
264     assert_operator 1, :<, json_response['items_available']
265     assert_equal 0, json_response['items'].count
266   end
267
268   test 'get group-owned objects with additional filter matching nothing' do
269     authorize_with :active
270     get :contents, {
271       id: groups(:aproject).uuid,
272       filters: [['uuid', 'in', ['foo_not_a_uuid','bar_not_a_uuid']]],
273       format: :json,
274     }
275     assert_response :success
276     assert_equal [], json_response['items']
277     assert_equal 0, json_response['items_available']
278   end
279
280   %w(offset limit).each do |arg|
281     ['foo', '', '1234five', '0x10', '-8'].each do |val|
282       test "Raise error on bogus #{arg} parameter #{val.inspect}" do
283         authorize_with :active
284         get :contents, {
285           :id => groups(:aproject).uuid,
286           :format => :json,
287           arg => val,
288         }
289         assert_response 422
290       end
291     end
292   end
293
294   test "Collection contents don't include manifest_text" do
295     authorize_with :active
296     get :contents, {
297       id: groups(:aproject).uuid,
298       filters: [["uuid", "is_a", "arvados#collection"]],
299       format: :json,
300     }
301     assert_response :success
302     refute(json_response["items"].any? { |c| not c["portable_data_hash"] },
303            "response included an item without a portable data hash")
304     refute(json_response["items"].any? { |c| c.include?("manifest_text") },
305            "response included an item with a manifest text")
306   end
307
308   test 'get writable_by list for owned group' do
309     authorize_with :active
310     get :show, {
311       id: groups(:aproject).uuid,
312       format: :json
313     }
314     assert_response :success
315     assert_not_nil(json_response['writable_by'],
316                    "Should receive uuid list in 'writable_by' field")
317     assert_includes(json_response['writable_by'], users(:active).uuid,
318                     "owner should be included in writable_by list")
319   end
320
321   test 'no writable_by list for group with read-only access' do
322     authorize_with :rominiadmin
323     get :show, {
324       id: groups(:testusergroup_admins).uuid,
325       format: :json
326     }
327     assert_response :success
328     assert_equal([json_response['owner_uuid']],
329                  json_response['writable_by'],
330                  "Should only see owner_uuid in 'writable_by' field")
331   end
332
333   test 'get writable_by list by admin user' do
334     authorize_with :admin
335     get :show, {
336       id: groups(:testusergroup_admins).uuid,
337       format: :json
338     }
339     assert_response :success
340     assert_not_nil(json_response['writable_by'],
341                    "Should receive uuid list in 'writable_by' field")
342     assert_includes(json_response['writable_by'],
343                     users(:admin).uuid,
344                     "Current user should be included in 'writable_by' field")
345   end
346
347   test 'creating subproject with duplicate name fails' do
348     authorize_with :active
349     post :create, {
350       group: {
351         name: 'A Project',
352         owner_uuid: users(:active).uuid,
353         group_class: 'project',
354       },
355     }
356     assert_response 422
357     response_errors = json_response['errors']
358     assert_not_nil response_errors, 'Expected error in response'
359     assert(response_errors.first.include?('duplicate key'),
360            "Expected 'duplicate key' error in #{response_errors.first}")
361   end
362
363   test 'creating duplicate named subproject succeeds with ensure_unique_name' do
364     authorize_with :active
365     post :create, {
366       group: {
367         name: 'A Project',
368         owner_uuid: users(:active).uuid,
369         group_class: 'project',
370       },
371       ensure_unique_name: true
372     }
373     assert_response :success
374     new_project = json_response
375     assert_not_equal(new_project['uuid'],
376                      groups(:aproject).uuid,
377                      "create returned same uuid as existing project")
378     assert_equal(new_project['name'],
379                  'A Project (2)',
380                  "new project name '#{new_project['name']}' was expected to be 'A Project (2)'")
381   end
382 end