11870: minor update
[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     assert_includes group_uuids, groups(:group_with_no_class).uuid
45   end
46
47   test "get list of groups with bogus group_class" do
48     authorize_with :active
49     get :index, {
50       filters: [['group_class', '=', 'nogrouphasthislittleclass']],
51       format: :json,
52     }
53     assert_response :success
54     assert_equal [], json_response['items']
55     assert_equal 0, json_response['items_available']
56   end
57
58   def check_project_contents_response disabled_kinds=[]
59     assert_response :success
60     assert_operator 2, :<=, json_response['items_available']
61     assert_operator 2, :<=, json_response['items'].count
62     kinds = json_response['items'].collect { |i| i['kind'] }.uniq
63     expect_kinds = %w'arvados#group arvados#specimen arvados#pipelineTemplate arvados#job' - disabled_kinds
64     assert_equal expect_kinds, (expect_kinds & kinds)
65
66     json_response['items'].each do |i|
67       if i['kind'] == 'arvados#group'
68         assert(i['group_class'] == 'project',
69                "group#contents returned a non-project group")
70       end
71     end
72
73     disabled_kinds.each do |d|
74       assert_equal true, !kinds.include?(d)
75     end
76   end
77
78   test 'get group-owned objects' do
79     authorize_with :active
80     get :contents, {
81       id: groups(:aproject).uuid,
82       format: :json,
83     }
84     check_project_contents_response
85   end
86
87   test "user with project read permission can see project objects" do
88     authorize_with :project_viewer
89     get :contents, {
90       id: groups(:aproject).uuid,
91       format: :json,
92     }
93     check_project_contents_response
94   end
95
96   test "list objects across projects" do
97     authorize_with :project_viewer
98     get :contents, {
99       format: :json,
100       filters: [['uuid', 'is_a', 'arvados#specimen']]
101     }
102     assert_response :success
103     found_uuids = json_response['items'].collect { |i| i['uuid'] }
104     [[:in_aproject, true],
105      [:in_asubproject, true],
106      [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
107       if should_find
108         assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
109       else
110         refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
111       end
112     end
113   end
114
115   test "list objects in home project" do
116     authorize_with :active
117     get :contents, {
118       format: :json,
119       limit: 200,
120       id: users(:active).uuid
121     }
122     assert_response :success
123     found_uuids = json_response['items'].collect { |i| i['uuid'] }
124     assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
125     refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
126   end
127
128   test "user with project read permission can see project collections" do
129     authorize_with :project_viewer
130     get :contents, {
131       id: groups(:asubproject).uuid,
132       format: :json,
133     }
134     ids = json_response['items'].map { |item| item["uuid"] }
135     assert_includes ids, collections(:baz_file_in_asubproject).uuid
136   end
137
138   [['asc', :<=],
139    ['desc', :>=]].each do |order, operator|
140     test "user with project read permission can sort project collections #{order}" do
141       authorize_with :project_viewer
142       get :contents, {
143         id: groups(:asubproject).uuid,
144         format: :json,
145         filters: [['uuid', 'is_a', "arvados#collection"]],
146         order: "collections.name #{order}"
147       }
148       sorted_names = json_response['items'].collect { |item| item["name"] }
149       # Here we avoid assuming too much about the database
150       # collation. Both "alice"<"Bob" and "alice">"Bob" can be
151       # correct. Hopefully it _is_ safe to assume that if "a" comes
152       # before "b" in the ascii alphabet, "aX">"bY" is never true for
153       # any strings X and Y.
154       reliably_sortable_names = sorted_names.select do |name|
155         name[0] >= 'a' and name[0] <= 'z'
156       end.uniq do |name|
157         name[0]
158       end
159       # Preserve order of sorted_names. But do not use &=. If
160       # sorted_names has out-of-order duplicates, we want to preserve
161       # them here, so we can detect them and fail the test below.
162       sorted_names.select! do |name|
163         reliably_sortable_names.include? name
164       end
165       actually_checked_anything = false
166       previous = nil
167       sorted_names.each do |entry|
168         if previous
169           assert_operator(previous, operator, entry,
170                           "Entries sorted incorrectly.")
171           actually_checked_anything = true
172         end
173         previous = entry
174       end
175       assert actually_checked_anything, "Didn't even find two names to compare."
176     end
177   end
178
179   test 'list objects across multiple projects' do
180     authorize_with :project_viewer
181     get :contents, {
182       format: :json,
183       filters: [['uuid', 'is_a', 'arvados#specimen']]
184     }
185     assert_response :success
186     found_uuids = json_response['items'].collect { |i| i['uuid'] }
187     [[:in_aproject, true],
188      [:in_asubproject, true],
189      [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
190       if should_find
191         assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
192       else
193         refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
194       end
195     end
196   end
197
198   # Even though the project_viewer tests go through other controllers,
199   # I'm putting them here so they're easy to find alongside the other
200   # project tests.
201   def check_new_project_link_fails(link_attrs)
202     @controller = Arvados::V1::LinksController.new
203     post :create, link: {
204       link_class: "permission",
205       name: "can_read",
206       head_uuid: groups(:aproject).uuid,
207     }.merge(link_attrs)
208     assert_includes(403..422, response.status)
209   end
210
211   test "user with project read permission can't add users to it" do
212     authorize_with :project_viewer
213     check_new_project_link_fails(tail_uuid: users(:spectator).uuid)
214   end
215
216   test "user with project read permission can't add items to it" do
217     authorize_with :project_viewer
218     check_new_project_link_fails(tail_uuid: collections(:baz_file).uuid)
219   end
220
221   test "user with project read permission can't rename items in it" do
222     authorize_with :project_viewer
223     @controller = Arvados::V1::LinksController.new
224     post :update, {
225       id: jobs(:running).uuid,
226       name: "Denied test name",
227     }
228     assert_includes(403..404, response.status)
229   end
230
231   test "user with project read permission can't remove items from it" do
232     @controller = Arvados::V1::PipelineTemplatesController.new
233     authorize_with :project_viewer
234     post :update, {
235       id: pipeline_templates(:two_part).uuid,
236       pipeline_template: {
237         owner_uuid: users(:project_viewer).uuid,
238       }
239     }
240     assert_response 403
241   end
242
243   test "user with project read permission can't delete it" do
244     authorize_with :project_viewer
245     post :destroy, {id: groups(:aproject).uuid}
246     assert_response 403
247   end
248
249   test 'get group-owned objects with limit' do
250     authorize_with :active
251     get :contents, {
252       id: groups(:aproject).uuid,
253       limit: 1,
254       format: :json,
255     }
256     assert_response :success
257     assert_operator 1, :<, json_response['items_available']
258     assert_equal 1, json_response['items'].count
259   end
260
261   test 'get group-owned objects with limit and offset' do
262     authorize_with :active
263     get :contents, {
264       id: groups(:aproject).uuid,
265       limit: 1,
266       offset: 12345,
267       format: :json,
268     }
269     assert_response :success
270     assert_operator 1, :<, json_response['items_available']
271     assert_equal 0, json_response['items'].count
272   end
273
274   test 'get group-owned objects with additional filter matching nothing' do
275     authorize_with :active
276     get :contents, {
277       id: groups(:aproject).uuid,
278       filters: [['uuid', 'in', ['foo_not_a_uuid','bar_not_a_uuid']]],
279       format: :json,
280     }
281     assert_response :success
282     assert_equal [], json_response['items']
283     assert_equal 0, json_response['items_available']
284   end
285
286   %w(offset limit).each do |arg|
287     ['foo', '', '1234five', '0x10', '-8'].each do |val|
288       test "Raise error on bogus #{arg} parameter #{val.inspect}" do
289         authorize_with :active
290         get :contents, {
291           :id => groups(:aproject).uuid,
292           :format => :json,
293           arg => val,
294         }
295         assert_response 422
296       end
297     end
298   end
299
300   test "Collection contents don't include manifest_text" do
301     authorize_with :active
302     get :contents, {
303       id: groups(:aproject).uuid,
304       filters: [["uuid", "is_a", "arvados#collection"]],
305       format: :json,
306     }
307     assert_response :success
308     refute(json_response["items"].any? { |c| not c["portable_data_hash"] },
309            "response included an item without a portable data hash")
310     refute(json_response["items"].any? { |c| c.include?("manifest_text") },
311            "response included an item with a manifest text")
312   end
313
314   test 'get writable_by list for owned group' do
315     authorize_with :active
316     get :show, {
317       id: groups(:aproject).uuid,
318       format: :json
319     }
320     assert_response :success
321     assert_not_nil(json_response['writable_by'],
322                    "Should receive uuid list in 'writable_by' field")
323     assert_includes(json_response['writable_by'], users(:active).uuid,
324                     "owner should be included in writable_by list")
325   end
326
327   test 'no writable_by list for group with read-only access' do
328     authorize_with :rominiadmin
329     get :show, {
330       id: groups(:testusergroup_admins).uuid,
331       format: :json
332     }
333     assert_response :success
334     assert_equal([json_response['owner_uuid']],
335                  json_response['writable_by'],
336                  "Should only see owner_uuid in 'writable_by' field")
337   end
338
339   test 'get writable_by list by admin user' do
340     authorize_with :admin
341     get :show, {
342       id: groups(:testusergroup_admins).uuid,
343       format: :json
344     }
345     assert_response :success
346     assert_not_nil(json_response['writable_by'],
347                    "Should receive uuid list in 'writable_by' field")
348     assert_includes(json_response['writable_by'],
349                     users(:admin).uuid,
350                     "Current user should be included in 'writable_by' field")
351   end
352
353   test 'creating subproject with duplicate name fails' do
354     authorize_with :active
355     post :create, {
356       group: {
357         name: 'A Project',
358         owner_uuid: users(:active).uuid,
359         group_class: 'project',
360       },
361     }
362     assert_response 422
363     response_errors = json_response['errors']
364     assert_not_nil response_errors, 'Expected error in response'
365     assert(response_errors.first.include?('duplicate key'),
366            "Expected 'duplicate key' error in #{response_errors.first}")
367   end
368
369   test 'creating duplicate named subproject succeeds with ensure_unique_name' do
370     authorize_with :active
371     post :create, {
372       group: {
373         name: 'A Project',
374         owner_uuid: users(:active).uuid,
375         group_class: 'project',
376       },
377       ensure_unique_name: true
378     }
379     assert_response :success
380     new_project = json_response
381     assert_not_equal(new_project['uuid'],
382                      groups(:aproject).uuid,
383                      "create returned same uuid as existing project")
384     assert_match(/^A Project \(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z\)$/,
385                  new_project['name'])
386   end
387
388   test "unsharing a project results in hiding it from previously shared user" do
389     # remove sharing link for project
390     @controller = Arvados::V1::LinksController.new
391     authorize_with :admin
392     post :destroy, id: links(:share_starred_project_with_project_viewer).uuid
393     assert_response :success
394
395     # verify that the user can no longer see the project
396     @test_counter = 0  # Reset executed action counter
397     @controller = Arvados::V1::GroupsController.new
398     authorize_with :project_viewer
399     get :index, filters: [['group_class', '=', 'project']], format: :json
400     assert_response :success
401     found_projects = {}
402     json_response['items'].each do |g|
403       found_projects[g['uuid']] = g
404     end
405     assert_equal false, found_projects.include?(groups(:starred_and_shared_active_user_project).uuid)
406
407     # share the project
408     @test_counter = 0
409     @controller = Arvados::V1::LinksController.new
410     authorize_with :system_user
411     post :create, link: {
412       link_class: "permission",
413       name: "can_read",
414       head_uuid: groups(:starred_and_shared_active_user_project).uuid,
415       tail_uuid: users(:project_viewer).uuid,
416     }
417
418     # verify that project_viewer user can now see shared project again
419     @test_counter = 0
420     @controller = Arvados::V1::GroupsController.new
421     authorize_with :project_viewer
422     get :index, filters: [['group_class', '=', 'project']], format: :json
423     assert_response :success
424     found_projects = {}
425     json_response['items'].each do |g|
426       found_projects[g['uuid']] = g
427     end
428     assert_equal true, found_projects.include?(groups(:starred_and_shared_active_user_project).uuid)
429   end
430
431   [
432     [['owner_uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 200,
433         'zzzzz-d1hrv-subprojpipeline', 'zzzzz-d1hrv-1xfj6xkicf2muk2'],
434     [["pipeline_instances.state", "not in", ["Complete", "Failed"]], 200,
435         'zzzzz-d1hrv-1xfj6xkicf2muk2', 'zzzzz-d1hrv-i3e77t9z5y8j9cc'],
436     [['container_requests.requesting_container_uuid', '=', nil], 200,
437         'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4requestercn2'],
438     [['container_requests.no_such_column', '=', nil], 422],
439     [['container_requests.', '=', nil], 422],
440     [['.requesting_container_uuid', '=', nil], 422],
441     [['no_such_table.uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 422],
442   ].each do |filter, expect_code, expect_uuid, not_expect_uuid|
443     test "get contents with '#{filter}' filter" do
444       authorize_with :active
445       get :contents, filters: [filter], format: :json
446       assert_response expect_code
447       if expect_code == 200
448         assert_not_empty json_response['items']
449         item_uuids = json_response['items'].collect {|item| item['uuid']}
450         assert_includes(item_uuids, expect_uuid)
451         assert_not_includes(item_uuids, not_expect_uuid)
452       end
453     end
454   end
455
456   test 'get contents with jobs and pipeline instances disabled' do
457     Rails.configuration.disable_api_methods = ['jobs.index', 'pipeline_instances.index']
458
459     authorize_with :active
460     get :contents, {
461       id: groups(:aproject).uuid,
462       format: :json,
463     }
464     check_project_contents_response %w'arvados#pipelineInstance arvados#job'
465   end
466
467   test 'get contents with low max_index_database_read' do
468     # Some result will certainly have at least 12 bytes in a
469     # restricted column
470     Rails.configuration.max_index_database_read = 12
471     authorize_with :active
472     get :contents, {
473           id: groups(:aproject).uuid,
474           format: :json,
475         }
476     assert_response :success
477     assert_not_empty(json_response['items'])
478     assert_operator(json_response['items'].count,
479                     :<, json_response['items_available'])
480   end
481
482   test 'get contents, recursive=true' do
483     authorize_with :active
484     params = {
485       id: groups(:aproject).uuid,
486       recursive: true,
487       format: :json,
488     }
489     get :contents, params
490     owners = json_response['items'].map do |item|
491       item['owner_uuid']
492     end
493     assert_includes(owners, groups(:aproject).uuid)
494     assert_includes(owners, groups(:asubproject).uuid)
495   end
496
497   [false, nil].each do |recursive|
498     test "get contents, recursive=#{recursive.inspect}" do
499       authorize_with :active
500       params = {
501         id: groups(:aproject).uuid,
502         format: :json,
503       }
504       params[:recursive] = false if recursive == false
505       get :contents, params
506       owners = json_response['items'].map do |item|
507         item['owner_uuid']
508       end
509       assert_includes(owners, groups(:aproject).uuid)
510       refute_includes(owners, groups(:asubproject).uuid)
511     end
512   end
513
514   test 'get home project contents, recursive=true' do
515     authorize_with :active
516     get :contents, {
517           id: users(:active).uuid,
518           recursive: true,
519           format: :json,
520         }
521     owners = json_response['items'].map do |item|
522       item['owner_uuid']
523     end
524     assert_includes(owners, users(:active).uuid)
525     assert_includes(owners, groups(:aproject).uuid)
526     assert_includes(owners, groups(:asubproject).uuid)
527   end
528 end