1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
6 require 'helpers/share_object_helper'
8 class ProjectsControllerTest < ActionController::TestCase
9 include ShareObjectHelper
11 test "invited user is asked to sign user agreements on front page" do
12 get :index, params: {}, session: session_for(:inactive)
13 assert_response :redirect
14 assert_match(/^#{Regexp.escape(user_agreements_url)}\b/,
15 @response.redirect_url,
16 "Inactive user was not redirected to user_agreements page")
19 test "uninvited user is asked to wait for activation" do
20 get :index, params: {}, session: session_for(:inactive_uninvited)
21 assert_response :redirect
22 assert_match(/^#{Regexp.escape(inactive_users_url)}\b/,
23 @response.redirect_url,
24 "Uninvited user was not redirected to inactive user page")
28 [:project_viewer, false]].each do |which_user, should_show|
29 test "create subproject button #{'not ' unless should_show} shown to #{which_user}" do
30 readonly_project_uuid = api_fixture('groups')['aproject']['uuid']
32 id: readonly_project_uuid
33 }, session: session_for(which_user)
34 buttons = css_select('[data-method=post]').select do |el|
35 el.attributes['data-remote-href'].value.match /project.*owner_uuid.*#{readonly_project_uuid}/
38 assert_not_empty(buttons, "did not offer to create a subproject")
40 assert_empty(buttons.collect(&:to_s),
41 "offered to create a subproject in a non-writable project")
46 test "sharing a project with a user and group" do
47 uuid_list = [api_fixture("groups")["future_project_viewing_group"]["uuid"],
48 api_fixture("users")["future_project_user"]["uuid"]]
49 post(:share_with, params: {
50 id: api_fixture("groups")["asubproject"]["uuid"],
53 session: session_for(:active))
54 assert_response :success
55 assert_equal(uuid_list, json_response["success"])
58 test "user with project read permission can't add permissions" do
59 share_uuid = api_fixture("users")["spectator"]["uuid"]
60 post(:share_with, params: {
61 id: api_fixture("groups")["aproject"]["uuid"],
64 session: session_for(:project_viewer))
66 assert(json_response["errors"].andand.
67 any? { |msg| msg.start_with?("#{share_uuid}: ") },
68 "JSON response missing properly formatted sharing error")
71 test "admin can_manage aproject" do
72 assert user_can_manage(:admin, api_fixture("groups")["aproject"])
75 test "owner can_manage aproject" do
76 assert user_can_manage(:active, api_fixture("groups")["aproject"])
79 test "owner can_manage asubproject" do
80 assert user_can_manage(:active, api_fixture("groups")["asubproject"])
83 test "viewer can't manage aproject" do
84 refute user_can_manage(:project_viewer, api_fixture("groups")["aproject"])
87 test "viewer can't manage asubproject" do
88 refute user_can_manage(:project_viewer, api_fixture("groups")["asubproject"])
91 test "subproject_admin can_manage asubproject" do
92 assert user_can_manage(:subproject_admin, api_fixture("groups")["asubproject"])
95 test "detect ownership loop in project breadcrumbs" do
96 # This test has an arbitrary time limit -- otherwise we'd just sit
97 # here forever instead of reporting that the loop was not
98 # detected. The test passes quickly, but fails slowly.
99 Timeout::timeout 10 do
101 params: { id: api_fixture("groups")["project_owns_itself"]["uuid"] },
102 session: session_for(:admin))
104 assert_response :success
107 test "project admin can remove collections from the project" do
108 # Deleting an object that supports 'trash_at' should make it
109 # completely inaccessible to API queries, not simply moved out of
111 coll_key = "collection_to_remove_from_subproject"
112 coll_uuid = api_fixture("collections")[coll_key]["uuid"]
114 params: { id: api_fixture("groups")["asubproject"]["uuid"],
115 item_uuid: coll_uuid,
117 session: session_for(:subproject_admin))
118 assert_response :success
119 assert_match(/\b#{coll_uuid}\b/, @response.body,
120 "removed object not named in response")
122 use_token :subproject_admin
123 assert_raise ArvadosApiClient::NotFoundException do
124 Collection.find(coll_uuid, cache: false)
128 test "project admin can remove items from project other than collections" do
129 # An object which does not have an trash_at field (e.g. Specimen)
130 # should be implicitly moved to the user's Home project when removed.
131 specimen_uuid = api_fixture('specimens', 'in_asubproject')['uuid']
133 params: { id: api_fixture('groups', 'asubproject')['uuid'],
134 item_uuid: specimen_uuid,
136 session: session_for(:subproject_admin))
137 assert_response :success
138 assert_match(/\b#{specimen_uuid}\b/, @response.body,
139 "removed object not named in response")
141 use_token :subproject_admin
142 new_specimen = Specimen.find(specimen_uuid)
143 assert_equal api_fixture('users', 'subproject_admin')['uuid'], new_specimen.owner_uuid
146 # An object which does not offer an expired_at field but has a xx_owner_uuid_name_unique constraint
147 # will be renamed when removed and another object with the same name exists in user's home project.
149 ['pipeline_templates', 'template_in_asubproject_with_same_name_as_one_in_active_user_home'],
150 ].each do |dm, fixture|
151 test "removing #{dm} from a subproject results in renaming it when there is another such object with same name in home project" do
152 object = api_fixture(dm, fixture)
154 params: { id: api_fixture('groups', 'asubproject')['uuid'],
155 item_uuid: object['uuid'],
157 session: session_for(:active))
158 assert_response :success
159 assert_match(/\b#{object['uuid']}\b/, @response.body,
160 "removed object not named in response")
163 found = Group.find(object['uuid'])
165 found = PipelineTemplate.find(object['uuid'])
167 assert_equal api_fixture('users', 'active')['uuid'], found.owner_uuid
168 assert_equal true, found.name.include?(object['name'] + ' removed from ')
172 test 'projects#show tab infinite scroll partial obeys limit' do
173 get_contents_rows(limit: 1, filters: [['uuid','is_a',['arvados#job']]])
174 assert_response :success
175 assert_equal(1, json_response['content'].scan('<tr').count,
176 "Did not get exactly one row")
179 ['', ' asc', ' desc'].each do |direction|
180 test "projects#show tab partial orders correctly by #{direction}" do
181 _test_tab_content_order direction
185 def _test_tab_content_order direction
186 get_contents_rows(limit: 100,
187 order: "created_at#{direction}",
188 filters: [['uuid','is_a',['arvados#job',
189 'arvados#pipelineInstance']]])
190 assert_response :success
191 not_grouped_by_kind = nil
195 json_response['content'].scan /<tr[^>]+>/ do |tr_tag|
197 tr_tag.scan(/\ data-object-created-at=\"(.*?)\"/).each do |t,|
199 correct_operator = / desc$/ =~ direction ? :>= : :<=
200 assert_operator(last_timestamp, correct_operator, t,
201 "Rows are not sorted by created_at#{direction}")
204 found_timestamps += 1
206 assert_equal(1, found_timestamps,
207 "Content row did not have exactly one timestamp")
209 # Confirm that the test for timestamp ordering couldn't have
210 # passed merely because the test fixtures have convenient
211 # timestamps (e.g., there is only one pipeline and one job in
212 # the project being tested, or there are no pipelines at all in
213 # the project being tested):
214 tr_tag.scan /\ data-kind=\"(.*?)\"/ do |kind|
215 if last_kind and last_kind != kind and found_kind[kind]
216 # We saw this kind before, then a different kind, then
217 # this kind again. That means objects are not grouped by
219 not_grouped_by_kind = true
221 found_kind[kind] ||= 0
222 found_kind[kind] += 1
226 assert_equal(true, not_grouped_by_kind,
227 "Could not confirm that results are not grouped by kind")
230 def get_contents_rows params
232 id: api_fixture('users')['active']['uuid'],
233 partial: :contents_rows,
236 encoded_params = Hash[params.map { |k,v|
237 [k, (v.is_a?(Array) || v.is_a?(Hash)) ? v.to_json : v]
239 get :show, params: encoded_params, session: session_for(:active)
242 test "visit non-public project as anonymous when anonymous browsing is enabled and expect page not found" do
243 Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
244 get(:show, params: {id: api_fixture('groups')['aproject']['uuid']})
246 assert_match(/log ?in/i, @response.body)
249 test "visit home page as anonymous when anonymous browsing is enabled and expect login" do
250 Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
252 assert_response :redirect
253 assert_match /\/users\/welcome/, @response.redirect_url
260 test "visit public projects page when anon config is enabled, as user #{user}, and expect page" do
261 Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
264 get :public, params: {}, session: session_for(user)
269 assert_response :success
270 assert_not_nil assigns(:objects)
271 project_names = assigns(:objects).collect(&:name)
272 assert_includes project_names, 'Unrestricted public data'
273 assert_not_includes project_names, 'A Project'
274 refute_empty css_select('[href="/projects/public"]')
278 test "visit public projects page when anon config is not enabled as active user and expect 404" do
279 Rails.configuration.anonymous_user_token = nil
280 Rails.configuration.enable_public_projects_page = false
281 get :public, params: {}, session: session_for(:active)
285 test "visit public projects page when anon config is enabled but public projects page is disabled as active user and expect 404" do
286 Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
287 Rails.configuration.enable_public_projects_page = false
288 get :public, params: {}, session: session_for(:active)
292 test "visit public projects page when anon config is not enabled as anonymous and expect login page" do
293 Rails.configuration.anonymous_user_token = nil
294 Rails.configuration.enable_public_projects_page = false
296 assert_response :redirect
297 assert_match /\/users\/welcome/, @response.redirect_url
298 assert_empty css_select('[href="/projects/public"]')
301 test "visit public projects page when anon config is enabled and public projects page is disabled and expect login page" do
302 Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
303 Rails.configuration.enable_public_projects_page = false
305 assert_response :redirect
306 assert_match /\/users\/welcome/, @response.redirect_url
307 assert_empty css_select('[href="/projects/public"]')
310 test "visit public projects page when anon config is not enabled and public projects page is enabled and expect login page" do
311 Rails.configuration.enable_public_projects_page = true
313 assert_response :redirect
314 assert_match /\/users\/welcome/, @response.redirect_url
315 assert_empty css_select('[href="/projects/public"]')
318 test "find a project and edit its description" do
319 project = api_fixture('groups')['aproject']
321 found = Group.find(project['uuid'])
322 found.description = 'test description update'
324 get(:show, params: {id: project['uuid']}, session: session_for(:active))
325 assert_includes @response.body, 'test description update'
328 test "find a project and edit description to textile description" do
329 project = api_fixture('groups')['aproject']
331 found = Group.find(project['uuid'])
332 found.description = '*test bold description for textile formatting*'
334 get(:show, params: {id: project['uuid']}, session: session_for(:active))
335 assert_includes @response.body, '<strong>test bold description for textile formatting</strong>'
338 test "find a project and edit description to html description" do
339 project = api_fixture('groups')['aproject']
341 found = Group.find(project['uuid'])
342 found.description = '<b>Textile</b> description with link to home page <a href="/">take me home</a>.'
344 get(:show, params: {id: project['uuid']}, session: session_for(:active))
345 assert_includes @response.body, '<b>Textile</b> description with link to home page <a href="/">take me home</a>.'
348 test "find a project and edit description to unsafe html description" do
349 project = api_fixture('groups')['aproject']
351 found = Group.find(project['uuid'])
352 found.description = 'Textile description with unsafe script tag <script language="javascript">alert("Hello there")</script>.'
354 get(:show, params: {id: project['uuid']}, session: session_for(:active))
355 assert_includes @response.body, 'Textile description with unsafe script tag alert("Hello there").'
359 test "textile table on description renders as table html markup" do
361 project = api_fixture('groups')['aproject']
362 textile_table = <<EOT
363 table(table table-striped table-condensed).
364 |_. First Header |_. Second Header |
365 |Content Cell |Content Cell |
366 |Content Cell |Content Cell |
368 found = Group.find(project['uuid'])
369 found.description = textile_table
371 get(:show, params: {id: project['uuid']}, session: session_for(:active))
372 assert_includes @response.body, '<th>First Header'
373 assert_includes @response.body, '<td>Content Cell'
376 test "find a project and edit description to textile description with link to object" do
377 project = api_fixture('groups')['aproject']
379 found = Group.find(project['uuid'])
381 # uses 'Link to object' as a hyperlink for the object
382 found.description = '"Link to object":' + api_fixture('groups')['asubproject']['uuid']
384 get(:show, params: {id: project['uuid']}, session: session_for(:active))
386 # check that input was converted to textile, not staying as inputted
387 refute_includes @response.body,'"Link to object"'
388 refute_empty css_select('[href="/groups/zzzzz-j7d0g-axqo7eu9pwvna1x"]')
391 test "project viewer can't see project sharing tab" do
392 project = api_fixture('groups')['aproject']
393 get(:show, params: {id: project['uuid']}, session: session_for(:project_viewer))
394 refute_includes @response.body, '<div id="Sharing"'
395 assert_includes @response.body, '<div id="Data_collections"'
402 test "#{username} can see project sharing tab" do
403 project = api_fixture('groups')['aproject']
404 get(:show, params: {id: project['uuid']}, session: session_for(username))
405 assert_includes @response.body, '<div id="Sharing"'
406 assert_includes @response.body, '<div id="Data_collections"'
413 ['project_viewer',false],
414 ].each do |user, can_move|
415 test "#{user} can move subproject from project #{can_move}" do
416 get(:show, params: {id: api_fixture('groups')['aproject']['uuid']}, session: session_for(user))
418 assert_includes @response.body, 'Move project...'
420 refute_includes @response.body, 'Move project...'
428 ].each do |user, expect_all_nodes|
429 test "in dashboard other index page links as #{user}" do
430 get :index, params: {}, session: session_for(user)
432 [["processes", "/all_processes"],
433 ["collections", "/collections"],
434 ].each do |target, path|
435 assert_includes @response.body, "href=\"#{path}\""
436 assert_includes @response.body, "All #{target}"
440 assert_includes @response.body, "href=\"/nodes\""
441 assert_includes @response.body, "All nodes"
443 assert_not_includes @response.body, "href=\"/nodes\""
444 assert_not_includes @response.body, "All nodes"
449 test "dashboard should show the correct status for processes" do
450 get :index, params: {}, session: session_for(:active)
451 assert_select 'div.panel-body.recent-processes' do
454 fixture: 'container_requests',
456 selectors: [['div.progress', false],
457 ['span.label.label-success', true, 'Complete']]
460 fixture: 'container_requests',
461 state: 'uncommitted',
462 selectors: [['div.progress', false],
463 ['span.label.label-default', true, 'Uncommitted']]
466 fixture: 'container_requests',
468 selectors: [['div.progress', false],
469 ['span.label.label-default', true, 'Queued']]
472 fixture: 'container_requests',
474 selectors: [['.label-info', true, 'Running']]
477 fixture: 'pipeline_instances',
478 state: 'new_pipeline',
479 selectors: [['div.progress', false],
480 ['span.label.label-default', true, 'Not started']]
483 fixture: 'pipeline_instances',
484 state: 'pipeline_in_running_state',
485 selectors: [['.label-info', true, 'Running']]
488 uuid = api_fixture(c[:fixture])[c[:state]]['uuid']
489 assert_select "div.dashboard-panel-info-row.row-#{uuid}" do
490 if c.include? :selectors
491 c[:selectors].each do |selector, should_show, label|
492 assert_select selector, should_show, "UUID #{uuid} should #{should_show ? '' : 'not'} show '#{selector}'"
493 if should_show and not label.nil?
494 assert_select selector, label, "UUID #{uuid} state label should show #{label}"
503 test "visit a public project and verify the public projects page link exists" do
504 Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
505 uuid = api_fixture('groups')['anonymously_accessible_project']['uuid']
506 get :show, params: {id: uuid}
507 project = assigns(:object)
508 assert_equal uuid, project['uuid']
509 refute_empty css_select("[href=\"/projects/#{project['uuid']}\"]")
510 assert_includes @response.body, "<a href=\"/projects/public\">Public Projects</a>"
513 test 'all_projects unaffected by params after use by ProjectsController (#6640)' do
514 @controller = ProjectsController.new
515 project_uuid = api_fixture('groups')['aproject']['uuid']
516 get :index, params: {
517 filters: [['uuid', '<', project_uuid]].to_json,
520 }, session: session_for(:active)
521 assert_select "#projects-menu + ul li.divider ~ li a[href=\"/projects/#{project_uuid}\"]"
525 ["active", 5, ["aproject", "asubproject"], "anonymously_accessible_project"],
526 ["user1_with_load", 2, ["project_with_10_collections"], "project_with_2_pipelines_and_60_crs"],
527 ["admin", 5, ["anonymously_accessible_project", "subproject_in_anonymous_accessible_project"], "aproject"],
528 ].each do |user, page_size, tree_segment, unexpected|
529 # Note: this test is sensitive to database collation. It passes
531 test "build my projects tree for #{user} user and verify #{unexpected} is omitted" do
534 tree, _, _ = @controller.send(:my_wanted_projects_tree,
538 tree_segment_at_depth_1 = api_fixture('groups')[tree_segment[0]]
539 tree_segment_at_depth_2 = api_fixture('groups')[tree_segment[1]] if tree_segment[1]
543 node_depth[x[:object]['uuid']] = x[:depth]
546 assert_equal(1, node_depth[tree_segment_at_depth_1['uuid']])
547 assert_equal(2, node_depth[tree_segment_at_depth_2['uuid']]) if tree_segment[1]
549 unexpected_project = api_fixture('groups')[unexpected]
550 assert_nil(node_depth[unexpected_project['uuid']], node_depth.inspect)
556 ["project_viewer", 1],
558 ].each do |user, size|
559 test "starred projects for #{user}" do
561 ctrl = ProjectsController.new
562 current_user = User.find(api_fixture('users')[user]['uuid'])
563 my_starred_project = ctrl.send :my_starred_projects, current_user
564 assert_equal(size, my_starred_project.andand.size)
566 ctrl2 = ProjectsController.new
567 current_user = User.find(api_fixture('users')[user]['uuid'])
568 my_starred_project = ctrl2.send :my_starred_projects, current_user
569 assert_equal(size, my_starred_project.andand.size)
573 test "unshare project and verify that it is no longer included in shared user's starred projects" do
574 # remove sharing link
575 use_token :system_user
576 Link.find(api_fixture('links')['share_starred_project_with_project_viewer']['uuid']).destroy
578 # verify that project is no longer included in starred projects
579 use_token :project_viewer
580 current_user = User.find(api_fixture('users')['project_viewer']['uuid'])
581 ctrl = ProjectsController.new
582 my_starred_project = ctrl.send :my_starred_projects, current_user
583 assert_equal(0, my_starred_project.andand.size)
586 @controller = LinksController.new
587 post :create, params: {
589 link_class: 'permission',
591 head_uuid: api_fixture('groups')['starred_and_shared_active_user_project']['uuid'],
592 tail_uuid: api_fixture('users')['project_viewer']['uuid'],
595 }, session: session_for(:system_user)
597 # verify that the project is again included in starred projects
598 use_token :project_viewer
599 ctrl = ProjectsController.new
600 my_starred_project = ctrl.send :my_starred_projects, current_user
601 assert_equal(1, my_starred_project.andand.size)