11065: Merge branch 'master' into 11065-optional-audit-logging
[arvados.git] / services / api / test / functional / arvados / v1 / groups_controller_test.rb
index 5ae1e9a9e27615dfdcd664596c1c60707891b9ab..05bcec2e268f44c9a83bf754b3a51a3796c96b9b 100644 (file)
@@ -116,6 +116,25 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     end
   end
 
+  test "list trashed collections and projects" do
+    authorize_with :active
+    get(:contents, {
+          format: :json,
+          include_trash: true,
+          filters: [
+            ['uuid', 'is_a', ['arvados#collection', 'arvados#group']],
+            ['is_trashed', '=', true],
+          ],
+          limit: 10000,
+        })
+    assert_response :success
+    found_uuids = json_response['items'].collect { |i| i['uuid'] }
+    assert_includes found_uuids, groups(:trashed_project).uuid
+    refute_includes found_uuids, groups(:aproject).uuid
+    assert_includes found_uuids, collections(:expired_collection).uuid
+    refute_includes found_uuids, collections(:w_a_z_file).uuid
+  end
+
   test "list objects in home project" do
     authorize_with :active
     get :contents, {
@@ -139,64 +158,59 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_includes ids, collections(:baz_file_in_asubproject).uuid
   end
 
-  [['asc', :<=],
-   ['desc', :>=]].each do |order, operator|
-    test "user with project read permission can sort project collections #{order}" do
+  [
+    ['collections.name', 'asc', :<=, "name"],
+    ['collections.name', 'desc', :>=, "name"],
+    ['name', 'asc', :<=, "name"],
+    ['name', 'desc', :>=, "name"],
+    ['collections.created_at', 'asc', :<=, "created_at"],
+    ['collections.created_at', 'desc', :>=, "created_at"],
+    ['created_at', 'asc', :<=, "created_at"],
+    ['created_at', 'desc', :>=, "created_at"],
+  ].each do |column, order, operator, field|
+    test "user with project read permission can sort projects on #{column} #{order}" do
       authorize_with :project_viewer
       get :contents, {
         id: groups(:asubproject).uuid,
         format: :json,
         filters: [['uuid', 'is_a', "arvados#collection"]],
-        order: "collections.name #{order}"
+        order: "#{column} #{order}"
       }
-      sorted_names = json_response['items'].collect { |item| item["name"] }
-      # Here we avoid assuming too much about the database
-      # collation. Both "alice"<"Bob" and "alice">"Bob" can be
-      # correct. Hopefully it _is_ safe to assume that if "a" comes
-      # before "b" in the ascii alphabet, "aX">"bY" is never true for
-      # any strings X and Y.
-      reliably_sortable_names = sorted_names.select do |name|
-        name[0] >= 'a' and name[0] <= 'z'
-      end.uniq do |name|
-        name[0]
-      end
-      # Preserve order of sorted_names. But do not use &=. If
-      # sorted_names has out-of-order duplicates, we want to preserve
-      # them here, so we can detect them and fail the test below.
-      sorted_names.select! do |name|
-        reliably_sortable_names.include? name
-      end
-      actually_checked_anything = false
-      previous = nil
-      sorted_names.each do |entry|
-        if previous
-          assert_operator(previous, operator, entry,
-                          "Entries sorted incorrectly.")
-          actually_checked_anything = true
+      sorted_values = json_response['items'].collect { |item| item[field] }
+      if field == "name"
+        # Here we avoid assuming too much about the database
+        # collation. Both "alice"<"Bob" and "alice">"Bob" can be
+        # correct. Hopefully it _is_ safe to assume that if "a" comes
+        # before "b" in the ascii alphabet, "aX">"bY" is never true for
+        # any strings X and Y.
+        reliably_sortable_names = sorted_values.select do |name|
+          name[0] >= 'a' && name[0] <= 'z'
+        end.uniq do |name|
+          name[0]
+        end
+        # Preserve order of sorted_values. But do not use &=. If
+        # sorted_values has out-of-order duplicates, we want to preserve
+        # them here, so we can detect them and fail the test below.
+        sorted_values.select! do |name|
+          reliably_sortable_names.include? name
         end
-        previous = entry
       end
-      assert actually_checked_anything, "Didn't even find two names to compare."
+      assert_sorted(operator, sorted_values)
     end
   end
 
-  test 'list objects across multiple projects' do
-    authorize_with :project_viewer
-    get :contents, {
-      format: :json,
-      filters: [['uuid', 'is_a', 'arvados#specimen']]
-    }
-    assert_response :success
-    found_uuids = json_response['items'].collect { |i| i['uuid'] }
-    [[:in_aproject, true],
-     [:in_asubproject, true],
-     [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
-      if should_find
-        assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
-      else
-        refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
+  def assert_sorted(operator, sorted_items)
+    actually_checked_anything = false
+    previous = nil
+    sorted_items.each do |entry|
+      if !previous.nil?
+        assert_operator(previous, operator, entry,
+                        "Entries sorted incorrectly.")
+        actually_checked_anything = true
       end
+      previous = entry
     end
+    assert actually_checked_anything, "Didn't even find two items to compare."
   end
 
   # Even though the project_viewer tests go through other controllers,
@@ -529,4 +543,237 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_includes(owners, groups(:aproject).uuid)
     assert_includes(owners, groups(:asubproject).uuid)
   end
+
+  ### trashed project tests ###
+
+  [:active, :admin].each do |auth|
+    # project: to query,    to untrash,    is visible, parent contents listing success
+    [[:trashed_project,     [],                 false, true],
+     [:trashed_project,     [:trashed_project], true,  true],
+     [:trashed_subproject,  [],                 false, false],
+     [:trashed_subproject,  [:trashed_project], true,  true],
+     [:trashed_subproject3, [:trashed_project], false, true],
+     [:trashed_subproject3, [:trashed_subproject3], false, false],
+     [:trashed_subproject3, [:trashed_project, :trashed_subproject3], true, true],
+    ].each do |project, untrash, visible, success|
+
+      test "contents listing #{project} #{untrash} as #{auth}" do
+        authorize_with auth
+        untrash.each do |pr|
+          Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+        end
+        get :contents, {
+              id: groups(project).owner_uuid,
+              format: :json
+            }
+        if success
+          assert_response :success
+          item_uuids = json_response['items'].map do |item|
+            item['uuid']
+          end
+          if visible
+            assert_includes(item_uuids, groups(project).uuid)
+          else
+            assert_not_includes(item_uuids, groups(project).uuid)
+          end
+        else
+          assert_response 404
+        end
+      end
+
+      test "contents of #{project} #{untrash} as #{auth}" do
+        authorize_with auth
+        untrash.each do |pr|
+          Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+        end
+        get :contents, {
+              id: groups(project).uuid,
+              format: :json
+            }
+        if visible
+          assert_response :success
+        else
+          assert_response 404
+        end
+      end
+
+      test "index #{project} #{untrash} as #{auth}" do
+        authorize_with auth
+        untrash.each do |pr|
+          Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+        end
+        get :index, {
+              format: :json,
+            }
+        assert_response :success
+        item_uuids = json_response['items'].map do |item|
+          item['uuid']
+        end
+        if visible
+          assert_includes(item_uuids, groups(project).uuid)
+        else
+          assert_not_includes(item_uuids, groups(project).uuid)
+        end
+      end
+
+      test "show #{project} #{untrash} as #{auth}" do
+        authorize_with auth
+        untrash.each do |pr|
+          Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+        end
+        get :show, {
+              id: groups(project).uuid,
+              format: :json
+            }
+        if visible
+          assert_response :success
+        else
+          assert_response 404
+        end
+      end
+
+      test "show include_trash #{project} #{untrash} as #{auth}" do
+        authorize_with auth
+        untrash.each do |pr|
+          Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+        end
+        get :show, {
+              id: groups(project).uuid,
+              format: :json,
+              include_trash: true
+            }
+        assert_response :success
+      end
+
+      test "index include_trash #{project} #{untrash} as #{auth}" do
+        authorize_with auth
+        untrash.each do |pr|
+          Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+        end
+        get :index, {
+              format: :json,
+              include_trash: true
+            }
+        assert_response :success
+        item_uuids = json_response['items'].map do |item|
+          item['uuid']
+        end
+        assert_includes(item_uuids, groups(project).uuid)
+      end
+    end
+
+    test "delete project #{auth}" do
+      authorize_with auth
+      [:trashed_project].each do |pr|
+        Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+      end
+      assert !Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
+      post :destroy, {
+            id: groups(:trashed_project).uuid,
+            format: :json,
+          }
+      assert_response :success
+      assert Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
+    end
+
+    test "untrash project #{auth}" do
+      authorize_with auth
+      assert Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
+      post :untrash, {
+            id: groups(:trashed_project).uuid,
+            format: :json,
+          }
+      assert_response :success
+      assert !Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
+    end
+
+    test "untrash project with name conflict #{auth}" do
+      authorize_with auth
+      [:trashed_project].each do |pr|
+        Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+      end
+      gc = Group.create!({owner_uuid: "zzzzz-j7d0g-trashedproject1",
+                         name: "trashed subproject 3",
+                         group_class: "project"})
+      post :untrash, {
+            id: groups(:trashed_subproject3).uuid,
+            format: :json,
+            ensure_unique_name: true
+           }
+      assert_response :success
+      assert_match /^trashed subproject 3 \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
+    end
+
+    test "move trashed subproject to new owner #{auth}" do
+      authorize_with auth
+      assert_nil Group.readable_by(users(auth)).where(uuid: groups(:trashed_subproject).uuid).first
+      put :update, {
+            id: groups(:trashed_subproject).uuid,
+            group: {
+              owner_uuid: users(:active).uuid
+            },
+            include_trash: true,
+            format: :json,
+          }
+      assert_response :success
+      assert_not_nil Group.readable_by(users(auth)).where(uuid: groups(:trashed_subproject).uuid).first
+    end
+  end
+
+  test 'get shared owned by another user' do
+    authorize_with :user_bar_in_sharing_group
+
+    act_as_system_user do
+      Link.create!(
+        tail_uuid: users(:user_bar_in_sharing_group).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_uuid: groups(:project_owned_by_foo).uuid)
+    end
+
+    get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
+
+    assert_equal 1, json_response['items'].length
+    assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
+
+    assert_equal 1, json_response['included'].length
+    assert_equal json_response['included'][0]["uuid"], users(:user_foo_in_sharing_group).uuid
+  end
+
+  test 'get shared, owned by unreadable project' do
+    authorize_with :user_bar_in_sharing_group
+
+    act_as_system_user do
+      Group.find_by_uuid(groups(:project_owned_by_foo).uuid).update!(owner_uuid: groups(:aproject).uuid)
+      Link.create!(
+        tail_uuid: users(:user_bar_in_sharing_group).uuid,
+        link_class: 'permission',
+        name: 'can_read',
+        head_uuid: groups(:project_owned_by_foo).uuid)
+    end
+
+    get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
+
+    assert_equal 1, json_response['items'].length
+    assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
+
+    assert_equal 0, json_response['included'].length
+  end
+
+  test 'get shared, owned by non-project' do
+    authorize_with :user_bar_in_sharing_group
+
+    act_as_system_user do
+      Group.find_by_uuid(groups(:project_owned_by_foo).uuid).update!(owner_uuid: groups(:group_for_sharing_tests).uuid)
+    end
+
+    get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
+
+    assert_equal 1, json_response['items'].length
+    assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
+
+    assert_equal 1, json_response['included'].length
+    assert_equal json_response['included'][0]["uuid"], groups(:group_for_sharing_tests).uuid
+  end
+
 end