Merge branch '2411-check-copyright'
[arvados.git] / apps / workbench / test / controllers / projects_controller_test.rb
index 5d7b1e27b3b46453fad4c32ddbdf2b4bba3400c7..f45f1783959d050c4f8291aa95672068e9fb1d69 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 require 'test_helper'
 require 'helpers/share_object_helper'
 
@@ -28,7 +32,7 @@ class ProjectsControllerTest < ActionController::TestCase
         id: readonly_project_uuid
       }, session_for(which_user)
       buttons = css_select('[data-method=post]').select do |el|
-        el.attributes['href'].match /project.*owner_uuid.*#{readonly_project_uuid}/
+        el.attributes['data-remote-href'].match /project.*owner_uuid.*#{readonly_project_uuid}/
       end
       if should_show
         assert_not_empty(buttons, "did not offer to create a subproject")
@@ -101,8 +105,9 @@ class ProjectsControllerTest < ActionController::TestCase
   end
 
   test "project admin can remove collections from the project" do
-    # Deleting an object that supports 'expires_at' should make it
-    # completely inaccessible to API queries, not simply moved out of the project.
+    # Deleting an object that supports 'trash_at' should make it
+    # completely inaccessible to API queries, not simply moved out of
+    # the project.
     coll_key = "collection_to_remove_from_subproject"
     coll_uuid = api_fixture("collections")[coll_key]["uuid"]
     delete(:remove_item,
@@ -116,12 +121,12 @@ class ProjectsControllerTest < ActionController::TestCase
 
     use_token :subproject_admin
     assert_raise ArvadosApiClient::NotFoundException do
-      Collection.find(coll_uuid)
+      Collection.find(coll_uuid, cache: false)
     end
   end
 
   test "project admin can remove items from project other than collections" do
-    # An object which does not have an expired_at field (e.g. Specimen)
+    # An object which does not have an trash_at field (e.g. Specimen)
     # should be implicitly moved to the user's Home project when removed.
     specimen_uuid = api_fixture('specimens', 'in_asubproject')['uuid']
     delete(:remove_item,
@@ -138,6 +143,33 @@ class ProjectsControllerTest < ActionController::TestCase
     assert_equal api_fixture('users', 'subproject_admin')['uuid'], new_specimen.owner_uuid
   end
 
+  # An object which does not offer an expired_at field but has a xx_owner_uuid_name_unique constraint
+  # will be renamed when removed and another object with the same name exists in user's home project.
+  [
+    ['groups', 'subproject_in_asubproject_with_same_name_as_one_in_active_user_home'],
+    ['pipeline_templates', 'template_in_asubproject_with_same_name_as_one_in_active_user_home'],
+  ].each do |dm, fixture|
+    test "removing #{dm} from a subproject results in renaming it when there is another such object with same name in home project" do
+      object = api_fixture(dm, fixture)
+      delete(:remove_item,
+             { id: api_fixture('groups', 'asubproject')['uuid'],
+               item_uuid: object['uuid'],
+               format: 'js' },
+             session_for(:active))
+      assert_response :success
+      assert_match(/\b#{object['uuid']}\b/, @response.body,
+                   "removed object not named in response")
+      use_token :active
+      if dm.eql?('groups')
+        found = Group.find(object['uuid'])
+      else
+        found = PipelineTemplate.find(object['uuid'])
+      end
+      assert_equal api_fixture('users', 'active')['uuid'], found.owner_uuid
+      assert_equal true, found.name.include?(object['name'] + ' removed from ')
+    end
+  end
+
   test 'projects#show tab infinite scroll partial obeys limit' do
     get_contents_rows(limit: 1, filters: [['uuid','is_a',['arvados#job']]])
     assert_response :success
@@ -207,4 +239,334 @@ class ProjectsControllerTest < ActionController::TestCase
                           }]
     get :show, encoded_params, session_for(:active)
   end
+
+  test "visit non-public project as anonymous when anonymous browsing is enabled and expect page not found" do
+    Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
+    get(:show, {id: api_fixture('groups')['aproject']['uuid']})
+    assert_response 404
+    assert_match(/log ?in/i, @response.body)
+  end
+
+  test "visit home page as anonymous when anonymous browsing is enabled and expect login" do
+    Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
+    get(:index)
+    assert_response :redirect
+    assert_match /\/users\/welcome/, @response.redirect_url
+  end
+
+  [
+    nil,
+    :active,
+  ].each do |user|
+    test "visit public projects page when anon config is enabled, as user #{user}, and expect page" do
+      Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
+
+      if user
+        get :public, {}, session_for(user)
+      else
+        get :public
+      end
+
+      assert_response :success
+      assert_not_nil assigns(:objects)
+      project_names = assigns(:objects).collect(&:name)
+      assert_includes project_names, 'Unrestricted public data'
+      assert_not_includes project_names, 'A Project'
+      refute_empty css_select('[href="/projects/public"]')
+    end
+  end
+
+  test "visit public projects page when anon config is not enabled as active user and expect 404" do
+    get :public, {}, session_for(:active)
+    assert_response 404
+  end
+
+  test "visit public projects page when anon config is enabled but public projects page is disabled as active user and expect 404" do
+    Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
+    Rails.configuration.enable_public_projects_page = false
+    get :public, {}, session_for(:active)
+    assert_response 404
+  end
+
+  test "visit public projects page when anon config is not enabled as anonymous and expect login page" do
+    get :public
+    assert_response :redirect
+    assert_match /\/users\/welcome/, @response.redirect_url
+    assert_empty css_select('[href="/projects/public"]')
+  end
+
+  test "visit public projects page when anon config is enabled and public projects page is disabled and expect login page" do
+    Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
+    Rails.configuration.enable_public_projects_page = false
+    get :index
+    assert_response :redirect
+    assert_match /\/users\/welcome/, @response.redirect_url
+    assert_empty css_select('[href="/projects/public"]')
+  end
+
+  test "visit public projects page when anon config is not enabled and public projects page is enabled and expect login page" do
+    Rails.configuration.enable_public_projects_page = true
+    get :index
+    assert_response :redirect
+    assert_match /\/users\/welcome/, @response.redirect_url
+    assert_empty css_select('[href="/projects/public"]')
+  end
+
+  test "find a project and edit its description" do
+    project = api_fixture('groups')['aproject']
+    use_token :active
+    found = Group.find(project['uuid'])
+    found.description = 'test description update'
+    found.save!
+    get(:show, {id: project['uuid']}, session_for(:active))
+    assert_includes @response.body, 'test description update'
+  end
+
+  test "find a project and edit description to textile description" do
+    project = api_fixture('groups')['aproject']
+    use_token :active
+    found = Group.find(project['uuid'])
+    found.description = '*test bold description for textile formatting*'
+    found.save!
+    get(:show, {id: project['uuid']}, session_for(:active))
+    assert_includes @response.body, '<strong>test bold description for textile formatting</strong>'
+  end
+
+  test "find a project and edit description to html description" do
+    project = api_fixture('groups')['aproject']
+    use_token :active
+    found = Group.find(project['uuid'])
+    found.description = 'Textile description with link to home page <a href="/">take me home</a>.'
+    found.save!
+    get(:show, {id: project['uuid']}, session_for(:active))
+    assert_includes @response.body, 'Textile description with link to home page <a href="/">take me home</a>.'
+  end
+
+  test "find a project and edit description to textile description with link to object" do
+    project = api_fixture('groups')['aproject']
+    use_token :active
+    found = Group.find(project['uuid'])
+
+    # uses 'Link to object' as a hyperlink for the object
+    found.description = '"Link to object":' + api_fixture('groups')['asubproject']['uuid']
+    found.save!
+    get(:show, {id: project['uuid']}, session_for(:active))
+
+    # check that input was converted to textile, not staying as inputted
+    refute_includes  @response.body,'"Link to object"'
+    refute_empty css_select('[href="/groups/zzzzz-j7d0g-axqo7eu9pwvna1x"]')
+  end
+
+  test "project viewer can't see project sharing tab" do
+    project = api_fixture('groups')['aproject']
+    get(:show, {id: project['uuid']}, session_for(:project_viewer))
+    refute_includes @response.body, '<div id="Sharing"'
+    assert_includes @response.body, '<div id="Data_collections"'
+  end
+
+  [
+    'admin',
+    'active',
+  ].each do |username|
+    test "#{username} can see project sharing tab" do
+     project = api_fixture('groups')['aproject']
+     get(:show, {id: project['uuid']}, session_for(username))
+     assert_includes @response.body, '<div id="Sharing"'
+     assert_includes @response.body, '<div id="Data_collections"'
+    end
+  end
+
+  [
+    ['admin',true],
+    ['active',true],
+    ['project_viewer',false],
+  ].each do |user, can_move|
+    test "#{user} can move subproject from project #{can_move}" do
+      get(:show, {id: api_fixture('groups')['aproject']['uuid']}, session_for(user))
+      if can_move
+        assert_includes @response.body, 'Move project...'
+      else
+        refute_includes @response.body, 'Move project...'
+      end
+    end
+  end
+
+  [
+    [:admin, true],
+    [:active, false],
+  ].each do |user, expect_all_nodes|
+    test "in dashboard other index page links as #{user}" do
+      get :index, {}, session_for(user)
+
+      [["processes", "/all_processes"],
+       ["collections", "/collections"],
+      ].each do |target, path|
+        assert_includes @response.body, "href=\"#{path}\""
+        assert_includes @response.body, "All #{target}"
+      end
+
+      if expect_all_nodes
+        assert_includes @response.body, "href=\"/nodes\""
+        assert_includes @response.body, "All nodes"
+      else
+        assert_not_includes @response.body, "href=\"/nodes\""
+        assert_not_includes @response.body, "All nodes"
+      end
+    end
+  end
+
+  test "dashboard should show the correct status for processes" do
+    get :index, {}, session_for(:active)
+    assert_select 'div.panel-body.recent-processes' do
+      [
+        {
+          fixture: 'container_requests',
+          state: 'completed',
+          selectors: [['div.progress', false],
+                      ['span.label.label-success', true, 'Complete']]
+        },
+        {
+          fixture: 'container_requests',
+          state: 'uncommitted',
+          selectors: [['div.progress', false],
+                      ['span.label.label-default', true, 'Uncommitted']]
+        },
+        {
+          fixture: 'container_requests',
+          state: 'queued',
+          selectors: [['div.progress', false],
+                      ['span.label.label-default', true, 'Queued']]
+        },
+        {
+          fixture: 'container_requests',
+          state: 'running',
+          selectors: [['div.progress', true]]
+        },
+        {
+          fixture: 'pipeline_instances',
+          state: 'new_pipeline',
+          selectors: [['div.progress', false],
+                      ['span.label.label-default', true, 'Not started']]
+        },
+        {
+          fixture: 'pipeline_instances',
+          state: 'pipeline_in_running_state',
+          selectors: [['div.progress', true]]
+        },
+      ].each do |c|
+        uuid = api_fixture(c[:fixture])[c[:state]]['uuid']
+        assert_select "div.dashboard-panel-info-row.row-#{uuid}" do
+          if c.include? :selectors
+            c[:selectors].each do |selector, should_show, label|
+              assert_select selector, should_show, "UUID #{uuid} should #{should_show ? '' : 'not'} show '#{selector}'"
+              if should_show and not label.nil?
+                assert_select selector, label, "UUID #{uuid} state label should show #{label}"
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+
+  test "visit a public project and verify the public projects page link exists" do
+    Rails.configuration.anonymous_user_token = api_fixture('api_client_authorizations')['anonymous']['api_token']
+    uuid = api_fixture('groups')['anonymously_accessible_project']['uuid']
+    get :show, {id: uuid}
+    project = assigns(:object)
+    assert_equal uuid, project['uuid']
+    refute_empty css_select("[href=\"/projects/#{project['uuid']}\"]")
+    assert_includes @response.body, "<a href=\"/projects/public\">Public Projects</a>"
+  end
+
+  test 'all_projects unaffected by params after use by ProjectsController (#6640)' do
+    @controller = ProjectsController.new
+    project_uuid = api_fixture('groups')['aproject']['uuid']
+    get :index, {
+      filters: [['uuid', '<', project_uuid]].to_json,
+      limit: 0,
+      offset: 1000,
+    }, session_for(:active)
+    assert_select "#projects-menu + ul li.divider ~ li a[href=/projects/#{project_uuid}]"
+  end
+
+  [
+    ["active", 5, ["aproject", "asubproject"], "anonymously_accessible_project"],
+    ["user1_with_load", 2, ["project_with_10_collections"], "project_with_2_pipelines_and_60_crs"],
+    ["admin", 5, ["anonymously_accessible_project", "subproject_in_anonymous_accessible_project"], "aproject"],
+  ].each do |user, page_size, tree_segment, unexpected|
+    # Note: this test is sensitive to database collation. It passes
+    # with en_US.UTF-8.
+    test "build my projects tree for #{user} user and verify #{unexpected} is omitted" do
+      use_token user
+
+      tree, _, _ = @controller.send(:my_wanted_projects_tree,
+                                    User.current,
+                                    page_size)
+
+      tree_segment_at_depth_1 = api_fixture('groups')[tree_segment[0]]
+      tree_segment_at_depth_2 = api_fixture('groups')[tree_segment[1]] if tree_segment[1]
+
+      node_depth = {}
+      tree.each do |x|
+        node_depth[x[:object]['uuid']] = x[:depth]
+      end
+
+      assert_equal(1, node_depth[tree_segment_at_depth_1['uuid']])
+      assert_equal(2, node_depth[tree_segment_at_depth_2['uuid']]) if tree_segment[1]
+
+      unexpected_project = api_fixture('groups')[unexpected]
+      assert_nil(node_depth[unexpected_project['uuid']], node_depth.inspect)
+    end
+  end
+
+  [
+    ["active", 1],
+    ["project_viewer", 1],
+    ["admin", 0],
+  ].each do |user, size|
+    test "starred projects for #{user}" do
+      use_token user
+      ctrl = ProjectsController.new
+      current_user = User.find(api_fixture('users')[user]['uuid'])
+      my_starred_project = ctrl.send :my_starred_projects, current_user
+      assert_equal(size, my_starred_project.andand.size)
+
+      ctrl2 = ProjectsController.new
+      current_user = User.find(api_fixture('users')[user]['uuid'])
+      my_starred_project = ctrl2.send :my_starred_projects, current_user
+      assert_equal(size, my_starred_project.andand.size)
+    end
+  end
+
+  test "unshare project and verify that it is no longer included in shared user's starred projects" do
+    # remove sharing link
+    use_token :system_user
+    Link.find(api_fixture('links')['share_starred_project_with_project_viewer']['uuid']).destroy
+
+    # verify that project is no longer included in starred projects
+    use_token :project_viewer
+    current_user = User.find(api_fixture('users')['project_viewer']['uuid'])
+    ctrl = ProjectsController.new
+    my_starred_project = ctrl.send :my_starred_projects, current_user
+    assert_equal(0, my_starred_project.andand.size)
+
+    # share it again
+    @controller = LinksController.new
+    post :create, {
+      link: {
+        link_class: 'permission',
+        name: 'can_read',
+        head_uuid: api_fixture('groups')['starred_and_shared_active_user_project']['uuid'],
+        tail_uuid: api_fixture('users')['project_viewer']['uuid'],
+      },
+      format: :json
+    }, session_for(:system_user)
+
+    # verify that the project is again included in starred projects
+    use_token :project_viewer
+    ctrl = ProjectsController.new
+    my_starred_project = ctrl.send :my_starred_projects, current_user
+    assert_equal(1, my_starred_project.andand.size)
+  end
 end