1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
7 class Arvados::V1::GroupsControllerTest < ActionController::TestCase
9 test "attempt to delete group without read or write access" do
10 authorize_with :active
11 post :destroy, id: groups(:empty_lonely_group).uuid
15 test "attempt to delete group without write access" do
16 authorize_with :active
17 post :destroy, id: groups(:all_users).uuid
21 test "get list of projects" do
22 authorize_with :active
23 get :index, filters: [['group_class', '=', 'project']], format: :json
24 assert_response :success
26 json_response['items'].each do |group|
27 assert_equal 'project', group['group_class']
28 group_uuids << group['uuid']
30 assert_includes group_uuids, groups(:aproject).uuid
31 assert_includes group_uuids, groups(:asubproject).uuid
32 assert_not_includes group_uuids, groups(:system_group).uuid
33 assert_not_includes group_uuids, groups(:private).uuid
36 test "get list of groups that are not projects" do
37 authorize_with :active
38 get :index, filters: [['group_class', '!=', 'project']], format: :json
39 assert_response :success
41 json_response['items'].each do |group|
42 assert_not_equal 'project', group['group_class']
43 group_uuids << group['uuid']
45 assert_not_includes group_uuids, groups(:aproject).uuid
46 assert_not_includes group_uuids, groups(:asubproject).uuid
47 assert_includes group_uuids, groups(:private).uuid
48 assert_includes group_uuids, groups(:group_with_no_class).uuid
51 test "get list of groups with bogus group_class" do
52 authorize_with :active
54 filters: [['group_class', '=', 'nogrouphasthislittleclass']],
57 assert_response :success
58 assert_equal [], json_response['items']
59 assert_equal 0, json_response['items_available']
62 def check_project_contents_response disabled_kinds=[]
63 assert_response :success
64 assert_operator 2, :<=, json_response['items_available']
65 assert_operator 2, :<=, json_response['items'].count
66 kinds = json_response['items'].collect { |i| i['kind'] }.uniq
67 expect_kinds = %w'arvados#group arvados#specimen arvados#pipelineTemplate arvados#job' - disabled_kinds
68 assert_equal expect_kinds, (expect_kinds & kinds)
70 json_response['items'].each do |i|
71 if i['kind'] == 'arvados#group'
72 assert(i['group_class'] == 'project',
73 "group#contents returned a non-project group")
77 disabled_kinds.each do |d|
78 assert_equal true, !kinds.include?(d)
82 test 'get group-owned objects' do
83 authorize_with :active
85 id: groups(:aproject).uuid,
88 check_project_contents_response
91 test "user with project read permission can see project objects" do
92 authorize_with :project_viewer
94 id: groups(:aproject).uuid,
97 check_project_contents_response
100 test "list objects across projects" do
101 authorize_with :project_viewer
104 filters: [['uuid', 'is_a', 'arvados#specimen']]
106 assert_response :success
107 found_uuids = json_response['items'].collect { |i| i['uuid'] }
108 [[:in_aproject, true],
109 [:in_asubproject, true],
110 [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
112 assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
114 refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
119 test "list objects in home project" do
120 authorize_with :active
124 id: users(:active).uuid
126 assert_response :success
127 found_uuids = json_response['items'].collect { |i| i['uuid'] }
128 assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
129 refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
132 test "user with project read permission can see project collections" do
133 authorize_with :project_viewer
135 id: groups(:asubproject).uuid,
138 ids = json_response['items'].map { |item| item["uuid"] }
139 assert_includes ids, collections(:baz_file_in_asubproject).uuid
143 ['collections.name', 'asc', :<=, "name"],
144 ['collections.name', 'desc', :>=, "name"],
145 ['name', 'asc', :<=, "name"],
146 ['name', 'desc', :>=, "name"],
147 ['collections.created_at', 'asc', :<=, "created_at"],
148 ['collections.created_at', 'desc', :>=, "created_at"],
149 ['created_at', 'asc', :<=, "created_at"],
150 ['created_at', 'desc', :>=, "created_at"],
151 ].each do |column, order, operator, field|
152 test "user with project read permission can sort projects on #{column} #{order}" do
153 authorize_with :project_viewer
155 id: groups(:asubproject).uuid,
157 filters: [['uuid', 'is_a', "arvados#collection"]],
158 order: "#{column} #{order}"
160 sorted_values = json_response['items'].collect { |item| item[field] }
162 # Here we avoid assuming too much about the database
163 # collation. Both "alice"<"Bob" and "alice">"Bob" can be
164 # correct. Hopefully it _is_ safe to assume that if "a" comes
165 # before "b" in the ascii alphabet, "aX">"bY" is never true for
166 # any strings X and Y.
167 reliably_sortable_names = sorted_values.select do |name|
168 name[0] >= 'a' && name[0] <= 'z'
172 # Preserve order of sorted_values. But do not use &=. If
173 # sorted_values has out-of-order duplicates, we want to preserve
174 # them here, so we can detect them and fail the test below.
175 sorted_values.select! do |name|
176 reliably_sortable_names.include? name
179 assert_sorted(operator, sorted_values)
183 def assert_sorted(operator, sorted_items)
184 actually_checked_anything = false
186 sorted_items.each do |entry|
188 assert_operator(previous, operator, entry,
189 "Entries sorted incorrectly.")
190 actually_checked_anything = true
194 assert actually_checked_anything, "Didn't even find two items to compare."
197 test 'list objects across multiple projects' do
198 authorize_with :project_viewer
201 filters: [['uuid', 'is_a', 'arvados#specimen']]
203 assert_response :success
204 found_uuids = json_response['items'].collect { |i| i['uuid'] }
205 [[:in_aproject, true],
206 [:in_asubproject, true],
207 [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
209 assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
211 refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
216 # Even though the project_viewer tests go through other controllers,
217 # I'm putting them here so they're easy to find alongside the other
219 def check_new_project_link_fails(link_attrs)
220 @controller = Arvados::V1::LinksController.new
221 post :create, link: {
222 link_class: "permission",
224 head_uuid: groups(:aproject).uuid,
226 assert_includes(403..422, response.status)
229 test "user with project read permission can't add users to it" do
230 authorize_with :project_viewer
231 check_new_project_link_fails(tail_uuid: users(:spectator).uuid)
234 test "user with project read permission can't add items to it" do
235 authorize_with :project_viewer
236 check_new_project_link_fails(tail_uuid: collections(:baz_file).uuid)
239 test "user with project read permission can't rename items in it" do
240 authorize_with :project_viewer
241 @controller = Arvados::V1::LinksController.new
243 id: jobs(:running).uuid,
244 name: "Denied test name",
246 assert_includes(403..404, response.status)
249 test "user with project read permission can't remove items from it" do
250 @controller = Arvados::V1::PipelineTemplatesController.new
251 authorize_with :project_viewer
253 id: pipeline_templates(:two_part).uuid,
255 owner_uuid: users(:project_viewer).uuid,
261 test "user with project read permission can't delete it" do
262 authorize_with :project_viewer
263 post :destroy, {id: groups(:aproject).uuid}
267 test 'get group-owned objects with limit' do
268 authorize_with :active
270 id: groups(:aproject).uuid,
274 assert_response :success
275 assert_operator 1, :<, json_response['items_available']
276 assert_equal 1, json_response['items'].count
279 test 'get group-owned objects with limit and offset' do
280 authorize_with :active
282 id: groups(:aproject).uuid,
287 assert_response :success
288 assert_operator 1, :<, json_response['items_available']
289 assert_equal 0, json_response['items'].count
292 test 'get group-owned objects with additional filter matching nothing' do
293 authorize_with :active
295 id: groups(:aproject).uuid,
296 filters: [['uuid', 'in', ['foo_not_a_uuid','bar_not_a_uuid']]],
299 assert_response :success
300 assert_equal [], json_response['items']
301 assert_equal 0, json_response['items_available']
304 %w(offset limit).each do |arg|
305 ['foo', '', '1234five', '0x10', '-8'].each do |val|
306 test "Raise error on bogus #{arg} parameter #{val.inspect}" do
307 authorize_with :active
309 :id => groups(:aproject).uuid,
318 test "Collection contents don't include manifest_text" do
319 authorize_with :active
321 id: groups(:aproject).uuid,
322 filters: [["uuid", "is_a", "arvados#collection"]],
325 assert_response :success
326 refute(json_response["items"].any? { |c| not c["portable_data_hash"] },
327 "response included an item without a portable data hash")
328 refute(json_response["items"].any? { |c| c.include?("manifest_text") },
329 "response included an item with a manifest text")
332 test 'get writable_by list for owned group' do
333 authorize_with :active
335 id: groups(:aproject).uuid,
338 assert_response :success
339 assert_not_nil(json_response['writable_by'],
340 "Should receive uuid list in 'writable_by' field")
341 assert_includes(json_response['writable_by'], users(:active).uuid,
342 "owner should be included in writable_by list")
345 test 'no writable_by list for group with read-only access' do
346 authorize_with :rominiadmin
348 id: groups(:testusergroup_admins).uuid,
351 assert_response :success
352 assert_equal([json_response['owner_uuid']],
353 json_response['writable_by'],
354 "Should only see owner_uuid in 'writable_by' field")
357 test 'get writable_by list by admin user' do
358 authorize_with :admin
360 id: groups(:testusergroup_admins).uuid,
363 assert_response :success
364 assert_not_nil(json_response['writable_by'],
365 "Should receive uuid list in 'writable_by' field")
366 assert_includes(json_response['writable_by'],
368 "Current user should be included in 'writable_by' field")
371 test 'creating subproject with duplicate name fails' do
372 authorize_with :active
376 owner_uuid: users(:active).uuid,
377 group_class: 'project',
381 response_errors = json_response['errors']
382 assert_not_nil response_errors, 'Expected error in response'
383 assert(response_errors.first.include?('duplicate key'),
384 "Expected 'duplicate key' error in #{response_errors.first}")
387 test 'creating duplicate named subproject succeeds with ensure_unique_name' do
388 authorize_with :active
392 owner_uuid: users(:active).uuid,
393 group_class: 'project',
395 ensure_unique_name: true
397 assert_response :success
398 new_project = json_response
399 assert_not_equal(new_project['uuid'],
400 groups(:aproject).uuid,
401 "create returned same uuid as existing project")
402 assert_match(/^A Project \(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z\)$/,
406 test "unsharing a project results in hiding it from previously shared user" do
407 # remove sharing link for project
408 @controller = Arvados::V1::LinksController.new
409 authorize_with :admin
410 post :destroy, id: links(:share_starred_project_with_project_viewer).uuid
411 assert_response :success
413 # verify that the user can no longer see the project
414 @test_counter = 0 # Reset executed action counter
415 @controller = Arvados::V1::GroupsController.new
416 authorize_with :project_viewer
417 get :index, filters: [['group_class', '=', 'project']], format: :json
418 assert_response :success
420 json_response['items'].each do |g|
421 found_projects[g['uuid']] = g
423 assert_equal false, found_projects.include?(groups(:starred_and_shared_active_user_project).uuid)
427 @controller = Arvados::V1::LinksController.new
428 authorize_with :system_user
429 post :create, link: {
430 link_class: "permission",
432 head_uuid: groups(:starred_and_shared_active_user_project).uuid,
433 tail_uuid: users(:project_viewer).uuid,
436 # verify that project_viewer user can now see shared project again
438 @controller = Arvados::V1::GroupsController.new
439 authorize_with :project_viewer
440 get :index, filters: [['group_class', '=', 'project']], format: :json
441 assert_response :success
443 json_response['items'].each do |g|
444 found_projects[g['uuid']] = g
446 assert_equal true, found_projects.include?(groups(:starred_and_shared_active_user_project).uuid)
450 [['owner_uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 200,
451 'zzzzz-d1hrv-subprojpipeline', 'zzzzz-d1hrv-1xfj6xkicf2muk2'],
452 [["pipeline_instances.state", "not in", ["Complete", "Failed"]], 200,
453 'zzzzz-d1hrv-1xfj6xkicf2muk2', 'zzzzz-d1hrv-i3e77t9z5y8j9cc'],
454 [['container_requests.requesting_container_uuid', '=', nil], 200,
455 'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4requestercn2'],
456 [['container_requests.no_such_column', '=', nil], 422],
457 [['container_requests.', '=', nil], 422],
458 [['.requesting_container_uuid', '=', nil], 422],
459 [['no_such_table.uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 422],
460 ].each do |filter, expect_code, expect_uuid, not_expect_uuid|
461 test "get contents with '#{filter}' filter" do
462 authorize_with :active
463 get :contents, filters: [filter], format: :json
464 assert_response expect_code
465 if expect_code == 200
466 assert_not_empty json_response['items']
467 item_uuids = json_response['items'].collect {|item| item['uuid']}
468 assert_includes(item_uuids, expect_uuid)
469 assert_not_includes(item_uuids, not_expect_uuid)
474 test 'get contents with jobs and pipeline instances disabled' do
475 Rails.configuration.disable_api_methods = ['jobs.index', 'pipeline_instances.index']
477 authorize_with :active
479 id: groups(:aproject).uuid,
482 check_project_contents_response %w'arvados#pipelineInstance arvados#job'
485 test 'get contents with low max_index_database_read' do
486 # Some result will certainly have at least 12 bytes in a
488 Rails.configuration.max_index_database_read = 12
489 authorize_with :active
491 id: groups(:aproject).uuid,
494 assert_response :success
495 assert_not_empty(json_response['items'])
496 assert_operator(json_response['items'].count,
497 :<, json_response['items_available'])
500 test 'get contents, recursive=true' do
501 authorize_with :active
503 id: groups(:aproject).uuid,
507 get :contents, params
508 owners = json_response['items'].map do |item|
511 assert_includes(owners, groups(:aproject).uuid)
512 assert_includes(owners, groups(:asubproject).uuid)
515 [false, nil].each do |recursive|
516 test "get contents, recursive=#{recursive.inspect}" do
517 authorize_with :active
519 id: groups(:aproject).uuid,
522 params[:recursive] = false if recursive == false
523 get :contents, params
524 owners = json_response['items'].map do |item|
527 assert_includes(owners, groups(:aproject).uuid)
528 refute_includes(owners, groups(:asubproject).uuid)
532 test 'get home project contents, recursive=true' do
533 authorize_with :active
535 id: users(:active).uuid,
539 owners = json_response['items'].map do |item|
542 assert_includes(owners, users(:active).uuid)
543 assert_includes(owners, groups(:aproject).uuid)
544 assert_includes(owners, groups(:asubproject).uuid)
547 ### trashed project tests ###
549 [:active, :admin].each do |auth|
550 # project: to query, to untrash, is visible, parent contents listing success
551 [[:trashed_project, [], false, true],
552 [:trashed_project, [:trashed_project], true, true],
553 [:trashed_subproject, [], false, false],
554 [:trashed_subproject, [:trashed_project], true, true],
555 [:trashed_subproject3, [:trashed_project], false, true],
556 [:trashed_subproject3, [:trashed_subproject3], false, false],
557 [:trashed_subproject3, [:trashed_project, :trashed_subproject3], true, true],
558 ].each do |project, untrash, visible, success|
560 test "contents listing #{project} #{untrash} as #{auth}" do
563 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
566 id: groups(project).owner_uuid,
570 assert_response :success
571 item_uuids = json_response['items'].map do |item|
575 assert_includes(item_uuids, groups(project).uuid)
577 assert_not_includes(item_uuids, groups(project).uuid)
584 test "contents of #{project} #{untrash} as #{auth}" do
587 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
590 id: groups(project).uuid,
594 assert_response :success
600 test "index #{project} #{untrash} as #{auth}" do
603 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
608 assert_response :success
609 item_uuids = json_response['items'].map do |item|
613 assert_includes(item_uuids, groups(project).uuid)
615 assert_not_includes(item_uuids, groups(project).uuid)
619 test "show #{project} #{untrash} as #{auth}" do
622 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
625 id: groups(project).uuid,
629 assert_response :success
635 test "show include_trash #{project} #{untrash} as #{auth}" do
638 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
641 id: groups(project).uuid,
645 assert_response :success
648 test "index include_trash #{project} #{untrash} as #{auth}" do
651 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
657 assert_response :success
658 item_uuids = json_response['items'].map do |item|
661 assert_includes(item_uuids, groups(project).uuid)
665 test "delete project #{auth}" do
667 [:trashed_project].each do |pr|
668 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
670 assert !Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
672 id: groups(:trashed_project).uuid,
675 assert_response :success
676 assert Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
679 test "untrash project #{auth}" do
681 assert Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
683 id: groups(:trashed_project).uuid,
686 assert_response :success
687 assert !Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
690 test "untrash project with name conflict #{auth}" do
692 [:trashed_project].each do |pr|
693 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
695 gc = Group.create!({owner_uuid: "zzzzz-j7d0g-trashedproject1",
696 name: "trashed subproject 3",
697 group_class: "project"})
699 id: groups(:trashed_subproject3).uuid,
701 ensure_unique_name: true
703 assert_response :success
704 assert_match /^trashed subproject 3 \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
707 test "move trashed subproject to new owner #{auth}" do
709 assert_nil Group.readable_by(users(auth)).where(uuid: groups(:trashed_subproject).uuid).first
711 id: groups(:trashed_subproject).uuid,
713 owner_uuid: users(:active).uuid
718 assert_response :success
719 assert_not_nil Group.readable_by(users(auth)).where(uuid: groups(:trashed_subproject).uuid).first
723 test 'get shared owned by another user' do
724 authorize_with :user_bar_in_sharing_group
726 act_as_system_user do
728 tail_uuid: users(:user_bar_in_sharing_group).uuid,
729 link_class: 'permission',
731 head_uuid: groups(:project_owned_by_foo).uuid)
734 get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
736 assert_equal 1, json_response['items'].length
737 assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
739 assert_equal 1, json_response['included'].length
740 assert_equal json_response['included'][0]["uuid"], users(:user_foo_in_sharing_group).uuid
743 test 'get shared, owned by unreadable project' do
744 authorize_with :user_bar_in_sharing_group
746 act_as_system_user do
747 Group.find_by_uuid(groups(:project_owned_by_foo).uuid).update!(owner_uuid: groups(:aproject).uuid)
749 tail_uuid: users(:user_bar_in_sharing_group).uuid,
750 link_class: 'permission',
752 head_uuid: groups(:project_owned_by_foo).uuid)
755 get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
757 assert_equal 1, json_response['items'].length
758 assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
760 assert_equal 0, json_response['included'].length
763 test 'get shared, owned by non-project' do
764 authorize_with :user_bar_in_sharing_group
766 act_as_system_user do
767 Group.find_by_uuid(groups(:project_owned_by_foo).uuid).update!(owner_uuid: groups(:group_for_sharing_tests).uuid)
770 get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
772 assert_equal 1, json_response['items'].length
773 assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
775 assert_equal 1, json_response['included'].length
776 assert_equal json_response['included'][0]["uuid"], groups(:group_for_sharing_tests).uuid