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 ['desc', :>=]].each do |order, operator|
144 test "user with project read permission can sort project collections #{order}" do
145 authorize_with :project_viewer
147 id: groups(:asubproject).uuid,
149 filters: [['uuid', 'is_a', "arvados#collection"]],
150 order: "collections.name #{order}"
152 sorted_names = json_response['items'].collect { |item| item["name"] }
153 # Here we avoid assuming too much about the database
154 # collation. Both "alice"<"Bob" and "alice">"Bob" can be
155 # correct. Hopefully it _is_ safe to assume that if "a" comes
156 # before "b" in the ascii alphabet, "aX">"bY" is never true for
157 # any strings X and Y.
158 reliably_sortable_names = sorted_names.select do |name|
159 name[0] >= 'a' and name[0] <= 'z'
163 # Preserve order of sorted_names. But do not use &=. If
164 # sorted_names has out-of-order duplicates, we want to preserve
165 # them here, so we can detect them and fail the test below.
166 sorted_names.select! do |name|
167 reliably_sortable_names.include? name
169 actually_checked_anything = false
171 sorted_names.each do |entry|
173 assert_operator(previous, operator, entry,
174 "Entries sorted incorrectly.")
175 actually_checked_anything = true
179 assert actually_checked_anything, "Didn't even find two names to compare."
183 test 'list objects across multiple projects' do
184 authorize_with :project_viewer
187 filters: [['uuid', 'is_a', 'arvados#specimen']]
189 assert_response :success
190 found_uuids = json_response['items'].collect { |i| i['uuid'] }
191 [[:in_aproject, true],
192 [:in_asubproject, true],
193 [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
195 assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
197 refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
202 # Even though the project_viewer tests go through other controllers,
203 # I'm putting them here so they're easy to find alongside the other
205 def check_new_project_link_fails(link_attrs)
206 @controller = Arvados::V1::LinksController.new
207 post :create, link: {
208 link_class: "permission",
210 head_uuid: groups(:aproject).uuid,
212 assert_includes(403..422, response.status)
215 test "user with project read permission can't add users to it" do
216 authorize_with :project_viewer
217 check_new_project_link_fails(tail_uuid: users(:spectator).uuid)
220 test "user with project read permission can't add items to it" do
221 authorize_with :project_viewer
222 check_new_project_link_fails(tail_uuid: collections(:baz_file).uuid)
225 test "user with project read permission can't rename items in it" do
226 authorize_with :project_viewer
227 @controller = Arvados::V1::LinksController.new
229 id: jobs(:running).uuid,
230 name: "Denied test name",
232 assert_includes(403..404, response.status)
235 test "user with project read permission can't remove items from it" do
236 @controller = Arvados::V1::PipelineTemplatesController.new
237 authorize_with :project_viewer
239 id: pipeline_templates(:two_part).uuid,
241 owner_uuid: users(:project_viewer).uuid,
247 test "user with project read permission can't delete it" do
248 authorize_with :project_viewer
249 post :destroy, {id: groups(:aproject).uuid}
253 test 'get group-owned objects with limit' do
254 authorize_with :active
256 id: groups(:aproject).uuid,
260 assert_response :success
261 assert_operator 1, :<, json_response['items_available']
262 assert_equal 1, json_response['items'].count
265 test 'get group-owned objects with limit and offset' do
266 authorize_with :active
268 id: groups(:aproject).uuid,
273 assert_response :success
274 assert_operator 1, :<, json_response['items_available']
275 assert_equal 0, json_response['items'].count
278 test 'get group-owned objects with additional filter matching nothing' do
279 authorize_with :active
281 id: groups(:aproject).uuid,
282 filters: [['uuid', 'in', ['foo_not_a_uuid','bar_not_a_uuid']]],
285 assert_response :success
286 assert_equal [], json_response['items']
287 assert_equal 0, json_response['items_available']
290 %w(offset limit).each do |arg|
291 ['foo', '', '1234five', '0x10', '-8'].each do |val|
292 test "Raise error on bogus #{arg} parameter #{val.inspect}" do
293 authorize_with :active
295 :id => groups(:aproject).uuid,
304 test "Collection contents don't include manifest_text" do
305 authorize_with :active
307 id: groups(:aproject).uuid,
308 filters: [["uuid", "is_a", "arvados#collection"]],
311 assert_response :success
312 refute(json_response["items"].any? { |c| not c["portable_data_hash"] },
313 "response included an item without a portable data hash")
314 refute(json_response["items"].any? { |c| c.include?("manifest_text") },
315 "response included an item with a manifest text")
318 test 'get writable_by list for owned group' do
319 authorize_with :active
321 id: groups(:aproject).uuid,
324 assert_response :success
325 assert_not_nil(json_response['writable_by'],
326 "Should receive uuid list in 'writable_by' field")
327 assert_includes(json_response['writable_by'], users(:active).uuid,
328 "owner should be included in writable_by list")
331 test 'no writable_by list for group with read-only access' do
332 authorize_with :rominiadmin
334 id: groups(:testusergroup_admins).uuid,
337 assert_response :success
338 assert_equal([json_response['owner_uuid']],
339 json_response['writable_by'],
340 "Should only see owner_uuid in 'writable_by' field")
343 test 'get writable_by list by admin user' do
344 authorize_with :admin
346 id: groups(:testusergroup_admins).uuid,
349 assert_response :success
350 assert_not_nil(json_response['writable_by'],
351 "Should receive uuid list in 'writable_by' field")
352 assert_includes(json_response['writable_by'],
354 "Current user should be included in 'writable_by' field")
357 test 'creating subproject with duplicate name fails' do
358 authorize_with :active
362 owner_uuid: users(:active).uuid,
363 group_class: 'project',
367 response_errors = json_response['errors']
368 assert_not_nil response_errors, 'Expected error in response'
369 assert(response_errors.first.include?('duplicate key'),
370 "Expected 'duplicate key' error in #{response_errors.first}")
373 test 'creating duplicate named subproject succeeds with ensure_unique_name' do
374 authorize_with :active
378 owner_uuid: users(:active).uuid,
379 group_class: 'project',
381 ensure_unique_name: true
383 assert_response :success
384 new_project = json_response
385 assert_not_equal(new_project['uuid'],
386 groups(:aproject).uuid,
387 "create returned same uuid as existing project")
388 assert_match(/^A Project \(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z\)$/,
392 test "unsharing a project results in hiding it from previously shared user" do
393 # remove sharing link for project
394 @controller = Arvados::V1::LinksController.new
395 authorize_with :admin
396 post :destroy, id: links(:share_starred_project_with_project_viewer).uuid
397 assert_response :success
399 # verify that the user can no longer see the project
400 @test_counter = 0 # Reset executed action counter
401 @controller = Arvados::V1::GroupsController.new
402 authorize_with :project_viewer
403 get :index, filters: [['group_class', '=', 'project']], format: :json
404 assert_response :success
406 json_response['items'].each do |g|
407 found_projects[g['uuid']] = g
409 assert_equal false, found_projects.include?(groups(:starred_and_shared_active_user_project).uuid)
413 @controller = Arvados::V1::LinksController.new
414 authorize_with :system_user
415 post :create, link: {
416 link_class: "permission",
418 head_uuid: groups(:starred_and_shared_active_user_project).uuid,
419 tail_uuid: users(:project_viewer).uuid,
422 # verify that project_viewer user can now see shared project again
424 @controller = Arvados::V1::GroupsController.new
425 authorize_with :project_viewer
426 get :index, filters: [['group_class', '=', 'project']], format: :json
427 assert_response :success
429 json_response['items'].each do |g|
430 found_projects[g['uuid']] = g
432 assert_equal true, found_projects.include?(groups(:starred_and_shared_active_user_project).uuid)
436 [['owner_uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 200,
437 'zzzzz-d1hrv-subprojpipeline', 'zzzzz-d1hrv-1xfj6xkicf2muk2'],
438 [["pipeline_instances.state", "not in", ["Complete", "Failed"]], 200,
439 'zzzzz-d1hrv-1xfj6xkicf2muk2', 'zzzzz-d1hrv-i3e77t9z5y8j9cc'],
440 [['container_requests.requesting_container_uuid', '=', nil], 200,
441 'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4requestercn2'],
442 [['container_requests.no_such_column', '=', nil], 422],
443 [['container_requests.', '=', nil], 422],
444 [['.requesting_container_uuid', '=', nil], 422],
445 [['no_such_table.uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 422],
446 ].each do |filter, expect_code, expect_uuid, not_expect_uuid|
447 test "get contents with '#{filter}' filter" do
448 authorize_with :active
449 get :contents, filters: [filter], format: :json
450 assert_response expect_code
451 if expect_code == 200
452 assert_not_empty json_response['items']
453 item_uuids = json_response['items'].collect {|item| item['uuid']}
454 assert_includes(item_uuids, expect_uuid)
455 assert_not_includes(item_uuids, not_expect_uuid)
460 test 'get contents with jobs and pipeline instances disabled' do
461 Rails.configuration.disable_api_methods = ['jobs.index', 'pipeline_instances.index']
463 authorize_with :active
465 id: groups(:aproject).uuid,
468 check_project_contents_response %w'arvados#pipelineInstance arvados#job'
471 test 'get contents with low max_index_database_read' do
472 # Some result will certainly have at least 12 bytes in a
474 Rails.configuration.max_index_database_read = 12
475 authorize_with :active
477 id: groups(:aproject).uuid,
480 assert_response :success
481 assert_not_empty(json_response['items'])
482 assert_operator(json_response['items'].count,
483 :<, json_response['items_available'])
486 test 'get contents, recursive=true' do
487 authorize_with :active
489 id: groups(:aproject).uuid,
493 get :contents, params
494 owners = json_response['items'].map do |item|
497 assert_includes(owners, groups(:aproject).uuid)
498 assert_includes(owners, groups(:asubproject).uuid)
501 [false, nil].each do |recursive|
502 test "get contents, recursive=#{recursive.inspect}" do
503 authorize_with :active
505 id: groups(:aproject).uuid,
508 params[:recursive] = false if recursive == false
509 get :contents, params
510 owners = json_response['items'].map do |item|
513 assert_includes(owners, groups(:aproject).uuid)
514 refute_includes(owners, groups(:asubproject).uuid)
518 test 'get home project contents, recursive=true' do
519 authorize_with :active
521 id: users(:active).uuid,
525 owners = json_response['items'].map do |item|
528 assert_includes(owners, users(:active).uuid)
529 assert_includes(owners, groups(:aproject).uuid)
530 assert_includes(owners, groups(:asubproject).uuid)
533 ### trashed project tests ###
535 [:active, :admin].each do |auth|
536 # project: to query, to untrash, is visible, parent contents listing success
537 [[:trashed_project, [], false, true],
538 [:trashed_project, [:trashed_project], true, true],
539 [:trashed_subproject, [], false, false],
540 [:trashed_subproject, [:trashed_project], true, true],
541 [:trashed_subproject3, [:trashed_project], false, true],
542 [:trashed_subproject3, [:trashed_subproject3], false, false],
543 [:trashed_subproject3, [:trashed_project, :trashed_subproject3], true, true],
544 ].each do |project, untrash, visible, success|
546 test "contents listing #{project} #{untrash} as #{auth}" do
549 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
552 id: groups(project).owner_uuid,
556 assert_response :success
557 item_uuids = json_response['items'].map do |item|
561 assert_includes(item_uuids, groups(project).uuid)
563 assert_not_includes(item_uuids, groups(project).uuid)
570 test "contents of #{project} #{untrash} as #{auth}" do
573 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
576 id: groups(project).uuid,
580 assert_response :success
586 test "index #{project} #{untrash} as #{auth}" do
589 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
594 assert_response :success
595 item_uuids = json_response['items'].map do |item|
599 assert_includes(item_uuids, groups(project).uuid)
601 assert_not_includes(item_uuids, groups(project).uuid)
605 test "show #{project} #{untrash} as #{auth}" do
608 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
611 id: groups(project).uuid,
615 assert_response :success
621 test "show include_trash #{project} #{untrash} as #{auth}" do
624 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
627 id: groups(project).uuid,
631 assert_response :success
634 test "index include_trash #{project} #{untrash} as #{auth}" do
637 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
643 assert_response :success
644 item_uuids = json_response['items'].map do |item|
647 assert_includes(item_uuids, groups(project).uuid)
651 test "delete project #{auth}" do
653 [:trashed_project].each do |pr|
654 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
656 assert !Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
658 id: groups(:trashed_project).uuid,
661 assert_response :success
662 assert Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
665 test "untrash project #{auth}" do
667 assert Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
669 id: groups(:trashed_project).uuid,
672 assert_response :success
673 assert !Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
676 test "untrash project with name conflict #{auth}" do
678 [:trashed_project].each do |pr|
679 Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
681 gc = Group.create!({owner_uuid: "zzzzz-j7d0g-trashedproject1",
682 name: "trashed subproject 3",
683 group_class: "project"})
685 id: groups(:trashed_subproject3).uuid,
687 ensure_unique_name: true
689 assert_response :success
690 assert_match /^trashed subproject 3 \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
693 test "move trashed subproject to new owner #{auth}" do
695 assert_nil Group.readable_by(users(auth)).where(uuid: groups(:trashed_subproject).uuid).first
697 id: groups(:trashed_subproject).uuid,
699 owner_uuid: users(:active).uuid
704 assert_response :success
705 assert_not_nil Group.readable_by(users(auth)).where(uuid: groups(:trashed_subproject).uuid).first